blob: 9d4040aa2acfd68a003cdda6fef5ebbb6aed083a [file] [log] [blame]
package com.gemstone.gemfire.test.junit.rules;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Matcher;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* Expect an Exception within a specified timeout.
*
* @author Kirk Lund
* @since 8.2
*/
public class ExpectedTimeout implements TestRule {
/**
* @return a Rule that expects no timeout (identical to behavior without this Rule)
*/
public static ExpectedTimeout none() {
return new ExpectedTimeout();
}
private ExpectedException delegate;
private boolean expectsThrowable;
private long minDuration;
private long maxDuration;
private TimeUnit timeUnit;
private ExpectedTimeout() {
this.delegate = ExpectedException.none();
}
public ExpectedTimeout expectMinimumDuration(final long minDuration) {
this.minDuration = minDuration;
return this;
}
public ExpectedTimeout expectMaximumDuration(final long maxDuration) {
this.maxDuration = maxDuration;
return this;
}
public ExpectedTimeout expectTimeUnit(final TimeUnit timeUnit) {
this.timeUnit = timeUnit;
return this;
}
public ExpectedTimeout handleAssertionErrors() {
this.delegate.handleAssertionErrors();
return this;
}
public ExpectedTimeout handleAssumptionViolatedExceptions() {
this.delegate.handleAssumptionViolatedExceptions();
return this;
}
/**
* Adds {@code matcher} to the list of requirements for any thrown
* exception.
*/
public void expect(final Matcher<?> matcher) {
this.delegate.expect(matcher);
}
/**
* Adds to the list of requirements for any thrown exception that it should
* be an instance of {@code type}
*/
public void expect(final Class<? extends Throwable> type) {
this.delegate.expect(type);
this.expectsThrowable = true;
}
/**
* Adds to the list of requirements for any thrown exception that it should
* <em>contain</em> string {@code substring}
*/
public void expectMessage(final String substring) {
this.delegate.expectMessage(substring);
}
/**
* Adds {@code matcher} to the list of requirements for the message returned
* from any thrown exception.
*/
public void expectMessage(final Matcher<String> matcher) {
this.delegate.expectMessage(matcher);
}
/**
* Adds {@code matcher} to the list of requirements for the cause of
* any thrown exception.
*/
public void expectCause(final Matcher<? extends Throwable> expectedCause) {
this.delegate.expectCause(expectedCause);
}
public boolean expectsTimeout() {
return minDuration > 0 || maxDuration > 0;
}
public boolean expectsThrowable() {
return expectsThrowable = true;
}
@Override
public Statement apply(final Statement base, final Description description) {
Statement next = delegate.apply(base, description);
return new ExpectedTimeoutStatement(next);
}
private void handleTime(final Long duration) {
if (expectsTimeout()) {
assertThat(timeUnit.convert(duration, TimeUnit.NANOSECONDS), new TimeMatcher(timeUnit, minDuration, maxDuration));
}
}
private static class TimeMatcher extends org.hamcrest.TypeSafeMatcher<Long> {
private final TimeUnit timeUnit;
private final long minDuration;
private final long maxDuration;
public TimeMatcher(final TimeUnit timeUnit, final long minDuration, final long maxDuration) {
this.timeUnit = timeUnit;
this.minDuration = minDuration;
this.maxDuration = maxDuration;
}
@Override
public boolean matchesSafely(final Long duration) {
return duration >= this.minDuration && duration <= this.maxDuration;
}
@Override
public void describeTo(final org.hamcrest.Description description) {
description.appendText("expects duration to be greater than or equal to ")
.appendValue(this.minDuration)
.appendText(" and less than or equal to ")
.appendValue(this.maxDuration)
.appendText(" ")
.appendValue(this.timeUnit);
}
}
private class ExpectedTimeoutStatement extends Statement {
private final Statement next;
public ExpectedTimeoutStatement(final Statement base) {
next = base;
}
@Override
public void evaluate() throws Throwable {
long start = System.nanoTime();
next.evaluate();
handleTime(System.nanoTime() - start);
}
}
}