001package org.reactivestreams.tck;
002
003import org.reactivestreams.spi.Publisher;
004import org.reactivestreams.spi.Subscriber;
005import org.reactivestreams.spi.Subscription;
006import org.testng.annotations.Test;
007
008import static org.reactivestreams.tck.TestEnvironment.*;
009
010public abstract class SubscriberVerification<T> {
011
012  private final TestEnvironment env;
013
014  protected SubscriberVerification(TestEnvironment env) {
015    this.env = env;
016  }
017
018  /**
019   * This is the main method you must implement in your test incarnation.
020   * It must create a new Subscriber instance to be subjected to the testing logic.
021   * <p/>
022   * In order to be meaningfully testable your Subscriber must inform the given
023   * `SubscriberProbe` of the respective events having been received.
024   */
025  abstract Subscriber<T> createSubscriber(SubscriberProbe<T> probe);
026
027  /**
028   * Helper method required for generating test elements.
029   * It must create a Publisher for a stream with exactly the given number of elements.
030   * If `elements` is zero the produced stream must be infinite.
031   */
032  abstract Publisher<T> createHelperPublisher(int elements);
033
034  ////////////////////// TEST SETUP VERIFICATION ///////////////////////////
035
036  @Test
037  void exerciseHappyPath() throws InterruptedException {
038    new TestSetup(env) {{
039      puppet().triggerRequestMore(1);
040
041      puppet().triggerRequestMore(1);
042      int receivedRequests = expectRequestMore();
043      sendNextTFromUpstream();
044      probe.expectNext(lastT);
045
046      puppet().triggerRequestMore(1);
047      if (receivedRequests == 1) {
048        expectRequestMore();
049      }
050      sendNextTFromUpstream();
051      probe.expectNext(lastT);
052
053      puppet().triggerCancel();
054      expectCancelling();
055
056      env.verifyNoAsyncErrors();
057    }};
058  }
059
060  ////////////////////// SPEC RULE VERIFICATION ///////////////////////////
061
062  // Subscriber::onSubscribe(Subscription), Subscriber::onNext(T)
063  //   must asynchronously schedule a respective event to the subscriber
064  //   must not call any methods on the Subscription, the Publisher or any other Publishers or Subscribers
065  @Test
066  void onSubscribeAndOnNextMustAsynchronouslyScheduleAnEvent() {
067    // cannot be meaningfully tested, or can it?
068  }
069
070  // Subscriber::onComplete, Subscriber::onError(Throwable)
071  //   must asynchronously schedule a respective event to the Subscriber
072  //   must not call any methods on the Subscription, the Publisher or any other Publishers or Subscribers
073  //   must consider the Subscription cancelled after having received the event
074  @Test
075  void onCompleteAndOnErrorMustAsynchronouslyScheduleAnEvent() {
076    // cannot be meaningfully tested, or can it?
077  }
078
079  // A Subscriber
080  //   must not accept an `onSubscribe` event if it already has an active Subscription
081  @Test
082  void mustNotAcceptAnOnSubscribeEventIfItAlreadyHasAnActiveSubscription() throws InterruptedException {
083    new TestSetup(env) {{
084      // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail
085      sub().onSubscribe(
086          new Subscription() {
087            public void requestMore(int elements) {
088              env.flop(String.format("Subscriber %s illegally called `subscription.requestMore(%s)`", sub(), elements));
089            }
090
091            public void cancel() {
092              env.flop(String.format("Subscriber %s illegally called `subscription.cancel()`", sub()));
093            }
094          });
095
096      env.verifyNoAsyncErrors();
097      }};
098  }
099
100  // A Subscriber
101  //   must call Subscription::cancel during shutdown if it still has an active Subscription
102  @Test
103  void mustCallSubscriptionCancelDuringShutdownIfItStillHasAnActiveSubscription() throws InterruptedException {
104    new TestSetup(env) {{
105      puppet().triggerShutdown();
106      expectCancelling();
107
108      env.verifyNoAsyncErrors();
109    }};
110  }
111
112  // A Subscriber
113  //   must ensure that all calls on a Subscription take place from the same thread or provide for respective external synchronization
114  @Test
115  void mustEnsureThatAllCallsOnASubscriptionTakePlaceFromTheSameThreadOrProvideExternalSync() {
116    // cannot be meaningfully tested, or can it?
117  }
118
119  // A Subscriber
120  //   must be prepared to receive one or more `onNext` events after having called Subscription::cancel
121  @Test
122  void mustBePreparedToReceiveOneOrMoreOnNextEventsAfterHavingCalledSubscriptionCancel() throws InterruptedException {
123    new TestSetup(env) {{
124      puppet().triggerRequestMore(1);
125      puppet().triggerCancel();
126      expectCancelling();
127      sendNextTFromUpstream();
128
129      env.verifyNoAsyncErrors();
130    }};
131  }
132
133  // A Subscriber
134  //   must be prepared to receive an `onComplete` event with a preceding Subscription::requestMore call
135  @Test
136  void mustBePreparedToReceiveAnOnCompleteEventWithAPrecedingSubscriptionRequestMore() throws InterruptedException {
137    new TestSetup(env) {{
138      puppet().triggerRequestMore(1);
139      sendCompletion();
140      probe.expectCompletion();
141
142      env.verifyNoAsyncErrors();
143    }};
144  }
145
146  // A Subscriber
147  //   must be prepared to receive an `onComplete` event without a preceding Subscription::requestMore call
148  @Test
149  void mustBePreparedToReceiveAnOnCompleteEventWithoutAPrecedingSubscriptionRequestMore() throws InterruptedException {
150    new TestSetup(env) {{
151      sendCompletion();
152      probe.expectCompletion();
153
154      env.verifyNoAsyncErrors();
155    }};
156  }
157
158  // A Subscriber
159  //   must be prepared to receive an `onError` event with a preceding Subscription::requestMore call
160  @Test
161  void mustBePreparedToReceiveAnOnErrorEventWithAPrecedingSubscriptionRequestMore() throws InterruptedException {
162    new TestSetup(env) {{
163      puppet().triggerRequestMore(1);
164      Exception ex = new RuntimeException("Test exception");
165      sendError(ex);
166      probe.expectError(ex);
167
168      env.verifyNoAsyncErrors();
169    }};
170  }
171
172  // A Subscriber
173  //   must be prepared to receive an `onError` event without a preceding Subscription::requestMore call
174  @Test
175  void mustBePreparedToReceiveAnOnErrorEventWithoutAPrecedingSubscriptionRequestMore() throws InterruptedException {
176    new TestSetup(env) {{
177      Exception ex = new RuntimeException("Test exception");
178      sendError(ex);
179      probe.expectError(ex);
180      env.verifyNoAsyncErrors();
181    }};
182  }
183
184  // A Subscriber
185  //   must make sure that all calls on its `onXXX` methods happen-before the processing of the respective events
186  @Test
187  void mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() {
188    // cannot be meaningfully tested, or can it?
189  }
190
191  /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////
192
193  /////////////////////// TEST INFRASTRUCTURE //////////////////////
194
195  class TestSetup extends ManualPublisher<T> {
196    ManualSubscriber<T> tees; // gives us access to an infinite stream of T values
197    Probe probe;
198    T lastT = null;
199
200    public TestSetup(TestEnvironment env) throws InterruptedException {
201      super(env);
202      tees = env.newManualSubscriber(createHelperPublisher(0));
203      probe = new Probe();
204      subscribe(createSubscriber(probe));
205      probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub()));
206    }
207
208    Subscriber<T> sub() {
209      return subscriber.get();
210    }
211
212    SubscriberPuppet puppet() {
213      return probe.puppet.value();
214    }
215
216    void sendNextTFromUpstream() throws InterruptedException {
217      sendNext(nextT());
218    }
219
220    T nextT() throws InterruptedException {
221      lastT = tees.requestNextElement();
222      return lastT;
223    }
224
225    class Probe implements SubscriberProbe<T> {
226      Promise<SubscriberPuppet> puppet = new Promise<SubscriberPuppet>(env);
227      Receptacle<T> elements = new Receptacle<T>(env);
228      Latch completed = new Latch(env);
229      Promise<Throwable> error = new Promise<Throwable>(env);
230
231      public void registerOnSubscribe(SubscriberPuppet p) {
232        if (!puppet.isCompleted()) {
233          puppet.complete(p);
234        } else {
235          env.flop(String.format("Subscriber %s illegally accepted a second Subscription", sub()));
236        }
237      }
238
239      public void registerOnNext(T element) {
240        elements.add(element);
241      }
242
243      public void registerOnComplete() {
244        completed.close();
245      }
246
247      public void registerOnError(Throwable cause) {
248        error.complete(cause);
249      }
250
251      void expectNext(T expected) throws InterruptedException {
252        expectNext(expected, env.defaultTimeoutMillis());
253      }
254
255      void expectNext(T expected, long timeoutMillis) throws InterruptedException {
256        T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected));
257        if (!received.equals(expected)) {
258          env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected));
259        }
260      }
261
262      void expectCompletion() throws InterruptedException {
263        expectCompletion(env.defaultTimeoutMillis());
264      }
265
266      void expectCompletion(long timeoutMillis) throws InterruptedException {
267        completed.expectClose(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub()));
268      }
269
270      void expectError(Throwable expected) throws InterruptedException {
271        expectError(expected, env.defaultTimeoutMillis());
272      }
273
274      void expectError(Throwable expected, long timeoutMillis) throws InterruptedException {
275        error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected));
276        if (error.value() != expected) {
277          env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected));
278        }
279      }
280
281      public void verifyNoAsyncErrors() {
282        env.verifyNoAsyncErrors();
283      }
284    }
285  }
286
287  interface SubscriberProbe<T> {
288    /**
289     * Must be called by the test subscriber when it has received the `onSubscribe` event.
290     */
291    void registerOnSubscribe(SubscriberPuppet puppet);
292
293    /**
294     * Must be called by the test subscriber when it has received an`onNext` event.
295     */
296    void registerOnNext(T element);
297
298    /**
299     * Must be called by the test subscriber when it has received an `onComplete` event.
300     */
301    void registerOnComplete();
302
303    /**
304     * Must be called by the test subscriber when it has received an `onError` event.
305     */
306    void registerOnError(Throwable cause);
307  }
308
309  interface SubscriberPuppet {
310    void triggerShutdown();
311
312    void triggerRequestMore(int elements);
313
314    void triggerCancel();
315  }
316}