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}