public abstract class TestThread extends Thread
This thread implementation has tools built into it that make it more useful for testing
concurrency problems. In particular, it has runTest(TestThread...), which will
launch the threads and await their completion, killing off those that do not complete in
the expected time frame (twice the default barrier timeout
unless given explicitly).
Thread.State, Thread.UncaughtExceptionHandler| Modifier and Type | Field and Description |
|---|---|
protected org.junit.rules.ExpectedException |
thrown
An
ExpectedException rule that can be used to declare that the test thread should be
expected to terminate abnormally. |
MAX_PRIORITY, MIN_PRIORITY, NORM_PRIORITY| Constructor and Description |
|---|
TestThread(String name)
Creates a new test thread.
|
| Modifier and Type | Method and Description |
|---|---|
static TestThread |
forRunnable(String name,
Runnable runnable)
This static factory converts a
Runnable into a test thread. |
static TestThread |
forStatement(String name,
org.junit.runners.model.Statement statement)
This static factory converts a unit testing
Statement into a test thread. |
Throwable |
getUncaughtException()
Returns the exception that terminated this thread.
|
protected abstract void |
go()
The actual test thread implementation, which is free to throw whatever exceptions it likes.
|
void |
kill()
Forcibly kill this thread and report what its stack apparently was at the time we killed it.
|
void |
run()
The base implementation of the test thread.
|
static void |
runTest(long timeout,
TestThread... threads)
Concurrently launches a group of test threads and verifies the result, using the given
timeout (in milliseconds) to limit how long the test is allowed to take.
|
static void |
runTest(TestThread... threads)
Concurrently launches a group of test threads and verifies the result, using twice the
barrier timeout as the time limit for the test. |
activeCount, checkAccess, clone, countStackFrames, currentThread, destroy, dumpStack, enumerate, getAllStackTraces, getContextClassLoader, getDefaultUncaughtExceptionHandler, getId, getName, getPriority, getStackTrace, getState, getThreadGroup, getUncaughtExceptionHandler, holdsLock, interrupt, interrupted, isAlive, isDaemon, isInterrupted, join, join, join, resume, setContextClassLoader, setDaemon, setDefaultUncaughtExceptionHandler, setName, setPriority, setUncaughtExceptionHandler, sleep, sleep, start, stop, stop, suspend, toString, yieldprotected final org.junit.rules.ExpectedException thrown
ExpectedException rule that can be used to declare that the test thread should be
expected to terminate abnormally.
Use it within the test thread exactly as you would within any normal JUnit test; that is, set it
to the expected exception information in the statement just before the one that is expected to
throw it to reduce the risk of false positives, say by a NullPointerException getting thrown
by a failed dereference earlier in the test than what you actually meant to be testing.
Example:
TestThread thd1 = new TestThread("thd1")
{
@Override
public void go() throws Exception
{
foo("hello"); // If this throws an NPE, the test still fails
foo("world"); // If this throws an NPE, the test still fails
thrown.expect(NullPointerException.class);
foo(null); // This MUST throw an NPE, or the test will fail
}
};
TestThread thd2 = // ...
runTests(thd1, thd2);
public static TestThread forStatement(String name, org.junit.runners.model.Statement statement)
Statement into a test thread.
This lets you define the test thread code once when the same test logic is to be run by multiple threads.
Example:
AtomicInteger counter = new AtomicInteger();
Statement increment = counter::incrementAndGet;
TestThread thd1 = forStatement("thd1", increment);
TestThread thd2 = forStatement("thd2", increment);
TestThread thd3 = forStatement("thd3", increment);
runTest(thd1, thd2, thd3);
assertThat(counter.get(), is(3));
name - the name of the test threadstatement - the statement to execute in go()forRunnable(String, Runnable)public static TestThread forRunnable(String name, Runnable runnable)
Runnable into a test thread.
This lets you define the test thread code once when the same test logic is to be run by
multiple threads. Note that it may be more convenient to use forStatement(String, Statement)
for writing tests, because that allows you to throw checked exceptions.
Example:
AtomicInteger counter = new AtomicInteger();
Runnable increment = counter::incrementAndGet;
TestThread thd1 = forRunnable("thd1", increment);
TestThread thd2 = forRunnable("thd2", increment);
TestThread thd3 = forRunnable("thd3", increment);
runTest(thd1, thd2, thd3);
assertThat(counter.get(), is(3));
name - the name of the test threadrunnable - the runnable to execute in go()forStatement(String, Statement)public final void run()
go().public void kill()
This uses the ThreadMXBean to fetch the current stack of the thread we are killing and constructs
an AssertionError with those stack frames. It then constructs an additional AssertionError
with its own honest stack frames and sets that as the first exceptions cause. It the reports the first
exception on behalf of the thread it is killing. The result looks something like this:
java.lang.AssertionError: Killed
at java.lang.Thread.sleep(Native Method)
at com.atlassian.utt.concurrency.TestThreadTest$1.go(TestThreadTest.java:x)
// ... more frames ...
at com.atlassian.utt.concurrency.TestThread.run(TestThread.java:x)
Caused by: java.lang.AssertionError: Called kill() on thread "TestThread[victim]"
at com.atlassian.utt.concurrency.TestThread.kill(TestThread.java:x)
at com.atlassian.utt.concurrency.TestThreadTest.testKill(TestThreadTest.java:x)
// ... more frames ...
Yes, this is terrible, evil, nasty stuff. You should never, ever do such a thing in production code, as there is no telling what state it would leave the system in. But this is strictly for testing, and we can do evil things for the greater good, here. Preventing test threads from running for an arbitrarily long time gets us test results more quickly, and knowing where the thread that got killed really was is helpful for figuring out where the real bugs live.
But enough excuses... I'm going to go take a shower, now.
@Nullable public Throwable getUncaughtException()
It does not make sense to call this if the thread is still running. The caller should
Thread.join() and/or check Thread.isAlive() before calling this.
IllegalStateException - if the thread is still runningprotected abstract void go()
throws Exception
runTest(TestThread...).Exception - for any uncaught exception from the threadpublic static void runTest(TestThread... threads)
barrier timeout as the time limit for the test.threads - the test threads to runrunTest(long, TestThread...)public static void runTest(long timeout,
TestThread... threads)
Threads that do not terminate on their own within the time limit are aggressively killed,
and the results of all of the threads are examined to find those that terminated abnormally
without first declaring their intention to do so with thrown. If any threads
did, then an AssertionError is thrown by this method to fail the tests.
Note that no effort is made to coordinate the launch of the threads. Due to arbitrary
scheduling, this would be pointless anyway. If you need to coordinate the threads starting,
do so explicitly with a Sync or Barrier.
timeout - the timeout, in millisecondsthreads - the test threads to runCopyright © 2017 Atlassian. All rights reserved.