/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.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. */ public class ExpectedTimeoutRule implements TestRule { /** * @return a Rule that expects no timeout (identical to behavior without this Rule) */ public static ExpectedTimeoutRule none() { return new ExpectedTimeoutRule(); } private ExpectedException delegate; private boolean expectsThrowable; private long minDuration; private long maxDuration; private TimeUnit timeUnit; private ExpectedTimeoutRule() { this.delegate = ExpectedException.none(); } public ExpectedTimeoutRule expectMinimumDuration(final long minDuration) { this.minDuration = minDuration; return this; } public ExpectedTimeoutRule expectMaximumDuration(final long maxDuration) { this.maxDuration = maxDuration; return this; } public ExpectedTimeoutRule expectTimeUnit(final TimeUnit timeUnit) { this.timeUnit = timeUnit; 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); } /** * Returns true if a timeout is expected. */ protected boolean expectsTimeout() { return this.minDuration > 0 || this.maxDuration > 0; } /** * Returns true if a Throwable is expected. */ protected boolean expectsThrowable() { return this.expectsThrowable; } @Override public Statement apply(final Statement base, final Description description) { Statement next = this.delegate.apply(base, description); return new ExpectedTimeoutStatement(next); } private void handleTime(final Long duration) { if (expectsTimeout()) { assertThat(this.timeUnit.convert(duration, TimeUnit.NANOSECONDS), new TimeMatcher(this.timeUnit, this.minDuration, this.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) { this.next = base; } @Override public void evaluate() throws Throwable { long start = System.nanoTime(); this.next.evaluate(); handleTime(System.nanoTime() - start); } } }