You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by rg...@apache.org on 2008/04/24 19:51:14 UTC
svn commit: r651325 [19/37] - in /incubator/qpid/trunk/qpid/java:
broker/src/main/java/org/apache/qpid/server/exchange/
broker/src/main/java/org/apache/qpid/server/handler/
broker/src/main/java/org/apache/qpid/server/output/
broker/src/main/java/org/ap...
Modified: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java?rev=651325&r1=651324&r2=651325&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java (original)
+++ incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java Thu Apr 24 10:49:03 2008
@@ -1,485 +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;
- }
-
-}
+/*
+ *
+ * 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;
+ }
+
+}
Propchange: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java?rev=651325&r1=651324&r2=651325&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java (original)
+++ incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java Thu Apr 24 10:49:03 2008
@@ -1,145 +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);
- }
- }
- }
-}
+/*
+ *
+ * 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);
+ }
+ }
+ }
+}
Propchange: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java?rev=651325&r1=651324&r2=651325&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java (original)
+++ incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java Thu Apr 24 10:49:03 2008
@@ -1,303 +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;
- }
-}
+/*
+ *
+ * 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;
+ }
+}
Propchange: incubator/qpid/trunk/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java
------------------------------------------------------------------------------
svn:eol-style = native