All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.reactivestreams.tck.IdentityProcessorVerification Maven / Gradle / Ivy

/***************************************************
 * Licensed under MIT No Attribution (SPDX: MIT-0) *
 ***************************************************/

package org.reactivestreams.tck;

import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.reactivestreams.tck.TestEnvironment.ManualPublisher;
import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport;
import org.reactivestreams.tck.TestEnvironment.Promise;
import org.reactivestreams.tck.flow.support.Function;
import org.reactivestreams.tck.flow.support.SubscriberWhiteboxVerificationRules;
import org.reactivestreams.tck.flow.support.PublisherVerificationRules;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.HashSet;
import java.util.Set;

public abstract class IdentityProcessorVerification extends WithHelperPublisher
  implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules {

  private final TestEnvironment env;

  ////////////////////// DELEGATED TO SPECS //////////////////////

  // for delegating tests
  private final SubscriberWhiteboxVerification subscriberVerification;

  // for delegating tests
  private final PublisherVerification publisherVerification;

  ////////////////// END OF DELEGATED TO SPECS //////////////////

  // number of elements the processor under test must be able ot buffer,
  // without dropping elements. Defaults to `TestEnvironment.TEST_BUFFER_SIZE`.
  private final int processorBufferSize;

  /**
   * Test class must specify the expected time it takes for the publisher to
   * shut itself down when the the last downstream {@code Subscription} is cancelled.
   *
   * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements.
   */
  @SuppressWarnings("unused")
  public IdentityProcessorVerification(final TestEnvironment env) {
    this(env, PublisherVerification.envPublisherReferenceGCTimeoutMillis(), TestEnvironment.TEST_BUFFER_SIZE);
  }

  /**
   * Test class must specify the expected time it takes for the publisher to
   * shut itself down when the the last downstream {@code Subscription} is cancelled.
   *
   * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements.
   *
   * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.
   */
  @SuppressWarnings("unused")
  public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis) {
    this(env, publisherReferenceGCTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE);
  }

  /**
   * Test class must specify the expected time it takes for the publisher to
   * shut itself down when the the last downstream {@code Subscription} is cancelled.
   *
   * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.
   * @param processorBufferSize            number of elements the processor is required to be able to buffer.
   */
  public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) {
    this.env = env;
    this.processorBufferSize = processorBufferSize;

    this.subscriberVerification = new SubscriberWhiteboxVerification(env) {
      @Override
      public Subscriber createSubscriber(WhiteboxSubscriberProbe probe) {
        return IdentityProcessorVerification.this.createSubscriber(probe);
      }

      @Override public T createElement(int element) {
        return IdentityProcessorVerification.this.createElement(element);
      }

      @Override
      public Publisher createHelperPublisher(long elements) {
        return IdentityProcessorVerification.this.createHelperPublisher(elements);
      }
    };

    publisherVerification = new PublisherVerification(env, publisherReferenceGCTimeoutMillis) {
      @Override
      public Publisher createPublisher(long elements) {
        return IdentityProcessorVerification.this.createPublisher(elements);
      }

      @Override
      public Publisher createFailedPublisher() {
        return IdentityProcessorVerification.this.createFailedPublisher();
      }

      @Override
      public long maxElementsFromPublisher() {
        return IdentityProcessorVerification.this.maxElementsFromPublisher();
      }

      @Override
      public long boundedDepthOfOnNextAndRequestRecursion() {
        return IdentityProcessorVerification.this.boundedDepthOfOnNextAndRequestRecursion();
      }

      @Override
      public boolean skipStochasticTests() {
        return IdentityProcessorVerification.this.skipStochasticTests();
      }
    };
  }

  /**
   * This is the main method you must implement in your test incarnation.
   * It must create a {@link Processor}, which simply forwards all stream elements from its upstream
   * to its downstream. It must be able to internally buffer the given number of elements.
   *
   * @param bufferSize number of elements the processor is required to be able to buffer.
   */
  public abstract Processor createIdentityProcessor(int bufferSize);

  /**
   * By implementing this method, additional TCK tests concerning a "failed" publishers will be run.
   *
   * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription,
   * followed by signalling {@code onError} on it, as specified by Rule 1.9.
   *
   * If you want to ignore these additional tests, return {@code null} from this method.
   */
  public abstract Publisher createFailedPublisher();

  /**
   * Override and return lower value if your Publisher is only able to produce a known number of elements.
   * For example, if it is designed to return at-most-one element, return {@code 1} from this method.
   *
   * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements.
   *
   * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE},
   * which will result in *skipping all tests which require an onComplete to be triggered* (!).
   */
  public long maxElementsFromPublisher() {
    return Long.MAX_VALUE - 1;
  }

  /**
   * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a
   * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of
   * recursive calls to exceed the number returned by this method.
   *
   * @see reactive streams spec, rule 3.3
   * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion()
   */
  public long boundedDepthOfOnNextAndRequestRecursion() {
    return 1;
  }

  /**
   * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}.
   * Stochastic in this case means that the Rule is impossible or infeasible to deterministically verify—
   * usually this means that this test case can yield false positives ("be green") even if for some case,
   * the given implementation may violate the tested behaviour.
   */
  public boolean skipStochasticTests() {
    return false;
  }

  /**
   * Describes the tested implementation in terms of how many subscribers they can support.
   * Some tests require the {@code Publisher} under test to support multiple Subscribers,
   * yet the spec does not require all publishers to be able to do so, thus – if an implementation
   * supports only a limited number of subscribers (e.g. only 1 subscriber, also known as "no fanout")
   * you MUST return that number from this method by overriding it.
   */
  public long maxSupportedSubscribers() {
      return Long.MAX_VALUE;
  }

  /**
   * Override this method and return {@code true} if the {@link Processor} returned by the
   * {@link #createIdentityProcessor(int)} coordinates its {@link Subscriber}s
   * request amounts and only delivers onNext signals if all Subscribers have
   * indicated (via their Subscription#request(long)) they are ready to receive elements.
   */
  public boolean doesCoordinatedEmission() {
    return false;
  }

  ////////////////////// TEST ENV CLEANUP /////////////////////////////////////

  @BeforeMethod
  public void setUp() throws Exception {
    publisherVerification.setUp();
    subscriberVerification.setUp();
  }

  ////////////////////// PUBLISHER RULES VERIFICATION ///////////////////////////

  // A Processor
  //   must obey all Publisher rules on its publishing side
  public Publisher createPublisher(long elements) {
    final Processor processor = createIdentityProcessor(processorBufferSize);
    final Publisher pub = createHelperPublisher(elements);
    pub.subscribe(processor);
    return processor; // we run the PublisherVerification against this
  }

  @Override @Test
  public void required_validate_maxElementsFromPublisher() throws Exception {
    publisherVerification.required_validate_maxElementsFromPublisher();
  }

  @Override @Test
  public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception {
    publisherVerification.required_validate_boundedDepthOfOnNextAndRequestRecursion();
  }

  /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER //////////////////////
  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1

  @Test
  public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable {
    publisherVerification.required_createPublisher1MustProduceAStreamOfExactly1Element();
  }

  @Test
  public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable {
    publisherVerification.required_createPublisher3MustProduceAStreamOfExactly3Elements();
  }

  @Override @Test
  public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
    publisherVerification.required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements();
  }

  @Override @Test
  public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable {
    publisherVerification.required_spec102_maySignalLessThanRequestedAndTerminateSubscription();
  }

  @Override @Test
  public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable {
    publisherVerification.stochastic_spec103_mustSignalOnMethodsSequentially();
  }

  @Override @Test
  public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable {
    publisherVerification.optional_spec104_mustSignalOnErrorWhenFails();
  }

  @Override @Test
  public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable {
    publisherVerification.required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates();
  }

  @Override @Test
  public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable {
    publisherVerification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete();
  }

  @Override @Test
  public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable {
    publisherVerification.untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled();
  }

  @Override @Test
  public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable {
    publisherVerification.required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled();
  }

  @Override @Test
  public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable {
    publisherVerification.untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled();
  }

  @Override @Test
  public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable {
    publisherVerification.untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals();
  }

  @Override @Test
  public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable {
    publisherVerification.untested_spec109_subscribeShouldNotThrowNonFatalThrowable();
  }

  @Override @Test
  public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable {
    publisherVerification.required_spec109_subscribeThrowNPEOnNullSubscriber();
  }

  @Override @Test
  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable {
    publisherVerification.required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe();
  }

  @Override @Test
  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable {
    publisherVerification.required_spec109_mustIssueOnSubscribeForNonNullSubscriber();
  }

  @Override @Test
  public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable {
    publisherVerification.untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice();
  }

  @Override @Test
  public void optional_spec111_maySupportMultiSubscribe() throws Throwable {
    publisherVerification.optional_spec111_maySupportMultiSubscribe();
  }

  @Override @Test
  public void optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals() throws Throwable {
    publisherVerification.optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals();
  }

  @Override @Test
  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable {
    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne();
  }

  @Override @Test
  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable {
    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront();
  }

  @Override @Test
  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable {
    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected();
  }

  @Override @Test
  public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable {
    publisherVerification.required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe();
  }

  @Override @Test
  public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable {
    publisherVerification.required_spec303_mustNotAllowUnboundedRecursion();
  }

  @Override @Test
  public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception {
    publisherVerification.untested_spec304_requestShouldNotPerformHeavyComputations();
  }

  @Override @Test
  public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyComputation() throws Exception {
    publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyComputation();
  }

  @Override @Test
  public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable {
    publisherVerification.required_spec306_afterSubscriptionIsCancelledRequestMustBeNops();
  }

  @Override @Test
  public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable {
    publisherVerification.required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops();
  }

  @Override @Test
  public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable {
    publisherVerification.required_spec309_requestZeroMustSignalIllegalArgumentException();
  }

  @Override @Test
  public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable {
    publisherVerification.required_spec309_requestNegativeNumberMustSignalIllegalArgumentException();
  }
  
  @Override @Test
  public void optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage() throws Throwable {
    publisherVerification.optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage();
  }

  @Override @Test
  public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable {
    publisherVerification.required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling();
  }

  @Override @Test
  public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable {
    publisherVerification.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber();
  }

  @Override @Test
  public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable {
    publisherVerification.required_spec317_mustSupportAPendingElementCountUpToLongMaxValue();
  }

  @Override @Test
  public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable {
    publisherVerification.required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue();
  }

  @Override @Test
  public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable {
    publisherVerification.required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue();
  }


  /**
   * Asks for a {@code Processor} that supports at least 2 {@code Subscriber}s at once and checks if two {@code Subscriber}s
   * receive the same items and a terminal {@code Exception}.
   * 

* If the {@code Processor} requests and/or emits items only when all of its {@code Subscriber}s have requested, * override {@link #doesCoordinatedEmission()} and return {@code true} to indicate this property. *

* Verifies rule: 1.4 with multiple * {@code Subscriber}s. *

* The test is not executed if {@link IdentityProcessorVerification#maxSupportedSubscribers()} is less than 2. *

* If this test fails, the following could be checked within the {@code Processor} implementation: *

    *
  • The {@code TestEnvironment} has large enough timeout specified in case the {@code Processor} has some time-delay behavior.
  • *
  • The {@code Processor} is able to fulfill requests of its {@code Subscriber}s independently of each other's requests or * else override {@link #doesCoordinatedEmission()} and return {@code true} to indicate the test {@code Subscriber}s * both have to request first.
  • *
*/ @Test public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Throwable { optionalMultipleSubscribersTest(2, new Function() { @Override public TestSetup apply(Long aLong) throws Throwable { return new TestSetup(env, processorBufferSize) {{ final ManualSubscriberWithErrorCollection sub1 = new ManualSubscriberWithErrorCollection(env); env.subscribe(processor, sub1); final ManualSubscriberWithErrorCollection sub2 = new ManualSubscriberWithErrorCollection(env); env.subscribe(processor, sub2); final Exception ex = new RuntimeException("Test exception"); if (doesCoordinatedEmission()) { sub1.request(1); sub2.request(1); expectRequest(); final T x = sendNextTFromUpstream(); expectNextElement(sub1, x); expectNextElement(sub2, x); sub1.request(1); sub2.request(1); } else { sub1.request(1); expectRequest(env.defaultTimeoutMillis(), "If the Processor coordinates requests/emissions when having multiple Subscribers" + " at once, please override doesCoordinatedEmission() to return true in this " + "IdentityProcessorVerification to allow this test to pass."); final T x = sendNextTFromUpstream(); expectNextElement(sub1, x, "If the Processor coordinates requests/emissions when having multiple Subscribers" + " at once, please override doesCoordinatedEmission() to return true in this " + "IdentityProcessorVerification to allow this test to pass."); sub1.request(1); // sub1 has received one element, and has one demand pending // sub2 has not yet requested anything } sendError(ex); sub1.expectError(ex); sub2.expectError(ex); env.verifyNoAsyncErrorsNoDelay(); }}; } }); } ////////////////////// SUBSCRIBER RULES VERIFICATION /////////////////////////// // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 // A Processor // must obey all Subscriber rules on its consuming side public Subscriber createSubscriber(final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe probe) { final Processor processor = createIdentityProcessor(processorBufferSize); processor.subscribe( new Subscriber() { private final Promise subs = new Promise(env); @Override public void onSubscribe(final Subscription subscription) { if (env.debugEnabled()) { env.debug(String.format("whiteboxSubscriber::onSubscribe(%s)", subscription)); } if (subs.isCompleted()) subscription.cancel(); // the Probe must also pass subscriber verification probe.registerOnSubscribe(new SubscriberWhiteboxVerification.SubscriberPuppet() { @Override public void triggerRequest(long elements) { subscription.request(elements); } @Override public void signalCancel() { subscription.cancel(); } }); } @Override public void onNext(T element) { if (env.debugEnabled()) { env.debug(String.format("whiteboxSubscriber::onNext(%s)", element)); } probe.registerOnNext(element); } @Override public void onComplete() { if (env.debugEnabled()) { env.debug("whiteboxSubscriber::onComplete()"); } probe.registerOnComplete(); } @Override public void onError(Throwable cause) { if (env.debugEnabled()) { env.debug(String.format("whiteboxSubscriber::onError(%s)", cause)); } probe.registerOnError(cause); } }); return processor; // we run the SubscriberVerification against this } ////////////////////// OTHER RULE VERIFICATION /////////////////////////// // A Processor // must immediately pass on `onError` events received from its upstream to its downstream @Test public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception { new TestSetup(env, processorBufferSize) {{ final ManualSubscriberWithErrorCollection sub = new ManualSubscriberWithErrorCollection(env); env.subscribe(processor, sub); final Exception ex = new RuntimeException("Test exception"); sendError(ex); sub.expectError(ex); // "immediately", i.e. without a preceding request env.verifyNoAsyncErrorsNoDelay(); }}; } /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER ////////////////////// // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 @Test public void required_exerciseWhiteboxHappyPath() throws Throwable { subscriberVerification.required_exerciseWhiteboxHappyPath(); } @Override @Test public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { subscriberVerification.required_spec201_mustSignalDemandViaSubscriptionRequest(); } @Override @Test public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { subscriberVerification.untested_spec202_shouldAsynchronouslyDispatch(); } @Override @Test public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); } @Override @Test public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); } @Override @Test public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { subscriberVerification.untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); } @Override @Test public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { subscriberVerification.required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); } @Override @Test public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { subscriberVerification.untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid(); } @Override @Test public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { subscriberVerification.untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); } @Override @Test public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { subscriberVerification.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); } @Override @Test public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); } @Override @Test public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); } @Override @Test public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); } @Override @Test public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall(); } @Override @Test public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { subscriberVerification.untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); } @Override @Test public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { subscriberVerification.untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation(); } @Override @Test public void untested_spec213_failingOnSignalInvocation() throws Exception { subscriberVerification.untested_spec213_failingOnSignalInvocation(); } @Override @Test public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { subscriberVerification.required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); } @Override @Test public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { subscriberVerification.required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); } @Override @Test public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { subscriberVerification.required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); } @Override @Test public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { subscriberVerification.untested_spec301_mustNotBeCalledOutsideSubscriberContext(); } @Override @Test public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { subscriberVerification.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced(); } @Override @Test public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { subscriberVerification.untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber(); } @Override @Test public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { subscriberVerification.untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError(); } @Override @Test public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { subscriberVerification.untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); } @Override @Test public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { subscriberVerification.untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError(); } @Override @Test public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { subscriberVerification.untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); } /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////// /** * Asks for a {@code Processor} that supports at least 2 {@code Subscriber}s at once and checks requests * from {@code Subscriber}s will eventually lead to requests towards the upstream of the {@code Processor}. *

* If the {@code Processor} requests and/or emits items only when all of its {@code Subscriber}s have requested, * override {@link #doesCoordinatedEmission()} and return {@code true} to indicate this property. *

* Verifies rule: 2.1 with multiple * {@code Subscriber}s. *

* The test is not executed if {@link IdentityProcessorVerification#maxSupportedSubscribers()} is less than 2. *

* If this test fails, the following could be checked within the {@code Processor} implementation: *

    *
  • The {@code TestEnvironment} has large enough timeout specified in case the {@code Processor} has some time-delay behavior.
  • *
  • The {@code Processor} is able to fulfill requests of its {@code Subscriber}s independently of each other's requests or * else override {@link #doesCoordinatedEmission()} and return {@code true} to indicate the test {@code Subscriber}s * both have to request first.
  • *
*/ @Test public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Throwable { optionalMultipleSubscribersTest(2, new Function() { @Override public TestSetup apply(Long subscribers) throws Throwable { return new TestSetup(env, processorBufferSize) {{ ManualSubscriber sub1 = newSubscriber(); sub1.request(20); long totalRequests = expectRequest(); final T x = sendNextTFromUpstream(); expectNextElement(sub1, x); if (totalRequests == 1) { totalRequests += expectRequest(); } final T y = sendNextTFromUpstream(); expectNextElement(sub1, y); if (totalRequests == 2) { totalRequests += expectRequest(); } final ManualSubscriber sub2 = newSubscriber(); // sub1 now has 18 pending // sub2 has 0 pending if (doesCoordinatedEmission()) { sub2.expectNone(); // since sub2 hasn't requested anything yet sub2.request(1); final T z = sendNextTFromUpstream(); expectNextElement(sub1, z); expectNextElement(sub2, z); } else { final T z = sendNextTFromUpstream(); expectNextElement(sub1, z, "If the Processor coordinates requests/emissions when having multiple Subscribers" + " at once, please override doesCoordinatedEmission() to return true in this " + "IdentityProcessorVerification to allow this test to pass."); sub2.expectNone(); // since sub2 hasn't requested anything yet sub2.request(1); expectNextElement(sub2, z); } if (totalRequests == 3) { expectRequest(); } // to avoid error messages during test harness shutdown sendCompletion(); sub1.expectCompletion(env.defaultTimeoutMillis()); sub2.expectCompletion(env.defaultTimeoutMillis()); env.verifyNoAsyncErrorsNoDelay(); }}; } }); } /////////////////////// TEST INFRASTRUCTURE ////////////////////// public void notVerified() { publisherVerification.notVerified(); } public void notVerified(String message) { publisherVerification.notVerified(message); } /** * Test for feature that REQUIRES multiple subscribers to be supported by Publisher. */ public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, Function body) throws Throwable { if (requiredSubscribersSupport > maxSupportedSubscribers()) notVerified(String.format("The Publisher under test only supports %d subscribers, while this test requires at least %d to run.", maxSupportedSubscribers(), requiredSubscribersSupport)); else body.apply(requiredSubscribersSupport); } public abstract class TestSetup extends ManualPublisher { final private ManualSubscriber tees; // gives us access to an infinite stream of T values private Set seenTees = new HashSet(); final Processor processor; public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException { super(env); tees = env.newManualSubscriber(createHelperPublisher(Long.MAX_VALUE)); processor = createIdentityProcessor(testBufferSize); subscribe(processor); } public ManualSubscriber newSubscriber() throws InterruptedException { return env.newManualSubscriber(processor); } public T nextT() throws InterruptedException { final T t = tees.requestNextElement(); if (seenTees.contains(t)) { env.flop(String.format("Helper publisher illegally produced the same element %s twice", t)); } seenTees.add(t); return t; } public void expectNextElement(ManualSubscriber sub, T expected) throws InterruptedException { final T elem = sub.nextElement(String.format("timeout while awaiting %s", expected)); if (!elem.equals(expected)) { env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected)); } } public void expectNextElement(ManualSubscriber sub, T expected, String errorMessageAddendum) throws InterruptedException { final T elem = sub.nextElement(String.format("timeout while awaiting %s. %s", expected, errorMessageAddendum)); if (!elem.equals(expected)) { env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected)); } } public T sendNextTFromUpstream() throws InterruptedException { final T x = nextT(); sendNext(x); return x; } } public class ManualSubscriberWithErrorCollection extends ManualSubscriberWithSubscriptionSupport { Promise error; public ManualSubscriberWithErrorCollection(TestEnvironment env) { super(env); error = new Promise(env); } @Override public void onError(Throwable cause) { error.complete(cause); } public void expectError(Throwable cause) throws InterruptedException { expectError(cause, env.defaultTimeoutMillis()); } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException { error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream"); if (!cause.equals(error.value())) { env.flop(String.format("Expected error %s but got %s", cause, error.value())); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy