package com.twitter.common.testing.runner;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
/**
* A listener that can be manually aborted while still retaining normal test suite finishing
* behavior for its underlying listeners.
*/
abstract class AbortableListener extends ForwardingListener {
private final Result result = new Result();
private final boolean failFast;
private Description started;
/**
* Creates a new abortable listener that optionally fails a test run on its first failure.
*
* @param failFast Pass {@code true} to {@link #abort(Result)} a test suite on its first failure.
*/
AbortableListener(boolean failFast) {
this.failFast = failFast;
addListener(result.createListener());
}
@Override
public void testStarted(Description description) throws Exception {
this.started = description;
super.testStarted(description);
}
@Override
public void testFailure(Failure failure) throws Exception {
// Allow any listeners to handle the failure in the normal way first.
super.testFailure(failure);
if (failFast) {
finish();
// Allow the subclass to actually stop the test run.
abort(result);
}
}
/**
* Signals a test run is aborting and and passes this signal to underlying listeners with a
* simulated test suite finishing cycle.
*
* @param reason The reason the test run is being stopped.
* @throws Exception If the underlying listener throws.
*/
void abort(Throwable reason) throws Exception {
if (started != null) {
super.testFailure(new Failure(started, reason));
}
finish();
}
private void finish() throws Exception {
// Simulate the junit test run lifecycle end.
testRunFinished(result);
}
/**
* Called on the first test failure. Its expected that subclasses will halt the test run in some
* way.
*
* @param failureResult The test result for the failing suite up to and including the first
* failure
*/
protected abstract void abort(Result failureResult);
}