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}