001package org.reactivestreams.tck; 002 003import org.reactivestreams.spi.Publisher; 004import org.reactivestreams.spi.Subscriber; 005import org.reactivestreams.spi.Subscription; 006import org.reactivestreams.tck.support.Optional; 007 008import java.util.LinkedList; 009import java.util.List; 010import java.util.concurrent.ArrayBlockingQueue; 011import java.util.concurrent.CopyOnWriteArrayList; 012import java.util.concurrent.CountDownLatch; 013import java.util.concurrent.TimeUnit; 014 015import static org.testng.Assert.fail; 016 017public class TestEnvironment { 018 public static final int TEST_BUFFER_SIZE = 16; 019 020 private final long defaultTimeoutMillis; 021 022 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); 023 024 /** 025 * Tests must specify the timeout for expected outcome of asynchronous 026 * interactions. Longer timeout does not invalidate the correctness of 027 * the implementation, but can in some cases result in longer time to 028 * run the tests. 029 */ 030 public TestEnvironment(long defaultTimeoutMillis) { 031 this.defaultTimeoutMillis = defaultTimeoutMillis; 032 } 033 034 // keeping method around 035 public long defaultTimeoutMillis() { return defaultTimeoutMillis; } 036 037 // don't use the name `fail` as it would collide with other `fail` definitions like the one in scalatest's traits 038 public void flop(String msg) { 039 try { 040 fail(msg); 041 } catch (Throwable t) { 042 asyncErrors.add(t); 043 throw new RuntimeException(t); 044 } 045 } 046 047 048 public <T extends Throwable> void expectThrowingOf(Class<T> clazz, String errorMsg, Runnable block) throws Throwable { 049 try { 050 block.run(); 051 flop(errorMsg); 052 } catch (Throwable e) { 053 if (clazz.isInstance(e)) { 054 // ok 055 } else if (org.reactivestreams.tck.support.NonFatal.apply(e)) { 056 flop(errorMsg + " but " + e); 057 } else { 058 throw e; 059 } 060 } 061 } 062 063 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { 064 subscribe(pub, sub, defaultTimeoutMillis); 065 } 066 067 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { 068 pub.subscribe(sub); 069 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); 070 verifyNoAsyncErrors(); 071 } 072 073 074 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { 075 return newManualSubscriber(pub, defaultTimeoutMillis()); 076 } 077 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { 078 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); 079 subscribe(pub, sub, timeoutMillis); 080 return sub; 081 } 082 083 public void verifyNoAsyncErrors() { 084 for (Throwable e : asyncErrors) { 085 if (e instanceof AssertionError) throw (AssertionError) e; 086 else fail("Async error during test execution: " + e); 087 } 088 } 089 090 // ---- classes ---- 091 092 static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { 093 094 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { 095 super(env); 096 } 097 098 public void onNext(T element) { 099 if (subscription.isCompleted()) { 100 super.onNext(element); 101 } else { 102 env.flop("Subscriber::onNext(" + element + ") called before Subscriber::onSubscribe"); 103 } 104 } 105 106 public void onComplete() { 107 if (subscription.isCompleted()) { 108 super.onComplete(); 109 } else { 110 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); 111 } 112 } 113 114 public void onSubscribe(Subscription s) { 115 if (!subscription.isCompleted()) { 116 subscription.complete(s); 117 } else { 118 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); 119 } 120 } 121 122 public void onError(Throwable cause) { 123 if (subscription.isCompleted()) { 124 super.onError(cause); 125 } else { 126 env.flop("Subscriber::onError(" + cause + ") called before Subscriber::onSubscribe"); 127 } 128 } 129 } 130 131 static class TestSubscriber<T> implements Subscriber<T> { 132 volatile Promise<Subscription> subscription; 133 134 protected final TestEnvironment env; 135 136 public TestSubscriber(TestEnvironment env) { 137 this.env = env; 138 subscription = new Promise<Subscription>(env); 139 } 140 141 @Override 142 public void onError(Throwable cause) { 143 env.flop(String.format("Unexpected Subscriber::onError(%s)", cause)); 144 } 145 146 @Override 147 public void onComplete() { 148 env.flop("Unexpected Subscriber::onComplete()"); 149 } 150 151 @Override 152 public void onNext(T element) { 153 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); 154 } 155 156 public void onSubscribe(Subscription subscription) { 157 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); 158 } 159 160 public void cancel() { 161 if (subscription.isCompleted()) { 162 subscription.value().cancel(); 163 subscription = new Promise<Subscription>(env); 164 } else env.flop("Cannot cancel a subscription before having received it"); 165 } 166 } 167 168 static class ManualSubscriber<T> extends TestSubscriber<T> { 169 Receptacle<T> received = new Receptacle<T>(env); 170 171 public ManualSubscriber(TestEnvironment env) { 172 super(env); 173 } 174 175 @Override 176 public void onNext(T element) { 177 received.add(element); 178 } 179 180 @Override 181 public void onComplete() { 182 received.complete(); 183 } 184 185 void requestMore(int elements) { 186 subscription.value().requestMore(elements); 187 } 188 189 public T requestNextElement() throws InterruptedException { 190 return requestNextElement(env.defaultTimeoutMillis()); 191 } 192 193 public T requestNextElement(long timeoutMillis) throws InterruptedException { 194 return requestNextElement(timeoutMillis, "Did not receive expected element"); 195 } 196 197 public T requestNextElement(String errorMsg) throws InterruptedException { 198 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); 199 } 200 201 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { 202 requestMore(1); 203 return nextElement(timeoutMillis, errorMsg); 204 } 205 206 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { 207 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); 208 } 209 210 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { 211 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); 212 } 213 214 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { 215 requestMore(1); 216 return nextElementOrEndOfStream(timeoutMillis, errorMsg); 217 } 218 219 public void requestEndOfStream() throws InterruptedException { 220 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); 221 } 222 223 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { 224 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); 225 } 226 227 public void requestEndOfStream(String errorMsg) throws InterruptedException { 228 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); 229 } 230 231 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { 232 requestMore(1); 233 expectCompletion(timeoutMillis, errorMsg); 234 } 235 236 public List<T> requestNextElements(int elements, long timeoutMillis, String errorMsg) throws InterruptedException { 237 requestMore(elements); 238 return nextElements(elements, timeoutMillis, errorMsg); 239 } 240 241 public T nextElement() throws InterruptedException { 242 return nextElement(env.defaultTimeoutMillis()); 243 } 244 245 public T nextElement(long timeoutMillis) throws InterruptedException { 246 return nextElement(timeoutMillis, "Did not receive expected element"); 247 } 248 249 public T nextElement(String errorMsg) throws InterruptedException { 250 return nextElement(env.defaultTimeoutMillis(), errorMsg); 251 } 252 253 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { 254 return received.next(timeoutMillis, errorMsg); 255 } 256 257 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { 258 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); 259 } 260 261 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { 262 return received.nextOrEndOfStream(timeoutMillis, errorMsg); 263 } 264 265 public List<T> nextElements(int elements) throws InterruptedException { 266 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); 267 } 268 269 public List<T> nextElements(int elements, String errorMsg) throws InterruptedException { 270 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); 271 } 272 273 public List<T> nextElements(int elements, long timeoutMillis) throws InterruptedException { 274 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); 275 } 276 277 public List<T> nextElements(int elements, long timeoutMillis, String errorMsg) throws InterruptedException { 278 return received.nextN(elements, timeoutMillis, errorMsg); 279 } 280 281 void expectNext(T expected) throws InterruptedException { 282 expectNext(expected, env.defaultTimeoutMillis()); 283 } 284 285 void expectNext(T expected, long timeoutMillis) throws InterruptedException { 286 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); 287 if (!received.equals(expected)) { 288 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); 289 } 290 } 291 292 void expectCompletion(long timeoutMillis) throws InterruptedException { 293 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); 294 } 295 296 void expectCompletion(String errorMsg) throws InterruptedException { 297 expectCompletion(env.defaultTimeoutMillis(), errorMsg); 298 } 299 300 void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { 301 received.expectCompletion(timeoutMillis, errorMsg); 302 } 303 304 public void expectNone() throws InterruptedException { 305 expectNone(env.defaultTimeoutMillis()); 306 } 307 308 public void expectNone(String errMsgPrefix) throws InterruptedException { 309 received.expectNone(env.defaultTimeoutMillis(), errMsgPrefix); 310 } 311 312 public void expectNone(long withinMillis) throws InterruptedException { 313 received.expectNone(withinMillis, "Did not expect an element but got "); 314 } 315 316 } 317 318 static class ManualPublisher<T> implements Publisher<T> { 319 protected final TestEnvironment env; 320 321 Optional<Subscriber<T>> subscriber = Optional.empty(); 322 Receptacle<Integer> requests; 323 Latch cancelled; 324 325 public ManualPublisher(TestEnvironment env) { 326 this.env = env; 327 requests = new Receptacle<Integer>(env); 328 cancelled = new Latch(env); 329 } 330 331 @Override 332 public void subscribe(Subscriber<T> s) { 333 if (subscriber.isEmpty()) { 334 subscriber = Optional.of(s); 335 336 Subscription subs = new Subscription() { 337 @Override 338 public void requestMore(int elements) { 339 requests.add(elements); 340 } 341 342 @Override 343 public void cancel() { 344 cancelled.close(); 345 } 346 }; 347 s.onSubscribe(subs); 348 349 } else { 350 env.flop("TestPublisher doesn't support more than one Subscriber"); 351 } 352 } 353 354 public void sendNext(T element) { 355 if (subscriber.isDefined()) subscriber.get().onNext(element); 356 else env.flop("Cannot sendNext before subscriber subscription"); 357 } 358 359 public void sendCompletion() { 360 if (subscriber.isDefined()) subscriber.get().onComplete(); 361 else env.flop("Cannot sendCompletion before subscriber subscription"); 362 } 363 364 public void sendError(Throwable cause) { 365 if (subscriber.isDefined()) subscriber.get().onError(cause); 366 else env.flop("Cannot sendError before subscriber subscription"); 367 } 368 369 public int nextRequestMore() throws InterruptedException { 370 return nextRequestMore(env.defaultTimeoutMillis()); 371 } 372 373 public int nextRequestMore(long timeoutMillis) throws InterruptedException { 374 return requests.next(timeoutMillis, "Did not receive expected `requestMore` call"); 375 } 376 377 public int expectRequestMore() throws InterruptedException { 378 return expectRequestMore(env.defaultTimeoutMillis()); 379 } 380 381 public int expectRequestMore(long timeoutMillis) throws InterruptedException { 382 int requested = nextRequestMore(timeoutMillis); 383 if (requested <= 0) { 384 env.flop(String.format("Requests cannot be zero or negative but received requestMore(%s)", requested)); 385 return 0; // keep compiler happy 386 } else 387 return requested; 388 } 389 390 public void expectExactRequestMore(int expected) throws InterruptedException { 391 expectExactRequestMore(expected, env.defaultTimeoutMillis()); 392 } 393 394 public void expectExactRequestMore(int expected, long timeoutMillis) throws InterruptedException { 395 int requested = expectRequestMore(timeoutMillis); 396 if (requested != expected) 397 env.flop(String.format("Received `requestMore(%d)` on upstream but expected `requestMore(%d)`", requested, expected)); 398 } 399 400 public void expectNoRequestMore() throws InterruptedException { 401 expectNoRequestMore(env.defaultTimeoutMillis()); 402 } 403 404 public void expectNoRequestMore(long timeoutMillis) throws InterruptedException { 405 requests.expectNone(timeoutMillis, "Received an unexpected call to: requestMore"); 406 } 407 408 public void expectCancelling() throws InterruptedException { 409 expectCancelling(env.defaultTimeoutMillis()); 410 } 411 412 public void expectCancelling(long timeoutMillis) throws InterruptedException { 413 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); 414 } 415 } 416 417 /** like a CountDownLatch, but resettable and with some convenience methods */ 418 static class Latch { 419 private final TestEnvironment env; 420 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); 421 422 public Latch(TestEnvironment env) { 423 this.env = env; 424 } 425 426 public void reOpen() { 427 countDownLatch = new CountDownLatch(1); 428 } 429 430 public boolean isClosed() { 431 return countDownLatch.getCount() == 0; 432 } 433 434 public void close() { 435 countDownLatch.countDown(); 436 } 437 438 public void assertClosed(String openErrorMsg) { 439 if (!isClosed()) { 440 env.flop(openErrorMsg); 441 } 442 } 443 444 public void assertOpen(String closedErrorMsg) { 445 if (isClosed()) { 446 env.flop(closedErrorMsg); 447 } 448 } 449 450 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { 451 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 452 if (countDownLatch.getCount() > 0) { 453 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); 454 } 455 } 456 } 457 458 // simple promise for *one* value, which cannot be reset 459 static class Promise<T> { 460 private final TestEnvironment env; 461 462 public Promise(TestEnvironment env) { 463 this.env = env; 464 } 465 466 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); 467 volatile private T _value = null; 468 469 public T value() { 470 if (isCompleted()) { 471 return _value; 472 } else { 473 env.flop("Cannot access promise value before completion"); 474 return null; 475 } 476 } 477 478 public boolean isCompleted() { 479 return _value != null; 480 } 481 482 public void complete(T value) { 483 abq.add(value); 484 } 485 486 public void assertCompleted(String errorMsg) { 487 if(!isCompleted()) 488 env.flop(errorMsg); 489 } 490 491 public void assertUncompleted(String errorMsg) { 492 if(isCompleted()) 493 env.flop(errorMsg); 494 } 495 496 497 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { 498 if (!isCompleted()) { 499 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); 500 if (val == null) { 501 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); 502 } else { 503 _value = val; 504 } 505 } 506 } 507 } 508 509 // a "Promise" for multiple values, which also supports "end-of-stream reached" 510 static class Receptacle<T> { 511 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; 512 private final TestEnvironment env; 513 514 private ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); 515 516 Receptacle(TestEnvironment env) { 517 this.env = env; 518 } 519 520 public void add(T value) { 521 abq.add(Optional.of(value)); 522 } 523 524 public void complete() { 525 abq.add(Optional.<T>empty()); 526 } 527 528 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { 529 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); 530 531 if (value.isEmpty()) { 532 env.flop("Expected element but got end-of-stream"); 533 } else if (value.get() == null) { 534 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); 535 } else { 536 return value.get(); 537 } 538 539 return null; // keep compiler happy 540 } 541 542 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { 543 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); 544 if (value.isDefined()) { 545 return value; 546 } else { 547 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); 548 return null; // keep compiler happy 549 } 550 } 551 552 public List<T> nextN(int elements, long timeoutMillis, String errorMsg) throws InterruptedException { 553 List<T> result = new LinkedList<T>(); 554 int remaining = elements; 555 while (remaining > 0) { 556 result.add(next(timeoutMillis, errorMsg)); // TODO: fix error messages showing wrong timeout info 557 remaining--; 558 } 559 560 return result; 561 } 562 563 564 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { 565 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); 566 567 if (value.isEmpty()) { 568 // ok 569 } else if (value.get() == null) { 570 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); 571 } else { 572 env.flop("Expected end-of-stream but got " + value.get()); 573 } 574 } 575 576 void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { 577 Thread.sleep(withinMillis); 578 Optional<T> value = abq.poll(); 579 580 if (value == null) { 581 // ok 582 } else if (value.isDefined()) { 583 env.flop(errorMsgPrefix + value.get()); 584 } else { 585 env.flop("Expected no element but got end-of-stream"); 586 } 587 } 588 } 589} 590