package org.junit.runner.notification; import org.junit.Test; import org.junit.runner.Description; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * Testing RunNotifier in concurrent access. * * @author Tibor Digana (tibor17) * @version 4.12 * @since 4.12 */ public final class ConcurrentRunNotifierTest { private static final long TIMEOUT = 3; private final RunNotifier fNotifier = new RunNotifier(); private static class ConcurrentRunListener extends RunListener { final AtomicInteger fTestStarted = new AtomicInteger(0); @Override public void testStarted(Description description) throws Exception { fTestStarted.incrementAndGet(); } } @Test public void realUsage() throws Exception { ConcurrentRunListener listener1 = new ConcurrentRunListener(); ConcurrentRunListener listener2 = new ConcurrentRunListener(); fNotifier.addListener(listener1); fNotifier.addListener(listener2); final int numParallelTests = 4; ExecutorService pool = Executors.newFixedThreadPool(numParallelTests); for (int i = 0; i < numParallelTests; ++i) { pool.submit(new Runnable() { public void run() { fNotifier.fireTestStarted(null); } }); } pool.shutdown(); assertTrue(pool.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); fNotifier.removeListener(listener1); fNotifier.removeListener(listener2); assertThat(listener1.fTestStarted.get(), is(numParallelTests)); assertThat(listener2.fTestStarted.get(), is(numParallelTests)); } private static class ExaminedListener extends RunListener { final boolean throwFromTestStarted; volatile boolean hasTestFailure = false; ExaminedListener(boolean throwFromTestStarted) { this.throwFromTestStarted = throwFromTestStarted; } @Override public void testStarted(Description description) throws Exception { if (throwFromTestStarted) { throw new Exception(); } } @Override public void testFailure(Failure failure) throws Exception { hasTestFailure = true; } } private abstract class AbstractConcurrentFailuresTest { protected abstract void addListener(ExaminedListener listener); public void test() throws Exception { int totalListenersFailures = 0; Random random = new Random(42); ExaminedListener[] examinedListeners = new ExaminedListener[1000]; for (int i = 0; i < examinedListeners.length; ++i) { boolean fail = random.nextDouble() >= 0.5d; if (fail) { ++totalListenersFailures; } examinedListeners[i] = new ExaminedListener(fail); } final AtomicBoolean condition = new AtomicBoolean(true); final CyclicBarrier trigger = new CyclicBarrier(2); final CountDownLatch latch = new CountDownLatch(10); ExecutorService notificationsPool = Executors.newFixedThreadPool(4); notificationsPool.submit(new Callable<Void>() { public Void call() throws Exception { trigger.await(); while (condition.get()) { fNotifier.fireTestStarted(null); latch.countDown(); } fNotifier.fireTestStarted(null); return null; } }); // Wait for callable to start trigger.await(TIMEOUT, TimeUnit.SECONDS); // Wait for callable to fire a few events latch.await(TIMEOUT, TimeUnit.SECONDS); for (ExaminedListener examinedListener : examinedListeners) { addListener(examinedListener); } notificationsPool.shutdown(); condition.set(false); assertTrue(notificationsPool.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); if (totalListenersFailures != 0) { // If no listener failures, then all the listeners do not report any failure. int countTestFailures = examinedListeners.length - countReportedTestFailures(examinedListeners); assertThat(totalListenersFailures, is(countTestFailures)); } } } /** * Verifies that listeners added while tests are run concurrently are * notified about test failures. */ @Test public void reportConcurrentFailuresAfterAddListener() throws Exception { new AbstractConcurrentFailuresTest() { @Override protected void addListener(ExaminedListener listener) { fNotifier.addListener(listener); } }.test(); } /** * Verifies that listeners added with addFirstListener() while tests are run concurrently are * notified about test failures. */ @Test public void reportConcurrentFailuresAfterAddFirstListener() throws Exception { new AbstractConcurrentFailuresTest() { @Override protected void addListener(ExaminedListener listener) { fNotifier.addFirstListener(listener); } }.test(); } private static int countReportedTestFailures(ExaminedListener[] listeners) { int count = 0; for (ExaminedListener listener : listeners) { if (listener.hasTestFailure) { ++count; } } return count; } }