Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TCK: Receptacle#expectError timeout approach #452

Merged
merged 4 commits into from May 6, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
121 changes: 95 additions & 26 deletions tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java
Expand Up @@ -14,8 +14,8 @@
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.reactivestreams.tck.flow.support.SubscriberBufferOverflowException;
import org.reactivestreams.tck.flow.support.Optional;
import org.reactivestreams.tck.flow.support.SubscriberBufferOverflowException;

import java.util.Collections;
import java.util.LinkedList;
Expand All @@ -24,7 +24,6 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

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

Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
private final long defaultTimeoutMillis;
private final long defaultPollTimeoutMillis;
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
private final long defaultNoSignalsTimeoutMillis;
private final boolean printlnDebug;

Expand All @@ -50,15 +50,47 @@ public class TestEnvironment {
* the implementation, but can in some cases result in longer time to
* run the tests.
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
* @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
* preempted by an asynchronous event.
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
* @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
*/
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, boolean printlnDebug) {
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis,
boolean printlnDebug) {
this.defaultTimeoutMillis = defaultTimeoutMillis;
this.defaultPollTimeoutMillis = defaultPollTimeoutMillis;
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
this.defaultNoSignalsTimeoutMillis = defaultNoSignalsTimeoutMillis;
this.printlnDebug = printlnDebug;
}

/**
* Tests must specify the timeout for expected outcome of asynchronous
* interactions. Longer timeout does not invalidate the correctness of
* the implementation, but can in some cases result in longer time to
* run the tests.
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
* @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
*/
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, boolean printlnDebug) {
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultTimeoutMillis, printlnDebug);
}

/**
* Tests must specify the timeout for expected outcome of asynchronous
* interactions. Longer timeout does not invalidate the correctness of
* the implementation, but can in some cases result in longer time to
* run the tests.
*
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
* @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
* preempted by an asynchronous event.
*/
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis) {
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultPollTimeoutMillis, false);
}
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved

/**
* Tests must specify the timeout for expected outcome of asynchronous
* interactions. Longer timeout does not invalidate the correctness of
Expand All @@ -69,7 +101,7 @@ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMi
* @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
*/
public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis) {
this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, false);
this(defaultTimeoutMillis, defaultTimeoutMillis, defaultNoSignalsTimeoutMillis);
}

/**
Expand All @@ -81,7 +113,7 @@ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMi
* @param defaultTimeoutMillis default timeout to be used in all expect* methods
*/
public TestEnvironment(long defaultTimeoutMillis) {
this(defaultTimeoutMillis, defaultTimeoutMillis, false);
this(defaultTimeoutMillis, defaultTimeoutMillis, defaultTimeoutMillis);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -519,42 +551,57 @@ public void expectCompletion(long timeoutMillis, String errorMsg) throws Interru
}

public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception {
expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis());
expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives) throws Exception {
expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis());
expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception {
expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), timeoutMillis);
}

public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives, long timeoutMillis) throws Exception {
final E err = expectError(expected, timeoutMillis);
expectErrorWithMessage(expected, requiredMessagePartAlternatives, timeoutMillis, timeoutMillis);
}

public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives,
long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
final E err = expectError(expected, totalTimeoutMillis, pollTimeoutMillis);
final String message = err.getMessage();

boolean contains = false;
for (String requiredMessagePart : requiredMessagePartAlternatives)
for (String requiredMessagePart : requiredMessagePartAlternatives)
if (message.contains(requiredMessagePart)) contains = true; // not short-circuting loop, it is expected to
assertTrue(contains,
String.format("Got expected exception [%s] but missing message part [%s], was: %s",
err.getClass(), "anyOf: " + requiredMessagePartAlternatives, err.getMessage()));
String.format("Got expected exception [%s] but missing message part [%s], was: %s",
err.getClass(), "anyOf: " + requiredMessagePartAlternatives, err.getMessage()));
}

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

public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception {
return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName()));
return expectError(expected, timeoutMillis, env.defaultPollTimeoutMillis);
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
}

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

public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception {
return received.expectError(expected, timeoutMillis, errorMsg);
return expectError(expected, timeoutMillis, env.defaultPollTimeoutMillis, errorMsg);
}

public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
return expectError(expected, totalTimeoutMillis, pollTimeoutMillis, String.format("Expected onError(%s)", expected.getName()));
}

public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis,
String errorMsg) throws Exception {
return received.expectError(expected, totalTimeoutMillis, pollTimeoutMillis, errorMsg);
}

public void expectNone() throws InterruptedException {
Expand Down Expand Up @@ -1025,22 +1072,44 @@ public void expectCompletion(long timeoutMillis, String errorMsg) throws Interru
} // else, ok
}

@SuppressWarnings("unchecked")
/**
* @deprecated Deprecated in favor of {@link #expectError(Class, long, long, String)}.
*/
@Deprecated
public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception {
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
Thread.sleep(timeoutMillis);

if (env.asyncErrors.isEmpty()) {
return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis));
} else {
// ok, there was an expected error
Throwable thrown = env.asyncErrors.remove(0);
return expectError(clazz, timeoutMillis, timeoutMillis, errorMsg);
}

if (clazz.isInstance(thrown)) {
return (E) thrown;
@SuppressWarnings("unchecked")
final <E extends Throwable> E expectError(Class<E> clazz, final long totalTimeoutMillis,
long pollTimeoutMillis,
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
String errorMsg) throws Exception {
long totalTimeoutRemainingMillis = totalTimeoutMillis;
long timeStampA = System.nanoTime();
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
long timeStampB;

for (;;) {
Thread.sleep(Math.min(pollTimeoutMillis, totalTimeoutRemainingMillis));
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved

if (env.asyncErrors.isEmpty()) {
timeStampB = System.nanoTime();
totalTimeoutRemainingMillis =- timeStampB - timeStampA;
Scottmitch marked this conversation as resolved.
Show resolved Hide resolved
timeStampA = timeStampB;

if (totalTimeoutRemainingMillis <= 0) {
return env.flopAndFail(String.format("%s within %d ms", errorMsg, totalTimeoutMillis));
}
} else {
// ok, there was an expected error
Throwable thrown = env.asyncErrors.remove(0);

return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s",
errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName()));
if (clazz.isInstance(thrown)) {
return (E) thrown;
} else {

return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s",
errorMsg, totalTimeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName()));
}
}
}
}
Expand Down