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 [2/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/concurrency/ThreadTestCoordinator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,485 @@
+/*
+ *
+ * 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.concurrency;
+
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * ThreadTestCoordinator provides an array of binary latches that allows threads to wait for other threads or to send
+ * them a signal that allows them to continue running or to wait for another thread to signal them. The binary latch
+ * array is always a square array, allowing one latch from and to every thread. Upon accepting an allow signal from one
+ * sender the latches for all senders for a are cleared. This class is always used in conjunction with
+ * {@link TestRunnable} for writing concurrent test code that coordinates multi-threaded activity in order to reproduce
+ * concurrency bugs.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Accept test threads to coordinate.
+ * <tr><td> Allow test threads to send 'allow to continue' signals.
+ * <tr><td> Allow test threads to wait on this coordinator for 'allow to continue' signals.
+ * <tr><td> Report error messages from test threads.
+ * <tr><td> Report exceptions from test threads.
+ * <tr><td> Provide method to wait until all test threads have completed.
+ * </table>
+ *
+ * @todo This code was hacked together as a bit of an experiment, because I wasn't sure if this idea would work. It has
+ *       proved extremely usefull. Some documentation for this needs to be written to explain it better.
+ *
+ * @todo Consider how deadlock detection will be handled. If all threads are blocking on the coordinator, waiting for
+ *       each other, they are deadlocked and there is something wrong with the test code that put them in that
+ *       situation. If they are all blocked elsewhere, they may be deadlocked, or could just be waiting on some
+ *       external event. A timeout should be used. Timeout is already implemented, just need to sanity check how
+ *       this is working and document it.
+ *
+ * @todo Consider how livelock detection could be implemented? LockFree data structures might cause live locks. I
+ *       guess a longish timeout is the only thing that can be done for that.
+ *
+ * @todo Only course grained synchronous at the method class level can be obtained. This is because test code can
+ *       only insert synchronization points between method calls it makes. So this code will not be usefull for
+ *       checking sequences of events within methods, unless the code under test is explicitly instrumented for it.
+ *       It might be possible to instrument code by using labels, and then use the debugger/profiler interface to
+ *       put breakpoints on the labels and use them as synchronization points. Not perfect, but at the unused labels
+ *       can be left in the code, without altering its behaviour.
+ *
+ * @author Rupert Smith
+ */
+public class ThreadTestCoordinator
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(ThreadTestCoordinator.class);
+
+    /** Keeps track of the test threads by their ids. */
+    private TestRunnable[] testThreads; // = new TestRunnable[2];
+
+    /** An explicit thread monitor for the coordinator. Threads wait on the coordinator whilst waiting for events. */
+    private final Object coordinatorLock = new Object();
+
+    /** A set of monitors for each test thread. */
+    private Object[] locks;
+
+    /** The binary latch array, this is always a square array allowing one event from and to every thread. */
+    private boolean[][] allowEvents;
+
+    /** Keeps track of the number of threads being coordinated. */
+    private int threadCount = 0;
+
+    /** Accumulates any exceptions resulting from the threads run methods. */
+    private Collection<Exception> exceptions = new ArrayList<Exception>();
+
+    /**
+     * Holds the deadlock timeout after which threads are given a runtime exception to signal that a potential
+     * deadlock may be happening.
+     */
+    private long deadlockTimeout = 1000 * 1000000;
+
+    /** Holds the factory to create test thread with. */
+    private ThreadFactory threadFactory;
+
+    /**
+     * Creates a new test thread coordinator. The number of threads to run must be specified here.
+     *
+     * @param numThreads The number of threads to run.
+     */
+    public ThreadTestCoordinator(int numThreads)
+    {
+        this.threadCount = numThreads;
+
+        // Create an array big enough to hold all the test threads.
+        testThreads = new TestRunnable[threadCount];
+
+        // Use the default thread factory, as none specified.
+        threadFactory = new DefaultThreadFactory();
+    }
+
+    /**
+     * Creates a new test thread coordinator with a specific thread factory. The number of threads to run must be
+     * specified here.
+     *
+     * @param numThreads    The number of threads to run.
+     * @param threadFactory The factory to use to create the test threads.
+     */
+    public ThreadTestCoordinator(int numThreads, ThreadFactory threadFactory)
+    {
+        this.threadCount = numThreads;
+
+        // Create an array big enough to hold all the test threads.
+        testThreads = new TestRunnable[threadCount];
+
+        // Use the specified thread factory.
+        this.threadFactory = threadFactory;
+    }
+
+    /**
+     * Adds a thread to this coordinator and assigns an id to it. The ids must be numbered sequentially from 0 and
+     * it is up to the caller to do this.
+     *
+     * @param runnable The test thread.
+     * @param id       The explicit id to assign to the test thread.
+     */
+    public void addTestThread(TestRunnable runnable, int id)
+    {
+        testThreads[id] = runnable;
+        runnable.setCoordinator(this);
+        runnable.setId(id);
+    }
+
+    /**
+     * Starts all the coordinated threads running.
+     */
+    public void run()
+    {
+        // Create the monitors for each thread.
+        locks = new Object[threadCount];
+
+        // Create an appropriately sized event queue to allow one event from and to each thread.
+        allowEvents = new boolean[threadCount][threadCount];
+
+        // Initialize the monitors and clear the event queues.
+        for (int i = 0; i < locks.length; i++)
+        {
+            locks[i] = new Object();
+
+            for (int j = 0; j < locks.length; j++)
+            {
+                allowEvents[i][j] = false;
+            }
+        }
+
+        // Start all the threads running.
+        for (TestRunnable nextRunnable : testThreads)
+        {
+            // Create a Java thread for the test thread.
+            Thread newThread = threadFactory.newThread(nextRunnable);
+            nextRunnable.setThread(newThread);
+
+            // Start it running.
+            newThread.start();
+        }
+    }
+
+    /**
+     * Waits until all the test threads have completed and returns any accumulated error messages from them. Any
+     * exceptions thrown by their run methods are also kept at this point.
+     *
+     * @return The accumulated error messages from all the threads concatenated together.
+     */
+    public String joinAndRetrieveMessages()
+    {
+        // Create an empty error message.
+        String errorMessage = "";
+
+        // Join all the test threads.
+        for (TestRunnable r : testThreads)
+        {
+            Thread t = r.getThread();
+
+            try
+            {
+                t.join();
+            }
+            catch (InterruptedException e)
+            { }
+
+            // Add any accumulated error messages to the return value.
+            errorMessage += r.getErrorMessage();
+
+            // Keep any exceptions resulting from the threads run method.
+            Exception e = r.getException();
+
+            if (e != null)
+            {
+                exceptions.add(e);
+            }
+        }
+
+        return errorMessage;
+    }
+
+    /**
+     * Reports any accumulated exceptions from the test threads run methods. This method must be called after
+     * {@link #joinAndRetrieveMessages}.
+     *
+     * @return Any accumulated exceptions from the test threads run methods. This method must be called after
+     */
+    public Collection<Exception> getExceptions()
+    {
+        return exceptions;
+    }
+
+    /**
+     * Sets a timeout to break out of potential deadlocks. If all threads are waiting for other threads to send
+     * them continue events for longer than this timeout then the threads are all terminated.
+     *
+     * @param millis The minimum time to allow to pass before breaking out of any potential deadlocks.
+     *
+     * @todo This has not been implemented yet. If a potential deadlock happens then the joinAndRetrieveMessages
+     *       method should throw a PotentialDeadlockException.
+     */
+    public void setDeadlockTimeout(long millis)
+    {
+        deadlockTimeout = millis * 1000000;
+    }
+
+    /**
+     * Creates a set of 'allow to continue' events on the event queues of the specified threads.
+     *
+     * @param threads  The set of threads to allow to continue.
+     * @param callerId The explicit id of the calling test thread.
+     * @param caller   The calling test thread.
+     */
+    void produceAllowEvents(int[] threads, int callerId, TestRunnable caller)
+    {
+        // Generate some debugging messages. Very usefull to know how thread synchronization is progressing.
+        String message = "Thread " + callerId + " is allowing threads [ ";
+
+        for (int j = 0; j < threads.length; j++)
+        {
+            message += threads[j] + ((j < (threads.length - 1)) ? ", " : "");
+        }
+
+        message += " ] to continue.";
+        log.debug(message);
+
+        // For each allow event, synchronize on the threads lock then set the event flag to true.
+        for (int id : threads)
+        {
+            // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+            // being blocked at this time.
+            caller.setWaitingOnCoordinator(true);
+
+            synchronized (locks[id])
+            {
+                // Release the wating on coordinator flag now that this thread is running again.
+                caller.setWaitingOnCoordinator(false);
+
+                // Send the allow to continue event to the receiving thread.
+                allowEvents[id][callerId] = true;
+            }
+        }
+
+        // Wake up any threads waiting on the coordinator lock to recheck their event queues.
+        // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+        // being blocked at this time.
+        caller.setWaitingOnCoordinator(true);
+
+        synchronized (coordinatorLock)
+        {
+            // Release the wating on coordinator flag now that this thread is running again.
+            caller.setWaitingOnCoordinator(false);
+            coordinatorLock.notifyAll();
+        }
+    }
+
+    /**
+     * Consumes an 'allow to continue' from one of the specified threads or waits until one is available or in some
+     * cases if one of the specified threads is blocked elsewhere to accept that as an 'allow to continue' event.
+     *
+     * @param threads          The set of threads to accept an allow to continue event from.
+     * @param otherWaitIsAllow Whether or not to accept threads being blocked elsewhere as permission to continue.
+     * @param callerId         The explicit id of the calling test thread.
+     * @param caller           The calling test thread.
+     *
+     * @return If the <tt>otherWaitIsAllow</tt> flag is set, then <tt>true</tt> is returned when the thread being waited on is found
+     *         to be blocked outside of the thread test coordinator. <tt>false</tt> under all other conditions.
+     */
+    boolean consumeAllowEvent(int[] threads, boolean otherWaitIsAllow, int callerId, TestRunnable caller)
+    {
+        // Generate some debugging messages. Very usefull to know how thread synchronization is progressing.
+        String message = "Thread " + callerId + " is requesting threads [ ";
+
+        // Record the time at which this method was called. Will be used for breaking out of potential deadlocks.
+        long startTime = System.nanoTime();
+
+        for (int j = 0; j < threads.length; j++)
+        {
+            message += threads[j] + ((j < (threads.length - 1)) ? ", " : "");
+        }
+
+        message += " ] to allow it to continue.";
+        log.debug(message);
+
+        // Loop until an allow to continue event is received.
+        while (true)
+        {
+            // Look at all the allowing thread to see if one has created an event for consumption.
+            for (int allowerId : threads)
+            {
+                // Get the threads lock for the event to consume.
+                // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+                // being blocked at this time.
+                caller.setWaitingOnCoordinator(true);
+
+                synchronized (locks[callerId])
+                {
+                    // Release the wating on coordinator flag now that this thread is running again.
+                    caller.setWaitingOnCoordinator(false);
+
+                    // Check if there is an event on the queue from the allowing thread to this one.
+                    if (allowEvents[callerId][allowerId])
+                    {
+                        log.debug("Found an allow event, thread " + allowerId + ", is allowing thread " + callerId
+                            + ", to continue.");
+
+                        // Consume all the allow events for this thread.
+                        /*for (int i = 0; i < allowEvents[callerId].length; i++)
+                        {
+                            allowEvents[callerId][i] = false;
+                        }*/
+
+                        // Consume just the event from the allower to the consumer, leaving other pending allow events alone.
+                        allowEvents[callerId][allowerId] = false;
+
+                        return false;
+                    }
+                }
+            }
+
+            // If waiting elsewhere is to be interpreted as an 'allow to continue' event, then look at the thread status
+            // for the threads being waited on to see if any are blocked on other resources.
+            if (otherWaitIsAllow)
+            {
+                log.debug("Other wait is to be interpreted as an allow event.");
+
+                // Look at all the potential allower threads.
+                for (int allowerId : threads)
+                {
+                    // Get the Java thread state for the allowing thread.
+                    Thread threadToTest = testThreads[allowerId].getThread();
+                    Thread.State state = threadToTest.getState();
+
+                    // Check if the thread is blocked and so a potential candidate for releasing this one.
+                    if ((state == Thread.State.BLOCKED) || (state == Thread.State.WAITING)
+                            || (state == Thread.State.TIMED_WAITING))
+                    {
+                        log.debug("Found an allower thread, id = " + allowerId + ", that is blocked or wating.");
+
+                        // Check that the allower thread is not waiting on the coordinator lock or any of the
+                        // individual thread locks. It must be waiting or blocked on another monitor.
+                        TestRunnable allowingRunnable = testThreads[allowerId];
+                        boolean isWaitingOnCoordinator = allowingRunnable.isWaitingOnCoordinator();
+
+                        if (!isWaitingOnCoordinator)
+                        {
+                            log.debug("The allower thread, id = " + allowerId
+                                + ", is blocked or waiting other than on the coordinator.");
+
+                            // Get the threads lock for the event to consume.
+                            caller.setWaitingOnCoordinator(true);
+
+                            synchronized (locks[callerId])
+                            {
+                                caller.setWaitingOnCoordinator(false);
+
+                                // Consume all the allow events for this thread.
+                                for (int i = 0; i < allowEvents[callerId].length; i++)
+                                {
+                                    allowEvents[callerId][i] = false;
+                                }
+
+                                return true;
+                            }
+                        }
+                        else
+                        {
+                            log.debug("The waiting allower thread, " + allowerId
+                                + ", is waiting on the coordinator so does not allow thread " + callerId + " to continue.");
+                        }
+                    }
+                }
+            }
+
+            // Keep waiting until an 'allow to continue' event can be consumed.
+            try
+            {
+                // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+                // being blocked at this time.
+                caller.setWaitingOnCoordinator(true);
+
+                synchronized (coordinatorLock)
+                {
+                    // Release the wating on coordinator flag now that this thread is running again.
+                    caller.setWaitingOnCoordinator(false);
+
+                    log.debug("Thread " + callerId + " is waiting on coordinator lock for more allow events.");
+
+                    // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+                    // being blocked at this time.
+                    caller.setWaitingOnCoordinator(true);
+                    coordinatorLock.wait(10);
+                }
+            }
+            catch (InterruptedException e)
+            { }
+
+            // Release the waiting on coordinator flag now that this thread is running again.
+            caller.setWaitingOnCoordinator(false);
+
+            // Check if this thread has been waiting for longer than the deadlock timeout and raise a possible
+            // deadlock exception if so.
+            long waitTime = System.nanoTime() - startTime;
+            log.debug("Thread " + callerId + " has been waiting for " + (waitTime / 1000000) + " milliseconds.");
+
+            if (waitTime > deadlockTimeout)
+            {
+                // Throw a possible deadlock exception.
+                throw new PossibleDeadlockException("Possible deadlock due to timeout with state:\n" + this);
+            }
+
+            log.debug("Thread " + callerId + " has woken up, was waiting for more allow events to become available.");
+        }
+    }
+
+    /**
+     * Pretty prints the state of the thread test coordinator, for debugging purposes.
+     *
+     * @return Pretty printed state of the thread test coordinator.
+     */
+    public String toString()
+    {
+        String result = "[";
+
+        for (int i = 0; i < allowEvents.length; i++)
+        {
+            for (int j = 0; j < allowEvents[i].length; j++)
+            {
+                result += allowEvents[i][j];
+
+                result += (j < (allowEvents[i].length - 1)) ? ", " : "";
+            }
+
+            result += (i < (allowEvents.length - 1)) ? ",\n " : "";
+        }
+
+        result += "]";
+
+        for (int i = 0; i < testThreads.length; i++)
+        {
+            result += "thread[" + i + "] = " + testThreads[i].toString();
+        }
+
+        return result;
+    }
+
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,145 @@
+/*
+ *
+ * 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.concurrency;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An example to illustrate the use of the {@link ThreadTestCoordinator} and {@link TestRunnable}s.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Demo multi-threaded testing.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class ThreadTestExample
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(ThreadTestExample.class);
+
+    /** Test thread 1. */
+    TestRunnable testThread1 =
+        new TestRunnable()
+        {
+            public void runWithExceptions() throws Exception
+            {
+                log.debug("public void run(): called");
+                log.info("in testThread0, block 1");
+
+                // Wait for t2 to allow t1 to continue.
+                allow(new int[] { 1 });
+                waitFor(new int[] { 1 }, false);
+
+                log.info("in testThread0, block 2");
+
+                // Wait for t2 to allow t1 to continue. T2 is allowed to be blocked elsewhere than giving explicit
+                // permission to allow t1 to continue.
+                allow(new int[] { 1 });
+                waitFor(new int[] { 1 }, true);
+
+                log.info("in testThread0, block 3");
+
+                // Release thread 2 from waiting on the shared lock.
+                synchronized (sharedLock)
+                {
+                    sharedLock.notifyAll();
+                }
+
+                allow(new int[] { 1 });
+            }
+        };
+
+    /** A shared lock between the test threads. */
+    final Object sharedLock = new Object();
+
+    /** Test thread 2. */
+    TestRunnable testThread2 =
+        new TestRunnable()
+        {
+            public void runWithExceptions() throws Exception
+            {
+                log.debug("public void run(): called");
+                log.info("in testThread1, block 1");
+
+                // Wait for t1 to allow t2 to continue.
+                allow(new int[] { 0 });
+                waitFor(new int[] { 0 }, false);
+
+                log.info("in testThread1, block 2");
+
+                // Wait on another resource. T1 should accept this as permission to continue.
+                try
+                {
+                    synchronized (sharedLock)
+                    {
+                        log.debug("in testThread1, waiting on shared lock.");
+                        sharedLock.wait();
+                    }
+                }
+                catch (InterruptedException e)
+                {
+                    // Bail-out with a runtime if this happens.
+                    throw new RuntimeException("Interrupted whilst waiting for shared lock.", e);
+                }
+
+                log.info("in testThread1, finished waiting on shared lock.");
+
+                // allow(new int[] { 0 });
+
+                // Wait for t1 to allow t2 to continue.
+                waitFor(new int[] { 0 }, false);
+
+                log.info("in testThread1, block 3");
+
+                allow(new int[] { 0 });
+            }
+        };
+
+    /**
+     * Executes the test threads with coordination.
+     *
+     * @param args Ignored.
+     */
+    public void main(String[] args)
+    {
+        ThreadTestCoordinator tt = new ThreadTestCoordinator(2);
+
+        tt.addTestThread(testThread1, 0);
+        tt.addTestThread(testThread2, 1);
+        tt.setDeadlockTimeout(500);
+        tt.run();
+
+        String errorMessage = tt.joinAndRetrieveMessages();
+
+        // Print any error messages or exceptions.
+        log.info(errorMessage);
+
+        if (!tt.getExceptions().isEmpty())
+        {
+            for (Exception e : tt.getExceptions())
+            {
+                log.warn("Exception thrown during test thread: ", e);
+            }
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html Wed Feb 20 08:04:25 2008
@@ -0,0 +1,7 @@
+<html>
+<body>
+Contains code to assist in testing concurrency issues using coordinated threads to present code under test with
+oportunities to expose concurrency bugs. Some example concurrency bugs that may be tested using these techniques are
+race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on.
+</body>
+</html>
\ No newline at end of file

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,303 @@
+/*
+ *
+ * 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.TestCase;
+
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * AsymptoticTestCase is an extension of TestCase for writing unit tests to analyze asymptotic time and space behaviour.
+ *
+ * <p>ParameterizedTestCases allow tests to be defined which have test methods that take a single int argument. Normal
+ * JUnit test methods do not take any arguments. This int argument can be interpreted in any way by the test but it is
+ * intended to denote the 'size' of the test to be run. For example, when testing the performance of a data structure
+ * for different numbers of data elements held in the data structure the int parameter should be interpreted as the
+ * number of elements. Test timings for different numbers of elements can then be captured and the asymptotic behaviour
+ * of the data structure with respect to time analyzed. Any non-parameterized tests defined in extensions of this class
+ * will also be run.
+ *
+ * <p>TestCases derived from this class may also define tear down methods to clean up their memory usage. This is
+ * intended to be used in conjunction with memory listeners that report the amount of memory a test uses. The idea is
+ * to write a test that allocates memory in the main test method in such a way that it leaves that memory still
+ * allocated at the end of the test. The amount of memory used can then be measured before calling the tear down method
+ * to clean it up. In the data structure example above, a test will allocate as many elements as are requested by the
+ * int parameter and deallocate them in the tear down method. In this way memory readings for different numbers of
+ * elements can be captured and the asymptotic behaviour of the data structure with respect to space analyzed.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Store the current int parameter value. <td> {@link TKTestResult} and see {@link AsymptoticTestDecorator} too.
+ * <tr><td> Invoke parameterized test methods.
+ * </table>
+ *
+ * @todo If possible try to move the code that invokes the test and setup/teardown methods into {@link TKTestResult} or
+ *       {@link AsymptoticTestDecorator} rather than this class. This would mean that tests don't have to extend this
+ *       class to do time and space performance analysis, these methods could be added to any JUnit TestCase class
+ *       instead. This would be an improvement because existing unit tests wouldn't have to extend a different class to
+ *       work with this extension, and also tests that extend other junit extension classes could have parameterized
+ *       and tear down methods too.
+ *
+ * @author Rupert Smith
+ */
+public class AsymptoticTestCase extends TestCase implements InstrumentedTest
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(AsymptoticTestCase.class);
+
+    /** The name of the test case. */
+    private String testCaseName;
+
+    /** Thread local for holding measurements on a per thread basis. */
+    ThreadLocal<TestMeasurements> threadLocalMeasurement =
+        new ThreadLocal<TestMeasurements>()
+        {
+            /**
+             * Sets up a default set test measurements (zeroed, apart from the size param which defaults to 1).
+             *
+             * @return A set of default test measurements.
+             */
+            protected synchronized TestMeasurements initialValue()
+            {
+                return new TestMeasurements();
+            }
+        };
+
+    /**
+     * Constructs a test case with the given name.
+     *
+     * @param name The name of the test.
+     */
+    public AsymptoticTestCase(String name)
+    {
+        super(name);
+
+        log.debug("public AsymptoticTestCase(String " + name + "): called");
+        testCaseName = name;
+    }
+
+    /**
+     * Gets the current value of the integer parameter to be passed to the parameterized test.
+     *
+     * @return The current value of the integer parameter.
+     */
+    public int getN()
+    {
+        log.debug("public int getN(): called");
+        int n = threadLocalMeasurement.get().n;
+
+        log.debug("return: " + n);
+
+        return n;
+    }
+
+    /**
+     * Sets the current value of the integer parameter to be passed to the parameterized test.
+     *
+     * @param n The new current value of the integer parameter.
+     */
+    public void setN(int n)
+    {
+        log.debug("public void setN(int " + n + "): called");
+        threadLocalMeasurement.get().n = n;
+    }
+
+    /**
+     * Reports how long the test took to run.
+     *
+     * @return The time in milliseconds that the test took to run.
+     */
+    public long getTestTime()
+    {
+        log.debug("public long getTestTime(): called");
+        long startTime = threadLocalMeasurement.get().startTime;
+        long endTime = threadLocalMeasurement.get().endTime;
+        long testTime = endTime - startTime;
+
+        log.debug("return: " + testTime);
+
+        return testTime;
+    }
+
+    /**
+     * Reports the memory usage at the start of the test.
+     *
+     * @return The memory usage at the start of the test.
+     */
+    public long getTestStartMemory()
+    {
+        // log.debug("public long getTestStartMemory(): called");
+        long startMem = threadLocalMeasurement.get().startMem;
+
+        // log.debug("return: " + startMem);
+
+        return startMem;
+    }
+
+    /**
+     * Reports the memory usage at the end of the test.
+     *
+     * @return The memory usage at the end of the test.
+     */
+    public long getTestEndMemory()
+    {
+        // log.debug("public long getTestEndMemory(): called");
+        long endMem = threadLocalMeasurement.get().endMem;
+
+        // log.debug("return: " + endMem);
+        return endMem;
+    }
+
+    /**
+     * Resets the instrumentation values to zero, and nulls any references to held measurements so that the memory
+     * can be reclaimed.
+     */
+    public void reset()
+    {
+        log.debug("public void reset(): called");
+        threadLocalMeasurement.remove();
+    }
+
+    /**
+     * Runs the test method for this test case.
+     *
+     * @throws Throwable Any Throwables from the test methods invoked are allowed to fall through.
+     */
+    protected void runTest() throws Throwable
+    {
+        log.debug("protected void runTest(): called");
+
+        // Check that a test name has been set. This is used to define which method to run.
+        assertNotNull(testCaseName);
+        log.debug("testCaseName = " + testCaseName);
+
+        // Try to get the method with matching name.
+        Method runMethod = null;
+        boolean isParameterized = false;
+
+        // Check if a parameterized test method is available.
+        try
+        {
+            // Use getMethod to get all public inherited methods. getDeclaredMethods returns all
+            // methods of this class but excludes the inherited ones.
+            runMethod = getClass().getMethod(testCaseName, int.class);
+            isParameterized = true;
+        }
+        catch (NoSuchMethodException e)
+        {
+            // log.debug("Parameterized method \"" + testCaseName + "\" not found.");
+            // Set run method to null (it already will be but...) to indicate that no parameterized method
+            // version could be found.
+            runMethod = null;
+        }
+
+        // If no parameterized method is available, try and get the unparameterized method.
+        if (runMethod == null)
+        {
+            try
+            {
+                runMethod = getClass().getMethod(testCaseName);
+                isParameterized = false;
+
+            }
+            catch (NoSuchMethodException e)
+            {
+                fail("Method \"" + testCaseName + "\" not found.");
+            }
+        }
+
+        // Check that the method is publicly accessable.
+        if (!Modifier.isPublic(runMethod.getModifiers()))
+        {
+            fail("Method \"" + testCaseName + "\" should be public.");
+        }
+
+        // Try to execute the method, passing it the current int parameter value. Allow any invocation exceptions or
+        // resulting exceptions from the method to fall through.
+        try
+        {
+            Integer paramN = getN();
+            log.debug("paramN = " + paramN);
+
+            // Calculate parameters for parameterized tests so new does not get called during memory measurement.
+            Object[] params = new Object[] { paramN };
+
+            // Take the test start memory and start time.
+            threadLocalMeasurement.get().startMem = 0; // SizeOf.getUsedMemory();
+
+            threadLocalMeasurement.get().startTime = System.nanoTime();
+
+            if (isParameterized)
+            {
+                runMethod.invoke(this, params);
+            }
+            else
+            {
+                runMethod.invoke(this);
+            }
+        }
+        catch (InvocationTargetException e)
+        {
+            e.fillInStackTrace();
+            throw e.getTargetException();
+        }
+        catch (IllegalAccessException e)
+        {
+            e.fillInStackTrace();
+            throw e;
+        }
+        finally
+        {
+            // Take the test end memory and end time and calculate how long it took to run.
+            long endTime = System.nanoTime();
+            threadLocalMeasurement.get().endTime = endTime;
+            log.debug("startTime = " + threadLocalMeasurement.get().startTime + ", endTime = " + endTime + ", testTime = "
+                + getTestTime());
+
+            threadLocalMeasurement.get().endMem = 0; // SizeOf.getUsedMemory();
+        }
+    }
+
+    /**
+     * The test parameters, encapsulated as a unit for attaching on a per thread basis.
+     */
+    private static class TestMeasurements
+    {
+        /** Holds the current value of the integer parameter to run tests on. */
+        public int n = 1;
+
+        /** Holds the test start memory. */
+        public long startTime = 0;
+
+        /** Holds the test end memory. */
+        public long endTime = 0;
+
+        /** Holds the test start memory. */
+        public long startMem = 0;
+
+        /** Holds the test end memory. */
+        public long endMem = 0;
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,170 @@
+/*
+ *
+ * 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.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.MathUtils;
+
+/**
+ * A Decorator that runs a test repeatedly on an increasing int parameter, or for a fixed number of repeats. If both
+ * a set of integer parameters and a repeat count are specified, then each test is run for the repeat count at each
+ * integer parameter.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Repeat a test for each of a set of integer parameters. <td> {@link TKTestResult}
+ * <tr><td> Repeat a test multiple times.
+ * <tr><td>
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class AsymptoticTestDecorator extends WrappedSuiteTestDecorator
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(AsymptoticTestDecorator.class);
+
+    /** The int size parameters to run the test with. */
+    private int[] params;
+
+    /** The number of times the whole test should be repeated. */
+    private int repeat;
+
+    /**
+     * Creates an asymptotic test decorator that wraps a test with repeats and a set of integer 'size' paramters
+     * to call the test with.
+     *
+     * @param test   The test to wrap.
+     * @param params The integer 'size' parameters.
+     * @param repeat The number of times to repeat the test.
+     */
+    public AsymptoticTestDecorator(WrappedSuiteTestDecorator test, int[] params, int repeat)
+    {
+        super(test);
+
+        log.debug("public AsymptoticTestDecorator(Test \"" + test + "\", int[] "
+            + ((params == null) ? null : MathUtils.printArray(params)) + ", int " + repeat + "): called");
+
+        this.params = params;
+        this.repeat = repeat;
+    }
+
+    /**
+     * Creates a new AsymptoticTestDecorator object.
+     *
+     * @param test   The test to decorate.
+     * @param start  The starting asymptotic integer parameter value.
+     * @param end    The ending asymptotic integer parameter value.
+     * @param step   The increment size to move from the start to end values by.
+     * @param repeat The number of times to repeat the test at each step of the cycle.
+     */
+    public AsymptoticTestDecorator(WrappedSuiteTestDecorator test, int start, int end, int step, int repeat)
+    {
+        super(test);
+
+        if (start < 0)
+        {
+            throw new IllegalArgumentException("Start must be >= 0");
+        }
+
+        if (end < start)
+        {
+            throw new IllegalArgumentException("End must be >= start");
+        }
+
+        if (step < 1)
+        {
+            throw new IllegalArgumentException("Step must be >= 1");
+        }
+
+        if (repeat < 1)
+        {
+            throw new IllegalArgumentException("Repeat must be >= 1");
+        }
+
+        // Generate the sequence.
+        params = new int[((end - start) / step) + 1];
+        int i = 0;
+        for (int n = start; n <= end; n += step)
+        {
+            params[i++] = n;
+        }
+
+        this.repeat = repeat;
+    }
+
+    /**
+     * Runs the test repeatedly for each value of the int parameter specified and for the correct number of test
+     * repeats.
+     *
+     * @param result The test result object that the tests will indicate their results to. This is also used
+     *               to pass the int parameter from this class to the decorated test class.
+     */
+    public void run(TestResult result)
+    {
+        log.debug("public void run(TestResult result): called");
+
+        if (!(result instanceof TKTestResult))
+        {
+            throw new IllegalArgumentException("AsymptoticTestDecorator only works with TKTestResult");
+        }
+
+        // Cast the test result into a TKTestResult to place the current parameter into.
+        TKTestResult tkResult = (TKTestResult) result;
+
+        log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+        log.debug("repeat = " + repeat);
+
+        for (int n : params)
+        {
+            for (int j = 0; j < repeat; j++)
+            {
+                log.debug("n = " + n);
+
+                // Set the integer parameter in the TKTestResult to be passed to the tests.
+                tkResult.setN(n);
+
+                if (tkResult.shouldStop())
+                {
+                    log.debug("tkResult.shouldStop = " + true);
+
+                    break;
+                }
+
+                log.debug("Calling super#run");
+                super.run(tkResult);
+            }
+        }
+    }
+
+    /**
+     * Prints out the name of this test with the string "(parameterized)" appended onto it for debugging purposes.
+     *
+     * @return The name of this test with the string "(parameterized)" appended onto it.
+     */
+    public String toString()
+    {
+        return super.toString() + "(parameterized)";
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+/**
+ * Provides a base implementation of the non-waiting throttle checking method, using the system nano timer.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check against a throttle speed without waiting.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public abstract class BaseThrottle implements Throttle
+{
+    /** Holds the length of a single cycle in nano seconds. */
+    protected long cycleTimeNanos;
+
+    /** Holds the time of the last succesfull call to the check method. */
+    private long lastCheckTimeNanos;
+
+    /** Flag used to detect the first call to the {@link #checkThrottle()} method. */
+    boolean firstCheckCall = true;
+
+    /**
+     * Flag used to detect the first call to the {@link #throttle()} method. Zero or negative start time cannot be
+     * relied on to detect this as System.nanoTime can return zero or negative values.
+     */
+    boolean firstCall = true;
+
+    /**
+     * Specifies the throttling rate in operations per second. This must be called with with a value, the inverse
+     * of which is a measurement in nano seconds, such that the number of nano seconds do not overflow a long integer.
+     * The value must also be larger than zero.
+     *
+     * @param hertz The throttling rate in cycles per second.
+     */
+    public void setRate(float hertz)
+    {
+        // Check that the argument is above zero.
+        if (hertz <= 0.0f)
+        {
+            throw new IllegalArgumentException("The throttle rate must be above zero.");
+        }
+
+        // Calculate the cycle time.
+        cycleTimeNanos = (long)(1000000000f / hertz);
+
+        // Reset the first pass flag.
+        firstCall = false;
+        firstCheckCall = false;
+    }
+
+    /**
+     * Checks but does not enforce the throttle rate. When this method is called, it checks if a length of time greater
+     * than that equal to the inverse of the throttling rate has passed since it was last called and returned <tt>true</tt>
+     *
+     * @return <tt>true</tt> if a length of time greater than that equal to the inverse of the throttling rate has
+     *         passed since this method was last called and returned <tt>true</tt>, <tt>false</tt> otherwise. The very
+     *         first time this method is called on a throttle, it returns <tt>true</tt> as the base case to the above
+     *         self-referential definition.
+     */
+    public boolean checkThrottle()
+    {
+        long now = System.nanoTime();
+
+        if ((now > (cycleTimeNanos + lastCheckTimeNanos)) || firstCheckCall)
+        {
+            firstCheckCall = false;
+            lastCheckTimeNanos = now;
+
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,94 @@
+/*
+ *
+ * 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;
+
+/**
+ * BatchedThrottle is a {@link SleepThrottle} that uses batching to achieve much higher throttling rates than a sleep
+ * throttle can. Sleep throttle has difficulties once the rate gets above a few hundred hertz, because the JVM cannot
+ * generate timed pauses that are that short. BatchedThrottle gets around this by only inserting pauses once every so
+ * many calls to the {@link #throttle()} method, and using a sleep throttle run at a lower rate. The rate for the sleep
+ * throttle is chosen so that it remains under 100hz. The final throttling rate of this throttle is equal to the batch
+ * size times the rate of the underlying sleep throttle.
+ *
+ * <p/>The batching calculation involves taking the log to the base 100 of the desired rate and rounding this to
+ * an integer. The batch size is always an exact power of 100 because of the rounding. The rate for an underlying
+ * sleep throttle is then chosen appropriately.
+ *
+ * <p/>In practice, the accuracy of a BacthedThrottle skews off but can sometimes even be reasonable up to ten thousand
+ * hertz compared with 100 Hz for a {@link SleepThrottle}.
+ *
+ * <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, occasionaly, to fill out processing cycles to a specified rate.
+ * <tr><td> Check against a throttle speed without waiting.
+ * </table>
+ *
+ * @todo Should always round the log base 100 down to the nearest integer?
+ *
+ * @author Rupert Smith
+ */
+public class BatchedThrottle extends BaseThrottle
+{
+    /** Holds the batch size. */
+    int batchSize;
+
+    /** The call count within the current batch. */
+    long callCount;
+
+    /** Holds a sleep throttle configured to run at the batched rate. */
+    private Throttle batchRateThrottle = new SleepThrottle();
+
+    /**
+     * Specifies the throttling rate in operations per second.
+     *
+     * @param hertz The throttling rate in cycles per second.
+     */
+    public void setRate(float hertz)
+    {
+        // Pass the rate unaltered down to the base implementation, for the check method.
+        super.setRate(hertz);
+
+        // Log base 10 over 2 is used here to get a feel for what power of 100 the total rate is.
+        // As the total rate goes up the powers of 100 the batch size goes up by powers of 100 to keep the
+        // throttle rate in the range 1 to 100.
+        int x = (int) (Math.log10(hertz) / 2);
+        batchSize = (int) Math.pow(100, x);
+        float throttleRate = hertz / batchSize;
+
+        // Reset the call count.
+        callCount = 0;
+
+        // Set the sleep throttle wrapped implementation at a rate within its abilities.
+        batchRateThrottle.setRate(throttleRate);
+    }
+
+    /**
+     * Throttle calls to this method to the rate specified by the {@link #setRate(float)} method.
+     */
+    public void throttle()
+    {
+        if ((callCount++ % batchSize) == 0)
+        {
+            batchRateThrottle.throttle();
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A test decorator that runs a test repeatedly until a specified length of time has passed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Repeatedly run a test for a fixed length of time.
+ * </table>
+ *
+ * @todo The count of the number of tests run is an important number to keep. Also num passed/error/failed is also
+ *       important to record. What to do with these numbers? They are already logged to the test listeners.
+ *
+ * @todo The duration test runner wraps on top of size, repeat or thread wrappers, need a way for it to tell
+ *       TKTestResult when the duration is up, so that it can terminate any repeats in progress. It should end
+ *       as soon as possible once the test method exits.
+ *
+ * @author Rupert Smith
+ */
+public class DurationTestDecorator extends WrappedSuiteTestDecorator implements ShutdownHookable
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(DurationTestDecorator.class);
+
+    /** The test to run. */
+    private Test test;
+
+    /** The length of time to run the test for. */
+    private long duration;
+
+    /** 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 DurationTestDecorator(WrappedSuiteTestDecorator test)
+    {
+        super(test);
+        this.test = test;
+    }
+
+    /**
+     * Creates active test with default multiplier (1).
+     *
+     * @param test     The target test.
+     * @param duration The duration in milliseconds.
+     */
+    public DurationTestDecorator(WrappedSuiteTestDecorator test, long duration)
+    {
+        super(test);
+
+        // log.debug("public DurationTestDecorator(Test \"" + test + "\", long " + duration + "): called");
+
+        this.test = test;
+        this.duration = duration;
+    }
+
+    /**
+     * Runs the test repeatedly for the fixed duration.
+     *
+     * @param testResult The the results object to monitor the test results with.
+     */
+    public void run(TestResult testResult)
+    {
+        log.debug("public void run(TestResult testResult): called");
+
+        // Cast the test result to expose it as a TKTestResult if the test is running under the TKTestRunner.
+        TKTestResult tkTestResult = null;
+
+        if (testResult instanceof TKTestResult)
+        {
+            tkTestResult = (TKTestResult)testResult;
+        }
+
+        // Work out when the test should end.
+        long now = System.nanoTime();
+        long end = (duration * 1000000) + now;
+
+        // If running under the TKTestRunner, set up a timer to notify the test framework when the test reaches its
+        // completion time.
+        Timer durationTimer = null;
+
+        if (tkTestResult != null)
+        {
+            log.debug("Creating duration timer.");
+
+            durationTimer = new Timer();
+            durationTimer.schedule(new DurationTimerTask((TKTestResult)testResult), duration);
+        }
+
+        // Run the test until the duration times out or the shutdown flag is set. The test method may not exit until
+        // interrupted in some cases, in which case the timer will do the interrupting.
+        while ((now < end) && !shutdown)
+        {
+            test.run(testResult);
+
+            now = System.nanoTime();
+        }
+
+        // Clean up any timer that was used.
+        if (durationTimer != null)
+        {
+            log.debug("Cancelling duration timer.");
+
+            durationTimer.cancel();
+        }
+    }
+
+    /**
+     * Supplies the shutdown hook. This shutdown hook does not call {@link TKTestResult#shutdownNow()} because the
+     * {@link ScaledTestDecorator} already takes care of that.
+     *
+     * @return The shut down hook.
+     */
+    public Thread getShutdownHook()
+    {
+        return new Thread(new Runnable()
+                {
+                    public void run()
+                    {
+                        // log.debug("DurationTestDecorator::ShutdownHook: called");
+
+                        // Set the shutdown flag so that no new tests are started.
+                        shutdown = true;
+                    }
+                });
+    }
+
+    /**
+     * DurationTimerTask is a timer task that is configured, upon expiry of its timer, to invoke
+     * {@link TKTestResult#shutdownNow()}, for the test result object on which it is set. It also sets
+     * the {@link DurationTestDecorator#shutdown} flag to indicate that no new tests should be run.
+     *
+     * <p/>The test loop implemented by DurationTestDecorator checks that the duration has not expired, on each
+     * test case that it runs. However, it is possible to write test cases that never return until explicitly
+     * interrupted by the test framework. This timer task exists to notify the test framework
+     */
+    private class DurationTimerTask extends TimerTask
+    {
+        /** Used for debugging purposes. */
+        private final Logger log = Logger.getLogger(DurationTimerTask.class);
+
+        /** Holds the test result for the test to which a duration limit is being applied. */
+        TKTestResult testResult;
+
+        /**
+         * Creates a duration limit timer which will notify the specified test result when the duration has
+         * expired.
+         *
+         * @param testResult The test result to notify upon expiry of the test duration.
+         */
+        public DurationTimerTask(TKTestResult testResult)
+        {
+            this.testResult = testResult;
+        }
+
+        /**
+         * The action to be performed by this timer task.
+         */
+        public void run()
+        {
+            log.debug("public void run(): called");
+
+            shutdown = true;
+            testResult.shutdownNow();
+        }
+    }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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;
+
+/**
+ * An InstrumentedTest is one which can supply some additional instrumentation on top of the pass/fail/error behaviour
+ * of normal junit tests. Tests implementing this interface must additionally supply information about how long they
+ * took to run and how much memory they used.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Report test run time.
+ * <tr><td> Report test memory usage.
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public interface InstrumentedTest extends Test
+{
+    /**
+     * Reports how long the test took to run.
+     *
+     * @return The time in milliseconds that the test took to run.
+     */
+    public long getTestTime();
+
+    /**
+     * Reports the memory usage at the start of the test.
+     *
+     * @return The memory usage at the start of the test.
+     */
+    public long getTestStartMemory();
+
+    /**
+     * Reports the memory usage at the end of the test.
+     *
+     * @return The memory usage at the end of the test.
+     */
+    public long getTestEndMemory();
+
+    /**
+     * Resets the instrumentation values to zero, and nulls any references to held measurements so that the memory
+     * can be reclaimed.
+     */
+    public void reset();
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.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 junit.framework.AssertionFailedError;
+import junit.framework.Test;
+
+import junit.textui.ResultPrinter;
+
+import java.io.PrintStream;
+
+/**
+ * A ResultPrinter that prints nothing. This exists, in order to provide a replacement to JUnit's ResultPrinter, which
+ * is refered to directly by JUnit code, rather that as an abstracted TestListener. JUnit's text ui TestRunner must
+ * have a ResultPrinter. This provides an implementation of it that prints nothing, so that a better mechanism can
+ * be used for providing feedback to the console instead.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo See todo in TKTestRunner about completely replacing the test ui runner. Doing things like this in order to
+ *       extend JUnit is not nice, and there needs to be a better way to do it. Delete this class and use a listener
+ *       instead.
+ *
+ * @author Rupert Smith
+ */
+public class NullResultPrinter extends ResultPrinter
+{
+    /**
+     * Builds a fake ResultPrinter that prints nothing.
+     *
+     * @param writer The writer to send output to.
+     */
+    public NullResultPrinter(PrintStream writer)
+    {
+        super(writer);
+    }
+
+    /**
+     * Does nothing.
+     *
+     * @param test Ignored.
+     * @param t    Ignored.
+     */
+    public void addError(Test test, Throwable t)
+    { }
+
+    /**
+     * Does nothing.
+     *
+     * @param test Ignored.
+     * @param t    Ignored.
+     */
+    public void addFailure(Test test, AssertionFailedError t)
+    { }
+
+    /**
+     * Does nothing.
+     *
+     * @param test Ignored.
+     */
+    public void endTest(Test test)
+    { }
+
+    /**
+     * Does nothing.
+     *
+     * @param test Ignored.
+     */
+    public void startTest(Test test)
+    { }
+}

Added: incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java?rev=629518&view=auto
==============================================================================
--- incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java (added)
+++ incubator/qpid/branches/M2.1/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java Wed Feb 20 08:04:25 2008
@@ -0,0 +1,172 @@
+/*
+ *
+ * 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.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.MathUtils;
+
+/**
+ * ParameterVariationTestDecorator is a test decorator that runs a test repeatedly under all permutations of its
+ * test parameters.
+ *
+ * a set of integer parameters and a repeat count are specified, then each test is run for the repeat count at each
+ * integer parameter.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Repeat a test for each of a set of integer parameters. <td> {@link org.apache.qpid.junit.extensions.TKTestResult}
+ * <tr><td> Repeat a test multiple times.
+ * <tr><td>
+ * </table>
+ *
+ * @author Rupert Smith
+ */
+public class ParameterVariationTestDecorator extends WrappedSuiteTestDecorator
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(ParameterVariationTestDecorator.class);
+
+    /** The int size parameters to run the test with. */
+    private int[] params;
+
+    /** The number of times the whole test should be repeated. */
+    private int repeat;
+
+    /**
+     * Creates an asymptotic test decorator that wraps a test with repeats and a set of integer 'size' paramters
+     * to call the test with.
+     *
+     * @param test   The test to wrap.
+     * @param params The integer 'size' parameters.
+     * @param repeat The number of times to repeat the test.
+     */
+    public ParameterVariationTestDecorator(WrappedSuiteTestDecorator test, int[] params, int repeat)
+    {
+        super(test);
+
+        log.debug("public AsymptoticTestDecorator(Test \"" + test + "\", int[] "
+            + ((params == null) ? null : MathUtils.printArray(params)) + ", int " + repeat + "): called");
+
+        this.params = params;
+        this.repeat = repeat;
+    }
+
+    /**
+     * Creates a new AsymptoticTestDecorator object.
+     *
+     * @param test   The test to decorate.
+     * @param start  The starting asymptotic integer parameter value.
+     * @param end    The ending asymptotic integer parameter value.
+     * @param step   The increment size to move from the start to end values by.
+     * @param repeat The number of times to repeat the test at each step of the cycle.
+     */
+    public ParameterVariationTestDecorator(WrappedSuiteTestDecorator test, int start, int end, int step, int repeat)
+    {
+        super(test);
+
+        if (start < 0)
+        {
+            throw new IllegalArgumentException("Start must be >= 0");
+        }
+
+        if (end < start)
+        {
+            throw new IllegalArgumentException("End must be >= start");
+        }
+
+        if (step < 1)
+        {
+            throw new IllegalArgumentException("Step must be >= 1");
+        }
+
+        if (repeat < 1)
+        {
+            throw new IllegalArgumentException("Repeat must be >= 1");
+        }
+
+        // Generate the sequence.
+        params = new int[((end - start) / step) + 1];
+        int i = 0;
+        for (int n = start; n <= end; n += step)
+        {
+            params[i++] = n;
+        }
+
+        this.repeat = repeat;
+    }
+
+    /**
+     * Runs the test repeatedly for each value of the int parameter specified and for the correct number of test
+     * repeats.
+     *
+     * @param result The test result object that the tests will indicate their results to. This is also used
+     *               to pass the int parameter from this class to the decorated test class.
+     */
+    public void run(TestResult result)
+    {
+        log.debug("public void run(TestResult result): called");
+
+        if (!(result instanceof TKTestResult))
+        {
+            throw new IllegalArgumentException("AsymptoticTestDecorator only works with TKTestResult");
+        }
+
+        // Cast the test result into a TKTestResult to place the current parameter into.
+        TKTestResult tkResult = (TKTestResult) result;
+
+        log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+        log.debug("repeat = " + repeat);
+
+        for (int n : params)
+        {
+            for (int j = 0; j < repeat; j++)
+            {
+                log.debug("n = " + n);
+
+                // Set the integer parameter in the TKTestResult to be passed to the tests.
+                tkResult.setN(n);
+
+                if (tkResult.shouldStop())
+                {
+                    log.debug("tkResult.shouldStop = " + true);
+
+                    break;
+                }
+
+                log.debug("Calling super#run");
+                super.run(tkResult);
+            }
+        }
+    }
+
+    /**
+     * Prints out the name of this test with the string "(parameterized)" appended onto it for debugging purposes.
+     *
+     * @return The name of this test with the string "(parameterized)" appended onto it.
+     */
+    public String toString()
+    {
+        return super.toString() + "(parameterized)";
+    }
+}