You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ru...@apache.org on 2008/02/20 17:05:08 UTC

svn commit: r629518 [3/7] - in /incubator/qpid/branches/M2.1/java: ./ broker/src/main/java/org/apache/qpid/server/plugins/ broker/src/main/java/org/apache/qpid/server/txn/ client-java14/ client/ client/src/main/java/org/apache/qpid/client/ client/src/t...

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,375 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+/**
+ * A test decorator that runs a test many times simultaneously in many threads.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Clone a test run into many threads and run them simultaneously.
+ * <tr><td> Inform the test results of the start and end of each concurrent test batch. <td> {@link TKTestResult}
+ * <tr><td> Inform the test results of the concurrency level. <td> {@link TKTestResult}
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class ScaledTestDecorator extends WrappedSuiteTestDecorator implements ShutdownHookable // TestDecorator
+{
+    /** Used for logging. */
+    // private static final Logger log = Logger.getLogger(ScaledTestDecorator.class);
+
+    /** Determines how long to wait for tests to cleanly exit on shutdown. */
+    private static final long SHUTDOWN_PAUSE = 3000;
+
+    /**
+     * The stress levels or numbers of simultaneous threads to run the test in. The test is repeated at each of
+     * the concurrency levels specified here. Defaults to 1 thread.
+     */
+    private int[] threads = new int[] { 1 };
+
+    /** Used to hold the number of tests currently being run in parallel. */
+    private int concurrencyLevel;
+
+    /** The test to run. */
+    private WrappedSuiteTestDecorator test;
+
+    /**
+     * Used to hold the current {@link TKTestResult} for the tests currently being run. This is made available so that
+     * the shutdown hook can ask it to cleanly end the current tests in the event of a shutdown.
+     */
+    private TKTestResult currentTestResult;
+
+    /** Flag set by the shutdown hook. This decorator will not start any new tests when this is set. */
+    private boolean shutdown = false;
+
+    /**
+     * Creates an active test with default multiplier (1).
+     *
+     * @param test The target test.
+     */
+    public ScaledTestDecorator(WrappedSuiteTestDecorator test)
+    {
+        super(test);
+        this.test = test;
+    }
+
+    /**
+     * Creates a concurrently scaled test with the specified number of threads.
+     *
+     * @param test       The target test.
+     * @param numThreads The stress level.
+     */
+    public ScaledTestDecorator(WrappedSuiteTestDecorator test, int numThreads)
+    {
+        this(test, new int[] { numThreads });
+    }
+
+    /**
+     * Creates a concurrently scaled test with the specified thread levels, the test is repeated at each level.
+     *
+     * @param test    The target test.
+     * @param threads The concurrency levels.
+     */
+    public ScaledTestDecorator(WrappedSuiteTestDecorator test, int[] threads)
+    {
+        super(test);
+
+        /*log.debug("public ScaledTestDecorator(WrappedSuiteTestDecorator test = \"" + test + "\", int[] threads = "
+                  + MathUtils.printArray(threads) + "): called");*/
+
+        this.test = test;
+        this.threads = threads;
+    }
+
+    /**
+     * Runs the test simultaneously in at the specified concurrency levels.
+     *
+     * @param testResult The results object to monitor the test results with.
+     */
+    public void run(TestResult testResult)
+    {
+        // log.debug("public void run(TestResult testResult = " + testResult + "): called");
+
+        // Loop through all of the specified concurrent levels for the test, provided shutdown has not been called.
+        for (int i = 0; (i < threads.length) && !shutdown; i++)
+        {
+            // Get the number of threads for this run.
+            int numThreads = threads[i];
+
+            // Create test thread handlers for all the threads.
+            TestThreadHandler[] threadHandlers = new TestThreadHandler[numThreads];
+
+            // Create a cyclic barrier for the test threads to synch their setups and teardowns on.
+            CyclicBarrier barrier = new CyclicBarrier(numThreads);
+
+            // Set up the test thread handlers to output results to the same test results object.
+            for (int j = 0; j < numThreads; j++)
+            {
+                threadHandlers[j] = new TestThreadHandler(testResult, test, barrier);
+            }
+
+            // Ensure the concurrency level statistic is set up correctly.
+            concurrencyLevel = numThreads;
+
+            // Begin batch.
+            if (testResult instanceof TKTestResult)
+            {
+                TKTestResult tkResult = (TKTestResult) testResult;
+                // tkResult.notifyStartBatch();
+                tkResult.setConcurrencyLevel(numThreads);
+
+                // Set the test result for the currently running tests, so that the shutdown hook can call it if necessary.
+                currentTestResult = tkResult;
+            }
+
+            // Run all the tests and wait for them all to finish.
+            executeAndWaitForRunnables(threadHandlers);
+
+            // Clear the test result for the currently running tests.
+            currentTestResult = null;
+
+            // End batch.
+            if (testResult instanceof TKTestResult)
+            {
+                TKTestResult tkResult = (TKTestResult) testResult;
+                tkResult.notifyEndBatch();
+            }
+
+            // Clear up all the test threads, they hold references to their associated TestResult object and Test object,
+            // which may prevent them from being garbage collected as the TestResult and Test objects are long lived.
+            for (int j = 0; j < numThreads; j++)
+            {
+                threadHandlers[j].testResult = null;
+                threadHandlers[j].test = null;
+                threadHandlers[j] = null;
+            }
+        }
+    }
+
+    /**
+     * Reports the number of tests that the scaled decorator is currently running concurrently.
+     *
+     * @return The number of tests that the scaled decorator is currently running concurrently.
+     */
+    public int getConcurrencyLevel()
+    {
+        return concurrencyLevel;
+    }
+
+    /**
+     * Executes all of the specifed runnable using the thread pool and waits for them all to complete.
+     *
+     * @param runnables The set of runnables to execute concurrently.
+     */
+    private void executeAndWaitForRunnables(Runnable[] runnables)
+    {
+        int numThreads = runnables.length;
+
+        // Used to keep track of the test threads in order to know when they have all completed.
+        Thread[] threads = new Thread[numThreads];
+
+        // Create all the test threads.
+        for (int j = 0; j < numThreads; j++)
+        {
+            threads[j] = new Thread(runnables[j]);
+        }
+
+        // Start all the test threads.
+        for (int j = 0; j < numThreads; j++)
+        {
+            threads[j].start();
+        }
+
+        // Wait for all the test threads to complete.
+        for (int j = 0; j < numThreads; j++)
+        {
+            try
+            {
+                threads[j].join();
+            }
+            catch (InterruptedException e)
+            {
+                // Restore the interrupted state of the thread.
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Supplies the shut-down hook.
+     *
+     * @return The shut-down hook.
+     */
+    public Thread getShutdownHook()
+    {
+        return new Thread(new Runnable()
+                {
+                    public void run()
+                    {
+                        // log.debug("ScaledTestDecorator::ShutdownHook: called");
+
+                        // Set the shutdown flag so that no new tests are started.
+                        shutdown = true;
+
+                        // Check if tests are currently running, and ask them to complete as soon as possible. Allow
+                        // a short pause for this to happen.
+                        TKTestResult testResult = currentTestResult;
+
+                        if (testResult != null)
+                        {
+                            // log.debug("There is a test result currently running tests, asking it to terminate ASAP.");
+                            testResult.shutdownNow();
+
+                            try
+                            {
+                                Thread.sleep(SHUTDOWN_PAUSE);
+                            }
+                            catch (InterruptedException e)
+                            {
+                                // Restore the interrupted state of the thread.
+                                Thread.currentThread().interrupt();
+                            }
+                        }
+                    }
+                });
+    }
+
+    /**
+     * Prints a string summarizing this test decorator, mainly for debugging purposes.
+     *
+     * @return String representation for debugging purposes.
+     */
+    public String toString()
+    {
+        return "ScaledTestDecorator: [ test = " + test + ", concurrencyLevel = " + concurrencyLevel + " ]";
+    }
+
+    /**
+     * TestThreadHandler is a runnable used to execute a test in. This is static to avoid implicit 'this' reference to
+     * the longer lived ScaledTestDecorator class. The scaled test decorator may execute many repeats but creates fresh
+     * handlers for each one. It re-uses the threads in a pool but does not re-use these handlers.
+     */
+    private static class TestThreadHandler implements Runnable
+    {
+        /** The test result object for the test to be run with. */
+        TestResult testResult;
+
+        /** The test to run. */
+        WrappedSuiteTestDecorator test;
+
+        /** Holds the cyclic barrier to synchronize on the end of the setups and before the tear downs. */
+        CyclicBarrier barrier;
+
+        /**
+         * Creates a new TestThreadHandler object.
+         *
+         * @param testResult The test result object for the test to be run with.
+         * @param test       The test to run in a sperate thread.
+         * @param barrier    The barrier implementation to use to synchronize per-thread setup completion and test
+         *                   completion before moving on through the setup, test, teardown phases. The barrier should
+         *                   be configured for the number of test threads.
+         */
+        TestThreadHandler(TestResult testResult, WrappedSuiteTestDecorator test, CyclicBarrier barrier)
+        {
+            this.testResult = testResult;
+            this.test = test;
+            this.barrier = barrier;
+        }
+
+        /**
+         * Runs the test associated with this pool.
+         */
+        public void run()
+        {
+            try
+            {
+                // Call setup on all underlying tests in the suite that are thread aware.
+                for (Test childTest : test.getAllUnderlyingTests())
+                {
+                    // Check that the test is concurrency aware, so provides a setup method to call.
+                    if (childTest instanceof TestThreadAware)
+                    {
+                        // Call the tests per thread setup.
+                        TestThreadAware setupTest = (TestThreadAware) childTest;
+                        setupTest.threadSetUp();
+                    }
+                }
+
+                // Wait until all test threads have completed their setups.
+                barrier.await();
+
+                // Start timing the test batch, only after thread setups have completed.
+                if (testResult instanceof TKTestResult)
+                {
+                    ((TKTestResult) testResult).notifyStartBatch();
+                }
+
+                // Run the tests.
+                test.run(testResult);
+
+                // Wait unitl all test threads have completed their tests.
+                barrier.await();
+
+                // Call tear down on all underlying tests in the suite that are thread aware.
+                for (Test childTest : test.getAllUnderlyingTests())
+                {
+                    // Check that the test is concurrency aware, so provides a teardown method to call.
+                    if (childTest instanceof TestThreadAware)
+                    {
+                        // Call the tests per thread tear down.
+                        TestThreadAware setupTest = (TestThreadAware) childTest;
+                        setupTest.threadTearDown();
+                    }
+                }
+            }
+            catch (InterruptedException e)
+            {
+                // Restore the interrupted state of the thread.
+                Thread.currentThread().interrupt();
+            }
+            catch (BrokenBarrierException e)
+            {
+                // Set the interrupted state on the thread. The BrokenBarrierException may be caused where one thread
+                // waiting for the barrier is interrupted, causing the remaining threads correctly waiting on the
+                // barrier to fail. This condition is expected during test interruptions, and the response to it is to
+                // interrupt all the other threads running in the same scaled test.
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        /**
+         * Prints the name of the test for debugging purposes.
+         *
+         * @return The name of the test.
+         */
+        public String toString()
+        {
+            return "ScaledTestDecorator: [test = \"" + test + "\"]";
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,50 @@
+/*
+ * 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 org.apache.qpid.junit.extensions;
+
+/**
+ * SetupTaskAware is an interface that tests that can accept injectable setup tasks may implement. Typically this
+ * is used by configurable decorator stack to inject setup tasks into tests. It is then up to the test case to run
+ * the tasks in the setup or threadSetup methods as it chooses.
+ *
+ * <p/>Set up tasks should be chained so that they are executed in the order that they are applied. Tear down tasks
+ * should be chained so that they are executed in the reverse order to which they are applied. That way the set up and
+ * tear down tasks act as a 'task' stack, with nested setups and tear downs.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities.
+ * <tr><td> Handle injection of set up tasks.
+ * <tr><td> Handle injection of tear down tasks.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public interface SetupTaskAware
+{
+    /**
+     * Adds the specified task to the tests setup.
+     *
+     * @param task The task to add to the tests setup.
+     */
+    public void chainSetupTask(Runnable task);
+
+    /**
+     * Adds the specified task to the tests tear down.
+     *
+     * @param task The task to add to the tests tear down.
+     */
+    public void chainTearDownTask(Runnable task);
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,92 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+import org.apache.qpid.junit.extensions.util.StackQueue;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * SetupTaskHandler implements a task stack. It can be used, by delegation, as a base implementation for tests that want
+ * to have configurable setup/teardown task stacks. Typically it is up to the test implementation to decide whether the
+ * stack is executed in the setup/teardown methods or in the threadSetup/threadTeaddown methods.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Handle injection of set up tasks.
+ * <tr><td> Handle injection of tear down tasks.
+ * <tr><td> Run set up tasks in chain order.
+ * <tr><td> Run tear down tasks in reverse chain order.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class SetupTaskHandler implements SetupTaskAware
+{
+    /** Holds the set up tasks. */
+    Queue<Runnable> setups = new LinkedList<Runnable>();
+
+    /** Holds the tear down tasks. */
+    Queue<Runnable> teardowns = new StackQueue<Runnable>();
+
+    /**
+     * Adds the specified task to the tests setup.
+     *
+     * @param task The task to add to the tests setup.
+     */
+    public void chainSetupTask(Runnable task)
+    {
+        setups.offer(task);
+    }
+
+    /**
+     * Adds the specified task to the tests tear down.
+     *
+     * @param task The task to add to the tests tear down.
+     */
+    public void chainTearDownTask(Runnable task)
+    {
+        teardowns.offer(task);
+    }
+
+    /**
+     * Runs the set up tasks in the order that they way chained.
+     */
+    public void runSetupTasks()
+    {
+        while (!setups.isEmpty())
+        {
+            setups.remove().run();
+        }
+    }
+
+    /**
+     * Runs the tear down tasks in the reverse of the order in which they were chained.
+     */
+    public void runTearDownTasks()
+    {
+        while (!teardowns.isEmpty())
+        {
+            teardowns.remove().run();
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+/**
+ * Defines an interface that classes which supply shutdown hooks implement. Code that creates these classes can check
+ * if they supply a shutdown hook and register these hooks when the obejct are created.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Supply a shutdown hook.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public interface ShutdownHookable
+{
+    /**
+     * Supplies the shutdown hook.
+     *
+     * @return The shut down hook.
+     */
+    public Thread getShutdownHook();
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,81 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+/**
+ * SleepThrottle is a Throttle implementation that generates short pauses using the thread sleep methods. As the pauses
+ * get shorter, this technique gets more innacurate. In practice, around 100 Hz is the cap rate for accuracy.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Accept throttling rate in operations per second.
+ * <tr><td> Inject short pauses to fill out processing cycles to a specified rate.
+ * <tr><td> Check against a throttle speed without waiting.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class SleepThrottle extends BaseThrottle implements Throttle
+{
+    /** Holds the time of the last call to the throttle method in nano seconds. */
+    private long lastTimeNanos;
+
+    /**
+     * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this
+     * it will inject short pauses to restrict the call rate to that rate.
+     */
+    public void throttle()
+    {
+        // Get the current time in nanos.
+        long currentTimeNanos = System.nanoTime();
+
+        // Don't introduce any pause on the first call.
+        if (!firstCall)
+        {
+            // Check if there is any time left in the cycle since the last call to this method and introduce a short pause
+            // to fill that time if there is.
+            long remainingTimeNanos = cycleTimeNanos - (currentTimeNanos - lastTimeNanos);
+
+            if (remainingTimeNanos > 0)
+            {
+                long milliPause = remainingTimeNanos / 1000000;
+                int nanoPause = (int) (remainingTimeNanos % 1000000);
+
+                try
+                {
+                    Thread.sleep(milliPause, nanoPause);
+                }
+                catch (InterruptedException e)
+                {
+                    // Restore the interrupted thread, in-case the caller is checking for it.
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+        else
+        {
+            firstCall = false;
+        }
+
+        // Update the last time stamp.
+        lastTimeNanos = System.nanoTime();
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,625 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.listeners.TKTestListener;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Properties;
+
+/**
+ * TKTestResult extends TestResult in order to calculate test timings, to pass the variable integer parameter for
+ * parameterized test cases to those test cases and to introduce an optional delay before test starts. Interested
+ * {@link TKTestListener}s may be attached to this and will be informed of all relevant test statistics.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Calculate test timings.
+ * <tr><td> Inform timing listeners of timings.
+ * <tr><td> Inform memory listeners of memory readings.
+ * <tr><td> Inform parameters listeners of parameters.
+ * <tr><td> Pass the integer parameter to parameterized test cases.
+ * <tr><td> Provide verbose test information on test start and end.
+ * </table>
+ *
+ * @todo Move the verbose test information on test start/end into a test listener instead. It confuses the intention
+ *       of this class. Could also move the delay into a listener but that seems less appropriate as it would be a
+ *       side-effecting listener. Delay and timing calculation are fundamental enough to this class.
+ *
+ * @todo The need for this class to act as a place-holder for the integer parameter for parameterized test cases is
+ *       because this behaviour has been factored out into a test decorator class, see {@link AsymptoticTestDecorator}.
+ *       The {@link AsymptoticTestDecorator#run} method takes a TestResult as an argument and cannot easily get to the
+ *       {@link AsymptoticTestCase} class other than through this class. The option of using this class as a place hold
+ *       for this value was chosen. Alternatively this class could provide a method for decorators to access the
+ *       underlying test case through and then leave the setting of this parameter to the decorator which is a more
+ *       natural home for this behaviour. It would also provide a more general framework for decorators.
+ *
+ * @todo The memory usage may need to be moved in closer to the test method invocation so that as little code as possible
+ *       exists between it and the test or the results may be obscured. In fact it certainly does as the teardown method
+ *       is getting called first. Wouldn't be a bad idea to move the timing code in closer too.
+ *
+ * @todo Get rid of the delay logic. Will be replaced by throttle control.
+ *
+ * @author Rupert Smith
+ */
+public class TKTestResult extends TestResult
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(TKTestResult.class);
+
+    /** The delay between two tests. */
+    private int delay = 0;
+
+    /**
+     * This flag indicates that the #completeTest method of the timing controller has been called. Once this has
+     * been called once, the end test event for the whole test method should be ignored because tests have taken
+     * charge of outputing their own timings.
+     */
+    private boolean completeTestUsed = false;
+
+    /**
+     * Thread locals to hold test start time for non-instrumented tests. (Instrumented tests hold their own
+     * measurement data).
+     */
+    // private Hashtable threadStartTimeMap = new Hashtable();
+    private ThreadLocal<ThreadLocalSettings> threadLocals = new ThreadLocal<ThreadLocalSettings>();
+
+    /** Used to hold the current integer parameter to pass to parameterized tests. This defaults to 1. */
+    private int n = 1;
+
+    /** The timing listeners. */
+    private Collection<TKTestListener> tkListeners;
+
+    /** The test case name. */
+    private String testCaseName;
+
+    /** Used to hold the current concurrency level, set by the {@link ScaledTestDecorator}. */
+    private int concurrencyLevel = 1;
+
+    /** Flag used to indicate that this test result should attempt to complete its current tests as soon as possible. */
+    private boolean shutdownNow = false;
+
+    /** Holds the parametes that the test is run with. */
+    private Properties testParameters;
+
+    /**
+     * Creates a new TKTestResult object.
+     *
+     * @param delay        A delay in milliseconds to introduce before every test start.
+     * @param testCaseName The name of the test case that this is the TestResult object for.
+     */
+    public TKTestResult(int delay, String testCaseName)
+    {
+        super();
+
+        /*log.debug("public TKTestResult(PrintStream writer, int " + delay + ", boolean " + verbose + ", String "
+            + testCaseName + "): called");*/
+
+        // Keep all the parameters that this is created with.
+        this.delay = delay;
+        this.testCaseName = testCaseName;
+    }
+
+    /**
+     * Callback method use to inform this test result that a test will be started. Waits for the configured delay time
+     * if one has been set, starts the timer, then delegates to the super class implementation.
+     *
+     * @param test The test to be started.
+     */
+    public void startTest(Test test)
+    {
+        // log.debug("public void startTest(Test test): called");
+
+        // If a delay time has been specified then wait for that length of time.
+        if (this.delay > 0)
+        {
+            try
+            {
+                Thread.sleep(delay);
+            }
+            catch (InterruptedException e)
+            {
+                // Ignore, but restore the interrupted flag.
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        // Create the thread local settings for the test.
+        ThreadLocalSettings threadLocalSettings = new ThreadLocalSettings();
+        threadLocals.set(threadLocalSettings);
+
+        // Record the test start time against this thread for calculating the test timing. (Consider using ThreadLocal
+        // instead?)
+        Long startTime = System.nanoTime();
+        threadLocalSettings.startTime = startTime;
+        // log.debug("startTime = " + startTime);
+
+        // Check if the test is timing controller aware, in which case set up a new timing controller and hold it
+        // in the thread local settings.
+        if (test instanceof TimingControllerAware)
+        {
+            TimingControllerAware controllerAware = (TimingControllerAware) test;
+            TimingControllerImpl controller =
+                new TimingControllerImpl(this, test, startTime, Thread.currentThread().getId());
+            controllerAware.setTimingController(controller);
+
+            threadLocalSettings.timingController = controller;
+        }
+
+        // Delegate to the super method to notify test event listeners.
+        super.startTest(test);
+    }
+
+    /**
+     * Callback method use to inform this result that a test was completed. This calculates how long the test took
+     * to run, then delegates to the super class implementation.
+     *
+     * @param test The test that has ended.
+     */
+    public void endTest(Test test)
+    {
+        // log.debug("public void endTest(Test test): called");
+
+        long runTime = 0;
+
+        // Recover the thread local settings.
+        ThreadLocalSettings threadLocalSettings = threadLocals.get();
+
+        // Check if the test is an instrumented test and get the timing information from the instrumentation as this
+        // will be more accurate.
+        if (test instanceof InstrumentedTest)
+        {
+            InstrumentedTest iTest = (InstrumentedTest) test;
+
+            // Calculate the test run time.
+            runTime = iTest.getTestTime();
+            // log.debug("runTime = " + runTime);
+
+            // Calculate the test memory usage.
+            long startMem = iTest.getTestStartMemory();
+            long endMem = iTest.getTestEndMemory();
+
+            // log.debug("startMem = " + startMem);
+            // log.debug("endMem = " + endMem);
+
+            // Inform any memory listeners of the test memory.
+            if (tkListeners != null)
+            {
+                for (TKTestListener memoryListener : tkListeners)
+                {
+                    memoryListener.memoryUsed(test, startMem, endMem, null);
+                }
+            }
+        }
+        else
+        {
+            // Calculate the test run time.
+            long endTime = System.nanoTime();
+            Long startTime = threadLocalSettings.startTime;
+            runTime = endTime - startTime;
+            // log.debug("runTime = " + runTime);
+
+            threadLocals.remove();
+        }
+
+        // Output end test stats. This is only done when the tests have not used the timing controller to output
+        // mutiple timings.
+        if (!completeTestUsed)
+        {
+            // Check if the test is an asymptotic test case and get its int parameter if so.
+            if (test instanceof AsymptoticTestCase)
+            {
+                AsymptoticTestCase pTest = (AsymptoticTestCase) test;
+
+                // Set the parameter.
+                int paramValue = pTest.getN();
+
+                // Inform any parameter listeners of the test parameter.
+                if (tkListeners != null)
+                {
+                    for (TKTestListener parameterListener : tkListeners)
+                    {
+                        parameterListener.parameterValue(test, paramValue, null);
+                    }
+                }
+            }
+
+            // Inform any timing listeners of the test timing and concurrency level.
+            if (tkListeners != null)
+            {
+                for (TKTestListener tkListener : tkListeners)
+                {
+                    TKTestListener next = tkListener;
+
+                    next.timing(test, runTime, null);
+                    next.concurrencyLevel(test, concurrencyLevel, null);
+                }
+            }
+
+            // Call the super method to notify test event listeners of the end event.
+            super.endTest(test);
+        }
+    }
+
+    /**
+     * Gets the integer parameter to pass to parameterized test cases.
+     *
+     * @return The value of the integer parameter.
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * Sets the integer parameter to pass to parameterized test cases.
+     *
+     * @param n The new value of the integer parameter.
+     */
+    public void setN(int n)
+    {
+        // log.debug("public void setN(int " + n + "): called");
+
+        this.n = n;
+    }
+
+    /**
+     * Adds a timing listener to pass all timing events to.
+     *
+     * @param listener The timing listener to register.
+     */
+    public void addTKTestListener(TKTestListener listener)
+    {
+        // Create the collection to hold the timing listeners if it does not already exist.
+        if (tkListeners == null)
+        {
+            tkListeners = new ArrayList<TKTestListener>();
+        }
+
+        // Keep the new timing listener.
+        tkListeners.add(listener);
+    }
+
+    /**
+     * Called by the test runner to notify this that a new test batch is being begun. This method forwards this
+     * notification to all batch listeners.
+     */
+    public void notifyStartBatch()
+    {
+        if (tkListeners != null)
+        {
+            for (TKTestListener batchListener : tkListeners)
+            {
+                batchListener.startBatch();
+            }
+        }
+    }
+
+    /**
+     * Called by the test runner to notify this that the current test batch has been ended. This method forwards this
+     * notification to all batch listener.
+     */
+    public void notifyEndBatch()
+    {
+        // log.debug("public void notifyEndBatch(): called");
+
+        if (tkListeners != null)
+        {
+            for (TKTestListener batchListener : tkListeners)
+            {
+                batchListener.endBatch(testParameters);
+            }
+        }
+    }
+
+    /**
+     * Called by the test runner to notify this of the properties that the test is using.
+     *
+     * @param properties The tests set/read properties.
+     */
+    public void notifyTestProperties(Properties properties)
+    {
+        // log.debug("public void notifyTestProperties(Properties properties): called");
+
+        this.testParameters = properties;
+
+        /*
+        if (tkListeners != null)
+        {
+            for (TKTestListener batchListener : tkListeners)
+            {
+                batchListener.properties(properties);
+            }
+        }
+         */
+    }
+
+    /**
+     * Intercepts the execution of a test case to pass the variable integer parameter to a test if it is a parameterized
+     * test case.
+     *
+     * @param test The test to run.
+     */
+    protected void run(final TestCase test)
+    {
+        // log.debug("protected void run(final TestCase test): called");
+
+        // Check if the test case is a parameterized test and set its integer parameter if so.
+        if (test instanceof AsymptoticTestCase)
+        {
+            AsymptoticTestCase pTest = (AsymptoticTestCase) test;
+
+            // Set up the integer parameter.
+            pTest.setN(n);
+        }
+
+        // Delegate to the super method to run the test.
+        super.run(test);
+    }
+
+    /**
+     * Helper method that generats a String of verbose information about a test. This includes the thread name, test
+     * class name and test method name.
+     *
+     * @param test The test to generate the info string for.
+     *
+     * @return Returns a string with the thread name, test class name and test method name.
+     */
+    protected String getTestInfo(Test test)
+    {
+        // log.debug("protected String getTestInfo(Test test): called");
+
+        return "[" + Thread.currentThread().getName() + "@" + test.getClass().getName() + "."
+            + ((test instanceof TestCase) ? ((TestCase) test).getName() : "") + "]";
+    }
+
+    /**
+     * Sets the concurrency level to pass into the test result.
+     *
+     * @param concurrencyLevel The concurrency level the tests are running out.
+     */
+    public void setConcurrencyLevel(int concurrencyLevel)
+    {
+        this.concurrencyLevel = concurrencyLevel;
+    }
+
+    /**
+     * Tells this test result that it should stop running tests. Once this method has been called this test result
+     * will not start any new tests, and any tests that use the timing controller will be passed interrupted exceptions,
+     * to indicate that they should end immediately. Usually the caller of this method will introduce a short wait
+     * to allow an opporunity for running tests to complete, before forcing the shutdown of the JVM.
+     */
+    public void shutdownNow()
+    {
+        log.debug("public void shutdownNow(): called on " + this);
+
+        shutdownNow = true;
+    }
+
+    /**
+     * Prints a string summary of this class, mainly for debugging purposes.
+     *
+     * @return A string summary of this class, mainly for debugging purposes.
+     */
+    public String toString()
+    {
+        return "TKTestResult@" + Integer.toString(hashCode(), 16) + ": [ testCaseName = " + testCaseName + ", n = " + n
+            + ", tkListeners = " + tkListeners + " ]";
+    }
+
+    /**
+     * Holds things that need to be kept on a per thread basis for each test invocation, such as the test start
+     * time and its timing controller.
+     */
+    private static class ThreadLocalSettings
+    {
+        /** Holds the test start time. */
+        Long startTime;
+
+        /** Holds the test threads timing controller. */
+        TimingController timingController;
+    }
+
+    /**
+     * Provides an implementation of the {@link TimingController} interface that timing aware tests can use to call
+     * back to reset timers, and register additional test timings.
+     */
+    private static class TimingControllerImpl implements TimingController
+    {
+        /** Holds an explicit reference to the test TKTestResult that created this. */
+        TKTestResult testResult;
+
+        /** Holds a reference to the test that this is the timing controller for. */
+        Test test;
+
+        /** Holds the start time for this timing controller. This gets reset to now on each completed test. */
+        long startTime;
+
+        /**
+         * Holds the thread id of the thread that started the test, so that this controller may be called from other
+         * threads but still identify itself correctly to {@link TKTestListener}s as being associated with the
+         * thread that called the test method.
+         */
+        long threadId;
+
+        /**
+         * Creates a timing controller on a specified TKTestResult and a test.
+         *
+         * @param testResult       The TKTestResult that this controller interacts with.
+         * @param test             The test that this is the timing controller for.
+         * @param startTime        The test start time in nanoseconds.
+         * @param threadId         The thread id of the thread that is calling the test method.
+         */
+        public TimingControllerImpl(TKTestResult testResult, Test test, long startTime, long threadId)
+        {
+            this.testResult = testResult;
+            this.test = test;
+            this.startTime = startTime;
+            this.threadId = threadId;
+        }
+
+        /**
+         * Gets the timing controller associated with the current test thread. Tests that use timing controller should
+         * always get the timing controller from this method in the same thread that called the setUp, tearDown or test
+         * method. The controller returned by this method may be called from any thread because it remembers the thread
+         * id of the original test thread.
+         *
+         * @return The timing controller associated with the current test thread.
+         */
+        public TimingController getControllerForCurrentThread()
+        {
+            // Recover the thread local settings and extract the timing controller from them.
+            ThreadLocalSettings threadLocalSettings = testResult.threadLocals.get();
+
+            return threadLocalSettings.timingController;
+        }
+
+        /**
+         * Not implemented yet.
+         *
+         * @return Nothing.
+         */
+        public long suspend()
+        {
+            throw new RuntimeException("Method not implemented.");
+        }
+
+        /**
+         * Not implemented yet.
+         *
+         * @return Nothing.
+         */
+        public long resume()
+        {
+            throw new RuntimeException("Method not implemented.");
+        }
+
+        /**
+         * Resets the timer start time to now.
+         *
+         * @return The new value of the start time.
+         */
+        public long restart()
+        {
+            startTime = System.nanoTime();
+
+            return startTime;
+        }
+
+        /**
+         * Register an additional pass/fail for the current test. The test result is assumed to apply to a test of
+         * 'size' parmeter 1. Use the {@link #completeTest(boolean, int)} method to register timings with parameters.
+         *
+         * @param testPassed Whether or not this timing is for a test pass or fail.
+         *
+         * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+         *                              indicate to the test method that it should stop immediately.
+         */
+        public void completeTest(boolean testPassed) throws InterruptedException
+        {
+            completeTest(testPassed, 1);
+        }
+
+        /**
+         * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+         * 'size' parmeter.
+         *
+         * @param testPassed Whether or not this timing is for a test pass or fail.
+         * @param param      The test parameter size for parameterized tests.
+         *
+         * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+         *                              indicate to the test method that it should stop immediately.
+         */
+        public void completeTest(boolean testPassed, int param) throws InterruptedException
+        {
+            /*log.debug("public long completeTest(boolean testPassed = " + testPassed + ", int param = " + param
+                + "): called");*/
+
+            // Calculate the test run time.
+            long endTime = System.nanoTime();
+            long runTime = endTime - startTime;
+            // log.debug("runTime = " + runTime);
+
+            // Reset the test start time to now, to reset the timer for the next result.
+            startTime = endTime;
+
+            completeTest(testPassed, param, runTime);
+        }
+
+        /**
+         * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+         * 'size' parmeter and allows the caller to sepecify the timing to log.
+         *
+         * @param testPassed Whether or not this timing is for a test pass or fail.
+         * @param param      The test parameter size for parameterized tests.
+         * @param timeNanos  The time in nano-seconds to log the test result with.
+         *
+         * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+         *                              indicate to the test method that it should stop immediately.
+         */
+        public void completeTest(boolean testPassed, int param, long timeNanos) throws InterruptedException
+        {
+            log.debug("public void completeTest(boolean testPassed, int param, long timeNanos): called");
+            log.debug("testResult = " + testResult);
+
+            // Tell the test result that completeTest has been used, so to not register end test events for the whole
+            // test method.
+            testResult.completeTestUsed = true;
+
+            // Inform any timing listeners of the test timings and parameters and send an end test notification using
+            // the thread id of the thread that started the test.
+            if (testResult.tkListeners != null)
+            {
+                for (TKTestListener listener : testResult.tkListeners)
+                {
+                    listener.reset(test, threadId);
+                    listener.timing(test, timeNanos, threadId);
+                    listener.parameterValue(test, param, threadId);
+                    listener.concurrencyLevel(test, testResult.concurrencyLevel, threadId);
+
+                    if (!testPassed)
+                    {
+                        listener.addFailure(test, null, threadId);
+                    }
+
+                    listener.endTest(test, threadId);
+                }
+            }
+
+            // log.debug("testResult.shutdownNow = " + testResult.shutdownNow);
+
+            // Check if the test runner has been asked to shutdown and raise an interuppted exception if so.
+            if (testResult.shutdownNow)
+            {
+                // log.debug("The shutdown flag is set.");
+
+                throw new InterruptedException("Attempting clean shutdown by suspending current test.");
+            }
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,694 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.qpid.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.listeners.CSVTestListener;
+import org.apache.qpid.junit.extensions.listeners.ConsoleTestListener;
+import org.apache.qpid.junit.extensions.listeners.XMLTestListener;
+import org.apache.qpid.junit.extensions.util.CommandLineParser;
+import org.apache.qpid.junit.extensions.util.MathUtils;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * TKTestRunner extends {@link junit.textui.TestRunner} with the ability to run tests multiple times, to execute a test
+ * simultaneously using many threads, to put a delay between test runs and adds support for tests that take integer
+ * parameters that can be 'stepped' through on multiple test runs. These features can be accessed by using this class
+ * as an entry point and passing command line arguments to specify which features to use:
+ *
+ * <pre>
+ * -w ms       The number of milliseconds between invocations of test cases.
+ * -c pattern  The number of tests to run concurrently.
+ * -r num      The number of times to repeat each test.
+ * -d duration The length of time to run the tests for.
+ * -t name     The name of the test case to execute.
+ * -s pattern  The size parameter to run tests with.
+ * -o dir      The name of the directory to output test timings to.
+ * --csv       Output test results in CSV format.
+ * --xml       Output test results in XML format.
+ * </pre>
+ *
+ * <p/>This command line may also have trailing 'name=value' parameters added to it. All of these values are added
+ * to the test context properties and passed to the test, which can access them by name.
+ *
+ * <p/>The pattern arguments are of the form [lowest(: ...)(: highest)](:sample=s)(:exp), where round brackets
+ * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided
+ * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples.
+ *
+ * <p/>The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least
+ * one of the optional values must be present.
+ *
+ * <p/>When specifying optional test parameters on the command line, in 'name=value' format, it is also possible to use
+ * the format 'name=[value1:value2:value3:...]', to specify multiple values for a parameter. All permutations of all
+ * parameters with multiple values will be created and tested. If the values are numerical, it is also possible to use
+ * the sequence generation patterns instead of fully specifying all of the values.
+ *
+ * <p/>Here are some examples:
+ *
+ * <p/><table>
+ * <tr><td><pre> -c [10:20:30:40:50] </pre><td> Runs the test with 10,20,...,50 threads.
+ * <tr><td><pre> -s [1:100]:samples=10 </pre>
+ *     <td> Runs the test with ten different size parameters evenly spaced between 1 and 100.
+ * <tr><td><pre> -s [1:1000000]:samples=10:exp </pre>
+ *     <td> Runs the test with ten different size parameters exponentially spaced between 1 and 1000000.
+ * <tr><td><pre> -r 10 </pre><td> Runs each test ten times.
+ * <tr><td><pre> -d 10H </pre><td> Runs the test repeatedly for 10 hours.
+ * <tr><td><pre> -d 1M, -r 10 </pre>
+ *     <td> Runs the test repeatedly for 1 minute but only takes a timing sample every 10 test runs.
+ * <tr><td><pre> -r 10, -c [1:5:10:50], -s [100:1000:10000] </pre>
+ *     <td> Runs 12 test cycles (4 concurrency samples * 3 size sample), with 10 repeats each. In total the test
+ *          will be run 199 times (3 + 15 + 30 + 150)
+ * <tr><td><pre> cache=true </pre><td> Passes the 'cache' parameter with value 'true' to the test.
+ * <tr><td><pre> cache=[true:false] </pre><td> Runs the test with the 'cache' parameter set to 'true' and 'false'.
+ * <tr><td><pre> cacheSize=[1000:1000000],samples=4,exp </pre>
+ *     <td> Runs the test with the 'cache' parameter set to a series of exponentially increasing sizes.
+ * </table>
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create the test configuration specified by the command line parameters.
+ * </table>
+ *
+ * @todo Verify that the output directory exists or can be created.
+ *
+ * @todo Verify that the specific named test case to execute exists.
+ *
+ * @todo Drop the delay parameter as it is being replaced by throttling.
+ *
+ * @todo Completely replace the test ui test runner, instead of having TKTestRunner inherit from it, its just not
+ *       good code to extend.
+ *
+ * @author Rupert Smith
+ */
+public class TKTestRunner extends TestRunnerImprovedErrorHandling
+{
+    /** Used for debugging. */
+    private static final Logger log = Logger.getLogger(TKTestRunner.class);
+
+    /** Used for displaying information on the console. */
+    // private static final Logger console = Logger.getLogger("CONSOLE." + TKTestRunner.class.getName());
+
+    /** Used for generating the timestamp when naming output files. */
+    protected static final DateFormat TIME_STAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
+
+    /** Number of times to rerun the test. */
+    protected Integer repetitions = 1;
+
+    /** The length of time to run the tests for. */
+    protected Long duration;
+
+    /** Number of threads running the tests. */
+    protected int[] threads;
+
+    /** Delay in ms to wait between two test cases. */
+    protected int delay = 0;
+
+    /** The parameter values to pass to parameterized tests. */
+    protected int[] params;
+
+    /** Name of the single test case to execute. */
+    protected String testCaseName = null;
+
+    /** Name of the test class. */
+    protected String testClassName = null;
+
+    /** Name of the test run. */
+    protected String testRunName = null;
+
+    /** Directory to output XML reports into, if specified. */
+    protected String reportDir = null;
+
+    /** Flag that indicates the CSV results listener should be used to output results. */
+    protected boolean csvResults;
+
+    /** Flag that indiciates the XML results listener should be used to output results. */
+    protected boolean xmlResults;
+
+    /**
+     * Holds the name of the class of the test currently being run. Ideally passed into the {@link #createTestResult}
+     * method, but as the signature is already fixed for this, the current value gets pushed here as a member variable.
+     */
+    protected String currentTestClassName;
+
+    /** Holds the test results object, which is reponsible for instrumenting tests/threads to record results. */
+    protected TKTestResult result;
+
+    /** Holds a list of factories for instantiating optional user specified test decorators. */
+    protected List<TestDecoratorFactory> decoratorFactories;
+
+    /**
+     * Constructs a TKTestRunner using System.out for all the output.
+     *
+     * @param repetitions        The number of times to repeat the test, or test batch size.
+     * @param duration           The length of time to run the tests for. -1 means no duration has been set.
+     * @param threads            The concurrency levels to ramp up to.
+     * @param delay              A delay in milliseconds between test runs.
+     * @param params             The sets of 'size' parameters to pass to test.
+     * @param testCaseName       The name of the test case to run.
+     * @param reportDir          The directory to output the test results to.
+     * @param runName            The name of the test run; used to name the output file.
+     * @param csvResults         <tt>true</tt> if the CSV results listener should be attached.
+     * @param xmlResults         <tt>true</tt> if the XML results listener should be attached.
+     * @param decoratorFactories List of factories for user specified decorators.
+     */
+    public TKTestRunner(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName,
+        String reportDir, String runName, boolean csvResults, boolean xmlResults,
+        List<TestDecoratorFactory> decoratorFactories)
+    {
+        super(new NullResultPrinter(System.out));
+
+        log.debug("public TKTestRunner(): called");
+
+        // Keep all the test parameters.
+        this.repetitions = repetitions;
+        this.duration = duration;
+        this.threads = threads;
+        this.delay = delay;
+        this.params = params;
+        this.testCaseName = testCaseName;
+        this.reportDir = reportDir;
+        this.testRunName = runName;
+        this.csvResults = csvResults;
+        this.xmlResults = xmlResults;
+        this.decoratorFactories = decoratorFactories;
+    }
+
+    /**
+     * The entry point for the toolkit test runner.
+     *
+     * @param args The command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        // Use the command line parser to evaluate the command line.
+        CommandLineParser commandLine =
+            new CommandLineParser(
+                new String[][]
+                {
+                    { "w", "The number of milliseconds between invocations of test cases.", "ms", "false" },
+                    { "c", "The number of tests to run concurrently.", "num", "false", MathUtils.SEQUENCE_REGEXP },
+                    { "r", "The number of times to repeat each test.", "num", "false" },
+                    { "d", "The length of time to run the tests for.", "duration", "false", MathUtils.DURATION_REGEXP },
+                    { "f", "The maximum rate to call the tests at.", "frequency", "false", "^([1-9][0-9]*)/([1-9][0-9]*)$" },
+                    { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP },
+                    { "t", "The name of the test case to execute.", "name", "false" },
+                    { "o", "The name of the directory to output test timings to.", "dir", "false" },
+                    { "n", "A name for this test run, used to name the output file.", "name", "true" },
+                    {
+                        "X:decorators", "A list of additional test decorators to wrap the tests in.",
+                        "\"class.name[:class.name]*\"", "false"
+                    },
+                    { "1", "Test class.", "class", "true" },
+                    { "-csv", "Output test results in CSV format.", null, "false" },
+                    { "-xml", "Output test results in XML format.", null, "false" }
+                });
+
+        // Capture the command line arguments or display errors and correct usage and then exit.
+        ParsedProperties options = null;
+
+        try
+        {
+            options = new ParsedProperties(commandLine.parseCommandLine(args));
+        }
+        catch (IllegalArgumentException e)
+        {
+            System.out.println(commandLine.getErrors());
+            System.out.println(commandLine.getUsage());
+            System.exit(FAILURE_EXIT);
+        }
+
+        // Extract the command line options.
+        Integer delay = options.getPropertyAsInteger("w");
+        String threadsString = options.getProperty("c");
+        Integer repetitions = options.getPropertyAsInteger("r");
+        String durationString = options.getProperty("d");
+        String paramsString = options.getProperty("s");
+        String testCaseName = options.getProperty("t");
+        String reportDir = options.getProperty("o");
+        String testRunName = options.getProperty("n");
+        String decorators = options.getProperty("X:decorators");
+        String testClassName = options.getProperty("1");
+        boolean csvResults = options.getPropertyAsBoolean("-csv");
+        boolean xmlResults = options.getPropertyAsBoolean("-xml");
+
+        int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString);
+        int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString);
+        Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString);
+
+        // The test run name defaults to the test class name unless a value was specified for it.
+        testRunName = (testRunName == null) ? testClassName : testRunName;
+
+        // Add all the command line options and trailing settings to test context properties. Tests may pick up
+        // overridden values from there, and these values will be logged in the test results, for analysis and
+        // to make tests repeatable.
+        commandLine.addTrailingPairsToProperties(TestContextProperties.getInstance());
+        commandLine.addOptionsToProperties(TestContextProperties.getInstance());
+
+        // Create and start the test runner.
+        try
+        {
+            // Create a list of test decorator factories for use specified decorators to be applied.
+            List<TestDecoratorFactory> decoratorFactories = parseDecorators(decorators);
+
+            TKTestRunner testRunner =
+                new TKTestRunner(repetitions, duration, threads, (delay == null) ? 0 : delay, params, testCaseName,
+                    reportDir, testRunName, csvResults, xmlResults, decoratorFactories);
+
+            TestResult testResult = testRunner.start(testClassName);
+
+            if (!testResult.wasSuccessful())
+            {
+                System.exit(FAILURE_EXIT);
+            }
+        }
+        catch (Exception e)
+        {
+            System.err.println(e.getMessage());
+            e.printStackTrace(new PrintStream(System.err));
+            System.exit(EXCEPTION_EXIT);
+        }
+    }
+
+    /**
+     * Parses a list of test decorators, in the form "class.name[:class.name]*", and creates factories for those
+     * TestDecorator classes , and returns a list of the factories. This list of factories will be in the same
+     * order as specified in the string. The factories can be used to succesively wrap tests in layers of
+     * decorators, as decorators themselves implement the 'Test' interface.
+     *
+     * <p/>If the string fails to parse, or if any of the decorators specified in it are cannot be loaded, or are not
+     * TestDecorators, a runtime exception with a suitable error message will be thrown. The factories themselves
+     * throw runtimes if the constructor method calls on the decorators fail.
+     *
+     * @param decorators The decorators list to be parsed.
+     *
+     * @return A list of instantiated decorators.
+     */
+    protected static List<TestDecoratorFactory> parseDecorators(String decorators)
+    {
+        List<TestDecoratorFactory> result = new LinkedList<TestDecoratorFactory>();
+        String toParse = decorators;
+
+        // Check that the decorators string is not null or empty, returning an empty list of decorator factories it
+        // it is.
+        if ((decorators == null) || "".equals(decorators))
+        {
+            return result;
+        }
+
+        // Strip any leading and trailing quotes from the string.
+        if (toParse.charAt(0) == '\"')
+        {
+            toParse = toParse.substring(1, toParse.length() - 1);
+        }
+
+        if (toParse.charAt(toParse.length() - 1) == '\"')
+        {
+            toParse = toParse.substring(0, toParse.length() - 2);
+        }
+
+        // Instantiate all decorators.
+        for (String decoratorClassName : toParse.split(":"))
+        {
+            try
+            {
+                Class decoratorClass = Class.forName(decoratorClassName);
+                final Constructor decoratorConstructor = decoratorClass.getConstructor(WrappedSuiteTestDecorator.class);
+
+                // Check that the decorator is an instance of WrappedSuiteTestDecorator.
+                if (!WrappedSuiteTestDecorator.class.isAssignableFrom(decoratorClass))
+                {
+                    throw new RuntimeException("The decorator class " + decoratorClassName
+                        + " is not a sub-class of WrappedSuiteTestDecorator, which it needs to be.");
+                }
+
+                result.add(new TestDecoratorFactory()
+                    {
+                        public WrappedSuiteTestDecorator decorateTest(Test test)
+                        {
+                            try
+                            {
+                                return (WrappedSuiteTestDecorator) decoratorConstructor.newInstance(test);
+                            }
+                            catch (InstantiationException e)
+                            {
+                                throw new RuntimeException(
+                                    "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+                                    + " cannot be instantiated.", e);
+                            }
+                            catch (IllegalAccessException e)
+                            {
+                                throw new RuntimeException(
+                                    "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+                                    + " does not have a publicly accessable constructor.", e);
+                            }
+                            catch (InvocationTargetException e)
+                            {
+                                throw new RuntimeException(
+                                    "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+                                    + " cannot be invoked.", e);
+                            }
+                        }
+                    });
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new RuntimeException("The decorator class " + decoratorClassName + " could not be found.", e);
+            }
+            catch (NoSuchMethodException e)
+            {
+                throw new RuntimeException("The decorator class " + decoratorClassName
+                    + " does not have a constructor that accepts a single 'WrappedSuiteTestDecorator' argument.", e);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * TestDecoratorFactory is a factory for creating test decorators from tests.
+     */
+    protected interface TestDecoratorFactory
+    {
+        /**
+         * Decorates the specified test with a new decorator.
+         *
+         * @param test The test to decorate.
+         *
+         * @return The decorated test.
+         */
+        public WrappedSuiteTestDecorator decorateTest(Test test);
+    }
+
+    /**
+     * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run
+     * in any test decorators needed to add in the configured toolkits enhanced junit functionality.
+     *
+     * @param test The test to run.
+     * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for.
+     *
+     * @return The results of the test run.
+     */
+    public TestResult doRun(Test test, boolean wait)
+    {
+        log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called");
+
+        // Wrap the tests in decorators for duration, scaling, repetition, parameterization etc.
+        WrappedSuiteTestDecorator targetTest = decorateTests(test);
+
+        // Delegate to the super method to run the decorated tests.
+        log.debug("About to call super.doRun");
+
+        TestResult result = super.doRun(targetTest, wait);
+        log.debug("super.doRun returned.");
+
+        /*if (result instanceof TKTestResult)
+        {
+            TKTestResult tkResult = (TKTestResult) result;
+
+            tkResult.notifyEndBatch();
+        }*/
+
+        return result;
+    }
+
+    /**
+     * Applies test decorators to the tests for parameterization, duration, scaling and repetition.
+     *
+     * @param test The test to decorat.
+     *
+     * @return The decorated test.
+     */
+    protected WrappedSuiteTestDecorator decorateTests(Test test)
+    {
+        log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+        log.debug("repetitions = " + repetitions);
+        log.debug("threads = " + ((threads == null) ? null : MathUtils.printArray(threads)));
+        log.debug("duration = " + duration);
+
+        // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling,
+        // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it.
+        WrappedSuiteTestDecorator targetTest = null;
+
+        if (test instanceof TestSuite)
+        {
+            log.debug("targetTest is a TestSuite");
+
+            TestSuite suite = (TestSuite) test;
+
+            int numTests = suite.countTestCases();
+            log.debug("There are " + numTests + " in the suite.");
+
+            for (int i = 0; i < numTests; i++)
+            {
+                Test nextTest = suite.testAt(i);
+                log.debug("suite.testAt(" + i + ") = " + nextTest);
+
+                if (nextTest instanceof TimingControllerAware)
+                {
+                    log.debug("nextTest is TimingControllerAware");
+                }
+
+                if (nextTest instanceof TestThreadAware)
+                {
+                    log.debug("nextTest is TestThreadAware");
+                }
+            }
+
+            targetTest = new WrappedSuiteTestDecorator(suite);
+            log.debug("Wrapped with a WrappedSuiteTestDecorator.");
+        }
+        // If the test has already been wrapped, no need to do it again.
+        else if (test instanceof WrappedSuiteTestDecorator)
+        {
+            targetTest = (WrappedSuiteTestDecorator) test;
+        }
+
+        // If size parameter values have been set, then wrap the test in an asymptotic test decorator.
+        if (params != null)
+        {
+            targetTest = new AsymptoticTestDecorator(targetTest, params, (repetitions == null) ? 1 : repetitions);
+            log.debug("Wrapped with asymptotic test decorator.");
+            log.debug("targetTest = " + targetTest);
+        }
+
+        // If no size parameters are set but the repitions parameter is, then wrap the test in an asymptotic test decorator.
+        else if ((repetitions != null) && (repetitions > 1))
+        {
+            targetTest = new AsymptoticTestDecorator(targetTest, new int[] { 1 }, repetitions);
+            log.debug("Wrapped with asymptotic test decorator.");
+            log.debug("targetTest = " + targetTest);
+        }
+
+        // Apply any optional user specified decorators.
+        targetTest = applyOptionalUserDecorators(targetTest);
+
+        // If a test run duration has been set then wrap the test in a duration test decorator. This will wrap on
+        // top of size, repeat or concurrency wrappings already applied.
+        if (duration != null)
+        {
+            DurationTestDecorator durationTest = new DurationTestDecorator(targetTest, duration);
+            targetTest = durationTest;
+
+            log.debug("Wrapped with duration test decorator.");
+            log.debug("targetTest = " + targetTest);
+
+            registerShutdownHook(durationTest);
+        }
+
+        // ParameterVariationTestDecorator...
+
+        // If a test thread concurrency level is set then wrap the test in a scaled test decorator. This will wrap on
+        // top of size scaling or repetition wrappings.
+        ScaledTestDecorator scaledDecorator;
+
+        if ((threads != null) && ((threads.length > 1) || (MathUtils.maxInArray(threads) > 1)))
+        {
+            scaledDecorator = new ScaledTestDecorator(targetTest, threads);
+            targetTest = scaledDecorator;
+            log.debug("Wrapped with scaled test decorator.");
+            log.debug("targetTest = " + targetTest);
+        }
+        else
+        {
+            scaledDecorator = new ScaledTestDecorator(targetTest, new int[] { 1 });
+            targetTest = scaledDecorator;
+            log.debug("Wrapped with scaled test decorator with default of 1 thread.");
+            log.debug("targetTest = " + targetTest);
+        }
+
+        // Register the scaled test decorators shutdown hook.
+        registerShutdownHook(scaledDecorator);
+
+        return targetTest;
+    }
+
+    /**
+     * If there were any user specified test decorators on the command line, this method instantiates them and wraps
+     * the test in them, from inner-most to outer-most in the order in which the decorators were supplied on the
+     * command line.
+     *
+     * @param targetTest The test to wrap.
+     *
+     * @return A wrapped test.
+     */
+    protected WrappedSuiteTestDecorator applyOptionalUserDecorators(WrappedSuiteTestDecorator targetTest)
+    {
+        // If there are user defined test decorators apply them in order now.
+        for (TestDecoratorFactory factory : decoratorFactories)
+        {
+            targetTest = factory.decorateTest(targetTest);
+        }
+
+        return targetTest;
+    }
+
+    /**
+     * Creates the TestResult object to be used for test runs. See {@link TKTestResult} for more information and the
+     * enhanced test result class that this uses.
+     *
+     * @return An instance of the enhanced test result object, {@link TKTestResult}.
+     */
+    protected TestResult createTestResult()
+    {
+        log.debug("protected TestResult createTestResult(): called");
+
+        TKTestResult result = new TKTestResult(delay, testCaseName);
+
+        // Check if a directory to output reports to has been specified and attach test listeners if so.
+        if (reportDir != null)
+        {
+            // Create the report directory if it does not already exist.
+            File reportDirFile = new File(reportDir);
+
+            if (!reportDirFile.exists())
+            {
+                reportDirFile.mkdir();
+            }
+
+            // Create the results file (make the name of this configurable as a command line parameter).
+            Writer timingsWriter;
+
+            // Always set up a console feedback listener.
+            ConsoleTestListener feedbackListener = new ConsoleTestListener();
+            result.addListener(feedbackListener);
+            result.addTKTestListener(feedbackListener);
+
+            // Set up an XML results listener to output the timings to the results file, if requested on the command line.
+            if (xmlResults)
+            {
+                try
+                {
+                    File timingsFile = new File(reportDirFile, "TEST-" + currentTestClassName + ".xml");
+                    timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+                }
+
+                XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName);
+                result.addListener(listener);
+                result.addTKTestListener(listener);
+
+                registerShutdownHook(listener);
+            }
+
+            // Set up an CSV results listener to output the timings to the results file, if requested on the command line.
+            if (csvResults)
+            {
+                try
+                {
+                    File timingsFile =
+                        new File(reportDirFile, testRunName + "-" + TIME_STAMP_FORMAT.format(new Date()) + "-timings.csv");
+                    timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+                }
+
+                CSVTestListener listener = new CSVTestListener(timingsWriter);
+                result.addListener(listener);
+                result.addTKTestListener(listener);
+
+                // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+                // prematurely.
+                registerShutdownHook(listener);
+            }
+
+            // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+            // prematurely.
+            // registerShutdownHook(listener);
+
+            // Record the start time of the batch.
+            // result.notifyStartBatch();
+
+            // At this point in time the test class has been instantiated, giving it an opportunity to read its parameters.
+            // Inform any test listers of the test properties.
+            result.notifyTestProperties(TestContextProperties.getAccessedProps());
+        }
+
+        return result;
+    }
+
+    /**
+     * Registers the shutdown hook of a {@link ShutdownHookable}.
+     *
+     * @param hookable The hookable to register.
+     */
+    protected void registerShutdownHook(ShutdownHookable hookable)
+    {
+        Runtime.getRuntime().addShutdownHook(hookable.getShutdownHook());
+    }
+
+    /**
+     * Initializes the test runner with the provided command line arguments and and starts the test run.
+     *
+     * @param testClassName The fully qualified name of the test class to run.
+     *
+     * @return The test results.
+     *
+     * @throws Exception Any exceptions from running the tests are allowed to fall through.
+     */
+    protected TestResult start(String testClassName) throws Exception
+    {
+        // Record the current test class, so that the test results can be output to a file incorporating this name.
+        this.currentTestClassName = testClassName;
+
+        // Delegate to the super method to run the tests.
+        return super.start(new String[] { testClassName });
+    }
+}