package net.jodah.concurrentunit; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import net.jodah.concurrentunit.internal.ReentrantCircuit; /** * Waits on a test, carrying out assertions, until being resumed.
*
* Copyright 2010-2016 the original author or authors. Licensed 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
* @author Jonathan Halterman */ public class Waiter { /** the message to show when there is a timeout of the thread. */ private static final String TIMEOUT_MESSAGE = "Test timed out while waiting for an expected result, expectedResumes: %d, actualResumes: %d"; /** the number of remaining resumes. */ private AtomicInteger remainingResumes = new AtomicInteger(0); /** the circuit ith open() and close() calls. */ private final ReentrantCircuit circuit = new ReentrantCircuit(); /** the failure when it occurs. */ private volatile Throwable failure; /** * Creates a new Waiter. */ public Waiter() { this.circuit.open(); } /** * Asserts that the {@code expected} values equals the {@code actual} value. * @param expected the expected value * @param actual the actual value * @throws AssertionError when the assertion fails */ public void assertEquals(final Object expected, final Object actual) { if (expected == null && actual == null) { return; } if (expected != null && expected.equals(actual)) { return; } fail(format(expected, actual)); } /** * Asserts that the {@code expected} values equals the {@code actual} value. * @param expected the expected value * @param actual the actual value * @param delta the allowed tolerance * @throws AssertionError when the assertion fails */ public void assertEquals(final double expected, final double actual, final double delta) { try { org.junit.Assert.assertEquals(expected, actual, delta); } catch (AssertionError ae) { fail(format(expected, actual)); } } /** * Asserts that the {@code condition} is false. * @param condition boolean to test to be false * @throws AssertionError when the assertion fails */ public void assertFalse(final boolean condition) { if (condition) { fail("expected false"); } } /** * Asserts that the {@code object} is not null. * @param object the object to test * @throws AssertionError when the assertion fails */ public void assertNotNull(final Object object) { if (object == null) { fail("expected not null"); } } /** * Asserts that the {@code object} is null. * @param object the object to test * @throws AssertionError when the assertion fails */ public void assertNull(final Object object) { if (object != null) { fail(format("null", object)); } } /** * Asserts that the {@code condition} is true. * @param condition boolean to test to be true * @throws AssertionError when the assertion fails */ public void assertTrue(final boolean condition) { if (!condition) { fail("expected true"); } } /** * Asserts that {@code actual} satisfies the condition specified by {@code matcher}. * @param actual the value to test * @param matcher the condition * @param the type of the actual * @throws AssertionError when the assertion fails */ public void assertThat(final T actual, final org.hamcrest.Matcher matcher) { try { org.hamcrest.MatcherAssert.assertThat(actual, matcher); } catch (AssertionError e) { fail(e); } } /** * Waits until {@link #resume()} is called, or the test is failed. * @throws TimeoutException if the operation times out while waiting * @throws InterruptedException if the operations is interrupted while waiting * @throws AssertionError if any assertion fails while waiting */ public void await() throws TimeoutException, InterruptedException { await(0, TimeUnit.MILLISECONDS, 1); } /** * Waits until the {@code delay} has elapsed, {@link #resume()} is called, or the test is failed. * @param delay Delay to wait in milliseconds * @throws TimeoutException if the operation times out while waiting * @throws InterruptedException if the operations is interrupted while waiting * @throws AssertionError if any assertion fails while waiting */ public void await(final long delay) throws TimeoutException, InterruptedException { await(delay, TimeUnit.MILLISECONDS, 1); } /** * Waits until the {@code delay} has elapsed, {@link #resume()} is called, or the test is failed. * @param delay Delay to wait for * @param timeUnit TimeUnit to delay for * @throws TimeoutException if the operation times out while waiting * @throws InterruptedException if the operations is interrupted while waiting * @throws AssertionError if any assertion fails while waiting */ public void await(final long delay, final TimeUnit timeUnit) throws TimeoutException, InterruptedException { await(delay, timeUnit, 1); } /** * Waits until the {@code delay} has elapsed, {@link #resume()} is called {@code expectedResumes} times, or the test is * failed. * @param delay Delay to wait for in milliseconds * @param expectedResumes Number of times {@link #resume()} is expected to be called before the awaiting thread is resumed * @throws TimeoutException if the operation times out while waiting * @throws InterruptedException if the operations is interrupted while waiting * @throws AssertionError if any assertion fails while waiting */ public void await(final long delay, final int expectedResumes) throws TimeoutException, InterruptedException { await(delay, TimeUnit.MILLISECONDS, expectedResumes); } /** * Waits until the {@code delay} has elapsed, {@link #resume()} is called {@code expectedResumes} times, or the test is * failed. * @param delay Delay to wait for * @param timeUnit TimeUnit to delay for * @param expectedResumes Number of times {@link #resume()} is expected to be called before the awaiting thread is resumed * @throws TimeoutException if the operation times out while waiting * @throws InterruptedException if the operations is interrupted while waiting * @throws AssertionError if any assertion fails while waiting */ public void await(final long delay, final TimeUnit timeUnit, final int expectedResumes) throws TimeoutException, InterruptedException { try { if (this.failure == null) { synchronized (this) { int remaining = this.remainingResumes.addAndGet(expectedResumes); if (remaining > 0) { this.circuit.open(); } } if (delay == 0) { this.circuit.await(); } else if (!this.circuit.await(delay, timeUnit)) { final int actualResumes = expectedResumes - this.remainingResumes.get(); throw new TimeoutException(String.format(TIMEOUT_MESSAGE, expectedResumes, actualResumes)); } } } finally { this.remainingResumes.set(0); this.circuit.open(); if (this.failure != null) { Throwable f = this.failure; this.failure = null; sneakyThrow(f); } } } /** * Resumes the waiter when the expected number of {@link #resume()} calls have occurred. */ public synchronized void resume() { if (this.remainingResumes.decrementAndGet() <= 0) { this.circuit.close(); } } /** * Fails the current test. * @throws AssertionError as a result of failure */ public void fail() { fail(new AssertionError()); } /** * Fails the current test for the given {@code reason}. * @param reason the reason for failure * @throws AssertionError wrapping the reason */ public void fail(final String reason) { fail(new AssertionError(reason)); } /** * Fails the current test with the given {@code reason}, sets the number of expected resumes to 0, and throws the * {@code reason} as an {@code AssertionError} in the main test thread and in the current thread. * @param reason the reason for failure * @throws AssertionError wrapping the {@code reason} */ public void fail(final Throwable reason) { AssertionError ae = null; if (reason instanceof AssertionError) { ae = (AssertionError) reason; } else { ae = new AssertionError(); ae.initCause(reason); } this.failure = ae; this.circuit.close(); throw ae; } /** * Rethrows the {@code failure} in the main test thread and in the current thread. Differs from {@link #fail(Throwable)} * which wraps a failure in an AssertionError before throwing. * @param rethrownFailure the reason for failure throws Throwable the {@code failure} */ public void rethrow(final Throwable rethrownFailure) { this.failure = rethrownFailure; this.circuit.close(); sneakyThrow(rethrownFailure); } /** * Throw the throwable without having to specify it in the signature. * @param t the throwable */ private static void sneakyThrow(final Throwable t) { Waiter. sneakyThrow2(t); } /** * Throw the throwable T. * @param t Throwable; the exception to throw * @throws T the thrown exception * @param the Throwable type */ @SuppressWarnings("unchecked") private static void sneakyThrow2(final Throwable t) throws T { throw (T) t; } /** * Format the expected - actual string of JUnit. * @param expected the expected value * @param actual the actual value * @return a formatted string */ private String format(final Object expected, final Object actual) { return "expected:<" + expected + "> but was:<" + actual + ">"; } }