001    package org.junit.runner.notification;
002    
003    import static java.util.Arrays.asList;
004    
005    import java.util.ArrayList;
006    import java.util.List;
007    import java.util.concurrent.CopyOnWriteArrayList;
008    
009    import org.junit.runner.Description;
010    import org.junit.runner.Result;
011    
012    /**
013     * If you write custom runners, you may need to notify JUnit of your progress running tests.
014     * Do this by invoking the <code>RunNotifier</code> passed to your implementation of
015     * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
016     * move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)}
017     * to a separate class since they should only be called once per run.
018     *
019     * @since 4.0
020     */
021    public class RunNotifier {
022        private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
023        private volatile boolean pleaseStop = false;
024    
025        /**
026         * Internal use only
027         */
028        public void addListener(RunListener listener) {
029            if (listener == null) {
030                throw new NullPointerException("Cannot add a null listener");
031            }
032            listeners.add(wrapIfNotThreadSafe(listener));
033        }
034    
035        /**
036         * Internal use only
037         */
038        public void removeListener(RunListener listener) {
039            if (listener == null) {
040                throw new NullPointerException("Cannot remove a null listener");
041            }
042            listeners.remove(wrapIfNotThreadSafe(listener));
043        }
044    
045        /**
046         * Wraps the given listener with {@link SynchronizedRunListener} if
047         * it is not annotated with {@link RunListener.ThreadSafe}.
048         */
049        RunListener wrapIfNotThreadSafe(RunListener listener) {
050            return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
051                    listener : new SynchronizedRunListener(listener, this);
052        }
053    
054    
055        private abstract class SafeNotifier {
056            private final List<RunListener> currentListeners;
057    
058            SafeNotifier() {
059                this(listeners);
060            }
061    
062            SafeNotifier(List<RunListener> currentListeners) {
063                this.currentListeners = currentListeners;
064            }
065    
066            void run() {
067                int capacity = currentListeners.size();
068                List<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
069                List<Failure> failures = new ArrayList<Failure>(capacity);
070                for (RunListener listener : currentListeners) {
071                    try {
072                        notifyListener(listener);
073                        safeListeners.add(listener);
074                    } catch (Exception e) {
075                        failures.add(new Failure(Description.TEST_MECHANISM, e));
076                    }
077                }
078                fireTestFailures(safeListeners, failures);
079            }
080    
081            abstract protected void notifyListener(RunListener each) throws Exception;
082        }
083    
084        /**
085         * Do not invoke.
086         */
087        public void fireTestRunStarted(final Description description) {
088            new SafeNotifier() {
089                @Override
090                protected void notifyListener(RunListener each) throws Exception {
091                    each.testRunStarted(description);
092                }
093            }.run();
094        }
095    
096        /**
097         * Do not invoke.
098         */
099        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 a test suite is about to start. Runners are strongly
110         * encouraged--but not required--to call this method. If this method is called for
111         * a given {@link Description} then {@link #fireTestSuiteFinished(Description)} MUST
112         * be called for the same {@code Description}.
113         *
114         * @param description the description of the suite test (generally a class name)
115         * @since 4.13
116         */
117        public void fireTestSuiteStarted(final Description description) {
118            new SafeNotifier() {
119                @Override
120                protected void notifyListener(RunListener each) throws Exception {
121                    each.testSuiteStarted(description);
122                }
123            }.run();
124        }
125    
126        /**
127         * Invoke to tell listeners that a test suite is about to finish. Always invoke
128         * this method if you invoke {@link #fireTestSuiteStarted(Description)}
129         * as listeners are likely to expect them to come in pairs.
130         *
131         * @param description the description of the suite test (generally a class name)
132         * @since 4.13
133         */
134        public void fireTestSuiteFinished(final Description description) {
135            new SafeNotifier() {
136                @Override
137                protected void notifyListener(RunListener each) throws Exception {
138                    each.testSuiteFinished(description);
139                }
140            }.run();
141        }
142    
143        /**
144         * Invoke to tell listeners that an atomic test is about to start.
145         *
146         * @param description the description of the atomic test (generally a class and method name)
147         * @throws StoppedByUserException thrown if a user has requested that the test run stop
148         */
149        public void fireTestStarted(final Description description) throws StoppedByUserException {
150            if (pleaseStop) {
151                throw new StoppedByUserException();
152            }
153            new SafeNotifier() {
154                @Override
155                protected void notifyListener(RunListener each) throws Exception {
156                    each.testStarted(description);
157                }
158            }.run();
159        }
160    
161        /**
162         * Invoke to tell listeners that an atomic test failed.
163         *
164         * @param failure the description of the test that failed and the exception thrown
165         */
166        public void fireTestFailure(Failure failure) {
167            fireTestFailures(listeners, asList(failure));
168        }
169    
170        private void fireTestFailures(List<RunListener> listeners,
171                final List<Failure> failures) {
172            if (!failures.isEmpty()) {
173                new SafeNotifier(listeners) {
174                    @Override
175                    protected void notifyListener(RunListener listener) throws Exception {
176                        for (Failure each : failures) {
177                            listener.testFailure(each);
178                        }
179                    }
180                }.run();
181            }
182        }
183    
184        /**
185         * Invoke to tell listeners that an atomic test flagged that it assumed
186         * something false.
187         *
188         * @param failure the description of the test that failed and the
189         * {@link org.junit.AssumptionViolatedException} thrown
190         */
191        public void fireTestAssumptionFailed(final Failure failure) {
192            new SafeNotifier() {
193                @Override
194                protected void notifyListener(RunListener each) throws Exception {
195                    each.testAssumptionFailure(failure);
196                }
197            }.run();
198        }
199    
200        /**
201         * Invoke to tell listeners that an atomic test was ignored.
202         *
203         * @param description the description of the ignored test
204         */
205        public void fireTestIgnored(final Description description) {
206            new SafeNotifier() {
207                @Override
208                protected void notifyListener(RunListener each) throws Exception {
209                    each.testIgnored(description);
210                }
211            }.run();
212        }
213    
214        /**
215         * Invoke to tell listeners that an atomic test finished. Always invoke
216         * this method if you invoke {@link #fireTestStarted(Description)}
217         * as listeners are likely to expect them to come in pairs.
218         *
219         * @param description the description of the test that finished
220         */
221        public void fireTestFinished(final Description description) {
222            new SafeNotifier() {
223                @Override
224                protected void notifyListener(RunListener each) throws Exception {
225                    each.testFinished(description);
226                }
227            }.run();
228        }
229    
230        /**
231         * Ask that the tests run stop before starting the next test. Phrased politely because
232         * the test currently running will not be interrupted. It seems a little odd to put this
233         * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
234         * to be shared amongst the many runners involved.
235         */
236        public void pleaseStop() {
237            pleaseStop = true;
238        }
239    
240        /**
241         * Internal use only. The Result's listener must be first.
242         */
243        public void addFirstListener(RunListener listener) {
244            if (listener == null) {
245                throw new NullPointerException("Cannot add a null listener");
246            }
247            listeners.add(0, wrapIfNotThreadSafe(listener));
248        }
249    }