001package org.reactivestreams.tck;
002
003import org.reactivestreams.spi.Publisher;
004import org.reactivestreams.spi.Subscription;
005import org.reactivestreams.tck.support.Optional;
006import org.testng.SkipException;
007import org.testng.annotations.Test;
008
009import java.lang.ref.ReferenceQueue;
010import java.lang.ref.WeakReference;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.Collections;
014import java.util.List;
015import java.util.concurrent.atomic.AtomicBoolean;
016
017import static org.reactivestreams.tck.TestEnvironment.*;
018import static org.testng.Assert.assertEquals;
019import static org.testng.Assert.assertTrue;
020
021public abstract class PublisherVerification<T> {
022
023  private final TestEnvironment env;
024  private final long publisherShutdownTimeoutMillis;
025
026  public PublisherVerification(TestEnvironment env, long publisherShutdownTimeoutMillis) {
027    this.env = env;
028    this.publisherShutdownTimeoutMillis = publisherShutdownTimeoutMillis;
029  }
030
031  /**
032   * This is the main method you must implement in your test incarnation.
033   * It must create a Publisher for a stream with exactly the given number of elements.
034   * If `elements` is zero the produced stream must be infinite.
035   */
036  public abstract Publisher<T> createPublisher(int elements);
037
038  /**
039   * Return a Publisher in {@code completed} state in order to run additional tests on it,
040   * or {@code null} in order to skip them.
041   */
042  public abstract Publisher<T> createCompletedStatePublisher();
043
044  /**
045   * Return a Publisher in {@code error} state in order to run additional tests on it,
046   * or {@code null} in order to skip them.
047   */
048  public abstract Publisher<T> createErrorStatePublisher();
049
050  ////////////////////// TEST SETUP VERIFICATION ///////////////////////////
051
052  @Test
053  public void createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable {
054    activePublisherTest(3, new PublisherTestRun<T>() {
055      @Override
056      public void run(Publisher<T> pub) throws InterruptedException {
057        TestEnvironment.ManualSubscriber<T> sub = env.newManualSubscriber(pub);
058        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub));
059        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 1 element", pub));
060        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 2 elements", pub));
061        sub.requestEndOfStream();
062      }
063
064      Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, TestEnvironment.ManualSubscriber<T> sub) throws InterruptedException {
065        return sub.requestNextElementOrEndOfStream("Timeout while waiting for next element from Publisher" + pub);
066      }
067
068    });
069  }
070
071
072  ////////////////////// SPEC RULE VERIFICATION ///////////////////////////
073
074  // Publisher::subscribe(Subscriber)
075  //   when Publisher is in `completed` state
076  //     must not call `onSubscribe` on the given Subscriber
077  //     must trigger a call to `onComplete` on the given Subscriber
078  @Test
079  public void publisherSubscribeWhenCompletedMustTriggerOnCompleteAndNotOnSubscribe() throws Throwable {
080    completedPublisherTest(new PublisherTestRun<T>() {
081      @Override
082      public void run(final Publisher<T> pub) throws InterruptedException {
083        final Latch latch = new Latch(env);
084        pub.subscribe(
085            new TestEnvironment.TestSubscriber<T>(env) {
086              public void onComplete() {
087                latch.assertOpen(String.format("Publisher %s called `onComplete` twice on new Subscriber", pub));
088                latch.close();
089              }
090
091              public void onSubscribe(Subscription subscription) {
092                env.flop(String.format("Publisher created by `createCompletedStatePublisher()` (%s) called `onSubscribe` on new Subscriber", pub));
093              }
094            });
095
096        latch.expectClose(env.defaultTimeoutMillis(), String.format("Publisher created by `createPublisher(0)` (%s) did not call `onComplete` on new Subscriber", pub));
097        Thread.sleep(env.defaultTimeoutMillis()); // wait for the Publisher to potentially call 'onSubscribe' or `onNext` which would trigger an async error
098      }
099    });
100  }
101
102  // Publisher::subscribe(Subscriber)
103  //   when Publisher is in `error` state
104  //     must not call `onSubscribe` on the given Subscriber
105  //     must trigger a call to `onError` on the given Subscriber
106  @Test
107  public void publisherSubscribeWhenInErrorStateMustTriggerOnErrorAndNotOnSubscribe() throws Throwable {
108    errorPublisherTest(new PublisherTestRun<T>() {
109      @Override
110      public void run(final Publisher<T> pub) throws InterruptedException {
111        final Latch latch = new Latch(env);
112        pub.subscribe(
113            new TestEnvironment.TestSubscriber<T>(env) {
114              public void onError(Throwable cause) {
115                latch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub));
116                latch.close();
117              }
118            });
119
120        latch.expectClose(env.defaultTimeoutMillis(), String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub));
121        Thread.sleep(env.defaultTimeoutMillis()); // wait for the Publisher to potentially call 'onSubscribe' or `onNext` which would trigger an async error
122
123      }
124    });
125  }
126
127  // Publisher::subscribe(Subscriber)
128  //   when Publisher is in `shut-down` state
129  //     must not call `onSubscribe` on the given Subscriber
130  //     must trigger a call to `onError` with a `java.lang.IllegalStateException` on the given Subscriber
131  // Subscription::cancel
132  //   when Subscription is not cancelled
133  //     the Publisher must shut itself down if the given Subscription is the last downstream Subscription
134  @Test
135  public void publisherSubscribeWhenInShutDownStateMustTriggerOnErrorAndNotOnSubscribe() throws Throwable {
136    activePublisherTest(3, new PublisherTestRun<T>() {
137      @Override
138      public void run(final Publisher<T> pub) throws InterruptedException {
139        TestEnvironment.ManualSubscriber<T> sub = env.newManualSubscriber(pub);
140        sub.cancel();
141
142        // we cannot meaningfully test whether the publisher has really shut down
143        // however, we can tests whether it reacts to new subscription requests with `onError`
144        // after a while
145        Thread.sleep(publisherShutdownTimeoutMillis);
146
147        final Latch latch = new Latch(env);
148        pub.subscribe(
149            new TestEnvironment.TestSubscriber<T>(env) {
150              public void onError(Throwable cause) {
151                latch.assertOpen(String.format("shut-down-state Publisher %s called `onError` twice on new Subscriber", pub));
152                latch.close();
153              }
154            });
155        latch.expectClose(env.defaultTimeoutMillis(), String.format("shut-down-state Publisher %s did not call `onError` on new Subscriber", pub));
156        Thread.sleep(env.defaultTimeoutMillis());// wait for the Publisher to potentially call 'onSubscribe' or `onNext` which would trigger an async error
157      }
158    });
159  }
160
161  // Publisher::subscribe(Subscriber)
162  //   when Publisher is neither in `completed` nor `error` state
163  //     must trigger a call to `onSubscribe` on the given Subscriber if the Subscription is to be accepted
164  @Test
165  public void publisherSubscribeWhenActiveMustCallOnSubscribeFirst() throws Throwable {
166    activePublisherTest(1, new PublisherTestRun<T>() {
167      @Override
168      public void run(Publisher<T> pub) throws InterruptedException {
169        final Latch latch = new Latch(env);
170        final Subscription[] sub = {null};
171        pub.subscribe(
172            new TestSubscriber<T>(env) {
173              public void onSubscribe(Subscription subscription) {
174                latch.close();
175                sub[0] = subscription;
176              }
177            });
178
179        latch.expectClose(env.defaultTimeoutMillis(), String.format("Active Publisher %s did not call `onSubscribe` on new subscription request", pub));
180        sub[0].cancel();
181      }
182    });
183  }
184
185  // Publisher::subscribe(Subscriber)
186  //   when Publisher is neither in `completed` nor `error` state
187  //     must trigger a call to `onError` on the given Subscriber if the Subscription is to be rejected
188  //     must reject the Subscription if the same Subscriber already has an active Subscription
189  @Test
190  public void publisherSubscribeWhenActiveMustRejectDoubleSubscription() throws Throwable {
191    activePublisherTest(1, new PublisherTestRun<T>() {
192      @Override
193      public void run(Publisher<T> pub) throws InterruptedException {
194        final Latch latch = new Latch(env);
195        final Promise<Throwable> errorCause = new Promise<Throwable>(env);
196        TestSubscriber<T> sub = new TestSubscriber<T>(env) {
197          public void onSubscribe(Subscription subscription) { latch.close(); }
198          public void onError(Throwable cause) { errorCause.complete(cause); }
199        };
200        pub.subscribe(sub);
201        latch.expectClose(env.defaultTimeoutMillis(), "Active Publisher "+ pub+" did not call `onSubscribe` on first subscription request");
202        errorCause.assertUncompleted("Active Publisher "+ pub+" unexpectedly called `onError` on first subscription request");
203
204        latch.reOpen();
205        pub.subscribe(sub);
206        errorCause.expectCompletion(env.defaultTimeoutMillis(), "Active Publisher "+ pub+" did not call `onError` on double subscription request");
207        if(!IllegalStateException.class.isInstance(errorCause.value()))
208          env.flop("Publisher " + pub + " called `onError` with " + errorCause.value() + " rather than an `IllegalStateException` on double subscription request");
209        latch.assertOpen("Active Publisher "+ pub+" unexpectedly called `onSubscribe` on double subscription request");
210
211      }
212    });
213  }
214
215  // Subscription::requestMore(Int)
216  //   when Subscription is cancelled
217  //     must ignore the call
218  @Test
219  public void subscriptionRequestMoreWhenCancelledMustIgnoreTheCall() throws Throwable {
220    activePublisherTest(1, new PublisherTestRun<T>() {
221      @Override
222      public void run(Publisher<T> pub) throws InterruptedException {
223        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
224              sub.subscription.value().cancel();
225              sub.subscription.value().requestMore(1); // must not throw
226      }
227    });
228    }
229
230  // Subscription::requestMore(Int)
231  //   when Subscription is not cancelled
232  //     must register the given number of additional elements to be produced to the respective subscriber
233  // A Publisher
234  //   must not call `onNext`
235  //    more times than the total number of elements that was previously requested with Subscription::requestMore by the corresponding subscriber
236  @Test
237  public void subscriptionRequestMoreMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
238    activePublisherTest(5, new PublisherTestRun<T>() {
239      @Override
240      public void run(Publisher<T> pub) throws InterruptedException {
241
242        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
243
244        sub.expectNone("Publisher " + pub + " produced value before the first `requestMore`: ");
245        sub.requestMore(1);
246        sub.nextElement("Publisher " + pub + " produced no element after first `requestMore`");
247        sub.expectNone("Publisher " + pub + " produced unrequested: ");
248
249        sub.requestMore(1);
250        sub.requestMore(2);
251        sub.nextElements(3, env.defaultTimeoutMillis(), "Publisher " + pub + " produced less than 3 elements after two respective `requestMore` calls");
252
253        sub.expectNone("Publisher " + pub + "produced unrequested ");
254      }
255    });
256  }
257
258  // Subscription::requestMore(Int)
259  //   when Subscription is not cancelled
260  //     must throw a `java.lang.IllegalArgumentException` if the argument is <= 0
261  @Test
262  public void subscriptionRequestMoreMustThrowIfArgumentIsNonPositive() throws Throwable {
263    activePublisherTest(1, new PublisherTestRun<T>() {
264      @Override
265      public void run(Publisher<T> pub) throws Throwable {
266
267        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
268        env.expectThrowingOf(
269            IllegalArgumentException.class,
270            "Calling `requestMore(-1)` a subscription to " + pub + " did not fail with an `IllegalArgumentException`",
271            new Runnable() {
272              @Override
273              public void run() {
274                sub.subscription.value().requestMore(-1);
275              }
276            });
277
278        env.expectThrowingOf(
279            IllegalArgumentException.class,
280            "Calling `requestMore(0)` a subscription to " + pub + " did not fail with an `IllegalArgumentException`",
281            new Runnable() {
282              @Override
283              public void run() {
284                sub.subscription.value().requestMore(0);
285              }
286            });
287        sub.cancel();
288      }
289    });
290  }
291
292  // Subscription::cancel
293  //   when Subscription is cancelled
294  //     must ignore the call
295  @Test
296  public void subscriptionCancelWhenCancelledMustIgnoreCall() throws Throwable {
297    activePublisherTest(1, new PublisherTestRun<T>() {
298      @Override
299      public void run(Publisher<T> pub) throws InterruptedException {
300        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
301        sub.subscription.value().cancel(); // first time must succeed
302        sub.subscription.value().cancel(); // the second time must not throw
303      }
304    });
305  }
306
307  // Subscription::cancel
308  //   when Subscription is not cancelled
309  //     the Publisher must eventually cease to call any methods on the corresponding subscriber
310  @Test
311  public void onSubscriptionCancelThePublisherMustEventuallyCeaseToCallAnyMethodsOnTheSubscriber() throws Throwable {
312    activePublisherTest(0, new PublisherTestRun<T>() {
313      @Override
314      public void run(Publisher<T> pub) throws InterruptedException {
315        // infinite stream
316
317        final AtomicBoolean drop = new AtomicBoolean(true);
318        ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {
319          public void onNext(T element) {
320            if (!drop.get()) {
321              super.onNext(element);
322            }
323          }
324        };
325        env.subscribe(pub, sub);
326        sub.requestMore(Integer.MAX_VALUE);
327        sub.cancel();
328        Thread.sleep(env.defaultTimeoutMillis());
329
330        drop.set(false);// "switch on" element collection
331        sub.expectNone(env.defaultTimeoutMillis());
332      }
333    });
334  }
335
336  private interface Function<In, Out> {
337    public Out apply(In in) throws Exception;
338  }
339
340  // Subscription::cancel
341  //   when Subscription is not cancelled
342  //     the Publisher must eventually drop any references to the corresponding subscriber
343  @Test
344  public void onSubscriptionCancelThePublisherMustEventuallyDropAllReferencesToTheSubscriber() throws Throwable {
345    final ReferenceQueue<ManualSubscriber<T>> queue = new ReferenceQueue<ManualSubscriber<T>>();
346
347    final Function<Publisher<T>, WeakReference<ManualSubscriber<T>>> run = new Function<Publisher<T>, WeakReference<ManualSubscriber<T>>>() {
348      @Override
349      public WeakReference<ManualSubscriber<T>> apply(Publisher<T> pub) throws Exception {
350        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
351        WeakReference<ManualSubscriber<T>> ref = new WeakReference<ManualSubscriber<T>>(sub, queue);
352        sub.requestMore(1);
353        sub.nextElement();
354        sub.cancel();
355        return ref;
356      }
357    };
358
359    activePublisherTest(3, new PublisherTestRun<T>() {
360      @Override
361      public void run(Publisher<T> pub) throws Exception {
362        WeakReference<ManualSubscriber<T>> ref = run.apply(pub);
363
364        // cancel may be run asynchronously so we add a sleep before running the GC
365        // to "resolve" the race
366        Thread.sleep(publisherShutdownTimeoutMillis);
367        System.gc();
368
369        if (!queue.remove(100).equals(ref)) {
370          env.flop("Publisher " + pub + " did not drop reference to test subscriber after subscription cancellation");
371        }
372      }
373    });
374  }
375
376  // A Publisher
377  //   must not call `onNext`
378  //     after having issued an `onComplete` or `onError` call on a subscriber
379  @Test
380  public void mustNotCallOnNextAfterHavingIssuedAnOnCompleteOrOnErrorCallOnASubscriber() {
381    // this is implicitly verified by the test infrastructure
382  }
383
384  // A Publisher
385  //   must produce the same elements in the same sequence for all its subscribers
386  @Test
387  public void mustProduceTheSameElementsInTheSameSequenceForAllItsSubscribers() throws Throwable {
388    activePublisherTest(5, new PublisherTestRun<T>() {
389
390      @Override
391      public void run(Publisher<T> pub) throws InterruptedException {
392        ManualSubscriber<T> sub1 = env.newManualSubscriber(pub);
393        ManualSubscriber<T> sub2 = env.newManualSubscriber(pub);
394        ManualSubscriber<T> sub3 = env.newManualSubscriber(pub);
395
396        sub1.requestMore(1);
397        T x1 = sub1.nextElement("Publisher " + pub + " did not produce the requested 1 element on 1st subscriber");
398        sub2.requestMore(2);
399        List<T> y1 = sub2.nextElements(2, "Publisher " + pub + " did not produce the requested 2 elements on 2nd subscriber");
400        sub1.requestMore(1);
401        T x2 = sub1.nextElement("Publisher " + pub + " did not produce the requested 1 element on 1st subscriber");
402        sub3.requestMore(3);
403        List<T> z1 = sub3.nextElements(3, "Publisher " + pub + " did not produce the requested 3 elements on 3rd subscriber");
404        sub3.requestMore(1);
405        T z2 = sub3.nextElement("Publisher " + pub + " did not produce the requested 1 element on 3rd subscriber");
406        sub3.requestMore(1);
407        T z3 = sub3.nextElement("Publisher " + pub + " did not produce the requested 1 element on 3rd subscriber");
408        sub3.requestEndOfStream("Publisher " + pub + " did not complete the stream as expected on 3rd subscriber");
409        sub2.requestMore(3);
410        List<T> y2 = sub2.nextElements(3, "Publisher " + pub + " did not produce the requested 3 elements on 2nd subscriber");
411        sub2.requestEndOfStream("Publisher " + pub + " did not complete the stream as expected on 2nd subscriber");
412        sub1.requestMore(2);
413        List<T> x3 = sub1.nextElements(2, "Publisher " + pub + " did not produce the requested 2 elements on 1st subscriber");
414        sub1.requestMore(1);
415        T x4 = sub1.nextElement("Publisher " + pub + " did not produce the requested 1 element on 1st subscriber");
416        sub1.requestEndOfStream("Publisher " + pub + " did not complete the stream as expected on 1st subscriber");
417
418        //noinspection unchecked
419        List<T> r = new ArrayList<T>(Arrays.asList(x1, x2));
420        r.addAll(x3);
421        r.addAll(Collections.singleton(x4));
422
423        List<T> check1 = new ArrayList<T>(y1);
424        check1.addAll(y2);
425
426        //noinspection unchecked
427        List<T> check2 = new ArrayList<T>(z1);
428        check2.add(z2);
429        check2.add(z3);
430
431        assertEquals(r, check1, "Publisher " + pub + " did not produce the same element sequence for subscribers 1 and 2");
432        assertEquals(r, check2, "Publisher " + pub + " did not produce the same element sequence for subscribers 1 and 3");
433      }
434    });
435    }
436
437  // A Publisher
438  //   must support a pending element count up to 2^63-1 (Long.MAX_VALUE) and provide for overflow protection
439  @Test
440  public void mustSupportAPendingElementCountUpToLongMaxValue() {
441    // not really testable without more control over the Publisher,
442    // we verify this part of the fanout logic with the IdentityProcessorVerification
443  }
444
445  // A Publisher
446  //   must call `onComplete` on a subscriber after having produced the final stream element to it
447  //   must call `onComplete` on a subscriber at the earliest possible point in time
448  @Test
449  public void mustCallOnCompleteOnASubscriberAfterHavingProducedTheFinalStreamElementToIt() throws Throwable {
450    activePublisherTest(3, new PublisherTestRun<T>() {
451      @Override
452      public void run(Publisher<T> pub) throws InterruptedException {
453        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
454        sub.requestNextElement();
455        sub.requestNextElement();
456        sub.requestNextElement();
457        sub.requestEndOfStream("Publisher " + pub + " did not complete the stream immediately after the final element");
458        sub.expectNone();
459      }
460    });
461  }
462
463  // A Publisher
464  //   must start producing with the oldest still available element for a new subscriber
465  @Test
466  public void mustStartProducingWithTheOldestStillAvailableElementForASubscriber() {
467    // can only be properly tested if we know more about the Producer implementation
468    // like buffer size and buffer retention logic
469  }
470
471  // A Publisher
472  //   must call `onError` on all its subscribers if it encounters a non-recoverable error
473  @Test
474  public void  mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() {
475    // not really testable without more control over the Publisher,
476    // we verify this part of the fanout logic with the IdentityProcessorVerification
477  }
478
479  // A Publisher
480  //   must not call `onComplete` or `onError` more than once per subscriber
481  @Test
482  public void mustNotCallOnCompleteOrOnErrorMoreThanOncePerSubscriber() {
483    // this is implicitly verified by the test infrastructure
484  }
485
486  /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////
487
488  /////////////////////// TEST INFRASTRUCTURE //////////////////////
489
490  interface PublisherTestRun<T> {
491    public void run(Publisher<T> pub) throws Throwable;
492  }
493
494  public void activePublisherTest(int elements, PublisherTestRun<T> body) throws Throwable {
495    Publisher<T> pub = createPublisher(elements);
496    body.run(pub);
497    env.verifyNoAsyncErrors();
498  }
499
500  public void completedPublisherTest(PublisherTestRun<T> body) throws Throwable {
501    potentiallyPendingTest(createCompletedStatePublisher(), body);
502  }
503
504  public void errorPublisherTest(PublisherTestRun<T> body) throws Throwable {
505    potentiallyPendingTest(createErrorStatePublisher(), body);
506  }
507
508  public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body) throws Throwable {
509    if (pub != null) {
510      body.run(pub);
511      env.verifyNoAsyncErrors();
512    } else throw new SkipException("Skipping, because no Publisher was provided for this type of test");
513  }
514}