uk.co.thebadgerset.junit.concurrency.TestRunnable Maven / Gradle / Ivy
Go to download
JUnit Toolkit enhances JUnit with performance testing, asymptotic behaviour analysis, and concurrency testing.
The newest version!
/*
* Copyright 2007 Rupert Smith.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.thebadgerset.junit.concurrency;
/**
* TestRunnable is an extension of java.util.Runnable that adds some features to make it easier to coordinate the
* activities of threads in such a way as to expose bugs in multi threaded code.
*
* Sometimes several threads will run in a particular order so that a bug is not revealed. Other times the ordering
* of the threads will expose a bug. Such bugs can be hard to replicate as the exact execution ordering of threads is not
* usually controlled. This class adds some methods that allow threads to synchronize other threads, either allowing them
* to run, or waiting for them to allow this thread to run. It also provides convenience methods to gather error messages
* and exceptions from threads, which will often be reported in unit testing code.
*
* Coordination between threads is handled by the {@link ThreadTestCoordinator}. It is called through the convenience
* methods {@link #allow} and {@link #waitFor}. Threads to be coordinated must be set up with the coordinator and assigned
* integer ids. It is then possible to call the coordinator with an array of thread ids requesting that those threads
* be allowed to continue, or to wait until one of them allows this thread to continue. The otherwise non-deterministic
* execution order of threads can be controlled into a carefully determined sequence using these methods in order
* to reproduce race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on.
*
* When waiting for another thread to give a signal to continue it is sometimes the case that the other thread has
* become blocked by the code under test. For example in testing for a dirty read (for example in database code),
* thread 1 lets thread 2 perform a write but not commit it, then thread 2 lets thread 1 run and attempt to perform a
* dirty read on its uncommitted write. Transaction synchronization code being tested against the possibility of a dirty
* write may make use of snapshots in which case both threads should be able to read and write without blocking. It may
* make use of explicit keys in which case thread 2 may become blocked on its write attempt because thread 1 holds a
* read lock and it must wait until thread 1 completes its transaction before it can acquire this lock. The
* {@link #waitFor} method accepts a boolean parameter to indicate that threads being blocked (other than on the
* coordinator) can be interpreted the same as if the thread explicitly allows the thread calling waitFor to continue.
* Using this technique a dirty read test could be written that works against either the snapshot or the locking
* implementation, allowing both approaches to pass the test yet arranging for multiple threads to run against the
* implementation in such a way that a potential dirty read bug is exposed.
*
* CRC Card
* Responsibilities Collaborations
* Wait for another thread to allow this one to continue.
* Allow another thread to continue.
* Accumulate error messages.
* Record exceptions from thread run.
* Maintain link to thread coordinator.
* Explicitly mark a thread with an integer id.
* Maintian a flag to indicate whether or not this thread is waiting on the coordinator.
*
*
* @todo The allow then waitFor operations are very often used as a pair. So create a method allowAndWait that combines
* them into a single method call.
*
* @author Rupert Smith
*/
public abstract class TestRunnable implements Runnable
{
/** Holds a reference to the thread coordinator. */
private ThreadTestCoordinator coordinator;
/** Holds the explicit integer id of this thread. */
private int id;
/** Used to indicate that this thread is waiting on the coordinator and not elsewhere. */
private boolean waitingOnCoordinator = false;
/** Used to accumulate error messsages. */
private String errorMessage = "";
/** Holds the Java thread object that this is running under. */
private Thread thisThread;
/** Used to hold any exceptions resulting from the run method. */
private Exception runException = null;
/**
* Implementations override this to perform coordinated thread sequencing.
*
* @throws Exception Any exception raised by the implementation will be caught by the default {@link #run()}
* implementation for later querying by the {@link #getException()} method.
*/
public abstract void runWithExceptions() throws Exception;
/**
* Provides a default implementation of the run method that allows exceptions to be thrown and keeps a record
* of those exceptions. Defers to the {@link #runWithExceptions()} method to provide the thread body implementation
* and catches any exceptions thrown by it.
*/
public void run()
{
try
{
runWithExceptions();
}
catch (Exception e)
{
this.runException = e;
}
}
/**
* Attempt to consume an allow event from one of the specified threads and blocks until such an event occurrs.
*
* @param threads The set of threads that can allow this one to continue.
* @param otherWaitIsAllow If set to true if the threads being waited on are blocked other than on
* the coordinator itself then this is to be interpreted as allowing this thread to
* continue.
*
* @return If the otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found
* to be blocked outside of the thread test coordinator. false under all other conditions.
*/
protected boolean waitFor(int[] threads, boolean otherWaitIsAllow)
{
return coordinator.consumeAllowEvent(threads, otherWaitIsAllow, id, this);
}
/**
* Produces allow events on each of the specified threads.
*
* @param threads The set of threads that are to be allowed to continue.
*/
protected void allow(int[] threads)
{
coordinator.produceAllowEvents(threads, id, this);
}
/**
* Keeps the error message for later reporting by the coordinator.
*
* @param message The error message to keep.
*/
protected void addErrorMessage(String message)
{
errorMessage += message;
}
/**
* Sets the coordinator for this thread.
*
* @param coordinator The coordinator for this thread.
*/
void setCoordinator(ThreadTestCoordinator coordinator)
{
this.coordinator = coordinator;
}
/**
* Reports whether or not this thread is waiting on the coordinator.
*
* @return If this thread is waiting on the coordinator.
*/
boolean isWaitingOnCoordinator()
{
return waitingOnCoordinator;
}
/**
* Sets the value of the waiting on coordinator flag.
*
* @param waiting The value of the waiting on coordinator flag.
*/
void setWaitingOnCoordinator(boolean waiting)
{
waitingOnCoordinator = waiting;
}
/**
* Sets up the explicit int id for this thread.
*
* @param id The integer id.
*/
void setId(int id)
{
this.id = id;
}
/**
* Reports any accumulated error messages.
*
* @return Any accumulated error messages.
*/
String getErrorMessage()
{
return errorMessage;
}
/**
* Reports any exception thrown by the {@link #runWithExceptions} method.
*
* @return Any exception thrown by the {@link #runWithExceptions} method.
*/
Exception getException()
{
return runException;
}
/**
* Sets the Java thread under which this runs.
*
* @param thread The Java thread under which this runs.
*/
void setThread(Thread thread)
{
thisThread = thread;
}
/**
* Gets the Java thread under which this runs.
*
* @return The Java thread under which this runs.
*/
Thread getThread()
{
return thisThread;
}
/**
* Provides a string summary of this test threads status.
*
* @return Summarizes this threads status.
*/
public String toString()
{
return "id = " + id + ", waitingOnCoordinator = " + waitingOnCoordinator;
}
}