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 NullPointerExceptions 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); } }