Skip to content

Commit 721b1c0

Browse files
committed
TCK: Receptacle#expectError timeout approach
Motivation: `defaultTimeoutMillis` is typically used as a "max time to wait until an expected event occurs". For example this value is used as the argument to a `ArrayBlockingQueue#poll(timeout)` [1][2][3] method call and therefore when things work as expected you rarely wait this full duration. However [Receptacle#expectError](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L1030) does a `Thread.sleep(..)` and then checks if there is something present in the error list (which is a [CopyOnWriteArrayList](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L45)). The value for the sleep is typically derived from [defaultTimeoutMillis](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L521-L526) which leads to unconditional delays. This makes it challenging to use `defaultTimeoutMillis` in tests to specify "maximum amount of time to wait until some event occurs". The impacts are if `defaultTimeoutMillis` is set very small you are more likely to see timeouts (especially on over utilized CI servers), but if `defaultTimeoutMillis` the tests take a long time to complete. [1] https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L1019 [2] https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L978 [3] https://github.com/reactive-streams/reactive-streams-jvm/blob/master/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java#L990 Modifications: - TestEnvironment now supports an additional poll timeout parameter to be used when there is no explicit async event to trigger off of. Result: TestEnvironment's defaultTimeoutMillis can be used as "max time until some event occurs" where defaultPollTimeoutMillis provides the polling interval to check for an event if there is no explicit async event to trigger off of. Fixes reactive-streams#451
1 parent e21820c commit 721b1c0

File tree

1 file changed

+95
-26
lines changed

1 file changed

+95
-26
lines changed

tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java

+95-26
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
import org.reactivestreams.Publisher;
1515
import org.reactivestreams.Subscriber;
1616
import org.reactivestreams.Subscription;
17-
import org.reactivestreams.tck.flow.support.SubscriberBufferOverflowException;
1817
import org.reactivestreams.tck.flow.support.Optional;
18+
import org.reactivestreams.tck.flow.support.SubscriberBufferOverflowException;
1919

2020
import java.util.Collections;
2121
import java.util.LinkedList;
@@ -24,7 +24,6 @@
2424
import java.util.concurrent.CopyOnWriteArrayList;
2525
import java.util.concurrent.CountDownLatch;
2626
import java.util.concurrent.TimeUnit;
27-
import java.util.concurrent.atomic.AtomicBoolean;
2827
import java.util.concurrent.atomic.AtomicReference;
2928

3029
import static org.testng.Assert.assertTrue;
@@ -39,6 +38,7 @@ public class TestEnvironment {
3938
private static final String DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS_ENV = "DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS";
4039

4140
private final long defaultTimeoutMillis;
41+
private final long defaultPollTimeoutMillis;
4242
private final long defaultNoSignalsTimeoutMillis;
4343
private final boolean printlnDebug;
4444

@@ -50,15 +50,47 @@ public class TestEnvironment {
5050
* the implementation, but can in some cases result in longer time to
5151
* run the tests.
5252
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
53+
* @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
54+
* preempted by an asynchronous event.
5355
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
5456
* @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
5557
*/
56-
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, boolean printlnDebug) {
58+
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis,
59+
boolean printlnDebug) {
5760
this.defaultTimeoutMillis = defaultTimeoutMillis;
61+
this.defaultPollTimeoutMillis = defaultPollTimeoutMillis;
5862
this.defaultNoSignalsTimeoutMillis = defaultNoSignalsTimeoutMillis;
5963
this.printlnDebug = printlnDebug;
6064
}
6165

66+
/**
67+
* Tests must specify the timeout for expected outcome of asynchronous
68+
* interactions. Longer timeout does not invalidate the correctness of
69+
* the implementation, but can in some cases result in longer time to
70+
* run the tests.
71+
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
72+
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
73+
* @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
74+
*/
75+
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, boolean printlnDebug) {
76+
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultTimeoutMillis, printlnDebug);
77+
}
78+
79+
/**
80+
* Tests must specify the timeout for expected outcome of asynchronous
81+
* interactions. Longer timeout does not invalidate the correctness of
82+
* the implementation, but can in some cases result in longer time to
83+
* run the tests.
84+
*
85+
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
86+
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
87+
* @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
88+
* preempted by an asynchronous event.
89+
*/
90+
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis) {
91+
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultPollTimeoutMillis, false);
92+
}
93+
6294
/**
6395
* Tests must specify the timeout for expected outcome of asynchronous
6496
* interactions. Longer timeout does not invalidate the correctness of
@@ -69,7 +101,7 @@ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMi
69101
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
70102
*/
71103
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis) {
72-
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, false);
104+
this(defaultTimeoutMillis, defaultTimeoutMillis, defaultNoSignalsTimeoutMillis);
73105
}
74106

75107
/**
@@ -81,7 +113,7 @@ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMi
81113
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
82114
*/
83115
public TestEnvironment(long defaultTimeoutMillis) {
84-
this(defaultTimeoutMillis, defaultTimeoutMillis, false);
116+
this(defaultTimeoutMillis, defaultTimeoutMillis, defaultTimeoutMillis);
85117
}
86118

87119
/**
@@ -519,42 +551,57 @@ public void expectCompletion(long timeoutMillis, String errorMsg) throws Interru
519551
}
520552

521553
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception {
522-
expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis());
554+
expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis);
523555
}
524556
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives) throws Exception {
525-
expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis());
557+
expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis);
526558
}
527559

528560
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
529561
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception {
530562
expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), timeoutMillis);
531563
}
564+
532565
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives, long timeoutMillis) throws Exception {
533-
final E err = expectError(expected, timeoutMillis);
566+
expectErrorWithMessage(expected, requiredMessagePartAlternatives, timeoutMillis, timeoutMillis);
567+
}
568+
569+
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives,
570+
long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
571+
final E err = expectError(expected, totalTimeoutMillis, pollTimeoutMillis);
534572
final String message = err.getMessage();
535-
573+
536574
boolean contains = false;
537-
for (String requiredMessagePart : requiredMessagePartAlternatives)
575+
for (String requiredMessagePart : requiredMessagePartAlternatives)
538576
if (message.contains(requiredMessagePart)) contains = true; // not short-circuting loop, it is expected to
539577
assertTrue(contains,
540-
String.format("Got expected exception [%s] but missing message part [%s], was: %s",
541-
err.getClass(), "anyOf: " + requiredMessagePartAlternatives, err.getMessage()));
578+
String.format("Got expected exception [%s] but missing message part [%s], was: %s",
579+
err.getClass(), "anyOf: " + requiredMessagePartAlternatives, err.getMessage()));
542580
}
543581

544582
public <E extends Throwable> E expectError(Class<E> expected) throws Exception {
545583
return expectError(expected, env.defaultTimeoutMillis());
546584
}
547585

548586
public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception {
549-
return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName()));
587+
return expectError(expected, timeoutMillis, env.defaultPollTimeoutMillis);
550588
}
551589

552590
public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception {
553591
return expectError(expected, env.defaultTimeoutMillis(), errorMsg);
554592
}
555593

556594
public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception {
557-
return received.expectError(expected, timeoutMillis, errorMsg);
595+
return expectError(expected, timeoutMillis, env.defaultPollTimeoutMillis, errorMsg);
596+
}
597+
598+
public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
599+
return expectError(expected, totalTimeoutMillis, pollTimeoutMillis, String.format("Expected onError(%s)", expected.getName()));
600+
}
601+
602+
public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis,
603+
String errorMsg) throws Exception {
604+
return received.expectError(expected, totalTimeoutMillis, pollTimeoutMillis, errorMsg);
558605
}
559606

560607
public void expectNone() throws InterruptedException {
@@ -1025,22 +1072,44 @@ public void expectCompletion(long timeoutMillis, String errorMsg) throws Interru
10251072
} // else, ok
10261073
}
10271074

1028-
@SuppressWarnings("unchecked")
1075+
/**
1076+
* @deprecated Deprecated in favor of {@link #expectError(Class, long, long, String)}.
1077+
*/
1078+
@Deprecated
10291079
public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception {
1030-
Thread.sleep(timeoutMillis);
1031-
1032-
if (env.asyncErrors.isEmpty()) {
1033-
return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis));
1034-
} else {
1035-
// ok, there was an expected error
1036-
Throwable thrown = env.asyncErrors.remove(0);
1080+
return expectError(clazz, timeoutMillis, timeoutMillis, errorMsg);
1081+
}
10371082

1038-
if (clazz.isInstance(thrown)) {
1039-
return (E) thrown;
1083+
@SuppressWarnings("unchecked")
1084+
final <E extends Throwable> E expectError(Class<E> clazz, final long totalTimeoutMillis,
1085+
long pollTimeoutMillis,
1086+
String errorMsg) throws Exception {
1087+
long totalTimeoutRemainingMillis = totalTimeoutMillis;
1088+
long timeStampA = System.nanoTime();
1089+
long timeStampB;
1090+
1091+
for (;;) {
1092+
Thread.sleep(Math.min(pollTimeoutMillis, totalTimeoutRemainingMillis));
1093+
1094+
if (env.asyncErrors.isEmpty()) {
1095+
timeStampB = System.nanoTime();
1096+
totalTimeoutRemainingMillis =- timeStampB - timeStampA;
1097+
timeStampA = timeStampB;
1098+
1099+
if (totalTimeoutRemainingMillis <= 0) {
1100+
return env.flopAndFail(String.format("%s within %d ms", errorMsg, totalTimeoutMillis));
1101+
}
10401102
} else {
1103+
// ok, there was an expected error
1104+
Throwable thrown = env.asyncErrors.remove(0);
10411105

1042-
return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s",
1043-
errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName()));
1106+
if (clazz.isInstance(thrown)) {
1107+
return (E) thrown;
1108+
} else {
1109+
1110+
return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s",
1111+
errorMsg, totalTimeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName()));
1112+
}
10441113
}
10451114
}
10461115
}

0 commit comments

Comments
 (0)