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                ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
069                ArrayList<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 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    }