aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/runner/notification
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/runner/notification')
-rw-r--r--src/main/java/org/junit/runner/notification/Failure.java116
-rw-r--r--src/main/java/org/junit/runner/notification/RunListener.java171
-rw-r--r--src/main/java/org/junit/runner/notification/RunNotifier.java346
-rw-r--r--src/main/java/org/junit/runner/notification/StoppedByUserException.java7
-rw-r--r--src/main/java/org/junit/runner/notification/SynchronizedRunListener.java103
5 files changed, 475 insertions, 268 deletions
diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java
index 501caa5..c03b4c1 100644
--- a/src/main/java/org/junit/runner/notification/Failure.java
+++ b/src/main/java/org/junit/runner/notification/Failure.java
@@ -12,68 +12,76 @@ import org.junit.runner.Description;
* will be of a single test. However, if problems are encountered while constructing the
* test (for example, if a {@link org.junit.BeforeClass} method is not static), it may describe
* something other than a single test.
+ *
+ * @since 4.0
*/
public class Failure implements Serializable {
- private static final long serialVersionUID = 1L;
- private final Description fDescription;
- private final Throwable fThrownException;
+ private static final long serialVersionUID = 1L;
- /**
- * Constructs a <code>Failure</code> with the given description and exception.
- * @param description a {@link org.junit.runner.Description} of the test that failed
- * @param thrownException the exception that was thrown while running the test
- */
- public Failure(Description description, Throwable thrownException) {
- fThrownException = thrownException;
- fDescription= description;
- }
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final Description fDescription;
+ private final Throwable fThrownException;
- /**
- * @return a user-understandable label for the test
- */
- public String getTestHeader() {
- return fDescription.getDisplayName();
- }
+ /**
+ * Constructs a <code>Failure</code> with the given description and exception.
+ *
+ * @param description a {@link org.junit.runner.Description} of the test that failed
+ * @param thrownException the exception that was thrown while running the test
+ */
+ public Failure(Description description, Throwable thrownException) {
+ this.fThrownException = thrownException;
+ this.fDescription = description;
+ }
- /**
- * @return the raw description of the context of the failure.
- */
- public Description getDescription() {
- return fDescription;
- }
+ /**
+ * @return a user-understandable label for the test
+ */
+ public String getTestHeader() {
+ return fDescription.getDisplayName();
+ }
- /**
- * @return the exception thrown
- */
+ /**
+ * @return the raw description of the context of the failure.
+ */
+ public Description getDescription() {
+ return fDescription;
+ }
- public Throwable getException() {
- return fThrownException;
- }
+ /**
+ * @return the exception thrown
+ */
- @Override
- public String toString() {
- StringBuffer buffer= new StringBuffer();
- buffer.append(getTestHeader() + ": "+fThrownException.getMessage());
- return buffer.toString();
- }
+ public Throwable getException() {
+ return fThrownException;
+ }
- /**
- * Convenience method
- * @return the printed form of the exception
- */
- public String getTrace() {
- StringWriter stringWriter= new StringWriter();
- PrintWriter writer= new PrintWriter(stringWriter);
- getException().printStackTrace(writer);
- StringBuffer buffer= stringWriter.getBuffer();
- return buffer.toString();
- }
+ @Override
+ public String toString() {
+ return getTestHeader() + ": " + fThrownException.getMessage();
+ }
- /**
- * Convenience method
- * @return the message of the thrown exception
- */
- public String getMessage() {
- return getException().getMessage();
- }
+ /**
+ * Convenience method
+ *
+ * @return the printed form of the exception
+ */
+ public String getTrace() {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ getException().printStackTrace(writer);
+ return stringWriter.toString();
+ }
+
+ /**
+ * Convenience method
+ *
+ * @return the message of the thrown exception
+ */
+ public String getMessage() {
+ return getException().getMessage();
+ }
}
diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java
index ffe8134..db9d8c1 100644
--- a/src/main/java/org/junit/runner/notification/RunListener.java
+++ b/src/main/java/org/junit/runner/notification/RunListener.java
@@ -1,15 +1,21 @@
package org.junit.runner.notification;
-import org.junit.internal.AssumptionViolatedException;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
import org.junit.runner.Description;
import org.junit.runner.Result;
/**
- * <p>If you need to respond to the events during a test run, extend <code>RunListener</code>
- * and override the appropriate methods. If a listener throws an exception while processing a
- * test event, it will be removed for the remainder of the test run.</p>
- *
- * <p>For example, suppose you have a <code>Cowbell</code>
+ * Register an instance of this class with {@link RunNotifier} to be notified
+ * of events that occur during a test run. All of the methods in this class
+ * are abstract and have no implementation; override one or more methods to
+ * receive events.
+ * <p>
+ * For example, suppose you have a <code>Cowbell</code>
* class that you want to make a noise whenever a test fails. You could write:
* <pre>
* public class RingingListener extends RunListener {
@@ -18,9 +24,8 @@ import org.junit.runner.Result;
* }
* }
* </pre>
- * </p>
- *
- * <p>To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
+ * <p>
+ * To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
* <pre>
* public void main(String... args) {
* JUnitCore core= new JUnitCore();
@@ -28,66 +33,108 @@ import org.junit.runner.Result;
* core.run(MyTestClass.class);
* }
* </pre>
- * </p>
+ * <p>
+ * If a listener throws an exception for a test event, the other listeners will
+ * have their {@link RunListener#testFailure(Failure)} called with a {@code Description}
+ * of {@link Description#TEST_MECHANISM} to indicate the failure.
+ * <p>
+ * By default, JUnit will synchronize calls to your listener. If your listener
+ * is thread-safe and you want to allow JUnit to call your listener from
+ * multiple threads when tests are run in parallel, you can annotate your
+ * test class with {@link RunListener.ThreadSafe}.
+ * <p>
+ * Listener methods will be called from the same thread as is running
+ * the test, unless otherwise indicated by the method Javadoc
+ *
* @see org.junit.runner.JUnitCore
+ * @since 4.0
*/
public class RunListener {
- /**
- * Called before any tests have been run.
- * @param description describes the tests to be run
- */
- public void testRunStarted(Description description) throws Exception {
- }
-
- /**
- * Called when all tests have finished
- * @param result the summary of the test run, including all the tests that failed
- */
- public void testRunFinished(Result result) throws Exception {
- }
-
- /**
- * Called when an atomic test is about to be started.
- * @param description the description of the test that is about to be run
- * (generally a class and method name)
- */
- public void testStarted(Description description) throws Exception {
- }
+ /**
+ * Called before any tests have been run. This may be called on an
+ * arbitrary thread.
+ *
+ * @param description describes the tests to be run
+ */
+ public void testRunStarted(Description description) throws Exception {
+ }
- /**
- * Called when an atomic test has finished, whether the test succeeds or fails.
- * @param description the description of the test that just ran
- */
- public void testFinished(Description description) throws Exception {
- }
+ /**
+ * Called when all tests have finished. This may be called on an
+ * arbitrary thread.
+ *
+ * @param result the summary of the test run, including all the tests that failed
+ */
+ public void testRunFinished(Result result) throws Exception {
+ }
- /**
- * Called when an atomic test fails.
- * @param failure describes the test that failed and the exception that was thrown
- */
- public void testFailure(Failure failure) throws Exception {
- }
+ /**
+ * Called when an atomic test is about to be started.
+ *
+ * @param description the description of the test that is about to be run
+ * (generally a class and method name)
+ */
+ public void testStarted(Description description) throws Exception {
+ }
- /**
- * Called when an atomic test flags that it assumes a condition that is
- * false
- *
- * @param failure
- * describes the test that failed and the
- * {@link AssumptionViolatedException} that was thrown
- */
- public void testAssumptionFailure(Failure failure) {
- }
+ /**
+ * Called when an atomic test has finished, whether the test succeeds or fails.
+ *
+ * @param description the description of the test that just ran
+ */
+ public void testFinished(Description description) throws Exception {
+ }
- /**
- * Called when a test will not be run, generally because a test method is annotated
- * with {@link org.junit.Ignore}.
- *
- * @param description describes the test that will not be run
- */
- public void testIgnored(Description description) throws Exception {
- }
-}
+ /**
+ * Called when an atomic test fails, or when a listener throws an exception.
+ *
+ * <p>In the case of a failure of an atomic test, this method will be called
+ * with the same {@code Description} passed to
+ * {@link #testStarted(Description)}, from the same thread that called
+ * {@link #testStarted(Description)}.
+ *
+ * <p>In the case of a listener throwing an exception, this will be called with
+ * a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called
+ * on an arbitrary thread.
+ *
+ * @param failure describes the test that failed and the exception that was thrown
+ */
+ public void testFailure(Failure failure) throws Exception {
+ }
+
+ /**
+ * Called when an atomic test flags that it assumes a condition that is
+ * false
+ *
+ * @param failure describes the test that failed and the
+ * {@link org.junit.AssumptionViolatedException} that was thrown
+ */
+ public void testAssumptionFailure(Failure failure) {
+ }
+ /**
+ * Called when a test will not be run, generally because a test method is annotated
+ * with {@link org.junit.Ignore}.
+ *
+ * @param description describes the test that will not be run
+ */
+ public void testIgnored(Description description) throws Exception {
+ }
+
+ /**
+ * Indicates a {@code RunListener} that can have its methods called
+ * concurrently. This implies that the class is thread-safe (i.e. no set of
+ * listener calls can put the listener into an invalid state, even if those
+ * listener calls are being made by multiple threads without
+ * synchronization).
+ *
+ * @since 4.12
+ */
+ @Documented
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ThreadSafe {
+ }
+}
diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java
index d0f6c85..6875f76 100644
--- a/src/main/java/org/junit/runner/notification/RunNotifier.java
+++ b/src/main/java/org/junit/runner/notification/RunNotifier.java
@@ -1,166 +1,214 @@
package org.junit.runner.notification;
+import static java.util.Arrays.asList;
+
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
-import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.Result;
/**
* If you write custom runners, you may need to notify JUnit of your progress running tests.
* Do this by invoking the <code>RunNotifier</code> passed to your implementation of
- * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
+ * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
* move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)}
* to a separate class since they should only be called once per run.
+ *
+ * @since 4.0
*/
public class RunNotifier {
- private final List<RunListener> fListeners=
- Collections.synchronizedList(new ArrayList<RunListener>());
- private boolean fPleaseStop= false;
-
- /** Internal use only
- */
- public void addListener(RunListener listener) {
- fListeners.add(listener);
- }
-
- /** Internal use only
- */
- public void removeListener(RunListener listener) {
- fListeners.remove(listener);
+ private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
+ private volatile boolean pleaseStop = false;
+
+ /**
+ * Internal use only
+ */
+ public void addListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot add a null listener");
+ }
+ listeners.add(wrapIfNotThreadSafe(listener));
+ }
+
+ /**
+ * Internal use only
+ */
+ public void removeListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot remove a null listener");
+ }
+ listeners.remove(wrapIfNotThreadSafe(listener));
+ }
+
+ /**
+ * Wraps the given listener with {@link SynchronizedRunListener} if
+ * it is not annotated with {@link RunListener.ThreadSafe}.
+ */
+ RunListener wrapIfNotThreadSafe(RunListener listener) {
+ return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
+ listener : new SynchronizedRunListener(listener, this);
+ }
+
+
+ private abstract class SafeNotifier {
+ private final List<RunListener> currentListeners;
+
+ SafeNotifier() {
+ this(listeners);
+ }
+
+ SafeNotifier(List<RunListener> currentListeners) {
+ this.currentListeners = currentListeners;
+ }
+
+ void run() {
+ int capacity = currentListeners.size();
+ ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
+ ArrayList<Failure> failures = new ArrayList<Failure>(capacity);
+ for (RunListener listener : currentListeners) {
+ try {
+ notifyListener(listener);
+ safeListeners.add(listener);
+ } catch (Exception e) {
+ failures.add(new Failure(Description.TEST_MECHANISM, e));
+ }
+ }
+ fireTestFailures(safeListeners, failures);
+ }
+
+ abstract protected void notifyListener(RunListener each) throws Exception;
+ }
+
+ /**
+ * Do not invoke.
+ */
+ public void fireTestRunStarted(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testRunStarted(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Do not invoke.
+ */
+ public void fireTestRunFinished(final Result result) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testRunFinished(result);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test is about to start.
+ *
+ * @param description the description of the atomic test (generally a class and method name)
+ * @throws StoppedByUserException thrown if a user has requested that the test run stop
+ */
+ public void fireTestStarted(final Description description) throws StoppedByUserException {
+ if (pleaseStop) {
+ throw new StoppedByUserException();
+ }
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testStarted(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test failed.
+ *
+ * @param failure the description of the test that failed and the exception thrown
+ */
+ public void fireTestFailure(Failure failure) {
+ fireTestFailures(listeners, asList(failure));
+ }
+
+ private void fireTestFailures(List<RunListener> listeners,
+ final List<Failure> failures) {
+ if (!failures.isEmpty()) {
+ new SafeNotifier(listeners) {
+ @Override
+ protected void notifyListener(RunListener listener) throws Exception {
+ for (Failure each : failures) {
+ listener.testFailure(each);
+ }
+ }
+ }.run();
+ }
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test flagged that it assumed
+ * something false.
+ *
+ * @param failure the description of the test that failed and the
+ * {@link org.junit.AssumptionViolatedException} thrown
+ */
+ public void fireTestAssumptionFailed(final Failure failure) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testAssumptionFailure(failure);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test was ignored.
+ *
+ * @param description the description of the ignored test
+ */
+ public void fireTestIgnored(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testIgnored(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test finished. Always invoke
+ * this method if you invoke {@link #fireTestStarted(Description)}
+ * as listeners are likely to expect them to come in pairs.
+ *
+ * @param description the description of the test that finished
+ */
+ public void fireTestFinished(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testFinished(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Ask that the tests run stop before starting the next test. Phrased politely because
+ * the test currently running will not be interrupted. It seems a little odd to put this
+ * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
+ * to be shared amongst the many runners involved.
+ */
+ public void pleaseStop() {
+ pleaseStop = true;
}
- private abstract class SafeNotifier {
- void run() {
- synchronized (fListeners) {
- for (Iterator<RunListener> all= fListeners.iterator(); all.hasNext();)
- try {
- notifyListener(all.next());
- } catch (Exception e) {
- all.remove(); // Remove the offending listener first to avoid an infinite loop
- fireTestFailure(new Failure(Description.TEST_MECHANISM, e));
- }
- }
- }
-
- abstract protected void notifyListener(RunListener each) throws Exception;
- }
-
- /**
- * Do not invoke.
- */
- public void fireTestRunStarted(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testRunStarted(description);
- };
- }.run();
- }
-
- /**
- * Do not invoke.
- */
- public void fireTestRunFinished(final Result result) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testRunFinished(result);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test is about to start.
- * @param description the description of the atomic test (generally a class and method name)
- * @throws StoppedByUserException thrown if a user has requested that the test run stop
- */
- public void fireTestStarted(final Description description) throws StoppedByUserException {
- if (fPleaseStop)
- throw new StoppedByUserException();
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testStarted(description);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test failed.
- * @param failure the description of the test that failed and the exception thrown
- */
- public void fireTestFailure(final Failure failure) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testFailure(failure);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test flagged that it assumed
- * something false.
- *
- * @param failure
- * the description of the test that failed and the
- * {@link AssumptionViolatedException} thrown
- */
- public void fireTestAssumptionFailed(final Failure failure) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testAssumptionFailure(failure);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test was ignored.
- * @param description the description of the ignored test
- */
- public void fireTestIgnored(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testIgnored(description);
- }
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test finished. Always invoke
- * {@link #fireTestFinished(Description)} if you invoke {@link #fireTestStarted(Description)}
- * as listeners are likely to expect them to come in pairs.
- * @param description the description of the test that finished
- */
- public void fireTestFinished(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testFinished(description);
- };
- }.run();
- }
-
- /**
- * Ask that the tests run stop before starting the next test. Phrased politely because
- * the test currently running will not be interrupted. It seems a little odd to put this
- * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
- * to be shared amongst the many runners involved.
- */
- public void pleaseStop() {
- fPleaseStop= true;
- }
-
- /**
- * Internal use only. The Result's listener must be first.
- */
- public void addFirstListener(RunListener listener) {
- fListeners.add(0, listener);
- }
-} \ No newline at end of file
+ /**
+ * Internal use only. The Result's listener must be first.
+ */
+ public void addFirstListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot add a null listener");
+ }
+ listeners.add(0, wrapIfNotThreadSafe(listener));
+ }
+}
diff --git a/src/main/java/org/junit/runner/notification/StoppedByUserException.java b/src/main/java/org/junit/runner/notification/StoppedByUserException.java
index 89be3ba..f5490f7 100644
--- a/src/main/java/org/junit/runner/notification/StoppedByUserException.java
+++ b/src/main/java/org/junit/runner/notification/StoppedByUserException.java
@@ -1,11 +1,12 @@
package org.junit.runner.notification;
/**
- * Thrown when a user has requested that the test run stop. Writers of
+ * Thrown when a user has requested that the test run stop. Writers of
* test running GUIs should be prepared to catch a <code>StoppedByUserException</code>.
- *
+ *
* @see org.junit.runner.notification.RunNotifier
+ * @since 4.0
*/
public class StoppedByUserException extends RuntimeException {
- private static final long serialVersionUID= 1L;
+ private static final long serialVersionUID = 1L;
}
diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
new file mode 100644
index 0000000..c53c1ee
--- /dev/null
+++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
@@ -0,0 +1,103 @@
+package org.junit.runner.notification;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+
+/**
+ * Thread-safe decorator for {@link RunListener} implementations that synchronizes
+ * calls to the delegate.
+ *
+ * <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because
+ * prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier,
+ * so no two listeners were ever called concurrently. If we instead made the methods here
+ * sychronized, clients that added multiple listeners that called common code might see
+ * issues due to the reduced synchronization.
+ *
+ * @author Tibor Digana (tibor17)
+ * @author Kevin Cooney (kcooney)
+ * @since 4.12
+ *
+ * @see RunNotifier
+ */
+@RunListener.ThreadSafe
+final class SynchronizedRunListener extends RunListener {
+ private final RunListener listener;
+ private final Object monitor;
+
+ SynchronizedRunListener(RunListener listener, Object monitor) {
+ this.listener = listener;
+ this.monitor = monitor;
+ }
+
+ @Override
+ public void testRunStarted(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testRunStarted(description);
+ }
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ synchronized (monitor) {
+ listener.testRunFinished(result);
+ }
+ }
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testStarted(description);
+ }
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testFinished(description);
+ }
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ synchronized (monitor) {
+ listener.testFailure(failure);
+ }
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ synchronized (monitor) {
+ listener.testAssumptionFailure(failure);
+ }
+ }
+
+ @Override
+ public void testIgnored(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testIgnored(description);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof SynchronizedRunListener)) {
+ return false;
+ }
+ SynchronizedRunListener that = (SynchronizedRunListener) other;
+
+ return listener.equals(that.listener);
+ }
+
+ @Override
+ public String toString() {
+ return listener.toString() + " (with synchronization wrapper)";
+ }
+}