Skip to content

Commit 5887ca0

Browse files
committed
Merge remote-tracking branch 'upstream/master'
Conflicts: src/main/java/org/junit/runner/notification/RunListener.java
2 parents a254e9c + 72af03c commit 5887ca0

File tree

9 files changed

+641
-58
lines changed

9 files changed

+641
-58
lines changed

src/main/java/org/junit/runner/Result.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public boolean wasSuccessful() {
6565
return getFailureCount() == 0;
6666
}
6767

68+
@RunListener.ThreadSafe
6869
private class Listener extends RunListener {
6970
@Override
7071
public void testRunStarted(Description description) throws Exception {

src/main/java/org/junit/runner/notification/RunListener.java

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package org.junit.runner.notification;
22

3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
39
import org.junit.internal.AssumptionViolatedException;
410
import org.junit.runner.Description;
511
import org.junit.runner.Result;
612

713
/**
8-
* If you need to respond to the events during a test run, extend <code>RunListener</code>
9-
* and override the appropriate methods. If a listener throws an exception while processing a
10-
* test event, it will be removed for the remainder of the test run.
11-
* <p>
12-
* For example, suppose you have a <code>Cowbell</code>
14+
* Register an instance of this class with {@link RunNotifier} to be notified
15+
* of events that occur during a test run. All of the methods in this class
16+
* are abstract and have no implementation; override one or more methods to
17+
* receive events.
18+
*
19+
* <p>For example, suppose you have a <code>Cowbell</code>
1320
* class that you want to make a noise whenever a test fails. You could write:
1421
* <pre>
1522
* public class RingingListener extends RunListener {
@@ -18,8 +25,8 @@
1825
* }
1926
* }
2027
* </pre>
21-
* <p>
22-
* To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
28+
*
29+
* <p>To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
2330
* <pre>
2431
* public void main(String... args) {
2532
* JUnitCore core= new JUnitCore();
@@ -28,21 +35,35 @@
2835
* }
2936
* </pre>
3037
*
38+
* <p>If a listener throws an exception for a test event, the other listeners will
39+
* have their {@link RunListener#testFailure(Failure)} called with a {@code Description}
40+
* of {@link Description#TEST_MECHANISM} to indicate the failure.
41+
*
42+
* <p>By default, JUnit will synchronize calls to your listener. If your listener
43+
* is thread-safe and you want to allow JUnit to call your listener from
44+
* multiple threads when tests are run in parallel, you can annotate your
45+
* test class with {@link RunListener.ThreadSafe}.
46+
*
47+
* <p>Listener methods will be called from the same thread as is running
48+
* the test, unless otherwise indicated by the method Javadoc
49+
*
3150
* @see org.junit.runner.JUnitCore
3251
* @since 4.0
3352
*/
3453
public class RunListener {
3554

3655
/**
37-
* Called before any tests have been run.
56+
* Called before any tests have been run. This may be called on an
57+
* arbitrary thread.
3858
*
3959
* @param description describes the tests to be run
4060
*/
4161
public void testRunStarted(Description description) throws Exception {
4262
}
4363

4464
/**
45-
* Called when all tests have finished
65+
* Called when all tests have finished. This may be called on an
66+
* arbitrary thread.
4667
*
4768
* @param result the summary of the test run, including all the tests that failed
4869
*/
@@ -67,7 +88,16 @@ public void testFinished(Description description) throws Exception {
6788
}
6889

6990
/**
70-
* Called when an atomic test fails.
91+
* Called when an atomic test fails, or when a listener throws an exception.
92+
*
93+
* <p>In the case of a failure of an atomic test, this method will be called
94+
* with the same {@code Description} passed to
95+
* {@link #testStarted(Description)}, from the same thread that called
96+
* {@link #testStarted(Description)}.
97+
*
98+
* <p>In the case of a listener throwing an exception, this will be called with
99+
* a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called
100+
* on an arbitrary thread.
71101
*
72102
* @param failure describes the test that failed and the exception that was thrown
73103
*/
@@ -92,6 +122,20 @@ public void testAssumptionFailure(Failure failure) {
92122
*/
93123
public void testIgnored(Description description) throws Exception {
94124
}
95-
}
96125

97126

127+
/**
128+
* Indicates a {@code RunListener} that can have its methods called
129+
* concurrently. This implies that the class is thread-safe (i.e. no set of
130+
* listener calls can put the listener into an invalid state, even if those
131+
* listener calls are being made by multiple threads without
132+
* synchronization).
133+
*
134+
* @since 4.12
135+
*/
136+
@Documented
137+
@Target(ElementType.TYPE)
138+
@Retention(RetentionPolicy.RUNTIME)
139+
public @interface ThreadSafe {
140+
}
141+
}

src/main/java/org/junit/runner/notification/RunNotifier.java

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import static java.util.Arrays.asList;
44

55
import java.util.ArrayList;
6-
import java.util.Collections;
7-
import java.util.Iterator;
6+
import java.util.Collection;
87
import java.util.List;
8+
import java.util.concurrent.CopyOnWriteArrayList;
99

1010
import org.junit.internal.AssumptionViolatedException;
1111
import org.junit.runner.Description;
@@ -21,24 +21,39 @@
2121
* @since 4.0
2222
*/
2323
public class RunNotifier {
24-
private final List<RunListener> fListeners =
25-
Collections.synchronizedList(new ArrayList<RunListener>());
24+
private final List<RunListener> fListeners = new CopyOnWriteArrayList<RunListener>();
2625
private volatile boolean fPleaseStop = false;
2726

2827
/**
2928
* Internal use only
3029
*/
3130
public void addListener(RunListener listener) {
32-
fListeners.add(listener);
31+
if (listener == null) {
32+
throw new NullPointerException("Cannot add a null listener");
33+
}
34+
fListeners.add(wrapIfNotThreadSafe(listener));
3335
}
3436

3537
/**
3638
* Internal use only
3739
*/
3840
public void removeListener(RunListener listener) {
39-
fListeners.remove(listener);
41+
if (listener == null) {
42+
throw new NullPointerException("Cannot remove a null listener");
43+
}
44+
fListeners.remove(wrapIfNotThreadSafe(listener));
45+
}
46+
47+
/**
48+
* Wraps the given listener with {@link SynchronizedRunListener} if
49+
* it is not annotated with {@link RunListener.ThreadSafe}.
50+
*/
51+
RunListener wrapIfNotThreadSafe(RunListener listener) {
52+
return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
53+
listener : new SynchronizedRunListener(listener, this);
4054
}
4155

56+
4257
private abstract class SafeNotifier {
4358
private final List<RunListener> fCurrentListeners;
4459

@@ -51,21 +66,18 @@ private abstract class SafeNotifier {
5166
}
5267

5368
void run() {
54-
synchronized (fListeners) {
55-
List<RunListener> safeListeners = new ArrayList<RunListener>();
56-
List<Failure> failures = new ArrayList<Failure>();
57-
for (Iterator<RunListener> all = fCurrentListeners.iterator(); all
58-
.hasNext(); ) {
59-
try {
60-
RunListener listener = all.next();
61-
notifyListener(listener);
62-
safeListeners.add(listener);
63-
} catch (Exception e) {
64-
failures.add(new Failure(Description.TEST_MECHANISM, e));
65-
}
69+
int capacity = fCurrentListeners.size();
70+
ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
71+
ArrayList<Failure> failures = new ArrayList<Failure>(capacity);
72+
for (RunListener listener : fCurrentListeners) {
73+
try {
74+
notifyListener(listener);
75+
safeListeners.add(listener);
76+
} catch (Exception e) {
77+
failures.add(new Failure(Description.TEST_MECHANISM, e));
6678
}
67-
fireTestFailures(safeListeners, failures);
6879
}
80+
fireTestFailures(safeListeners, failures);
6981
}
7082

7183
abstract protected void notifyListener(RunListener each) throws Exception;
@@ -80,8 +92,6 @@ public void fireTestRunStarted(final Description description) {
8092
protected void notifyListener(RunListener each) throws Exception {
8193
each.testRunStarted(description);
8294
}
83-
84-
;
8595
}.run();
8696
}
8797

@@ -94,8 +104,6 @@ public void fireTestRunFinished(final Result result) {
94104
protected void notifyListener(RunListener each) throws Exception {
95105
each.testRunFinished(result);
96106
}
97-
98-
;
99107
}.run();
100108
}
101109

@@ -114,8 +122,6 @@ public void fireTestStarted(final Description description) throws StoppedByUserE
114122
protected void notifyListener(RunListener each) throws Exception {
115123
each.testStarted(description);
116124
}
117-
118-
;
119125
}.run();
120126
}
121127

@@ -133,14 +139,11 @@ private void fireTestFailures(List<RunListener> listeners,
133139
if (!failures.isEmpty()) {
134140
new SafeNotifier(listeners) {
135141
@Override
136-
protected void notifyListener(RunListener listener)
137-
throws Exception {
142+
protected void notifyListener(RunListener listener) throws Exception {
138143
for (Failure each : failures) {
139144
listener.testFailure(each);
140145
}
141146
}
142-
143-
;
144147
}.run();
145148
}
146149
}
@@ -158,8 +161,6 @@ public void fireTestAssumptionFailed(final Failure failure) {
158161
protected void notifyListener(RunListener each) throws Exception {
159162
each.testAssumptionFailure(failure);
160163
}
161-
162-
;
163164
}.run();
164165
}
165166

@@ -190,8 +191,6 @@ public void fireTestFinished(final Description description) {
190191
protected void notifyListener(RunListener each) throws Exception {
191192
each.testFinished(description);
192193
}
193-
194-
;
195194
}.run();
196195
}
197196

@@ -209,6 +208,9 @@ public void pleaseStop() {
209208
* Internal use only. The Result's listener must be first.
210209
*/
211210
public void addFirstListener(RunListener listener) {
212-
fListeners.add(0, listener);
211+
if (listener == null) {
212+
throw new NullPointerException("Cannot add a null listener");
213+
}
214+
fListeners.add(0, wrapIfNotThreadSafe(listener));
213215
}
214-
}
216+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.junit.runner.notification;
2+
3+
import org.junit.runner.Description;
4+
import org.junit.runner.Result;
5+
6+
/**
7+
* Thread-safe decorator for {@link RunListener} implementations that synchronizes
8+
* calls to the delegate.
9+
*
10+
* <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because
11+
* prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier,
12+
* so no two listeners were ever called concurrently. If we instead made the methods here
13+
* sychronized, clients that added multiple listeners that called common code might see
14+
* issues due to the reduced synchronization.
15+
*
16+
* @author Tibor Digana (tibor17)
17+
* @author Kevin Cooney (kcooney)
18+
* @since 4.12
19+
*
20+
* @see RunNotifier
21+
*/
22+
@RunListener.ThreadSafe
23+
final class SynchronizedRunListener extends RunListener {
24+
private final RunListener fListener;
25+
private final Object fMonitor;
26+
27+
SynchronizedRunListener(RunListener listener, Object monitor) {
28+
fListener = listener;
29+
fMonitor = monitor;
30+
}
31+
32+
@Override
33+
public void testRunStarted(Description description) throws Exception {
34+
synchronized (fMonitor) {
35+
fListener.testRunStarted(description);
36+
}
37+
}
38+
39+
@Override
40+
public void testRunFinished(Result result) throws Exception {
41+
synchronized (fMonitor) {
42+
fListener.testRunFinished(result);
43+
}
44+
}
45+
46+
@Override
47+
public void testStarted(Description description) throws Exception {
48+
synchronized (fMonitor) {
49+
fListener.testStarted(description);
50+
}
51+
}
52+
53+
@Override
54+
public void testFinished(Description description) throws Exception {
55+
synchronized (fMonitor) {
56+
fListener.testFinished(description);
57+
}
58+
}
59+
60+
@Override
61+
public void testFailure(Failure failure) throws Exception {
62+
synchronized (fMonitor) {
63+
fListener.testFailure(failure);
64+
}
65+
}
66+
67+
@Override
68+
public void testAssumptionFailure(Failure failure) {
69+
synchronized (fMonitor) {
70+
fListener.testAssumptionFailure(failure);
71+
}
72+
}
73+
74+
@Override
75+
public void testIgnored(Description description) throws Exception {
76+
synchronized (fMonitor) {
77+
fListener.testIgnored(description);
78+
}
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
return fListener.hashCode();
84+
}
85+
86+
@Override
87+
public boolean equals(Object other) {
88+
if (this == other) {
89+
return true;
90+
}
91+
if (!(other instanceof SynchronizedRunListener)) {
92+
return false;
93+
}
94+
SynchronizedRunListener that = (SynchronizedRunListener) other;
95+
96+
return fListener.equals(that.fListener);
97+
}
98+
99+
@Override
100+
public String toString() {
101+
return fListener.toString() + " (with synchronization wrapper)";
102+
}
103+
}

0 commit comments

Comments
 (0)