001package org.reactivestreams.tck;
002
003import org.reactivestreams.api.Processor;
004import org.reactivestreams.spi.Publisher;
005import org.reactivestreams.spi.Subscriber;
006import org.reactivestreams.spi.Subscription;
007import org.reactivestreams.tck.TestEnvironment.ManualPublisher;
008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport;
010import org.reactivestreams.tck.TestEnvironment.Promise;
011import org.testng.annotations.Test;
012
013import java.util.HashSet;
014import java.util.Set;
015
016public abstract class IdentityProcessorVerification<T> {
017
018  private final TestEnvironment env;
019
020  ////////////////////// DELEGATED TO SPECS //////////////////////
021
022  // for delegating tests
023  private final SubscriberVerification<T> subscriberVerification;
024
025  // for delegating tests
026  private final PublisherVerification<T> publisherVerification;
027
028  ////////////////// END OF DELEGATED TO SPECS //////////////////
029
030
031  private final int testBufferSize;
032
033  /**
034   * Test class must specify the expected time it takes for the publisher to
035   * shut itself down when the the last downstream Subscription is cancelled.
036   * Used by `publisherSubscribeWhenInShutDownStateMustTriggerOnErrorAndNotOnSubscribe`.
037   */
038  public IdentityProcessorVerification(TestEnvironment env, long publisherShutdownTimeoutMillis) {
039    this(env, publisherShutdownTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE);
040  }
041
042  public IdentityProcessorVerification(final TestEnvironment env, long publisherShutdownTimeoutMillis, int testBufferSize) {
043    this.env = env;
044    this.testBufferSize = testBufferSize;
045
046    this.subscriberVerification = new SubscriberVerification<T>(env) {
047      @Override
048      Subscriber<T> createSubscriber(SubscriberProbe<T> probe) {
049        return IdentityProcessorVerification.this.createSubscriber(probe);
050      }
051
052      @Override
053      Publisher<T> createHelperPublisher(int elements) {
054        return IdentityProcessorVerification.this.createHelperPublisher(elements);
055      }
056    };
057
058    publisherVerification = new PublisherVerification<T>(env, publisherShutdownTimeoutMillis) {
059      @Override
060      public Publisher<T> createPublisher(int elements) {
061        return IdentityProcessorVerification.this.createPublisher(elements);
062      }
063
064      @Override
065      public Publisher<T> createCompletedStatePublisher() {
066        return IdentityProcessorVerification.this.createCompletedStatePublisher();
067      }
068
069      @Override
070      public Publisher<T> createErrorStatePublisher() {
071        return IdentityProcessorVerification.this.createErrorStatePublisher();
072      }
073
074    };
075  }
076
077  /**
078   * This is the main method you must implement in your test incarnation.
079   * It must create a Processor, which simply forwards all stream elements from its upstream
080   * to its downstream. It must be able to internally buffer the given number of elements.
081   */
082  public abstract Processor<T, T> createIdentityProcessor(int bufferSize);
083
084  /**
085   * Helper method required for running the Publisher rules against a Processor.
086   * It must create a Publisher for a stream with exactly the given number of elements.
087   * If `elements` is zero the produced stream must be infinite.
088   * The stream must not produce the same element twice (in case of an infinite stream this requirement
089   * is relaxed to only apply to the elements that are actually requested during all tests).
090   */
091  public abstract Publisher<T> createHelperPublisher(int elements);
092
093  /**
094   * Return a Publisher in {@code completed} state in order to run additional tests on it,
095   * or {@code null} in order to skip them.
096   */
097  public abstract Publisher<T> createCompletedStatePublisher();
098
099  /**
100   * Return a Publisher in {@code error} state in order to run additional tests on it,
101   * or {@code null} in order to skip them.
102   */
103  public abstract Publisher<T> createErrorStatePublisher();
104
105  ////////////////////// PUBLISHER RULES VERIFICATION ///////////////////////////
106
107  // A Processor
108  //   must obey all Publisher rules on its producing side
109  public Publisher<T> createPublisher(int elements) {
110    Processor<T, T> processor = createIdentityProcessor(testBufferSize);
111    Publisher<T> pub = createHelperPublisher(elements);
112    pub.subscribe(processor.getSubscriber());
113    return processor.getPublisher(); // we run the PublisherVerification against this
114  }
115
116  // A Publisher
117  //   must support a pending element count up to 2^63-1 (Long.MAX_VALUE) and provide for overflow protection
118  @Test
119  public void mustSupportAPendingElementCountUpToLongMaxValue() throws Exception {
120    new TestSetup(env, testBufferSize) {{
121      TestEnvironment.ManualSubscriber<T> sub = newSubscriber();
122      sub.requestMore(Integer.MAX_VALUE);
123      sub.requestMore(Integer.MAX_VALUE);
124      sub.requestMore(2); // if the Subscription only keeps an int counter without overflow protection it will now be at zero
125
126      final T x = sendNextTFromUpstream();
127      expectNextElement(sub, x);
128
129      final T y = sendNextTFromUpstream();
130      expectNextElement(sub, y);
131
132      // to avoid error messages during test harness shutdown
133      sendCompletion();
134      sub.expectCompletion(env.defaultTimeoutMillis());
135
136      env.verifyNoAsyncErrors();
137    }};
138  }
139
140  @Test
141  public void createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable {
142    publisherVerification.createPublisher3MustProduceAStreamOfExactly3Elements();
143  }
144
145  @Test
146  public void mustCallOnCompleteOnASubscriberAfterHavingProducedTheFinalStreamElementToIt() throws Throwable {
147    publisherVerification.mustCallOnCompleteOnASubscriberAfterHavingProducedTheFinalStreamElementToIt();
148  }
149
150  @Test
151  public void mustStartProducingWithTheOldestStillAvailableElementForASubscriber() {
152    publisherVerification.mustStartProducingWithTheOldestStillAvailableElementForASubscriber();
153  }
154
155  // A Publisher
156  //   must call `onError` on all its subscribers if it encounters a non-recoverable error
157  @Test
158  public void mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Exception {
159    new TestSetup(env, testBufferSize) {
160      {
161        ManualSubscriberWithErrorCollection<T> sub1 = new ManualSubscriberWithErrorCollection<T>(env);
162        env.subscribe(processor.getPublisher(), sub1);
163        ManualSubscriberWithErrorCollection<T> sub2 = new ManualSubscriberWithErrorCollection<T>(env);
164        env.subscribe(processor.getPublisher(), sub2);
165
166        sub1.requestMore(1);
167        expectRequestMore();
168        final T x = sendNextTFromUpstream();
169        expectNextElement(sub1, x);
170        sub1.requestMore(1);
171
172        // sub1 now has received and element and has 1 pending
173        // sub2 has not yet requested anything
174
175        Exception ex = new RuntimeException("Test exception");
176        sendError(ex);
177        sub1.expectError(ex);
178        sub2.expectError(ex);
179
180        env.verifyNoAsyncErrors();
181      }
182    };
183  }
184
185  @Test
186  public void mustNotCallOnCompleteOrOnErrorMoreThanOncePerSubscriber() {
187    publisherVerification.mustNotCallOnCompleteOrOnErrorMoreThanOncePerSubscriber();
188  }
189
190  ////////////////////// SUBSCRIBER RULES VERIFICATION ///////////////////////////
191
192  // A Processor
193  //   must obey all Subscriber rules on its consuming side
194  public Subscriber<T> createSubscriber(final SubscriberVerification.SubscriberProbe<T> probe) {
195    Processor<T, T> processor = createIdentityProcessor(testBufferSize);
196    processor.getPublisher().subscribe(
197        new Subscriber<T>() {
198          public void onSubscribe(final Subscription subscription) {
199            probe.registerOnSubscribe(
200                new SubscriberVerification.SubscriberPuppet() {
201                  public void triggerShutdown() {
202                    subscription.cancel();
203                  }
204
205                  public void triggerRequestMore(int elements) {
206                    subscription.requestMore(elements);
207                  }
208
209                  public void triggerCancel() {
210                    subscription.cancel();
211                  }
212                });
213          }
214
215          public void onNext(T element) {
216            probe.registerOnNext(element);
217          }
218
219          public void onComplete() {
220            probe.registerOnComplete();
221          }
222
223          public void onError(Throwable cause) {
224            probe.registerOnError(cause);
225          }
226        });
227
228    return processor.getSubscriber(); // we run the SubscriberVerification against this
229  }
230
231  ////////////////////// OTHER SPEC RULE VERIFICATION ///////////////////////////
232
233  // A Processor
234  //   must cancel its upstream Subscription if its last downstream Subscription has been cancelled
235  @Test
236  public void mustCancelItsUpstreamSubscriptionIfItsLastDownstreamSubscriptionHasBeenCancelled() throws Exception {
237    new TestSetup(env, testBufferSize) {{
238      TestEnvironment.ManualSubscriber<T> sub = newSubscriber();
239      sub.cancel();
240      expectCancelling();
241
242      env.verifyNoAsyncErrors();
243    }};
244  }
245
246  // A Processor
247  //   must immediately pass on `onError` events received from its upstream to its downstream
248  @Test
249  public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception {
250    new TestSetup(env, testBufferSize) {{
251      ManualSubscriberWithErrorCollection<T> sub = new ManualSubscriberWithErrorCollection<T>(env);
252      env.subscribe(processor.getPublisher(), sub);
253
254      Exception ex = new RuntimeException("Test exception");
255      sendError(ex);
256      sub.expectError(ex); // "immediately", i.e. without a preceding requestMore
257
258      env.verifyNoAsyncErrors();
259    }};
260  }
261
262  // A Processor
263  //   must be prepared to receive incoming elements from its upstream even if a downstream subscriber has not requested anything yet
264  @Test
265  public void mustBePreparedToReceiveIncomingElementsFromItsUpstreamEvenIfADownstreamSubscriberHasNotRequestedYet() throws Exception {
266    new TestSetup(env, testBufferSize) {{
267        ManualSubscriber<T> sub = newSubscriber();
268        final T x = sendNextTFromUpstream();
269        sub.expectNone(50);
270        final T y = sendNextTFromUpstream();
271        sub.expectNone(50);
272
273        sub.requestMore(2);
274        sub.expectNext(x);
275        sub.expectNext(y);
276
277        // to avoid error messages during test harness shutdown
278        sendCompletion();
279        sub.expectCompletion(env.defaultTimeoutMillis());
280
281        env.verifyNoAsyncErrors();
282      }};
283  }
284
285  /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER //////////////////////
286
287  @Test
288  public void exerciseHappyPath() throws InterruptedException {
289    subscriberVerification.exerciseHappyPath();
290  }
291
292  @Test
293  public void onSubscribeAndOnNextMustAsynchronouslyScheduleAnEvent() {
294    subscriberVerification.onSubscribeAndOnNextMustAsynchronouslyScheduleAnEvent();
295  }
296
297  @Test
298  public void onCompleteAndOnErrorMustAsynchronouslyScheduleAnEvent() {
299    subscriberVerification.onCompleteAndOnErrorMustAsynchronouslyScheduleAnEvent();
300  }
301
302  @Test
303  public void mustNotAcceptAnOnSubscribeEventIfItAlreadyHasAnActiveSubscription() throws InterruptedException {
304    subscriberVerification.mustNotAcceptAnOnSubscribeEventIfItAlreadyHasAnActiveSubscription();
305  }
306
307  @Test
308  public void mustCallSubscriptionCancelDuringShutdownIfItStillHasAnActiveSubscription() throws InterruptedException {
309    subscriberVerification.mustCallSubscriptionCancelDuringShutdownIfItStillHasAnActiveSubscription();
310  }
311
312  @Test
313  public void mustEnsureThatAllCallsOnASubscriptionTakePlaceFromTheSameThreadOrProvideExternalSync() {
314    subscriberVerification.mustEnsureThatAllCallsOnASubscriptionTakePlaceFromTheSameThreadOrProvideExternalSync();
315  }
316
317  @Test
318  public void mustBePreparedToReceiveOneOrMoreOnNextEventsAfterHavingCalledSubscriptionCancel() throws InterruptedException {
319    subscriberVerification.mustBePreparedToReceiveOneOrMoreOnNextEventsAfterHavingCalledSubscriptionCancel();
320  }
321
322  @Test
323  public void mustBePreparedToReceiveAnOnCompleteEventWithAPrecedingSubscriptionRequestMore() throws InterruptedException {
324    subscriberVerification.mustBePreparedToReceiveAnOnCompleteEventWithAPrecedingSubscriptionRequestMore();
325  }
326
327  @Test
328  public void mustBePreparedToReceiveAnOnCompleteEventWithoutAPrecedingSubscriptionRequestMore() throws InterruptedException {
329    subscriberVerification.mustBePreparedToReceiveAnOnCompleteEventWithoutAPrecedingSubscriptionRequestMore();
330  }
331
332  @Test
333  public void mustBePreparedToReceiveAnOnErrorEventWithAPrecedingSubscriptionRequestMore() throws InterruptedException {
334    subscriberVerification.mustBePreparedToReceiveAnOnErrorEventWithAPrecedingSubscriptionRequestMore();
335  }
336
337  @Test
338  public void mustBePreparedToReceiveAnOnErrorEventWithoutAPrecedingSubscriptionRequestMore() throws InterruptedException {
339    subscriberVerification.mustBePreparedToReceiveAnOnErrorEventWithoutAPrecedingSubscriptionRequestMore();
340  }
341
342  @Test
343  public void mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() {
344    subscriberVerification.mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents();
345  }
346
347
348  /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER //////////////////////
349
350  @Test
351  public void publisherSubscribeWhenCompletedMustTriggerOnCompleteAndNotOnSubscribe() throws Throwable {
352    publisherVerification.publisherSubscribeWhenCompletedMustTriggerOnCompleteAndNotOnSubscribe();
353  }
354
355  @Test
356  public void publisherSubscribeWhenInErrorStateMustTriggerOnErrorAndNotOnSubscribe() throws Throwable {
357    publisherVerification.publisherSubscribeWhenInErrorStateMustTriggerOnErrorAndNotOnSubscribe();
358  }
359
360  @Test
361  public void publisherSubscribeWhenInShutDownStateMustTriggerOnErrorAndNotOnSubscribe() throws Throwable {
362    publisherVerification.publisherSubscribeWhenInShutDownStateMustTriggerOnErrorAndNotOnSubscribe();
363  }
364
365  @Test
366  public void publisherSubscribeWhenActiveMustCallOnSubscribeFirst() throws Throwable {
367    publisherVerification.publisherSubscribeWhenActiveMustCallOnSubscribeFirst();
368  }
369
370  @Test
371  public void publisherSubscribeWhenActiveMustRejectDoubleSubscription() throws Throwable {
372    publisherVerification.publisherSubscribeWhenActiveMustRejectDoubleSubscription();
373  }
374
375  @Test
376  public void subscriptionRequestMoreWhenCancelledMustIgnoreTheCall() throws Throwable {
377    publisherVerification.subscriptionRequestMoreWhenCancelledMustIgnoreTheCall();
378  }
379
380  @Test
381  public void subscriptionRequestMoreMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
382    publisherVerification.subscriptionRequestMoreMustResultInTheCorrectNumberOfProducedElements();
383  }
384
385  @Test
386  public void subscriptionRequestMoreMustThrowIfArgumentIsNonPositive() throws Throwable {
387    publisherVerification.subscriptionRequestMoreMustThrowIfArgumentIsNonPositive();
388  }
389
390  @Test
391  public void subscriptionCancelWhenCancelledMustIgnoreCall() throws Throwable {
392    publisherVerification.subscriptionCancelWhenCancelledMustIgnoreCall();
393  }
394
395  @Test
396  public void onSubscriptionCancelThePublisherMustEventuallyCeaseToCallAnyMethodsOnTheSubscriber() throws Throwable {
397    publisherVerification.onSubscriptionCancelThePublisherMustEventuallyCeaseToCallAnyMethodsOnTheSubscriber();
398  }
399
400  @Test
401  public void onSubscriptionCancelThePublisherMustEventuallyDropAllReferencesToTheSubscriber() throws Throwable {
402    publisherVerification.onSubscriptionCancelThePublisherMustEventuallyDropAllReferencesToTheSubscriber();
403  }
404
405  @Test
406  public void mustNotCallOnNextAfterHavingIssuedAnOnCompleteOrOnErrorCallOnASubscriber() {
407    publisherVerification.mustNotCallOnNextAfterHavingIssuedAnOnCompleteOrOnErrorCallOnASubscriber();
408  }
409
410  @Test
411  public void mustProduceTheSameElementsInTheSameSequenceForAllItsSubscribers() throws Throwable {
412    publisherVerification.mustProduceTheSameElementsInTheSameSequenceForAllItsSubscribers();
413  }
414
415
416  /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////
417
418  @Test // trigger `requestFromUpstream` for elements that have been requested 'long ago'
419  public void mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Exception {
420    new TestSetup(env, testBufferSize) {{
421      TestEnvironment.ManualSubscriber<T> sub1 = newSubscriber();
422      sub1.requestMore(20);
423
424      int totalRequests = expectRequestMore();
425      final T x = sendNextTFromUpstream();
426      expectNextElement(sub1, x);
427
428      if (totalRequests == 1) {
429        totalRequests += expectRequestMore();
430      }
431      final T y = sendNextTFromUpstream();
432      expectNextElement(sub1, y);
433
434      if (totalRequests == 2) {
435        totalRequests += expectRequestMore();
436      }
437
438      TestEnvironment.ManualSubscriber<T> sub2 = newSubscriber();
439
440      // sub1 now has 18 pending
441      // sub2 has 0 pending
442
443      final T z = sendNextTFromUpstream();
444      expectNextElement(sub1, z);
445      sub2.expectNone(); // since sub2 hasn't requested anything yet
446
447      sub2.requestMore(1);
448      expectNextElement(sub2, z);
449
450      if (totalRequests == 3) {
451        expectRequestMore();
452      }
453
454      // to avoid error messages during test harness shutdown
455      sendCompletion();
456      sub1.expectCompletion(env.defaultTimeoutMillis());
457      sub2.expectCompletion(env.defaultTimeoutMillis());
458
459      env.verifyNoAsyncErrors();
460    }};
461  }
462
463  @Test // unblock the stream if a 'blocking' subscription has been cancelled
464  @SuppressWarnings("unchecked")
465  public void mustUnblockTheStreamIfABlockingSubscriptionHasBeenCancelled() throws InterruptedException {
466    new TestSetup(env, testBufferSize) {{
467      TestEnvironment.ManualSubscriber<T> sub1 = newSubscriber();
468      TestEnvironment.ManualSubscriber<T> sub2 = newSubscriber();
469
470      sub1.requestMore(testBufferSize + 1);
471      int pending = 0;
472      int sent = 0;
473      final T[] tees = (T[]) new Object[testBufferSize];
474      while (sent < testBufferSize) {
475        if (pending == 0) {
476          pending = expectRequestMore();
477        }
478        tees[sent] = nextT();
479        sendNext(tees[sent]);
480        sent += 1;
481        pending -= 1;
482      }
483
484      expectNoRequestMore(); // because we only have buffer size testBufferSize and sub2 hasn't seen the first value yet
485      sub2.cancel(); // must "unblock"
486
487      expectRequestMore();
488      for (T tee : tees) {
489        expectNextElement(sub1, tee);
490      }
491
492      sendCompletion();
493      sub1.expectCompletion(env.defaultTimeoutMillis());
494
495      env.verifyNoAsyncErrors();
496    }};
497  }
498
499  /////////////////////// TEST INFRASTRUCTURE //////////////////////
500
501  abstract class TestSetup extends ManualPublisher<T> {
502    private TestEnvironment.ManualSubscriber<T> tees; // gives us access to an infinite stream of T values
503    private Set<T> seenTees = new HashSet<T>();
504
505    final Processor<T, T> processor;
506    final int testBufferSize;
507
508    public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException {
509      super(env);
510      this.testBufferSize = testBufferSize;
511      tees = env.newManualSubscriber(createHelperPublisher(0));
512      processor = createIdentityProcessor(testBufferSize);
513      subscribe(processor.getSubscriber());
514    }
515
516    public TestEnvironment.ManualSubscriber<T> newSubscriber() throws InterruptedException {
517      return env.newManualSubscriber(processor.getPublisher());
518    }
519
520    public T nextT() throws InterruptedException {
521      final T t = tees.requestNextElement();
522      if (seenTees.contains(t)) {
523        env.flop("Helper publisher illegally produced the same element " + t + " twice");
524      }
525      seenTees.add(t);
526      return t;
527    }
528
529    public void expectNextElement(TestEnvironment.ManualSubscriber<T> sub, T expected) throws InterruptedException {
530      final T elem = sub.nextElement("timeout while awaiting " + expected);
531      if (!elem.equals(expected)) {
532        env.flop("Received `onNext(" + elem + ")` on downstream but expected `onNext(" + expected + ")`");
533      }
534    }
535
536    public T sendNextTFromUpstream() throws InterruptedException {
537      final T x = nextT();
538      sendNext(x);
539      return x;
540    }
541  }
542
543  private class ManualSubscriberWithErrorCollection<A> extends ManualSubscriberWithSubscriptionSupport<A> {
544    TestEnvironment.Promise<Throwable> error;
545
546    public ManualSubscriberWithErrorCollection(TestEnvironment env) {
547      super(env);
548      error = new Promise<Throwable>(env);
549    }
550
551    @Override
552    public void onError(Throwable cause) {
553      error.complete(cause);
554    }
555
556    public void expectError(Throwable cause) throws InterruptedException {
557      expectError(cause, env.defaultTimeoutMillis());
558    }
559
560    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
561    public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException {
562      error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream");
563      if (!error.value().equals(cause)) {
564        env.flop("Expected error " + cause + " but got " + error.value());
565      }
566    }
567  }
568}