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}