View Javadoc
1   package org.junit.runner.notification;
2   
3   import org.junit.Test;
4   import org.junit.runner.Description;
5   
6   import java.util.Random;
7   import java.util.concurrent.Callable;
8   import java.util.concurrent.CountDownLatch;
9   import java.util.concurrent.CyclicBarrier;
10  import java.util.concurrent.Executors;
11  import java.util.concurrent.ExecutorService;
12  import java.util.concurrent.TimeUnit;
13  import java.util.concurrent.atomic.AtomicBoolean;
14  import java.util.concurrent.atomic.AtomicInteger;
15  
16  import static org.hamcrest.core.Is.is;
17  import static org.junit.Assert.assertThat;
18  import static org.junit.Assert.assertTrue;
19  
20  /**
21   * Testing RunNotifier in concurrent access.
22   *
23   * @author Tibor Digana (tibor17)
24   * @version 4.12
25   * @since 4.12
26   */
27  public final class ConcurrentRunNotifierTest {
28      private static final long TIMEOUT = 3;
29      private final RunNotifier fNotifier = new RunNotifier();
30  
31      private static class ConcurrentRunListener extends RunListener {
32          final AtomicInteger fTestStarted = new AtomicInteger(0);
33  
34          @Override
35          public void testStarted(Description description) throws Exception {
36              fTestStarted.incrementAndGet();
37          }
38      }
39  
40      @Test
41      public void realUsage() throws Exception {
42          ConcurrentRunListener listener1 = new ConcurrentRunListener();
43          ConcurrentRunListener listener2 = new ConcurrentRunListener();
44          fNotifier.addListener(listener1);
45          fNotifier.addListener(listener2);
46  
47          final int numParallelTests = 4;
48          ExecutorService pool = Executors.newFixedThreadPool(numParallelTests);
49          for (int i = 0; i < numParallelTests; ++i) {
50              pool.submit(new Runnable() {
51                  public void run() {
52                      fNotifier.fireTestStarted(null);
53                  }
54              });
55          }
56          pool.shutdown();
57          assertTrue(pool.awaitTermination(TIMEOUT, TimeUnit.SECONDS));
58  
59          fNotifier.removeListener(listener1);
60          fNotifier.removeListener(listener2);
61  
62          assertThat(listener1.fTestStarted.get(), is(numParallelTests));
63          assertThat(listener2.fTestStarted.get(), is(numParallelTests));
64      }
65  
66      private static class ExaminedListener extends RunListener {
67          final boolean throwFromTestStarted;
68          volatile boolean hasTestFailure = false;
69  
70          ExaminedListener(boolean throwFromTestStarted) {
71              this.throwFromTestStarted = throwFromTestStarted;
72          }
73  
74          @Override
75          public void testStarted(Description description) throws Exception {
76              if (throwFromTestStarted) {
77                  throw new Exception();
78              }
79          }
80  
81          @Override
82          public void testFailure(Failure failure) throws Exception {
83              hasTestFailure = true;
84          }
85      }
86  
87      private abstract class AbstractConcurrentFailuresTest {
88  
89          protected abstract void addListener(ExaminedListener listener);
90  
91          public void test() throws Exception {
92              int totalListenersFailures = 0;
93  
94              Random random = new Random(42);
95              ExaminedListener[] examinedListeners = new ExaminedListener[1000];
96              for (int i = 0; i < examinedListeners.length; ++i) {
97                  boolean fail = random.nextDouble() >= 0.5d;
98                  if (fail) {
99                      ++totalListenersFailures;
100                 }
101                 examinedListeners[i] = new ExaminedListener(fail);
102             }
103 
104             final AtomicBoolean condition = new AtomicBoolean(true);
105             final CyclicBarrier trigger = new CyclicBarrier(2);
106             final CountDownLatch latch = new CountDownLatch(10);
107 
108             ExecutorService notificationsPool = Executors.newFixedThreadPool(4);
109             notificationsPool.submit(new Callable<Void>() {
110                 public Void call() throws Exception {
111                     trigger.await();
112                     while (condition.get()) {
113                         fNotifier.fireTestStarted(null);
114                         latch.countDown();
115                     }
116                     fNotifier.fireTestStarted(null);
117                     return null;
118                 }
119             });
120 
121             // Wait for callable to start
122             trigger.await(TIMEOUT, TimeUnit.SECONDS);
123 
124             // Wait for callable to fire a few events
125             latch.await(TIMEOUT, TimeUnit.SECONDS);
126 
127             for (ExaminedListener examinedListener : examinedListeners) {
128               addListener(examinedListener);
129             }
130 
131             notificationsPool.shutdown();
132             condition.set(false);
133             assertTrue(notificationsPool.awaitTermination(TIMEOUT, TimeUnit.SECONDS));
134 
135             if (totalListenersFailures != 0) {
136                 // If no listener failures, then all the listeners do not report any failure.
137                 int countTestFailures = examinedListeners.length - countReportedTestFailures(examinedListeners);
138                 assertThat(totalListenersFailures, is(countTestFailures));
139             }
140         }
141     }
142 
143     /**
144      * Verifies that listeners added while tests are run concurrently are
145      * notified about test failures.
146      */
147     @Test
148     public void reportConcurrentFailuresAfterAddListener() throws Exception {
149         new AbstractConcurrentFailuresTest() {
150             @Override
151             protected void addListener(ExaminedListener listener) {
152                 fNotifier.addListener(listener);
153             }
154         }.test();
155     }
156 
157     /**
158      * Verifies that listeners added with addFirstListener() while tests are run concurrently are
159      * notified about test failures.
160      */
161     @Test
162     public void reportConcurrentFailuresAfterAddFirstListener() throws Exception {
163         new AbstractConcurrentFailuresTest() {
164             @Override
165             protected void addListener(ExaminedListener listener) {
166                 fNotifier.addFirstListener(listener);
167             }
168         }.test();
169     }
170 
171     private static int countReportedTestFailures(ExaminedListener[] listeners) {
172         int count = 0;
173         for (ExaminedListener listener : listeners) {
174             if (listener.hasTestFailure) {
175                 ++count;
176             }
177         }
178         return count;
179     }
180 }