package test; /** Base class of tests.
Tests are created by subclassing Test
and overriding the
perform
method. The test author may optionally also override
the clean
and initialize
methods. Tests should
have a public no-argument constructor (such as the default constructor).
Optionally, tests may include two static fields, notice
and
prerequisites
, which are used by the testing library for
printing progress, and for deciding the order in which tests are run,
respectively. When a test has finished, it should call success
or failure
to indicate the outcome. A minimal test class
therefore is:
public class MinimalTest extends test.Test { protected void perform() { success(); } }A more complex example:
public class ComplexTest extends test.Test { public static final String notice = "checking property X"; public static final Class[] prerequisites = new Class[] {OtherTest.class, AnotherTest.class}; // Declarations of members go here, including system resources. protected void perform() { // Test body here. The test may start threads. success(); } protected void initialize() { // Initialize system resources. } protected void clean() { // Clean up system resources and terminate test threads. } }Exceptions thrown from
initialize
and perform
are
equivalent to calls to failure
. Exceptions thrown from
clean
are equivalent to calls to cleanupFailure
.
Returning from perform
or clean
is equivalent to
calling success
and cleanupSuccess
, respectively.
Tests are run as part of a test series
. This is captured by
{@link Series}
objects.
The perform
method runs the bulk of the test. This method, or
a method in another thread, should eventually call success
or
failure
to indicate the result of the test. If neither the
perform
method nor a method run by one of the threads started
by perform
calls either success
or
failure
within a certain time period, the test will be
terminated by a timer started by the testing library. The timer will itself
call failure
.
The first call to success
or failure
determines
the outcome of the test. Subsequent calls to either method have no effect.
After the test is stopped, whether by an explicit call to
success
or failure
, or due to timeout, the
clean
method is called. The purpose of the clean
method is to stop test threads and to release system resources acquired by
the test. The clean
method must likewise eventually cause
either cleanupSuccess
or cleanupFailure
to be
called. If the clean
method fails to do so, the cleanup
process will time out, and will be considered to have failed.
Because of the test timer, and because perform
might start many
threads, a test may, in principle, terminate at any point after
perform
is started, including while it is still running, and
therefore clean
may start at any time while
perform
is still running. The intended coding style for
perform
and clean
then is that clean
will set references to system objects to null
, so that
perform
will receive NullPointerException
s when it
attempts to use them. clean
should also close sockets, streams,
and other I/O objects that perform
or other test threads may be
blocked on, to ensure their speedy termination. The exceptions caused by
these disruptions will lead to calls to failure
by
perform
and the other threads. These calls, however, will be
safely ignored, as the outcome of the test will already have been determined
by the time clean
is started.
This coding style complicates initialization. If a system object reference
is being initialized inside perform
, there is a race condition
with clean
: clean
may run first, find that the
reference is still null
, and do nothing to release the object.
perform
, which has not yet been terminated by an exception,
may then set the reference to a new system object, and continue using it. To
prevent this scenario, a third method initialize
is provided to
the test author. initialize
is as perform
, the
difference being that it is guaranteed that clean
will only be
called after initialize
has completed. It is therefore safe to
initialize system objects within initialize
without taking
additional safety precautions.
Tests should perform no system object initialization within their
constructors. In fact, the intent is that the default constructor be used
for the majority of tests. A derived class should generally not have an
explicit written constructor. The reason is that if initialization fails
while partially complete in initialize
, the test author may
simply call failure
(or allow an exception to escape), and rely
on the subsequent call to clean
to clean up the objects that
were created. If, instead, initialization fails while partially complete in
the constructor, then the testing library cannot call clean
-
there is no object yet to call clean
on.
The testing library expects every test to have a public no-argument
constructor. The default constructor suffices for this purpose. If the
author wishes to write constructors, one must be provided which is public
and takes no arguments. Constructors written by the author may throw
exceptions, and these will be caught by the testing library. Generally, the
rules for constructor exceptions are the same as for exceptions thrown from
initialize
and perform
.
Failure of a test during the initialize
or perform
methods is considered to be regular test failure, and the testing library
may try to run more tests. Failure of a test during cleanup is considered a
fatal error, and the testing library will not attempt any more tests. This
is because the test that failed to clean up after itself may be holding
exclusive resources, which may cause subsequent tests to fail when they are
unable to acquire them.
As a side effect, timeout during initialization will likely lead to a fatal
error. This is because the clean
method does not attempt to
terminate initialize
, waiting instead for it to complete. If
the initialize
method does not stop on its own soon after it
times out, the clean
method will also time out and fail.
A test may make assumptions about the correctness of certain features, which
are tested by another test. In this case, it is highly desirable for the
other test to be run first. The test author may indicate this dependency to
the testing library by creating a public, static, final field in the test
class with the name prerequisites
, as shown in the example
above.
Another static field, notice
, may be used to give the message
that should be printed when the test is run by the Series.run
method, if printing is turned on.
Each test object has an optional task message, settable by the author. The task message describes the current activity of the test. If a test failure occurs while there is a task message set, the exception representing the failure will include the message. Later, when the failure report is printed, the message will be printed as part of the description of the failure. This is especially useful when a test performs long-running operations which may time out. If such an operation times out with no task message set, the report printed will simply state that there was a timeout, leaving the reader with no hint of what the test was doing when the timeout occurred. In this case, the test author should set the task message before starting the operation, and clear it after the operation completes.
Calls to failure
by the testing library itself, described
above, may instead bypass failure
and directly call a
library-private implementation method. It is therefore not, in general,
possible to override failure
and expect to capture every
attempt to stop the test. In particular, the timeout threads deliberately do
not call failure
, and instead call the underlying
implementation method.
*/
public abstract class Test
{
/** Test state reference. See {@link Series.TestState}.
This reference is set by a thread in the Series
object
immediately after the test object is constructor, but is not available
while the constructor is still running.
*/
Series.TestState state = null;
/** Called to initialize the test object.
This method is called before perform
. It is guaranteed that
the clean
method will not be called until
initialize
has exited. The initialize
method,
or threads started by initialize
, may call
success
or failure
to terminate the test. If
this occurs, clean
will be called afterwards. The test may
also be terminated by timeout. Therefore, clean
should be
prepared to handle a partially-initialized test object. Code in
clean
and initialize
should be written in a
way that allows partially-initialized objects to be reliably cleaned up.
Throwing any exception from initialize
results in a call to
failure
. If the exception thrown is assignable to
TestFailed
, the reason
argument given to
failure
is the exception. Otherwise, the exception is
wrapped in a TestFailed
object. Test authors overriding
initialize
are strongly encouraged to mark the subclass
implementation as throwing only TestFailed
, and throw only
that exception.
@throws Throwable Upon test failure. The Throwable
is
wrapped in a TestFailed
object, if it is
not already one, and passed to failure
.
*/
protected void initialize() throws Throwable
{
}
/** Called to perform the test.
This method is called after initialize
. The
clean
method may be called in another thread while this
method is running. perform
should therefore be ready to
handle the effects of the actions done by clean
. Generally,
clean
will set object references to null
and
close I/O streams that perform
or threads started by
perform
are using. It is acceptable for
perform
to throw exceptions in response to these actions.
These exception will not affect the outcome of the test, as the outcome
has been determined by the time clean
has started running.
The purpose of these exceptions is to terminate perform
and
any threads started by it as quickly as possible. The test author must
take care, however, that the exceptions do not adversely affect state
internal to the test itself, and do not damage system objects.
perform
or one of the threads started by it should call
success
or failure
once the outcome of the
test is known. perform
is run within its own thread by the
testing library. There is an additional parallel thread started by the
testing library, which will call failure
after a timeout
interval if perform
fails to terminate the test.
Throwing any exception from perform
results in a call to
failure
. Remarks regarding exceptions thrown from
perform
are the same as for exceptions thrown from
initialize
. In particular, the test author is strongly
encouraged to mark subclass implementations as throwing only
TestFailed
.
Returning from perform
causes success
to be
called. If this is not desired, the test author should have
perform
wait until some condition internal to the test is
satisfied.
@throws Throwable Upon test failure. The Throwable
is
wrapped in a TestFailed
object, if it is
not already one, and passed to failure
.
*/
protected abstract void perform() throws Throwable;
/** Called to terminate test threads and release system resources.
This method is called after success
or failure
has been called, and the test has been terminated. Note that in addition
to the code written by the test author, there is also a timeout thread
in the testing library which will call failure
after the
timeout interval. clean
is guaranteed to be called only
after initialize
has completed, but may be called during
perform
.
clean
, or another thread started by it, should call
cleanupSuccess
or cleanupFailure
. Throwing any
exception from clean
is equivalent to a call to
cleanupFailure
. A thread is started in parallel with
clean
. If the cleanup process fails to terminate before a
timeout interval, this other thread calls cleanupFailure
.
Cleanup failure is a fatal error. If a test is unable to release system resources, subsequent tests, which may demand them, will be unable to acquire them. This can lead to potentially misleading test failure messages. To prevent this, the testing library does not run any more tests after a test fails in the cleanup process.
Returning from clean
is considered to be an implicit call
to cleanupSuccess
.
@throws Throwable If cleanup cannot be completed. This results in a
fatal error.
*/
protected void clean() throws Throwable
{
}
/** Terminates the test as successful.
This method should be called from initialize
or
perform
, or from one of the threads started by them. If
success
or failure
have not yet been called,
calling this method results in a later call to clean
.
Otherwise, calling this method has no effect.
@throws IllegalStateException If the test object is still being
constructed, and therefore neither
initialize
not
perform
have been called.
*/
public void success()
{
if(state == null)
{
throw new IllegalStateException("test success method called " +
"while test is being constructed");
}
state.stop(null);
}
/** Terminates the test as a failure.
This method should be called from initialize
or
perform
, or from one of the threads started by them. If
success
or failure
have not yet been called,
calling this method results in a later call to clean
.
Otherwise, calling this method has no effect.
@param reason Reason for the test failure. The test author is strongly
encouraged to make the reason a descriptive TestFailed
object which explains the situation that lead to the
underlying exception, if there is one. However, for
convenience, it is possible to call failure
with any Throwable
as the argument.
@throws IllegalStateException If the test object is still being
constructed, and therefore neither
initialize
not
perform
have been called.
*/
public void failure(Throwable reason)
{
if(state == null)
{
throw new IllegalStateException("test failure method called " +
"while test is being constructed");
}
state.stop(reason);
}
/** Indicates that cleanup has been successfully completed.
This method should be called from clean
or from one of the
threads started by it. If cleanupSuccess
or
cleanupFailure
have not yet been called, this terminates
the cleanup process. Otherwise, calling this method has no effect.
Calling this method before clean
has been called results in
a call to failure
, and is a fatal error.
@throws IllegalStateException If the test object is still being
constructed, and therefore
clean
has not been called.
*/
public void cleanupSuccess()
{
if(state == null)
{
throw new IllegalStateException("test cleanupSuccess method " +
"called while test is being " +
"constructed");
}
state.stopCleanup(null);
}
/** Indicates that cleanup has failed.
This method should be called from clean
or from one of the
threads started by it. If cleanupSuccess
or
cleanupFailure
have not yet been called, this terminates
the cleanup process. Otherwise, calling this method has no effect.
Calling this method before clean
has been called results in
a call to failure
, and is a fatal error.
@param reason Reason for the cleanup failure.
@throws IllegalStateException If the test object is still being
constructed, and therefore
clean
has not been called.
*/
public void cleanupFailure(Throwable reason)
{
if(state == null)
{
throw new IllegalStateException("test cleanupFailure method " +
"called while test is being " +
"constructed");
}
state.stopCleanup(new FatalError("cleanup failed with explicit call " +
"to cleanupFailure", reason));
}
/** Sets the test task message.
Any existing task message is replaced. @param description Description of the current task. @throws IllegalStateException If the test object is still being constructed. */ public void task(String description) { if(state == null) { throw new IllegalStateException("test task method called while " + "test is being constructed"); } state.task(description); } /** Clears the task message. @throws IllegalStateException If the test object is still being constructed. */ public void task() { if(state == null) { throw new IllegalStateException("test task method called while " + "test is being constructed"); } state.task(null); } }