View Javadoc
1   package org.junit.runner.notification;
2   
3   import static java.util.Arrays.asList;
4   
5   import java.util.ArrayList;
6   import java.util.List;
7   import java.util.concurrent.CopyOnWriteArrayList;
8   
9   import org.junit.runner.Description;
10  import org.junit.runner.Result;
11  
12  /**
13   * If you write custom runners, you may need to notify JUnit of your progress running tests.
14   * Do this by invoking the <code>RunNotifier</code> passed to your implementation of
15   * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
16   * move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)}
17   * to a separate class since they should only be called once per run.
18   *
19   * @since 4.0
20   */
21  public class RunNotifier {
22      private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
23      private volatile boolean pleaseStop = false;
24  
25      /**
26       * Internal use only
27       */
28      public void addListener(RunListener listener) {
29          if (listener == null) {
30              throw new NullPointerException("Cannot add a null listener");
31          }
32          listeners.add(wrapIfNotThreadSafe(listener));
33      }
34  
35      /**
36       * Internal use only
37       */
38      public void removeListener(RunListener listener) {
39          if (listener == null) {
40              throw new NullPointerException("Cannot remove a null listener");
41          }
42          listeners.remove(wrapIfNotThreadSafe(listener));
43      }
44  
45      /**
46       * Wraps the given listener with {@link SynchronizedRunListener} if
47       * it is not annotated with {@link RunListener.ThreadSafe}.
48       */
49      RunListener wrapIfNotThreadSafe(RunListener listener) {
50          return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
51                  listener : new SynchronizedRunListener(listener, this);
52      }
53  
54  
55      private abstract class SafeNotifier {
56          private final List<RunListener> currentListeners;
57  
58          SafeNotifier() {
59              this(listeners);
60          }
61  
62          SafeNotifier(List<RunListener> currentListeners) {
63              this.currentListeners = currentListeners;
64          }
65  
66          void run() {
67              int capacity = currentListeners.size();
68              ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
69              ArrayList<Failure> failures = new ArrayList<Failure>(capacity);
70              for (RunListener listener : currentListeners) {
71                  try {
72                      notifyListener(listener);
73                      safeListeners.add(listener);
74                  } catch (Exception e) {
75                      failures.add(new Failure(Description.TEST_MECHANISM, e));
76                  }
77              }
78              fireTestFailures(safeListeners, failures);
79          }
80  
81          abstract protected void notifyListener(RunListener each) throws Exception;
82      }
83  
84      /**
85       * Do not invoke.
86       */
87      public void fireTestRunStarted(final Description description) {
88          new SafeNotifier() {
89              @Override
90              protected void notifyListener(RunListener each) throws Exception {
91                  each.testRunStarted(description);
92              }
93          }.run();
94      }
95  
96      /**
97       * Do not invoke.
98       */
99      public void fireTestRunFinished(final Result result) {
100         new SafeNotifier() {
101             @Override
102             protected void notifyListener(RunListener each) throws Exception {
103                 each.testRunFinished(result);
104             }
105         }.run();
106     }
107 
108     /**
109      * Invoke to tell listeners that an atomic test is about to start.
110      *
111      * @param description the description of the atomic test (generally a class and method name)
112      * @throws StoppedByUserException thrown if a user has requested that the test run stop
113      */
114     public void fireTestStarted(final Description description) throws StoppedByUserException {
115         if (pleaseStop) {
116             throw new StoppedByUserException();
117         }
118         new SafeNotifier() {
119             @Override
120             protected void notifyListener(RunListener each) throws Exception {
121                 each.testStarted(description);
122             }
123         }.run();
124     }
125 
126     /**
127      * Invoke to tell listeners that an atomic test failed.
128      *
129      * @param failure the description of the test that failed and the exception thrown
130      */
131     public void fireTestFailure(Failure failure) {
132         fireTestFailures(listeners, asList(failure));
133     }
134 
135     private void fireTestFailures(List<RunListener> listeners,
136             final List<Failure> failures) {
137         if (!failures.isEmpty()) {
138             new SafeNotifier(listeners) {
139                 @Override
140                 protected void notifyListener(RunListener listener) throws Exception {
141                     for (Failure each : failures) {
142                         listener.testFailure(each);
143                     }
144                 }
145             }.run();
146         }
147     }
148 
149     /**
150      * Invoke to tell listeners that an atomic test flagged that it assumed
151      * something false.
152      *
153      * @param failure the description of the test that failed and the
154      * {@link org.junit.AssumptionViolatedException} thrown
155      */
156     public void fireTestAssumptionFailed(final Failure failure) {
157         new SafeNotifier() {
158             @Override
159             protected void notifyListener(RunListener each) throws Exception {
160                 each.testAssumptionFailure(failure);
161             }
162         }.run();
163     }
164 
165     /**
166      * Invoke to tell listeners that an atomic test was ignored.
167      *
168      * @param description the description of the ignored test
169      */
170     public void fireTestIgnored(final Description description) {
171         new SafeNotifier() {
172             @Override
173             protected void notifyListener(RunListener each) throws Exception {
174                 each.testIgnored(description);
175             }
176         }.run();
177     }
178 
179     /**
180      * Invoke to tell listeners that an atomic test finished. Always invoke
181      * this method if you invoke {@link #fireTestStarted(Description)}
182      * as listeners are likely to expect them to come in pairs.
183      *
184      * @param description the description of the test that finished
185      */
186     public void fireTestFinished(final Description description) {
187         new SafeNotifier() {
188             @Override
189             protected void notifyListener(RunListener each) throws Exception {
190                 each.testFinished(description);
191             }
192         }.run();
193     }
194 
195     /**
196      * Ask that the tests run stop before starting the next test. Phrased politely because
197      * the test currently running will not be interrupted. It seems a little odd to put this
198      * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
199      * to be shared amongst the many runners involved.
200      */
201     public void pleaseStop() {
202         pleaseStop = true;
203     }
204 
205     /**
206      * Internal use only. The Result's listener must be first.
207      */
208     public void addFirstListener(RunListener listener) {
209         if (listener == null) {
210             throw new NullPointerException("Cannot add a null listener");
211         }
212         listeners.add(0, wrapIfNotThreadSafe(listener));
213     }
214 }