From e32debe1df7d0a837e30cd937fb7a18fc5cfa203 Mon Sep 17 00:00:00 2001 From: Robert Godfrey Date: Thu, 24 Apr 2008 17:49:03 +0000 Subject: QPID-832 : Fix eol-style git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk@651325 13f79535-47bb-0310-9956-ffa450edef68 --- .../junit/concurrency/DefaultThreadFactory.java | 96 +- .../concurrency/PossibleDeadlockException.java | 92 +- .../qpid/junit/concurrency/TestRunnable.java | 478 +++--- .../junit/concurrency/ThreadTestCoordinator.java | 970 ++++++------ .../qpid/junit/concurrency/ThreadTestExample.java | 290 ++-- .../qpid/junit/extensions/AsymptoticTestCase.java | 606 ++++---- .../junit/extensions/AsymptoticTestDecorator.java | 340 ++--- .../apache/qpid/junit/extensions/BaseThrottle.java | 196 +-- .../qpid/junit/extensions/BatchedThrottle.java | 188 +-- .../junit/extensions/DurationTestDecorator.java | 398 ++--- .../qpid/junit/extensions/InstrumentedTest.java | 132 +- .../qpid/junit/extensions/NullResultPrinter.java | 184 +-- .../ParameterVariationTestDecorator.java | 344 ++--- .../qpid/junit/extensions/ScaledTestDecorator.java | 750 +++++----- .../qpid/junit/extensions/SetupTaskAware.java | 110 +- .../qpid/junit/extensions/SetupTaskHandler.java | 184 +-- .../qpid/junit/extensions/ShutdownHookable.java | 84 +- .../qpid/junit/extensions/SleepThrottle.java | 162 +- .../apache/qpid/junit/extensions/TKTestResult.java | 1250 ++++++++-------- .../apache/qpid/junit/extensions/TKTestRunner.java | 1388 ++++++++--------- .../TestRunnerImprovedErrorHandling.java | 262 ++-- .../qpid/junit/extensions/TestThreadAware.java | 98 +- .../org/apache/qpid/junit/extensions/Throttle.java | 146 +- .../qpid/junit/extensions/TimingController.java | 350 ++--- .../junit/extensions/TimingControllerAware.java | 86 +- .../extensions/WrappedSuiteTestDecorator.java | 268 ++-- .../extensions/listeners/CSVTestListener.java | 1064 ++++++------- .../extensions/listeners/ConsoleTestListener.java | 528 +++---- .../junit/extensions/listeners/TKTestListener.java | 264 ++-- .../extensions/listeners/XMLTestListener.java | 800 +++++----- .../junit/extensions/util/CommandLineParser.java | 1574 ++++++++++---------- .../extensions/util/ContextualProperties.java | 988 ++++++------ .../qpid/junit/extensions/util/MathUtils.java | 856 +++++------ .../junit/extensions/util/ParsedProperties.java | 780 +++++----- .../apache/qpid/junit/extensions/util/SizeOf.java | 188 +-- .../qpid/junit/extensions/util/StackQueue.java | 262 ++-- .../extensions/util/TestContextProperties.java | 404 ++--- 37 files changed, 8580 insertions(+), 8580 deletions(-) (limited to 'qpid/java/junit-toolkit/src') diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java index 6c88d019c4..8fb0a6a90e 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java @@ -1,48 +1,48 @@ -/* - * - * 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 java.util.concurrent.ThreadFactory; - -/** - * Implements a default thread factory. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Create default threads with no specialization. - *
- * - * @author Rupert Smith - */ -public class DefaultThreadFactory implements ThreadFactory -{ - /** - * Constructs a new Thread. - * - * @param r A runnable to be executed by new thread instance. - * - * @return The constructed thread. - */ - public Thread newThread(Runnable r) - { - return new Thread(r); - } -} +/* + * + * 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 java.util.concurrent.ThreadFactory; + +/** + * Implements a default thread factory. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Create default threads with no specialization. + *
+ * + * @author Rupert Smith + */ +public class DefaultThreadFactory implements ThreadFactory +{ + /** + * Constructs a new Thread. + * + * @param r A runnable to be executed by new thread instance. + * + * @return The constructed thread. + */ + public Thread newThread(Runnable r) + { + return new Thread(r); + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java index 0bb07a4557..3bbfc2d502 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java @@ -1,46 +1,46 @@ -/* - * - * 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; - -/** - * PossibleDeadlockException is used to signal that two test threads being executed by a {@link ThreadTestCoordinator} - * may be in a state of deadlock because they are mutually blocking each other or one is waiting on the other and the - * other has been blocked elsewhere for longer than a specified timeout. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Signal a possible state of deadlock between coordinated test threads. - *
- * - * @author Rupert Smith - */ -public class PossibleDeadlockException extends RuntimeException -{ - /** - * Create a new possible deadlock execption. - * - * @param message The exception message. - */ - public PossibleDeadlockException(String message) - { - super(message); - } -} +/* + * + * 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; + +/** + * PossibleDeadlockException is used to signal that two test threads being executed by a {@link ThreadTestCoordinator} + * may be in a state of deadlock because they are mutually blocking each other or one is waiting on the other and the + * other has been blocked elsewhere for longer than a specified timeout. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Signal a possible state of deadlock between coordinated test threads. + *
+ * + * @author Rupert Smith + */ +public class PossibleDeadlockException extends RuntimeException +{ + /** + * Create a new possible deadlock execption. + * + * @param message The exception message. + */ + public PossibleDeadlockException(String message) + { + super(message); + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java index 5bf0c430cd..02e776a4ea 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java @@ -1,239 +1,239 @@ -/* - * - * 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; - -/** - * TestRunnable is an extension of java.util.Runnable that adds some features to make it easier to coordinate the - * activities of threads in such a way as to expose bugs in multi threaded code. - * - *

Sometimes several threads will run in a particular order so that a bug is not revealed. Other times the ordering - * of the threads will expose a bug. Such bugs can be hard to replicate as the exact execution ordering of threads is not - * usually controlled. This class adds some methods that allow threads to synchronize other threads, either allowing them - * to run, or waiting for them to allow this thread to run. It also provides convenience methods to gather error messages - * and exceptions from threads, which will often be reported in unit testing code. - * - *

Coordination between threads is handled by the {@link ThreadTestCoordinator}. It is called through the convenience - * methods {@link #allow} and {@link #waitFor}. Threads to be coordinated must be set up with the coordinator and assigned - * integer ids. It is then possible to call the coordinator with an array of thread ids requesting that those threads - * be allowed to continue, or to wait until one of them allows this thread to continue. The otherwise non-deterministic - * execution order of threads can be controlled into a carefully determined sequence using these methods in order - * to reproduce race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on. - * - *

When waiting for another thread to give a signal to continue it is sometimes the case that the other thread has - * become blocked by the code under test. For example in testing for a dirty read (for example in database code), - * thread 1 lets thread 2 perform a write but not commit it, then thread 2 lets thread 1 run and attempt to perform a - * dirty read on its uncommitted write. Transaction synchronization code being tested against the possibility of a dirty - * write may make use of snapshots in which case both threads should be able to read and write without blocking. It may - * make use of explicit keys in which case thread 2 may become blocked on its write attempt because thread 1 holds a - * read lock and it must wait until thread 1 completes its transaction before it can acquire this lock. The - * {@link #waitFor} method accepts a boolean parameter to indicate that threads being blocked (other than on the - * coordinator) can be interpreted the same as if the thread explicitly allows the thread calling waitFor to continue. - * Using this technique a dirty read test could be written that works against either the snapshot or the locking - * implementation, allowing both approaches to pass the test yet arranging for multiple threads to run against the - * implementation in such a way that a potential dirty read bug is exposed. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Wait for another thread to allow this one to continue. - *
Allow another thread to continue. - *
Accumulate error messages. - *
Record exceptions from thread run. - *
Maintain link to thread coordinator. - *
Explicitly mark a thread with an integer id. - *
Maintian a flag to indicate whether or not this thread is waiting on the coordinator. - *
- * - * @todo The allow then waitFor operations are very often used as a pair. So create a method allowAndWait that combines - * them into a single method call. - * - * @author Rupert Smith - */ -public abstract class TestRunnable implements Runnable -{ - /** Holds a reference to the thread coordinator. */ - private ThreadTestCoordinator coordinator; - - /** Holds the explicit integer id of this thread. */ - private int id; - - /** Used to indicate that this thread is waiting on the coordinator and not elsewhere. */ - private boolean waitingOnCoordinator = false; - - /** Used to accumulate error messsages. */ - private String errorMessage = ""; - - /** Holds the Java thread object that this is running under. */ - private Thread thisThread; - - /** Used to hold any exceptions resulting from the run method. */ - private Exception runException = null; - - /** - * Implementations override this to perform coordinated thread sequencing. - * - * @throws Exception Any exception raised by the implementation will be caught by the default {@link #run()} - * implementation for later querying by the {@link #getException()} method. - */ - public abstract void runWithExceptions() throws Exception; - - /** - * Provides a default implementation of the run method that allows exceptions to be thrown and keeps a record - * of those exceptions. Defers to the {@link #runWithExceptions()} method to provide the thread body implementation - * and catches any exceptions thrown by it. - */ - public void run() - { - try - { - runWithExceptions(); - } - catch (Exception e) - { - this.runException = e; - } - } - - /** - * Attempt to consume an allow event from one of the specified threads and blocks until such an event occurrs. - * - * @param threads The set of threads that can allow this one to continue. - * @param otherWaitIsAllow If set to true if the threads being waited on are blocked other than on - * the coordinator itself then this is to be interpreted as allowing this thread to - * continue. - * - * @return If the otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found - * to be blocked outside of the thread test coordinator. false under all other conditions. - */ - protected boolean waitFor(int[] threads, boolean otherWaitIsAllow) - { - return coordinator.consumeAllowEvent(threads, otherWaitIsAllow, id, this); - } - - /** - * Produces allow events on each of the specified threads. - * - * @param threads The set of threads that are to be allowed to continue. - */ - protected void allow(int[] threads) - { - coordinator.produceAllowEvents(threads, id, this); - } - - /** - * Keeps the error message for later reporting by the coordinator. - * - * @param message The error message to keep. - */ - protected void addErrorMessage(String message) - { - errorMessage += message; - } - - /** - * Sets the coordinator for this thread. - * - * @param coordinator The coordinator for this thread. - */ - void setCoordinator(ThreadTestCoordinator coordinator) - { - this.coordinator = coordinator; - } - - /** - * Reports whether or not this thread is waiting on the coordinator. - * - * @return If this thread is waiting on the coordinator. - */ - boolean isWaitingOnCoordinator() - { - return waitingOnCoordinator; - } - - /** - * Sets the value of the waiting on coordinator flag. - * - * @param waiting The value of the waiting on coordinator flag. - */ - void setWaitingOnCoordinator(boolean waiting) - { - waitingOnCoordinator = waiting; - } - - /** - * Sets up the explicit int id for this thread. - * - * @param id The integer id. - */ - void setId(int id) - { - this.id = id; - } - - /** - * Reports any accumulated error messages. - * - * @return Any accumulated error messages. - */ - String getErrorMessage() - { - return errorMessage; - } - - /** - * Reports any exception thrown by the {@link #runWithExceptions} method. - * - * @return Any exception thrown by the {@link #runWithExceptions} method. - */ - Exception getException() - { - return runException; - } - - /** - * Sets the Java thread under which this runs. - * - * @param thread The Java thread under which this runs. - */ - void setThread(Thread thread) - { - thisThread = thread; - } - - /** - * Gets the Java thread under which this runs. - * - * @return The Java thread under which this runs. - */ - Thread getThread() - { - return thisThread; - } - - /** - * Provides a string summary of this test threads status. - * - * @return Summarizes this threads status. - */ - public String toString() - { - return "id = " + id + ", waitingOnCoordinator = " + waitingOnCoordinator; - } -} +/* + * + * 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; + +/** + * TestRunnable is an extension of java.util.Runnable that adds some features to make it easier to coordinate the + * activities of threads in such a way as to expose bugs in multi threaded code. + * + *

Sometimes several threads will run in a particular order so that a bug is not revealed. Other times the ordering + * of the threads will expose a bug. Such bugs can be hard to replicate as the exact execution ordering of threads is not + * usually controlled. This class adds some methods that allow threads to synchronize other threads, either allowing them + * to run, or waiting for them to allow this thread to run. It also provides convenience methods to gather error messages + * and exceptions from threads, which will often be reported in unit testing code. + * + *

Coordination between threads is handled by the {@link ThreadTestCoordinator}. It is called through the convenience + * methods {@link #allow} and {@link #waitFor}. Threads to be coordinated must be set up with the coordinator and assigned + * integer ids. It is then possible to call the coordinator with an array of thread ids requesting that those threads + * be allowed to continue, or to wait until one of them allows this thread to continue. The otherwise non-deterministic + * execution order of threads can be controlled into a carefully determined sequence using these methods in order + * to reproduce race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on. + * + *

When waiting for another thread to give a signal to continue it is sometimes the case that the other thread has + * become blocked by the code under test. For example in testing for a dirty read (for example in database code), + * thread 1 lets thread 2 perform a write but not commit it, then thread 2 lets thread 1 run and attempt to perform a + * dirty read on its uncommitted write. Transaction synchronization code being tested against the possibility of a dirty + * write may make use of snapshots in which case both threads should be able to read and write without blocking. It may + * make use of explicit keys in which case thread 2 may become blocked on its write attempt because thread 1 holds a + * read lock and it must wait until thread 1 completes its transaction before it can acquire this lock. The + * {@link #waitFor} method accepts a boolean parameter to indicate that threads being blocked (other than on the + * coordinator) can be interpreted the same as if the thread explicitly allows the thread calling waitFor to continue. + * Using this technique a dirty read test could be written that works against either the snapshot or the locking + * implementation, allowing both approaches to pass the test yet arranging for multiple threads to run against the + * implementation in such a way that a potential dirty read bug is exposed. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Wait for another thread to allow this one to continue. + *
Allow another thread to continue. + *
Accumulate error messages. + *
Record exceptions from thread run. + *
Maintain link to thread coordinator. + *
Explicitly mark a thread with an integer id. + *
Maintian a flag to indicate whether or not this thread is waiting on the coordinator. + *
+ * + * @todo The allow then waitFor operations are very often used as a pair. So create a method allowAndWait that combines + * them into a single method call. + * + * @author Rupert Smith + */ +public abstract class TestRunnable implements Runnable +{ + /** Holds a reference to the thread coordinator. */ + private ThreadTestCoordinator coordinator; + + /** Holds the explicit integer id of this thread. */ + private int id; + + /** Used to indicate that this thread is waiting on the coordinator and not elsewhere. */ + private boolean waitingOnCoordinator = false; + + /** Used to accumulate error messsages. */ + private String errorMessage = ""; + + /** Holds the Java thread object that this is running under. */ + private Thread thisThread; + + /** Used to hold any exceptions resulting from the run method. */ + private Exception runException = null; + + /** + * Implementations override this to perform coordinated thread sequencing. + * + * @throws Exception Any exception raised by the implementation will be caught by the default {@link #run()} + * implementation for later querying by the {@link #getException()} method. + */ + public abstract void runWithExceptions() throws Exception; + + /** + * Provides a default implementation of the run method that allows exceptions to be thrown and keeps a record + * of those exceptions. Defers to the {@link #runWithExceptions()} method to provide the thread body implementation + * and catches any exceptions thrown by it. + */ + public void run() + { + try + { + runWithExceptions(); + } + catch (Exception e) + { + this.runException = e; + } + } + + /** + * Attempt to consume an allow event from one of the specified threads and blocks until such an event occurrs. + * + * @param threads The set of threads that can allow this one to continue. + * @param otherWaitIsAllow If set to true if the threads being waited on are blocked other than on + * the coordinator itself then this is to be interpreted as allowing this thread to + * continue. + * + * @return If the otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found + * to be blocked outside of the thread test coordinator. false under all other conditions. + */ + protected boolean waitFor(int[] threads, boolean otherWaitIsAllow) + { + return coordinator.consumeAllowEvent(threads, otherWaitIsAllow, id, this); + } + + /** + * Produces allow events on each of the specified threads. + * + * @param threads The set of threads that are to be allowed to continue. + */ + protected void allow(int[] threads) + { + coordinator.produceAllowEvents(threads, id, this); + } + + /** + * Keeps the error message for later reporting by the coordinator. + * + * @param message The error message to keep. + */ + protected void addErrorMessage(String message) + { + errorMessage += message; + } + + /** + * Sets the coordinator for this thread. + * + * @param coordinator The coordinator for this thread. + */ + void setCoordinator(ThreadTestCoordinator coordinator) + { + this.coordinator = coordinator; + } + + /** + * Reports whether or not this thread is waiting on the coordinator. + * + * @return If this thread is waiting on the coordinator. + */ + boolean isWaitingOnCoordinator() + { + return waitingOnCoordinator; + } + + /** + * Sets the value of the waiting on coordinator flag. + * + * @param waiting The value of the waiting on coordinator flag. + */ + void setWaitingOnCoordinator(boolean waiting) + { + waitingOnCoordinator = waiting; + } + + /** + * Sets up the explicit int id for this thread. + * + * @param id The integer id. + */ + void setId(int id) + { + this.id = id; + } + + /** + * Reports any accumulated error messages. + * + * @return Any accumulated error messages. + */ + String getErrorMessage() + { + return errorMessage; + } + + /** + * Reports any exception thrown by the {@link #runWithExceptions} method. + * + * @return Any exception thrown by the {@link #runWithExceptions} method. + */ + Exception getException() + { + return runException; + } + + /** + * Sets the Java thread under which this runs. + * + * @param thread The Java thread under which this runs. + */ + void setThread(Thread thread) + { + thisThread = thread; + } + + /** + * Gets the Java thread under which this runs. + * + * @return The Java thread under which this runs. + */ + Thread getThread() + { + return thisThread; + } + + /** + * Provides a string summary of this test threads status. + * + * @return Summarizes this threads status. + */ + public String toString() + { + return "id = " + id + ", waitingOnCoordinator = " + waitingOnCoordinator; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java index 0be0fe37dc..605c35feed 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java @@ -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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Accept test threads to coordinate. - *
Allow test threads to send 'allow to continue' signals. - *
Allow test threads to wait on this coordinator for 'allow to continue' signals. - *
Report error messages from test threads. - *
Report exceptions from test threads. - *
Provide method to wait until all test threads have completed. - *
- * - * @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 exceptions = new ArrayList(); - - /** - * 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 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 otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found - * to be blocked outside of the thread test coordinator. false 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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Accept test threads to coordinate. + *
Allow test threads to send 'allow to continue' signals. + *
Allow test threads to wait on this coordinator for 'allow to continue' signals. + *
Report error messages from test threads. + *
Report exceptions from test threads. + *
Provide method to wait until all test threads have completed. + *
+ * + * @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 exceptions = new ArrayList(); + + /** + * 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 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 otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found + * to be blocked outside of the thread test coordinator. false 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; + } + +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java index ef177a4255..b9865f2e22 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java @@ -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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Demo multi-threaded testing. - *
- * - * @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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Demo multi-threaded testing. + *
+ * + * @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); + } + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java index 03e465695e..58a7f60f3c 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java @@ -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. - * - *

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. - * - *

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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Store the current int parameter value. {@link TKTestResult} and see {@link AsymptoticTestDecorator} too. - *
Invoke parameterized test methods. - *
- * - * @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 threadLocalMeasurement = - new ThreadLocal() - { - /** - * 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. + * + *

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. + * + *

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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Store the current int parameter value. {@link TKTestResult} and see {@link AsymptoticTestDecorator} too. + *
Invoke parameterized test methods. + *
+ * + * @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 threadLocalMeasurement = + new ThreadLocal() + { + /** + * 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; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java index e99f904331..4faa58688f 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java @@ -1,170 +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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Repeat a test for each of a set of integer parameters. {@link TKTestResult} - *
Repeat a test multiple times. - *
- *
- * - * @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)"; - } -} +/* + * + * 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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Repeat a test for each of a set of integer parameters. {@link TKTestResult} + *
Repeat a test multiple times. + *
+ *
+ * + * @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)"; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java index e8e203f0a3..61d5746421 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java @@ -1,98 +1,98 @@ -/* - * - * 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; - -/** - * Provides a base implementation of the non-waiting throttle checking method, using the system nano timer. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Check against a throttle speed without waiting. - *
- * - * @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 true - * - * @return true 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 true, false otherwise. The very - * first time this method is called on a throttle, it returns true 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; - } - } -} +/* + * + * 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; + +/** + * Provides a base implementation of the non-waiting throttle checking method, using the system nano timer. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Check against a throttle speed without waiting. + *
+ * + * @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 true + * + * @return true 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 true, false otherwise. The very + * first time this method is called on a throttle, it returns true 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; + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java index 1d00fcf3b6..241e7aa2b7 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java @@ -1,94 +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. - * - *

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. - * - *

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}. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Accept throttling rate in operations per second. - *
Inject short pauses, occasionaly, to fill out processing cycles to a specified rate. - *
Check against a throttle speed without waiting. - *
- * - * @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(); - } - } -} +/* + * + * 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. + * + *

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. + * + *

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}. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Accept throttling rate in operations per second. + *
Inject short pauses, occasionaly, to fill out processing cycles to a specified rate. + *
Check against a throttle speed without waiting. + *
+ * + * @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(); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java index fe1e044e67..1c1c146361 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java @@ -1,199 +1,199 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -import junit.framework.Test; -import junit.framework.TestResult; - -import org.apache.log4j.Logger; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * A test decorator that runs a test repeatedly until a specified length of time has passed. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Repeatedly run a test for a fixed length of time. - *
- * - * @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. - * - *

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(); - } - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.junit.extensions; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.log4j.Logger; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * A test decorator that runs a test repeatedly until a specified length of time has passed. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Repeatedly run a test for a fixed length of time. + *
+ * + * @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. + * + *

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(); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java index ed792fcd5a..0804757dce 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java @@ -1,66 +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. - * - *

- *
CRC Card
Responsibilities - *
Report test run time. - *
Report test memory usage. - *
- * - * @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(); -} +/* + * + * 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. + * + *

+ *
CRC Card
Responsibilities + *
Report test run time. + *
Report test memory usage. + *
+ * + * @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(); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java index 2ffbcb5bb8..6727f6f152 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java @@ -1,92 +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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
- *
- * - * @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) - { } -} +/* + * + * 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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ * + * @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) + { } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java index 60ec156354..2c207635c7 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java @@ -1,172 +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. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Repeat a test for each of a set of integer parameters. {@link org.apache.qpid.junit.extensions.TKTestResult} - *
Repeat a test multiple times. - *
- *
- * - * @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)"; - } -} +/* + * + * 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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Repeat a test for each of a set of integer parameters. {@link org.apache.qpid.junit.extensions.TKTestResult} + *
Repeat a test multiple times. + *
+ *
+ * + * @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)"; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java index f5e3e1a758..e0af22cfb7 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java @@ -1,375 +1,375 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -import junit.framework.Test; -import junit.framework.TestResult; - -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; - -/** - * A test decorator that runs a test many times simultaneously in many threads. - * - *

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

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

Set up tasks should be chained so that they are executed in the order that they are applied. Tear down tasks - * should be chained so that they are executed in the reverse order to which they are applied. That way the set up and - * tear down tasks act as a 'task' stack, with nested setups and tear downs. - * - *

- *
CRC Card
Responsibilities. - *
Handle injection of set up tasks. - *
Handle injection of tear down tasks. - *
- * - * @author Rupert Smith - */ -public interface SetupTaskAware -{ - /** - * Adds the specified task to the tests setup. - * - * @param task The task to add to the tests setup. - */ - public void chainSetupTask(Runnable task); - - /** - * Adds the specified task to the tests tear down. - * - * @param task The task to add to the tests tear down. - */ - public void chainTearDownTask(Runnable task); -} +/* + * + * 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; + +/** + * SetupTaskAware is an interface that tests that can accept injectable setup tasks may implement. Typically this + * is used by configurable decorator stack to inject setup tasks into tests. It is then up to the test case to run + * the tasks in the setup or threadSetup methods as it chooses. + * + *

Set up tasks should be chained so that they are executed in the order that they are applied. Tear down tasks + * should be chained so that they are executed in the reverse order to which they are applied. That way the set up and + * tear down tasks act as a 'task' stack, with nested setups and tear downs. + * + *

+ *
CRC Card
Responsibilities. + *
Handle injection of set up tasks. + *
Handle injection of tear down tasks. + *
+ * + * @author Rupert Smith + */ +public interface SetupTaskAware +{ + /** + * Adds the specified task to the tests setup. + * + * @param task The task to add to the tests setup. + */ + public void chainSetupTask(Runnable task); + + /** + * Adds the specified task to the tests tear down. + * + * @param task The task to add to the tests tear down. + */ + public void chainTearDownTask(Runnable task); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java index 00736c59e5..b91ce41ad3 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java @@ -1,92 +1,92 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -import org.apache.qpid.junit.extensions.util.StackQueue; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * SetupTaskHandler implements a task stack. It can be used, by delegation, as a base implementation for tests that want - * to have configurable setup/teardown task stacks. Typically it is up to the test implementation to decide whether the - * stack is executed in the setup/teardown methods or in the threadSetup/threadTeaddown methods. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Handle injection of set up tasks. - *
Handle injection of tear down tasks. - *
Run set up tasks in chain order. - *
Run tear down tasks in reverse chain order. - *
- * - * @author Rupert Smith - */ -public class SetupTaskHandler implements SetupTaskAware -{ - /** Holds the set up tasks. */ - Queue setups = new LinkedList(); - - /** Holds the tear down tasks. */ - Queue teardowns = new StackQueue(); - - /** - * Adds the specified task to the tests setup. - * - * @param task The task to add to the tests setup. - */ - public void chainSetupTask(Runnable task) - { - setups.offer(task); - } - - /** - * Adds the specified task to the tests tear down. - * - * @param task The task to add to the tests tear down. - */ - public void chainTearDownTask(Runnable task) - { - teardowns.offer(task); - } - - /** - * Runs the set up tasks in the order that they way chained. - */ - public void runSetupTasks() - { - while (!setups.isEmpty()) - { - setups.remove().run(); - } - } - - /** - * Runs the tear down tasks in the reverse of the order in which they were chained. - */ - public void runTearDownTasks() - { - while (!teardowns.isEmpty()) - { - teardowns.remove().run(); - } - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.junit.extensions; + +import org.apache.qpid.junit.extensions.util.StackQueue; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * SetupTaskHandler implements a task stack. It can be used, by delegation, as a base implementation for tests that want + * to have configurable setup/teardown task stacks. Typically it is up to the test implementation to decide whether the + * stack is executed in the setup/teardown methods or in the threadSetup/threadTeaddown methods. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Handle injection of set up tasks. + *
Handle injection of tear down tasks. + *
Run set up tasks in chain order. + *
Run tear down tasks in reverse chain order. + *
+ * + * @author Rupert Smith + */ +public class SetupTaskHandler implements SetupTaskAware +{ + /** Holds the set up tasks. */ + Queue setups = new LinkedList(); + + /** Holds the tear down tasks. */ + Queue teardowns = new StackQueue(); + + /** + * Adds the specified task to the tests setup. + * + * @param task The task to add to the tests setup. + */ + public void chainSetupTask(Runnable task) + { + setups.offer(task); + } + + /** + * Adds the specified task to the tests tear down. + * + * @param task The task to add to the tests tear down. + */ + public void chainTearDownTask(Runnable task) + { + teardowns.offer(task); + } + + /** + * Runs the set up tasks in the order that they way chained. + */ + public void runSetupTasks() + { + while (!setups.isEmpty()) + { + setups.remove().run(); + } + } + + /** + * Runs the tear down tasks in the reverse of the order in which they were chained. + */ + public void runTearDownTasks() + { + while (!teardowns.isEmpty()) + { + teardowns.remove().run(); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java index 344d7abf82..dc6aa3c291 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java @@ -1,42 +1,42 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -/** - * Defines an interface that classes which supply shutdown hooks implement. Code that creates these classes can check - * if they supply a shutdown hook and register these hooks when the obejct are created. - * - *

- *
CRC Card
Responsibilities - *
Supply a shutdown hook. - *
- * - * @author Rupert Smith - */ -public interface ShutdownHookable -{ - /** - * Supplies the shutdown hook. - * - * @return The shut down hook. - */ - public Thread getShutdownHook(); -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.junit.extensions; + +/** + * Defines an interface that classes which supply shutdown hooks implement. Code that creates these classes can check + * if they supply a shutdown hook and register these hooks when the obejct are created. + * + *

+ *
CRC Card
Responsibilities + *
Supply a shutdown hook. + *
+ * + * @author Rupert Smith + */ +public interface ShutdownHookable +{ + /** + * Supplies the shutdown hook. + * + * @return The shut down hook. + */ + public Thread getShutdownHook(); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java index f7e350b1c7..2dc4c0e272 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java @@ -1,81 +1,81 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -/** - * SleepThrottle is a Throttle implementation that generates short pauses using the thread sleep methods. As the pauses - * get shorter, this technique gets more innacurate. In practice, around 100 Hz is the cap rate for accuracy. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Accept throttling rate in operations per second. - *
Inject short pauses to fill out processing cycles to a specified rate. - *
Check against a throttle speed without waiting. - *
- * - * @author Rupert Smith - */ -public class SleepThrottle extends BaseThrottle implements Throttle -{ - /** Holds the time of the last call to the throttle method in nano seconds. */ - private long lastTimeNanos; - - /** - * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this - * it will inject short pauses to restrict the call rate to that rate. - */ - public void throttle() - { - // Get the current time in nanos. - long currentTimeNanos = System.nanoTime(); - - // Don't introduce any pause on the first call. - if (!firstCall) - { - // Check if there is any time left in the cycle since the last call to this method and introduce a short pause - // to fill that time if there is. - long remainingTimeNanos = cycleTimeNanos - (currentTimeNanos - lastTimeNanos); - - if (remainingTimeNanos > 0) - { - long milliPause = remainingTimeNanos / 1000000; - int nanoPause = (int) (remainingTimeNanos % 1000000); - - try - { - Thread.sleep(milliPause, nanoPause); - } - catch (InterruptedException e) - { - // Restore the interrupted thread, in-case the caller is checking for it. - Thread.currentThread().interrupt(); - } - } - } - else - { - firstCall = false; - } - - // Update the last time stamp. - lastTimeNanos = System.nanoTime(); - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.junit.extensions; + +/** + * SleepThrottle is a Throttle implementation that generates short pauses using the thread sleep methods. As the pauses + * get shorter, this technique gets more innacurate. In practice, around 100 Hz is the cap rate for accuracy. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Accept throttling rate in operations per second. + *
Inject short pauses to fill out processing cycles to a specified rate. + *
Check against a throttle speed without waiting. + *
+ * + * @author Rupert Smith + */ +public class SleepThrottle extends BaseThrottle implements Throttle +{ + /** Holds the time of the last call to the throttle method in nano seconds. */ + private long lastTimeNanos; + + /** + * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this + * it will inject short pauses to restrict the call rate to that rate. + */ + public void throttle() + { + // Get the current time in nanos. + long currentTimeNanos = System.nanoTime(); + + // Don't introduce any pause on the first call. + if (!firstCall) + { + // Check if there is any time left in the cycle since the last call to this method and introduce a short pause + // to fill that time if there is. + long remainingTimeNanos = cycleTimeNanos - (currentTimeNanos - lastTimeNanos); + + if (remainingTimeNanos > 0) + { + long milliPause = remainingTimeNanos / 1000000; + int nanoPause = (int) (remainingTimeNanos % 1000000); + + try + { + Thread.sleep(milliPause, nanoPause); + } + catch (InterruptedException e) + { + // Restore the interrupted thread, in-case the caller is checking for it. + Thread.currentThread().interrupt(); + } + } + } + else + { + firstCall = false; + } + + // Update the last time stamp. + lastTimeNanos = System.nanoTime(); + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java index c9bcf3eb66..ae497c671b 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java @@ -1,625 +1,625 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestResult; - -import org.apache.log4j.Logger; - -import org.apache.qpid.junit.extensions.listeners.TKTestListener; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Properties; - -/** - * TKTestResult extends TestResult in order to calculate test timings, to pass the variable integer parameter for - * parameterized test cases to those test cases and to introduce an optional delay before test starts. Interested - * {@link TKTestListener}s may be attached to this and will be informed of all relevant test statistics. - * - *

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

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

- * -w ms       The number of milliseconds between invocations of test cases.
- * -c pattern  The number of tests to run concurrently.
- * -r num      The number of times to repeat each test.
- * -d duration The length of time to run the tests for.
- * -t name     The name of the test case to execute.
- * -s pattern  The size parameter to run tests with.
- * -o dir      The name of the directory to output test timings to.
- * --csv       Output test results in CSV format.
- * --xml       Output test results in XML format.
- * 
- * - *

This command line may also have trailing 'name=value' parameters added to it. All of these values are added - * to the test context properties and passed to the test, which can access them by name. - * - *

The pattern arguments are of the form [lowest(: ...)(: highest)](:sample=s)(:exp), where round brackets - * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided - * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. - * - *

The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least - * one of the optional values must be present. - * - *

When specifying optional test parameters on the command line, in 'name=value' format, it is also possible to use - * the format 'name=[value1:value2:value3:...]', to specify multiple values for a parameter. All permutations of all - * parameters with multiple values will be created and tested. If the values are numerical, it is also possible to use - * the sequence generation patterns instead of fully specifying all of the values. - * - *

Here are some examples: - * - *

- *
 -c [10:20:30:40:50] 
Runs the test with 10,20,...,50 threads. - *
 -s [1:100]:samples=10 
- *
Runs the test with ten different size parameters evenly spaced between 1 and 100. - *
 -s [1:1000000]:samples=10:exp 
- *
Runs the test with ten different size parameters exponentially spaced between 1 and 1000000. - *
 -r 10 
Runs each test ten times. - *
 -d 10H 
Runs the test repeatedly for 10 hours. - *
 -d 1M, -r 10 
- *
Runs the test repeatedly for 1 minute but only takes a timing sample every 10 test runs. - *
 -r 10, -c [1:5:10:50], -s [100:1000:10000] 
- *
Runs 12 test cycles (4 concurrency samples * 3 size sample), with 10 repeats each. In total the test - * will be run 199 times (3 + 15 + 30 + 150) - *
 cache=true 
Passes the 'cache' parameter with value 'true' to the test. - *
 cache=[true:false] 
Runs the test with the 'cache' parameter set to 'true' and 'false'. - *
 cacheSize=[1000:1000000],samples=4,exp 
- *
Runs the test with the 'cache' parameter set to a series of exponentially increasing sizes. - *
- * - *

- *
CRC Card
Responsibilities Collaborations - *
Create the test configuration specified by the command line parameters. - *
- * - * @todo Verify that the output directory exists or can be created. - * - * @todo Verify that the specific named test case to execute exists. - * - * @todo Drop the delay parameter as it is being replaced by throttling. - * - * @todo Completely replace the test ui test runner, instead of having TKTestRunner inherit from it, its just not - * good code to extend. - * - * @author Rupert Smith - */ -public class TKTestRunner extends TestRunnerImprovedErrorHandling -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(TKTestRunner.class); - - /** Used for displaying information on the console. */ - // private static final Logger console = Logger.getLogger("CONSOLE." + TKTestRunner.class.getName()); - - /** Used for generating the timestamp when naming output files. */ - protected static final DateFormat TIME_STAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); - - /** Number of times to rerun the test. */ - protected Integer repetitions = 1; - - /** The length of time to run the tests for. */ - protected Long duration; - - /** Number of threads running the tests. */ - protected int[] threads; - - /** Delay in ms to wait between two test cases. */ - protected int delay = 0; - - /** The parameter values to pass to parameterized tests. */ - protected int[] params; - - /** Name of the single test case to execute. */ - protected String testCaseName = null; - - /** Name of the test class. */ - protected String testClassName = null; - - /** Name of the test run. */ - protected String testRunName = null; - - /** Directory to output XML reports into, if specified. */ - protected String reportDir = null; - - /** Flag that indicates the CSV results listener should be used to output results. */ - protected boolean csvResults; - - /** Flag that indiciates the XML results listener should be used to output results. */ - protected boolean xmlResults; - - /** - * Holds the name of the class of the test currently being run. Ideally passed into the {@link #createTestResult} - * method, but as the signature is already fixed for this, the current value gets pushed here as a member variable. - */ - protected String currentTestClassName; - - /** Holds the test results object, which is reponsible for instrumenting tests/threads to record results. */ - protected TKTestResult result; - - /** Holds a list of factories for instantiating optional user specified test decorators. */ - protected List decoratorFactories; - - /** - * Constructs a TKTestRunner using System.out for all the output. - * - * @param repetitions The number of times to repeat the test, or test batch size. - * @param duration The length of time to run the tests for. -1 means no duration has been set. - * @param threads The concurrency levels to ramp up to. - * @param delay A delay in milliseconds between test runs. - * @param params The sets of 'size' parameters to pass to test. - * @param testCaseName The name of the test case to run. - * @param reportDir The directory to output the test results to. - * @param runName The name of the test run; used to name the output file. - * @param csvResults true if the CSV results listener should be attached. - * @param xmlResults true if the XML results listener should be attached. - * @param decoratorFactories List of factories for user specified decorators. - */ - public TKTestRunner(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName, - String reportDir, String runName, boolean csvResults, boolean xmlResults, - List decoratorFactories) - { - super(new NullResultPrinter(System.out)); - - log.debug("public TKTestRunner(): called"); - - // Keep all the test parameters. - this.repetitions = repetitions; - this.duration = duration; - this.threads = threads; - this.delay = delay; - this.params = params; - this.testCaseName = testCaseName; - this.reportDir = reportDir; - this.testRunName = runName; - this.csvResults = csvResults; - this.xmlResults = xmlResults; - this.decoratorFactories = decoratorFactories; - } - - /** - * The entry point for the toolkit test runner. - * - * @param args The command line arguments. - */ - public static void main(String[] args) - { - // Use the command line parser to evaluate the command line. - CommandLineParser commandLine = - new CommandLineParser( - new String[][] - { - { "w", "The number of milliseconds between invocations of test cases.", "ms", "false" }, - { "c", "The number of tests to run concurrently.", "num", "false", MathUtils.SEQUENCE_REGEXP }, - { "r", "The number of times to repeat each test.", "num", "false" }, - { "d", "The length of time to run the tests for.", "duration", "false", MathUtils.DURATION_REGEXP }, - { "f", "The maximum rate to call the tests at.", "frequency", "false", "^([1-9][0-9]*)/([1-9][0-9]*)$" }, - { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP }, - { "t", "The name of the test case to execute.", "name", "false" }, - { "o", "The name of the directory to output test timings to.", "dir", "false" }, - { "n", "A name for this test run, used to name the output file.", "name", "true" }, - { - "X:decorators", "A list of additional test decorators to wrap the tests in.", - "\"class.name[:class.name]*\"", "false" - }, - { "1", "Test class.", "class", "true" }, - { "-csv", "Output test results in CSV format.", null, "false" }, - { "-xml", "Output test results in XML format.", null, "false" } - }); - - // Capture the command line arguments or display errors and correct usage and then exit. - ParsedProperties options = null; - - try - { - options = new ParsedProperties(commandLine.parseCommandLine(args)); - } - catch (IllegalArgumentException e) - { - System.out.println(commandLine.getErrors()); - System.out.println(commandLine.getUsage()); - System.exit(FAILURE_EXIT); - } - - // Extract the command line options. - Integer delay = options.getPropertyAsInteger("w"); - String threadsString = options.getProperty("c"); - Integer repetitions = options.getPropertyAsInteger("r"); - String durationString = options.getProperty("d"); - String paramsString = options.getProperty("s"); - String testCaseName = options.getProperty("t"); - String reportDir = options.getProperty("o"); - String testRunName = options.getProperty("n"); - String decorators = options.getProperty("X:decorators"); - String testClassName = options.getProperty("1"); - boolean csvResults = options.getPropertyAsBoolean("-csv"); - boolean xmlResults = options.getPropertyAsBoolean("-xml"); - - int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString); - int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString); - Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString); - - // The test run name defaults to the test class name unless a value was specified for it. - testRunName = (testRunName == null) ? testClassName : testRunName; - - // Add all the command line options and trailing settings to test context properties. Tests may pick up - // overridden values from there, and these values will be logged in the test results, for analysis and - // to make tests repeatable. - commandLine.addTrailingPairsToProperties(TestContextProperties.getInstance()); - commandLine.addOptionsToProperties(TestContextProperties.getInstance()); - - // Create and start the test runner. - try - { - // Create a list of test decorator factories for use specified decorators to be applied. - List decoratorFactories = parseDecorators(decorators); - - TKTestRunner testRunner = - new TKTestRunner(repetitions, duration, threads, (delay == null) ? 0 : delay, params, testCaseName, - reportDir, testRunName, csvResults, xmlResults, decoratorFactories); - - TestResult testResult = testRunner.start(testClassName); - - if (!testResult.wasSuccessful()) - { - System.exit(FAILURE_EXIT); - } - } - catch (Exception e) - { - System.err.println(e.getMessage()); - e.printStackTrace(new PrintStream(System.err)); - System.exit(EXCEPTION_EXIT); - } - } - - /** - * Parses a list of test decorators, in the form "class.name[:class.name]*", and creates factories for those - * TestDecorator classes , and returns a list of the factories. This list of factories will be in the same - * order as specified in the string. The factories can be used to succesively wrap tests in layers of - * decorators, as decorators themselves implement the 'Test' interface. - * - *

If the string fails to parse, or if any of the decorators specified in it are cannot be loaded, or are not - * TestDecorators, a runtime exception with a suitable error message will be thrown. The factories themselves - * throw runtimes if the constructor method calls on the decorators fail. - * - * @param decorators The decorators list to be parsed. - * - * @return A list of instantiated decorators. - */ - protected static List parseDecorators(String decorators) - { - List result = new LinkedList(); - String toParse = decorators; - - // Check that the decorators string is not null or empty, returning an empty list of decorator factories it - // it is. - if ((decorators == null) || "".equals(decorators)) - { - return result; - } - - // Strip any leading and trailing quotes from the string. - if (toParse.charAt(0) == '\"') - { - toParse = toParse.substring(1, toParse.length() - 1); - } - - if (toParse.charAt(toParse.length() - 1) == '\"') - { - toParse = toParse.substring(0, toParse.length() - 2); - } - - // Instantiate all decorators. - for (String decoratorClassName : toParse.split(":")) - { - try - { - Class decoratorClass = Class.forName(decoratorClassName); - final Constructor decoratorConstructor = decoratorClass.getConstructor(WrappedSuiteTestDecorator.class); - - // Check that the decorator is an instance of WrappedSuiteTestDecorator. - if (!WrappedSuiteTestDecorator.class.isAssignableFrom(decoratorClass)) - { - throw new RuntimeException("The decorator class " + decoratorClassName - + " is not a sub-class of WrappedSuiteTestDecorator, which it needs to be."); - } - - result.add(new TestDecoratorFactory() - { - public WrappedSuiteTestDecorator decorateTest(Test test) - { - try - { - return (WrappedSuiteTestDecorator) decoratorConstructor.newInstance(test); - } - catch (InstantiationException e) - { - throw new RuntimeException( - "The decorator class " + decoratorConstructor.getDeclaringClass().getName() - + " cannot be instantiated.", e); - } - catch (IllegalAccessException e) - { - throw new RuntimeException( - "The decorator class " + decoratorConstructor.getDeclaringClass().getName() - + " does not have a publicly accessable constructor.", e); - } - catch (InvocationTargetException e) - { - throw new RuntimeException( - "The decorator class " + decoratorConstructor.getDeclaringClass().getName() - + " cannot be invoked.", e); - } - } - }); - } - catch (ClassNotFoundException e) - { - throw new RuntimeException("The decorator class " + decoratorClassName + " could not be found.", e); - } - catch (NoSuchMethodException e) - { - throw new RuntimeException("The decorator class " + decoratorClassName - + " does not have a constructor that accepts a single 'WrappedSuiteTestDecorator' argument.", e); - } - } - - return result; - } - - /** - * TestDecoratorFactory is a factory for creating test decorators from tests. - */ - protected interface TestDecoratorFactory - { - /** - * Decorates the specified test with a new decorator. - * - * @param test The test to decorate. - * - * @return The decorated test. - */ - public WrappedSuiteTestDecorator decorateTest(Test test); - } - - /** - * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run - * in any test decorators needed to add in the configured toolkits enhanced junit functionality. - * - * @param test The test to run. - * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for. - * - * @return The results of the test run. - */ - public TestResult doRun(Test test, boolean wait) - { - log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called"); - - // Wrap the tests in decorators for duration, scaling, repetition, parameterization etc. - WrappedSuiteTestDecorator targetTest = decorateTests(test); - - // Delegate to the super method to run the decorated tests. - log.debug("About to call super.doRun"); - - TestResult result = super.doRun(targetTest, wait); - log.debug("super.doRun returned."); - - /*if (result instanceof TKTestResult) - { - TKTestResult tkResult = (TKTestResult) result; - - tkResult.notifyEndBatch(); - }*/ - - return result; - } - - /** - * Applies test decorators to the tests for parameterization, duration, scaling and repetition. - * - * @param test The test to decorat. - * - * @return The decorated test. - */ - protected WrappedSuiteTestDecorator decorateTests(Test test) - { - log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params))); - log.debug("repetitions = " + repetitions); - log.debug("threads = " + ((threads == null) ? null : MathUtils.printArray(threads))); - log.debug("duration = " + duration); - - // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling, - // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it. - WrappedSuiteTestDecorator targetTest = null; - - if (test instanceof TestSuite) - { - log.debug("targetTest is a TestSuite"); - - TestSuite suite = (TestSuite) test; - - int numTests = suite.countTestCases(); - log.debug("There are " + numTests + " in the suite."); - - for (int i = 0; i < numTests; i++) - { - Test nextTest = suite.testAt(i); - log.debug("suite.testAt(" + i + ") = " + nextTest); - - if (nextTest instanceof TimingControllerAware) - { - log.debug("nextTest is TimingControllerAware"); - } - - if (nextTest instanceof TestThreadAware) - { - log.debug("nextTest is TestThreadAware"); - } - } - - targetTest = new WrappedSuiteTestDecorator(suite); - log.debug("Wrapped with a WrappedSuiteTestDecorator."); - } - // If the test has already been wrapped, no need to do it again. - else if (test instanceof WrappedSuiteTestDecorator) - { - targetTest = (WrappedSuiteTestDecorator) test; - } - - // If size parameter values have been set, then wrap the test in an asymptotic test decorator. - if (params != null) - { - targetTest = new AsymptoticTestDecorator(targetTest, params, (repetitions == null) ? 1 : repetitions); - log.debug("Wrapped with asymptotic test decorator."); - log.debug("targetTest = " + targetTest); - } - - // If no size parameters are set but the repitions parameter is, then wrap the test in an asymptotic test decorator. - else if ((repetitions != null) && (repetitions > 1)) - { - targetTest = new AsymptoticTestDecorator(targetTest, new int[] { 1 }, repetitions); - log.debug("Wrapped with asymptotic test decorator."); - log.debug("targetTest = " + targetTest); - } - - // Apply any optional user specified decorators. - targetTest = applyOptionalUserDecorators(targetTest); - - // If a test run duration has been set then wrap the test in a duration test decorator. This will wrap on - // top of size, repeat or concurrency wrappings already applied. - if (duration != null) - { - DurationTestDecorator durationTest = new DurationTestDecorator(targetTest, duration); - targetTest = durationTest; - - log.debug("Wrapped with duration test decorator."); - log.debug("targetTest = " + targetTest); - - registerShutdownHook(durationTest); - } - - // ParameterVariationTestDecorator... - - // If a test thread concurrency level is set then wrap the test in a scaled test decorator. This will wrap on - // top of size scaling or repetition wrappings. - ScaledTestDecorator scaledDecorator; - - if ((threads != null) && ((threads.length > 1) || (MathUtils.maxInArray(threads) > 1))) - { - scaledDecorator = new ScaledTestDecorator(targetTest, threads); - targetTest = scaledDecorator; - log.debug("Wrapped with scaled test decorator."); - log.debug("targetTest = " + targetTest); - } - else - { - scaledDecorator = new ScaledTestDecorator(targetTest, new int[] { 1 }); - targetTest = scaledDecorator; - log.debug("Wrapped with scaled test decorator with default of 1 thread."); - log.debug("targetTest = " + targetTest); - } - - // Register the scaled test decorators shutdown hook. - registerShutdownHook(scaledDecorator); - - return targetTest; - } - - /** - * If there were any user specified test decorators on the command line, this method instantiates them and wraps - * the test in them, from inner-most to outer-most in the order in which the decorators were supplied on the - * command line. - * - * @param targetTest The test to wrap. - * - * @return A wrapped test. - */ - protected WrappedSuiteTestDecorator applyOptionalUserDecorators(WrappedSuiteTestDecorator targetTest) - { - // If there are user defined test decorators apply them in order now. - for (TestDecoratorFactory factory : decoratorFactories) - { - targetTest = factory.decorateTest(targetTest); - } - - return targetTest; - } - - /** - * Creates the TestResult object to be used for test runs. See {@link TKTestResult} for more information and the - * enhanced test result class that this uses. - * - * @return An instance of the enhanced test result object, {@link TKTestResult}. - */ - protected TestResult createTestResult() - { - log.debug("protected TestResult createTestResult(): called"); - - TKTestResult result = new TKTestResult(delay, testCaseName); - - // Check if a directory to output reports to has been specified and attach test listeners if so. - if (reportDir != null) - { - // Create the report directory if it does not already exist. - File reportDirFile = new File(reportDir); - - if (!reportDirFile.exists()) - { - reportDirFile.mkdir(); - } - - // Create the results file (make the name of this configurable as a command line parameter). - Writer timingsWriter; - - // Always set up a console feedback listener. - ConsoleTestListener feedbackListener = new ConsoleTestListener(); - result.addListener(feedbackListener); - result.addTKTestListener(feedbackListener); - - // Set up an XML results listener to output the timings to the results file, if requested on the command line. - if (xmlResults) - { - try - { - File timingsFile = new File(reportDirFile, "TEST-" + currentTestClassName + ".xml"); - timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000); - } - catch (IOException e) - { - throw new RuntimeException("Unable to create the log file to write test results to: " + e, e); - } - - XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName); - result.addListener(listener); - result.addTKTestListener(listener); - - registerShutdownHook(listener); - } - - // Set up an CSV results listener to output the timings to the results file, if requested on the command line. - if (csvResults) - { - try - { - File timingsFile = - new File(reportDirFile, testRunName + "-" + TIME_STAMP_FORMAT.format(new Date()) + "-timings.csv"); - timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000); - } - catch (IOException e) - { - throw new RuntimeException("Unable to create the log file to write test results to: " + e, e); - } - - CSVTestListener listener = new CSVTestListener(timingsWriter); - result.addListener(listener); - result.addTKTestListener(listener); - - // Register the results listeners shutdown hook to flush its data if the test framework is shutdown - // prematurely. - registerShutdownHook(listener); - } - - // Register the results listeners shutdown hook to flush its data if the test framework is shutdown - // prematurely. - // registerShutdownHook(listener); - - // Record the start time of the batch. - // result.notifyStartBatch(); - - // At this point in time the test class has been instantiated, giving it an opportunity to read its parameters. - // Inform any test listers of the test properties. - result.notifyTestProperties(TestContextProperties.getAccessedProps()); - } - - return result; - } - - /** - * Registers the shutdown hook of a {@link ShutdownHookable}. - * - * @param hookable The hookable to register. - */ - protected void registerShutdownHook(ShutdownHookable hookable) - { - Runtime.getRuntime().addShutdownHook(hookable.getShutdownHook()); - } - - /** - * Initializes the test runner with the provided command line arguments and and starts the test run. - * - * @param testClassName The fully qualified name of the test class to run. - * - * @return The test results. - * - * @throws Exception Any exceptions from running the tests are allowed to fall through. - */ - protected TestResult start(String testClassName) throws Exception - { - // Record the current test class, so that the test results can be output to a file incorporating this name. - this.currentTestClassName = testClassName; - - // Delegate to the super method to run the tests. - return super.start(new String[] { testClassName }); - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.junit.extensions; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +import org.apache.log4j.Logger; + +import org.apache.qpid.junit.extensions.listeners.CSVTestListener; +import org.apache.qpid.junit.extensions.listeners.ConsoleTestListener; +import org.apache.qpid.junit.extensions.listeners.XMLTestListener; +import org.apache.qpid.junit.extensions.util.CommandLineParser; +import org.apache.qpid.junit.extensions.util.MathUtils; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * TKTestRunner extends {@link junit.textui.TestRunner} with the ability to run tests multiple times, to execute a test + * simultaneously using many threads, to put a delay between test runs and adds support for tests that take integer + * parameters that can be 'stepped' through on multiple test runs. These features can be accessed by using this class + * as an entry point and passing command line arguments to specify which features to use: + * + *

+ * -w ms       The number of milliseconds between invocations of test cases.
+ * -c pattern  The number of tests to run concurrently.
+ * -r num      The number of times to repeat each test.
+ * -d duration The length of time to run the tests for.
+ * -t name     The name of the test case to execute.
+ * -s pattern  The size parameter to run tests with.
+ * -o dir      The name of the directory to output test timings to.
+ * --csv       Output test results in CSV format.
+ * --xml       Output test results in XML format.
+ * 
+ * + *

This command line may also have trailing 'name=value' parameters added to it. All of these values are added + * to the test context properties and passed to the test, which can access them by name. + * + *

The pattern arguments are of the form [lowest(: ...)(: highest)](:sample=s)(:exp), where round brackets + * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided + * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. + * + *

The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least + * one of the optional values must be present. + * + *

When specifying optional test parameters on the command line, in 'name=value' format, it is also possible to use + * the format 'name=[value1:value2:value3:...]', to specify multiple values for a parameter. All permutations of all + * parameters with multiple values will be created and tested. If the values are numerical, it is also possible to use + * the sequence generation patterns instead of fully specifying all of the values. + * + *

Here are some examples: + * + *

+ *
 -c [10:20:30:40:50] 
Runs the test with 10,20,...,50 threads. + *
 -s [1:100]:samples=10 
+ *
Runs the test with ten different size parameters evenly spaced between 1 and 100. + *
 -s [1:1000000]:samples=10:exp 
+ *
Runs the test with ten different size parameters exponentially spaced between 1 and 1000000. + *
 -r 10 
Runs each test ten times. + *
 -d 10H 
Runs the test repeatedly for 10 hours. + *
 -d 1M, -r 10 
+ *
Runs the test repeatedly for 1 minute but only takes a timing sample every 10 test runs. + *
 -r 10, -c [1:5:10:50], -s [100:1000:10000] 
+ *
Runs 12 test cycles (4 concurrency samples * 3 size sample), with 10 repeats each. In total the test + * will be run 199 times (3 + 15 + 30 + 150) + *
 cache=true 
Passes the 'cache' parameter with value 'true' to the test. + *
 cache=[true:false] 
Runs the test with the 'cache' parameter set to 'true' and 'false'. + *
 cacheSize=[1000:1000000],samples=4,exp 
+ *
Runs the test with the 'cache' parameter set to a series of exponentially increasing sizes. + *
+ * + *

+ *
CRC Card
Responsibilities Collaborations + *
Create the test configuration specified by the command line parameters. + *
+ * + * @todo Verify that the output directory exists or can be created. + * + * @todo Verify that the specific named test case to execute exists. + * + * @todo Drop the delay parameter as it is being replaced by throttling. + * + * @todo Completely replace the test ui test runner, instead of having TKTestRunner inherit from it, its just not + * good code to extend. + * + * @author Rupert Smith + */ +public class TKTestRunner extends TestRunnerImprovedErrorHandling +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(TKTestRunner.class); + + /** Used for displaying information on the console. */ + // private static final Logger console = Logger.getLogger("CONSOLE." + TKTestRunner.class.getName()); + + /** Used for generating the timestamp when naming output files. */ + protected static final DateFormat TIME_STAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); + + /** Number of times to rerun the test. */ + protected Integer repetitions = 1; + + /** The length of time to run the tests for. */ + protected Long duration; + + /** Number of threads running the tests. */ + protected int[] threads; + + /** Delay in ms to wait between two test cases. */ + protected int delay = 0; + + /** The parameter values to pass to parameterized tests. */ + protected int[] params; + + /** Name of the single test case to execute. */ + protected String testCaseName = null; + + /** Name of the test class. */ + protected String testClassName = null; + + /** Name of the test run. */ + protected String testRunName = null; + + /** Directory to output XML reports into, if specified. */ + protected String reportDir = null; + + /** Flag that indicates the CSV results listener should be used to output results. */ + protected boolean csvResults; + + /** Flag that indiciates the XML results listener should be used to output results. */ + protected boolean xmlResults; + + /** + * Holds the name of the class of the test currently being run. Ideally passed into the {@link #createTestResult} + * method, but as the signature is already fixed for this, the current value gets pushed here as a member variable. + */ + protected String currentTestClassName; + + /** Holds the test results object, which is reponsible for instrumenting tests/threads to record results. */ + protected TKTestResult result; + + /** Holds a list of factories for instantiating optional user specified test decorators. */ + protected List decoratorFactories; + + /** + * Constructs a TKTestRunner using System.out for all the output. + * + * @param repetitions The number of times to repeat the test, or test batch size. + * @param duration The length of time to run the tests for. -1 means no duration has been set. + * @param threads The concurrency levels to ramp up to. + * @param delay A delay in milliseconds between test runs. + * @param params The sets of 'size' parameters to pass to test. + * @param testCaseName The name of the test case to run. + * @param reportDir The directory to output the test results to. + * @param runName The name of the test run; used to name the output file. + * @param csvResults true if the CSV results listener should be attached. + * @param xmlResults true if the XML results listener should be attached. + * @param decoratorFactories List of factories for user specified decorators. + */ + public TKTestRunner(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName, + String reportDir, String runName, boolean csvResults, boolean xmlResults, + List decoratorFactories) + { + super(new NullResultPrinter(System.out)); + + log.debug("public TKTestRunner(): called"); + + // Keep all the test parameters. + this.repetitions = repetitions; + this.duration = duration; + this.threads = threads; + this.delay = delay; + this.params = params; + this.testCaseName = testCaseName; + this.reportDir = reportDir; + this.testRunName = runName; + this.csvResults = csvResults; + this.xmlResults = xmlResults; + this.decoratorFactories = decoratorFactories; + } + + /** + * The entry point for the toolkit test runner. + * + * @param args The command line arguments. + */ + public static void main(String[] args) + { + // Use the command line parser to evaluate the command line. + CommandLineParser commandLine = + new CommandLineParser( + new String[][] + { + { "w", "The number of milliseconds between invocations of test cases.", "ms", "false" }, + { "c", "The number of tests to run concurrently.", "num", "false", MathUtils.SEQUENCE_REGEXP }, + { "r", "The number of times to repeat each test.", "num", "false" }, + { "d", "The length of time to run the tests for.", "duration", "false", MathUtils.DURATION_REGEXP }, + { "f", "The maximum rate to call the tests at.", "frequency", "false", "^([1-9][0-9]*)/([1-9][0-9]*)$" }, + { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP }, + { "t", "The name of the test case to execute.", "name", "false" }, + { "o", "The name of the directory to output test timings to.", "dir", "false" }, + { "n", "A name for this test run, used to name the output file.", "name", "true" }, + { + "X:decorators", "A list of additional test decorators to wrap the tests in.", + "\"class.name[:class.name]*\"", "false" + }, + { "1", "Test class.", "class", "true" }, + { "-csv", "Output test results in CSV format.", null, "false" }, + { "-xml", "Output test results in XML format.", null, "false" } + }); + + // Capture the command line arguments or display errors and correct usage and then exit. + ParsedProperties options = null; + + try + { + options = new ParsedProperties(commandLine.parseCommandLine(args)); + } + catch (IllegalArgumentException e) + { + System.out.println(commandLine.getErrors()); + System.out.println(commandLine.getUsage()); + System.exit(FAILURE_EXIT); + } + + // Extract the command line options. + Integer delay = options.getPropertyAsInteger("w"); + String threadsString = options.getProperty("c"); + Integer repetitions = options.getPropertyAsInteger("r"); + String durationString = options.getProperty("d"); + String paramsString = options.getProperty("s"); + String testCaseName = options.getProperty("t"); + String reportDir = options.getProperty("o"); + String testRunName = options.getProperty("n"); + String decorators = options.getProperty("X:decorators"); + String testClassName = options.getProperty("1"); + boolean csvResults = options.getPropertyAsBoolean("-csv"); + boolean xmlResults = options.getPropertyAsBoolean("-xml"); + + int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString); + int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString); + Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString); + + // The test run name defaults to the test class name unless a value was specified for it. + testRunName = (testRunName == null) ? testClassName : testRunName; + + // Add all the command line options and trailing settings to test context properties. Tests may pick up + // overridden values from there, and these values will be logged in the test results, for analysis and + // to make tests repeatable. + commandLine.addTrailingPairsToProperties(TestContextProperties.getInstance()); + commandLine.addOptionsToProperties(TestContextProperties.getInstance()); + + // Create and start the test runner. + try + { + // Create a list of test decorator factories for use specified decorators to be applied. + List decoratorFactories = parseDecorators(decorators); + + TKTestRunner testRunner = + new TKTestRunner(repetitions, duration, threads, (delay == null) ? 0 : delay, params, testCaseName, + reportDir, testRunName, csvResults, xmlResults, decoratorFactories); + + TestResult testResult = testRunner.start(testClassName); + + if (!testResult.wasSuccessful()) + { + System.exit(FAILURE_EXIT); + } + } + catch (Exception e) + { + System.err.println(e.getMessage()); + e.printStackTrace(new PrintStream(System.err)); + System.exit(EXCEPTION_EXIT); + } + } + + /** + * Parses a list of test decorators, in the form "class.name[:class.name]*", and creates factories for those + * TestDecorator classes , and returns a list of the factories. This list of factories will be in the same + * order as specified in the string. The factories can be used to succesively wrap tests in layers of + * decorators, as decorators themselves implement the 'Test' interface. + * + *

If the string fails to parse, or if any of the decorators specified in it are cannot be loaded, or are not + * TestDecorators, a runtime exception with a suitable error message will be thrown. The factories themselves + * throw runtimes if the constructor method calls on the decorators fail. + * + * @param decorators The decorators list to be parsed. + * + * @return A list of instantiated decorators. + */ + protected static List parseDecorators(String decorators) + { + List result = new LinkedList(); + String toParse = decorators; + + // Check that the decorators string is not null or empty, returning an empty list of decorator factories it + // it is. + if ((decorators == null) || "".equals(decorators)) + { + return result; + } + + // Strip any leading and trailing quotes from the string. + if (toParse.charAt(0) == '\"') + { + toParse = toParse.substring(1, toParse.length() - 1); + } + + if (toParse.charAt(toParse.length() - 1) == '\"') + { + toParse = toParse.substring(0, toParse.length() - 2); + } + + // Instantiate all decorators. + for (String decoratorClassName : toParse.split(":")) + { + try + { + Class decoratorClass = Class.forName(decoratorClassName); + final Constructor decoratorConstructor = decoratorClass.getConstructor(WrappedSuiteTestDecorator.class); + + // Check that the decorator is an instance of WrappedSuiteTestDecorator. + if (!WrappedSuiteTestDecorator.class.isAssignableFrom(decoratorClass)) + { + throw new RuntimeException("The decorator class " + decoratorClassName + + " is not a sub-class of WrappedSuiteTestDecorator, which it needs to be."); + } + + result.add(new TestDecoratorFactory() + { + public WrappedSuiteTestDecorator decorateTest(Test test) + { + try + { + return (WrappedSuiteTestDecorator) decoratorConstructor.newInstance(test); + } + catch (InstantiationException e) + { + throw new RuntimeException( + "The decorator class " + decoratorConstructor.getDeclaringClass().getName() + + " cannot be instantiated.", e); + } + catch (IllegalAccessException e) + { + throw new RuntimeException( + "The decorator class " + decoratorConstructor.getDeclaringClass().getName() + + " does not have a publicly accessable constructor.", e); + } + catch (InvocationTargetException e) + { + throw new RuntimeException( + "The decorator class " + decoratorConstructor.getDeclaringClass().getName() + + " cannot be invoked.", e); + } + } + }); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException("The decorator class " + decoratorClassName + " could not be found.", e); + } + catch (NoSuchMethodException e) + { + throw new RuntimeException("The decorator class " + decoratorClassName + + " does not have a constructor that accepts a single 'WrappedSuiteTestDecorator' argument.", e); + } + } + + return result; + } + + /** + * TestDecoratorFactory is a factory for creating test decorators from tests. + */ + protected interface TestDecoratorFactory + { + /** + * Decorates the specified test with a new decorator. + * + * @param test The test to decorate. + * + * @return The decorated test. + */ + public WrappedSuiteTestDecorator decorateTest(Test test); + } + + /** + * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run + * in any test decorators needed to add in the configured toolkits enhanced junit functionality. + * + * @param test The test to run. + * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for. + * + * @return The results of the test run. + */ + public TestResult doRun(Test test, boolean wait) + { + log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called"); + + // Wrap the tests in decorators for duration, scaling, repetition, parameterization etc. + WrappedSuiteTestDecorator targetTest = decorateTests(test); + + // Delegate to the super method to run the decorated tests. + log.debug("About to call super.doRun"); + + TestResult result = super.doRun(targetTest, wait); + log.debug("super.doRun returned."); + + /*if (result instanceof TKTestResult) + { + TKTestResult tkResult = (TKTestResult) result; + + tkResult.notifyEndBatch(); + }*/ + + return result; + } + + /** + * Applies test decorators to the tests for parameterization, duration, scaling and repetition. + * + * @param test The test to decorat. + * + * @return The decorated test. + */ + protected WrappedSuiteTestDecorator decorateTests(Test test) + { + log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params))); + log.debug("repetitions = " + repetitions); + log.debug("threads = " + ((threads == null) ? null : MathUtils.printArray(threads))); + log.debug("duration = " + duration); + + // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling, + // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it. + WrappedSuiteTestDecorator targetTest = null; + + if (test instanceof TestSuite) + { + log.debug("targetTest is a TestSuite"); + + TestSuite suite = (TestSuite) test; + + int numTests = suite.countTestCases(); + log.debug("There are " + numTests + " in the suite."); + + for (int i = 0; i < numTests; i++) + { + Test nextTest = suite.testAt(i); + log.debug("suite.testAt(" + i + ") = " + nextTest); + + if (nextTest instanceof TimingControllerAware) + { + log.debug("nextTest is TimingControllerAware"); + } + + if (nextTest instanceof TestThreadAware) + { + log.debug("nextTest is TestThreadAware"); + } + } + + targetTest = new WrappedSuiteTestDecorator(suite); + log.debug("Wrapped with a WrappedSuiteTestDecorator."); + } + // If the test has already been wrapped, no need to do it again. + else if (test instanceof WrappedSuiteTestDecorator) + { + targetTest = (WrappedSuiteTestDecorator) test; + } + + // If size parameter values have been set, then wrap the test in an asymptotic test decorator. + if (params != null) + { + targetTest = new AsymptoticTestDecorator(targetTest, params, (repetitions == null) ? 1 : repetitions); + log.debug("Wrapped with asymptotic test decorator."); + log.debug("targetTest = " + targetTest); + } + + // If no size parameters are set but the repitions parameter is, then wrap the test in an asymptotic test decorator. + else if ((repetitions != null) && (repetitions > 1)) + { + targetTest = new AsymptoticTestDecorator(targetTest, new int[] { 1 }, repetitions); + log.debug("Wrapped with asymptotic test decorator."); + log.debug("targetTest = " + targetTest); + } + + // Apply any optional user specified decorators. + targetTest = applyOptionalUserDecorators(targetTest); + + // If a test run duration has been set then wrap the test in a duration test decorator. This will wrap on + // top of size, repeat or concurrency wrappings already applied. + if (duration != null) + { + DurationTestDecorator durationTest = new DurationTestDecorator(targetTest, duration); + targetTest = durationTest; + + log.debug("Wrapped with duration test decorator."); + log.debug("targetTest = " + targetTest); + + registerShutdownHook(durationTest); + } + + // ParameterVariationTestDecorator... + + // If a test thread concurrency level is set then wrap the test in a scaled test decorator. This will wrap on + // top of size scaling or repetition wrappings. + ScaledTestDecorator scaledDecorator; + + if ((threads != null) && ((threads.length > 1) || (MathUtils.maxInArray(threads) > 1))) + { + scaledDecorator = new ScaledTestDecorator(targetTest, threads); + targetTest = scaledDecorator; + log.debug("Wrapped with scaled test decorator."); + log.debug("targetTest = " + targetTest); + } + else + { + scaledDecorator = new ScaledTestDecorator(targetTest, new int[] { 1 }); + targetTest = scaledDecorator; + log.debug("Wrapped with scaled test decorator with default of 1 thread."); + log.debug("targetTest = " + targetTest); + } + + // Register the scaled test decorators shutdown hook. + registerShutdownHook(scaledDecorator); + + return targetTest; + } + + /** + * If there were any user specified test decorators on the command line, this method instantiates them and wraps + * the test in them, from inner-most to outer-most in the order in which the decorators were supplied on the + * command line. + * + * @param targetTest The test to wrap. + * + * @return A wrapped test. + */ + protected WrappedSuiteTestDecorator applyOptionalUserDecorators(WrappedSuiteTestDecorator targetTest) + { + // If there are user defined test decorators apply them in order now. + for (TestDecoratorFactory factory : decoratorFactories) + { + targetTest = factory.decorateTest(targetTest); + } + + return targetTest; + } + + /** + * Creates the TestResult object to be used for test runs. See {@link TKTestResult} for more information and the + * enhanced test result class that this uses. + * + * @return An instance of the enhanced test result object, {@link TKTestResult}. + */ + protected TestResult createTestResult() + { + log.debug("protected TestResult createTestResult(): called"); + + TKTestResult result = new TKTestResult(delay, testCaseName); + + // Check if a directory to output reports to has been specified and attach test listeners if so. + if (reportDir != null) + { + // Create the report directory if it does not already exist. + File reportDirFile = new File(reportDir); + + if (!reportDirFile.exists()) + { + reportDirFile.mkdir(); + } + + // Create the results file (make the name of this configurable as a command line parameter). + Writer timingsWriter; + + // Always set up a console feedback listener. + ConsoleTestListener feedbackListener = new ConsoleTestListener(); + result.addListener(feedbackListener); + result.addTKTestListener(feedbackListener); + + // Set up an XML results listener to output the timings to the results file, if requested on the command line. + if (xmlResults) + { + try + { + File timingsFile = new File(reportDirFile, "TEST-" + currentTestClassName + ".xml"); + timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000); + } + catch (IOException e) + { + throw new RuntimeException("Unable to create the log file to write test results to: " + e, e); + } + + XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName); + result.addListener(listener); + result.addTKTestListener(listener); + + registerShutdownHook(listener); + } + + // Set up an CSV results listener to output the timings to the results file, if requested on the command line. + if (csvResults) + { + try + { + File timingsFile = + new File(reportDirFile, testRunName + "-" + TIME_STAMP_FORMAT.format(new Date()) + "-timings.csv"); + timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000); + } + catch (IOException e) + { + throw new RuntimeException("Unable to create the log file to write test results to: " + e, e); + } + + CSVTestListener listener = new CSVTestListener(timingsWriter); + result.addListener(listener); + result.addTKTestListener(listener); + + // Register the results listeners shutdown hook to flush its data if the test framework is shutdown + // prematurely. + registerShutdownHook(listener); + } + + // Register the results listeners shutdown hook to flush its data if the test framework is shutdown + // prematurely. + // registerShutdownHook(listener); + + // Record the start time of the batch. + // result.notifyStartBatch(); + + // At this point in time the test class has been instantiated, giving it an opportunity to read its parameters. + // Inform any test listers of the test properties. + result.notifyTestProperties(TestContextProperties.getAccessedProps()); + } + + return result; + } + + /** + * Registers the shutdown hook of a {@link ShutdownHookable}. + * + * @param hookable The hookable to register. + */ + protected void registerShutdownHook(ShutdownHookable hookable) + { + Runtime.getRuntime().addShutdownHook(hookable.getShutdownHook()); + } + + /** + * Initializes the test runner with the provided command line arguments and and starts the test run. + * + * @param testClassName The fully qualified name of the test class to run. + * + * @return The test results. + * + * @throws Exception Any exceptions from running the tests are allowed to fall through. + */ + protected TestResult start(String testClassName) throws Exception + { + // Record the current test class, so that the test results can be output to a file incorporating this name. + this.currentTestClassName = testClassName; + + // Delegate to the super method to run the tests. + return super.start(new String[] { testClassName }); + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java index 9b4a8707db..edd79b3697 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java @@ -1,131 +1,131 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.junit.extensions; - -import junit.framework.Test; -import junit.framework.TestResult; - -import junit.runner.Version; - -import junit.textui.ResultPrinter; -import junit.textui.TestRunner; - -import org.apache.log4j.Logger; - -import java.io.PrintStream; - -/** - * The {@link junit.textui.TestRunner} does not provide very good error handling. It does not wrap exceptions and - * does not print out stack traces, losing valuable error tracing information. This class overrides methods in it - * in order to improve their error handling. The {@link TKTestRunner} is then built on top of this. - * - *

- *
CRC Card
Responsibilities Collaborations - *
- * - * @author Rupert Smith - */ -public class TestRunnerImprovedErrorHandling extends TestRunner -{ - /** Used for logging. */ - Logger log = Logger.getLogger(TestRunnerImprovedErrorHandling.class); - - /** - * Delegates to the super constructor. - */ - public TestRunnerImprovedErrorHandling() - { - super(); - } - - /** - * Delegates to the super constructor. - * - * @param printStream The location to write test results to. - */ - public TestRunnerImprovedErrorHandling(PrintStream printStream) - { - super(printStream); - } - - /** - * Delegates to the super constructor. - * - * @param resultPrinter The location to write test results to. - */ - public TestRunnerImprovedErrorHandling(ResultPrinter resultPrinter) - { - super(resultPrinter); - } - - /** - * Starts a test run. Analyzes the command line arguments - * and runs the given test suite. - * - * @param args The command line arguments. - * - * @return The test results. - * - * @throws Exception Any exceptions falling through the tests are wrapped in Exception and rethrown. - */ - protected TestResult start(String[] args) throws Exception - { - String testCase = ""; - boolean wait = false; - - for (int i = 0; i < args.length; i++) - { - if (args[i].equals("-wait")) - { - wait = true; - } - else if (args[i].equals("-c")) - { - testCase = extractClassName(args[++i]); - } - else if (args[i].equals("-v")) - { - System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma"); - } - else - { - testCase = args[i]; - } - } - - if (testCase.equals("")) - { - throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class"); - } - - try - { - Test suite = getTest(testCase); - - return doRun(suite, wait); - } - catch (Exception e) - { - log.warn("Got exception whilst creating and running test suite.", e); - throw new Exception("Could not create and run the test suite.", 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.extensions; + +import junit.framework.Test; +import junit.framework.TestResult; + +import junit.runner.Version; + +import junit.textui.ResultPrinter; +import junit.textui.TestRunner; + +import org.apache.log4j.Logger; + +import java.io.PrintStream; + +/** + * The {@link junit.textui.TestRunner} does not provide very good error handling. It does not wrap exceptions and + * does not print out stack traces, losing valuable error tracing information. This class overrides methods in it + * in order to improve their error handling. The {@link TKTestRunner} is then built on top of this. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ * + * @author Rupert Smith + */ +public class TestRunnerImprovedErrorHandling extends TestRunner +{ + /** Used for logging. */ + Logger log = Logger.getLogger(TestRunnerImprovedErrorHandling.class); + + /** + * Delegates to the super constructor. + */ + public TestRunnerImprovedErrorHandling() + { + super(); + } + + /** + * Delegates to the super constructor. + * + * @param printStream The location to write test results to. + */ + public TestRunnerImprovedErrorHandling(PrintStream printStream) + { + super(printStream); + } + + /** + * Delegates to the super constructor. + * + * @param resultPrinter The location to write test results to. + */ + public TestRunnerImprovedErrorHandling(ResultPrinter resultPrinter) + { + super(resultPrinter); + } + + /** + * Starts a test run. Analyzes the command line arguments + * and runs the given test suite. + * + * @param args The command line arguments. + * + * @return The test results. + * + * @throws Exception Any exceptions falling through the tests are wrapped in Exception and rethrown. + */ + protected TestResult start(String[] args) throws Exception + { + String testCase = ""; + boolean wait = false; + + for (int i = 0; i < args.length; i++) + { + if (args[i].equals("-wait")) + { + wait = true; + } + else if (args[i].equals("-c")) + { + testCase = extractClassName(args[++i]); + } + else if (args[i].equals("-v")) + { + System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma"); + } + else + { + testCase = args[i]; + } + } + + if (testCase.equals("")) + { + throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class"); + } + + try + { + Test suite = getTest(testCase); + + return doRun(suite, wait); + } + catch (Exception e) + { + log.warn("Got exception whilst creating and running test suite.", e); + throw new Exception("Could not create and run the test suite.", e); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java index aaa773260d..d7de2822a2 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java @@ -1,49 +1,49 @@ -/* - * - * 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; - -/** - * This interface can be implemented by tests that want to know if they are being run concurrently. It provides - * lifecycle notification events to tell the test implementation when test threads are being created and destroyed. - * This can assist tests in creating and destroying resources that exist over the life of a test thread. A single - * test thread can excute the same test many times, and often it is convenient to keep resources, for example network - * connections, open over many test calls. - * - *

- *
CRC Card
Responsibilities - *
Set up per thread test fixtures. - *
Clean up per thread test fixtures. - *
- * - * @author Rupert Smith - */ -public interface TestThreadAware -{ - /** - * Called when a test thread is created. - */ - public void threadSetUp(); - - /** - * Called when a test thread is destroyed. - */ - public void threadTearDown(); -} +/* + * + * 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; + +/** + * This interface can be implemented by tests that want to know if they are being run concurrently. It provides + * lifecycle notification events to tell the test implementation when test threads are being created and destroyed. + * This can assist tests in creating and destroying resources that exist over the life of a test thread. A single + * test thread can excute the same test many times, and often it is convenient to keep resources, for example network + * connections, open over many test calls. + * + *

+ *
CRC Card
Responsibilities + *
Set up per thread test fixtures. + *
Clean up per thread test fixtures. + *
+ * + * @author Rupert Smith + */ +public interface TestThreadAware +{ + /** + * Called when a test thread is created. + */ + public void threadSetUp(); + + /** + * Called when a test thread is destroyed. + */ + public void threadTearDown(); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java index 955e47c25b..1ea8e8e2be 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java @@ -1,73 +1,73 @@ -/* - * - * 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; - -/** - * Throttle is an interface that supplies a {@link #throttle} method, that can only be called at the rate specified - * in a call to the {@link #setRate} method. This can be used to restict processing to run at a certain number - * of operations per second. - * - *

Throttle also supplies a method to check the throttle rate, without waiting. This could be used to update a user - * interface every time an event occurs, but only up to a maximum rate. For example, as elements are added to a list, - * a count of elements is updated for the user to see, but only up to a maximum rate of ten updates a second, as updating - * faster than that slows the processing of element-by-element additions to the list unnecessarily. - * - *

- *
CRC Card
Responsibilities - *
Accept throttling rate in operations per second. - *
Inject short pauses to fill-out processing cycles to a specified rate. - *
Check against a throttle speed without waiting. - *
- * - * @author Rupert Smith - */ -public interface Throttle -{ - /** - * 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); - - /** - * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this - * it will inject short pauses to restrict the call rate to that rate. - * - *

If the thread executing this method is interrupted, it must ensure that the threads interrupt thread - * remains set upon exit from the method. This method does not expose InterruptedException, to indicate interruption - * of the throttle during a timed wait. It may be changed so that it does. - */ - public void throttle(); - - /** - * 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 true - * - * @return true 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 true, false otherwise. The very - * first time this method is called on a throttle, it returns true as the base case to the above - * self-referential definition. - */ - public boolean checkThrottle(); -} +/* + * + * 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; + +/** + * Throttle is an interface that supplies a {@link #throttle} method, that can only be called at the rate specified + * in a call to the {@link #setRate} method. This can be used to restict processing to run at a certain number + * of operations per second. + * + *

Throttle also supplies a method to check the throttle rate, without waiting. This could be used to update a user + * interface every time an event occurs, but only up to a maximum rate. For example, as elements are added to a list, + * a count of elements is updated for the user to see, but only up to a maximum rate of ten updates a second, as updating + * faster than that slows the processing of element-by-element additions to the list unnecessarily. + * + *

+ *
CRC Card
Responsibilities + *
Accept throttling rate in operations per second. + *
Inject short pauses to fill-out processing cycles to a specified rate. + *
Check against a throttle speed without waiting. + *
+ * + * @author Rupert Smith + */ +public interface Throttle +{ + /** + * 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); + + /** + * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this + * it will inject short pauses to restrict the call rate to that rate. + * + *

If the thread executing this method is interrupted, it must ensure that the threads interrupt thread + * remains set upon exit from the method. This method does not expose InterruptedException, to indicate interruption + * of the throttle during a timed wait. It may be changed so that it does. + */ + public void throttle(); + + /** + * 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 true + * + * @return true 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 true, false otherwise. The very + * first time this method is called on a throttle, it returns true as the base case to the above + * self-referential definition. + */ + public boolean checkThrottle(); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java index 7b5763f1de..b69df84045 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java @@ -1,175 +1,175 @@ -/* - * - * 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; - -/** - * A TimingController is a interface that a test that is aware of the fact that it is being timed can use to manage - * the timer. Using this interface tests can suspend and resume test timers. This is usefull if you want to exclude - * some expensive preparation from being timed as part of a test. In general when timing tests to measure the - * performance of code, you should try to set up data in the #setUp where possible, or as static members in the test - * class. This is not always convenient, this interface gives you a way to suspend and resume, or event completely - * restart test timers, to get accurate measurements. - * - *

The interface can also be used to register multiple test pass/fails and timings from a single test method. - * In some cases it is easier to write tests in this way. For example a concurrent and asynchronous test may make - * many asynchronous requests and then wait for replies to all its requests. Writing such a test with one send/reply - * per test method and trying to scale up using many threads will quickly run into limitations if more than about - * 100 asynchronous calls need to be made at once. A better way to write such a test is as a single method that sends - * many (perhaps thousands or millions) and waits for replies in two threads, one for send, one for replies. It can - * then log pass/fails and timings on each individual reply as they come back in, even though the test has been written - * to send thousands of requests per test method in order to do volume testing. - * - *

If when the {@link #completeTest(boolean)} is called, the test runner decides that testing should stop (perhaps - * because a duration test has expired), it throws an InterruptedException to indicate that the test method should stop - * immediately. The test method can do this by allowing this exception to fall through, if no other clean-up handling - * is necessary, or it can simply return as soon as it possibly can. The test runner will still call the tearDown - * method in the usual way when this happens. - * - *

Below are some examples of how this can be used. Not how checking that the timing controller is really available - * rather than assuming it is, means that the test can run as an ordinary JUnit test under the default test runners. In - * general code should be written to take advantage of the extended capabilities of junit toolkit, without assuming they - * are going to be run under its test runner. - * - *

- * public class MyTest extends TestCase implements TimingControllerAware {
- * ...
- *
- *    timingUtils = this.getTimingController();
- *
- *    // Do expensive data preparation here...
- *
- *    if (timingUtils != null)
- *        timingUtils.restart();
- * 
- * - *
- * public class MyTest extends TestCase implements TimingControllerAware {
- * ...
- *
- *   public void myVolumeTest(int size) {
- *
- *    timingUtils = this.getTimingController();
- *
- *    boolean stopNow = false;
- *
- *    // In Sender thread.
- *      for(int i = 0; !stopNow && i < size; i++)
- *        // Send request i.
- *        ...
- *
- *    // In Receiver thread.
- *    onReceive(Object o) {
- *      try {
- *      // Check o is as expected.
- *      if (....)
- *      {
- *        if (timingUtils != null)
- *          timingUtils.completeTest(true);
- *      }
- *      else
- *      {
- *        if (timingUtils != null)
- *          timingUtils.completeTest(false);
- *      }
- *      } catch (InterruptedException e) {
- *        stopNow = true;
- *        return;
- *      }
- *    }
- * 
- * - *

- *
CRC Card
Responsibilities - *
Allow test timers to be suspended, restarted or reset. - *
Allow tests to register multiple pass/fails and timings. - *
- * - * @author Rupert Smith - */ -public interface TimingController -{ - /** - * Gets the timing controller associated with the current test thread. Tests that use timing controller should - * always get the timing controller from this method in the same thread that called the setUp, tearDown or test - * method. The controller returned by this method may be called from any thread because it remembers the thread - * id of the original test thread. - * - * @return The timing controller associated with the current test thread. - */ - public TimingController getControllerForCurrentThread(); - - /** - * Suspends the test timer. - * - * @return The current time in nanoseconds. - */ - public long suspend(); - - /** - * Allows the test timer to continue running after a suspend. - * - * @return The current time in nanoseconds. - */ - public long resume(); - - /** - * Completely restarts the test timer from zero. - * - * @return The current time in nanoseconds. - */ - public long restart(); - - /** - * Register an additional pass/fail for the current test. The test result is assumed to apply to a test of - * 'size' parmeter 1. Use the {@link #completeTest(boolean, int)} method to register timings with parameters. - * - * @param testPassed Whether or not this timing is for a test pass or fail. - * - * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to - * indicate to the test method that it should stop immediately. - */ - public void completeTest(boolean testPassed) throws InterruptedException; - - /** - * Register an additional pass/fail for the current test. The test result is applies to a test of the specified - * 'size' parmeter. - * - * @param testPassed Whether or not this timing is for a test pass or fail. - * @param param The test parameter size for parameterized tests. - * - * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to - * indicate to the test method that it should stop immediately. - */ - public void completeTest(boolean testPassed, int param) throws InterruptedException; - - /** - * Register an additional pass/fail for the current test. The test result is applies to a test of the specified - * 'size' parmeter and allows the caller to sepecify the timing to log. - * - * @param testPassed Whether or not this timing is for a test pass or fail. - * @param param The test parameter size for parameterized tests. - * @param timeNanos The time in nano seconds to log the test result with. - * - * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to - * indicate to the test method that it should stop immediately. - */ - public void completeTest(boolean testPassed, int param, long timeNanos) throws InterruptedException; -} +/* + * + * 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; + +/** + * A TimingController is a interface that a test that is aware of the fact that it is being timed can use to manage + * the timer. Using this interface tests can suspend and resume test timers. This is usefull if you want to exclude + * some expensive preparation from being timed as part of a test. In general when timing tests to measure the + * performance of code, you should try to set up data in the #setUp where possible, or as static members in the test + * class. This is not always convenient, this interface gives you a way to suspend and resume, or event completely + * restart test timers, to get accurate measurements. + * + *

The interface can also be used to register multiple test pass/fails and timings from a single test method. + * In some cases it is easier to write tests in this way. For example a concurrent and asynchronous test may make + * many asynchronous requests and then wait for replies to all its requests. Writing such a test with one send/reply + * per test method and trying to scale up using many threads will quickly run into limitations if more than about + * 100 asynchronous calls need to be made at once. A better way to write such a test is as a single method that sends + * many (perhaps thousands or millions) and waits for replies in two threads, one for send, one for replies. It can + * then log pass/fails and timings on each individual reply as they come back in, even though the test has been written + * to send thousands of requests per test method in order to do volume testing. + * + *

If when the {@link #completeTest(boolean)} is called, the test runner decides that testing should stop (perhaps + * because a duration test has expired), it throws an InterruptedException to indicate that the test method should stop + * immediately. The test method can do this by allowing this exception to fall through, if no other clean-up handling + * is necessary, or it can simply return as soon as it possibly can. The test runner will still call the tearDown + * method in the usual way when this happens. + * + *

Below are some examples of how this can be used. Not how checking that the timing controller is really available + * rather than assuming it is, means that the test can run as an ordinary JUnit test under the default test runners. In + * general code should be written to take advantage of the extended capabilities of junit toolkit, without assuming they + * are going to be run under its test runner. + * + *

+ * public class MyTest extends TestCase implements TimingControllerAware {
+ * ...
+ *
+ *    timingUtils = this.getTimingController();
+ *
+ *    // Do expensive data preparation here...
+ *
+ *    if (timingUtils != null)
+ *        timingUtils.restart();
+ * 
+ * + *
+ * public class MyTest extends TestCase implements TimingControllerAware {
+ * ...
+ *
+ *   public void myVolumeTest(int size) {
+ *
+ *    timingUtils = this.getTimingController();
+ *
+ *    boolean stopNow = false;
+ *
+ *    // In Sender thread.
+ *      for(int i = 0; !stopNow && i < size; i++)
+ *        // Send request i.
+ *        ...
+ *
+ *    // In Receiver thread.
+ *    onReceive(Object o) {
+ *      try {
+ *      // Check o is as expected.
+ *      if (....)
+ *      {
+ *        if (timingUtils != null)
+ *          timingUtils.completeTest(true);
+ *      }
+ *      else
+ *      {
+ *        if (timingUtils != null)
+ *          timingUtils.completeTest(false);
+ *      }
+ *      } catch (InterruptedException e) {
+ *        stopNow = true;
+ *        return;
+ *      }
+ *    }
+ * 
+ * + *

+ *
CRC Card
Responsibilities + *
Allow test timers to be suspended, restarted or reset. + *
Allow tests to register multiple pass/fails and timings. + *
+ * + * @author Rupert Smith + */ +public interface TimingController +{ + /** + * Gets the timing controller associated with the current test thread. Tests that use timing controller should + * always get the timing controller from this method in the same thread that called the setUp, tearDown or test + * method. The controller returned by this method may be called from any thread because it remembers the thread + * id of the original test thread. + * + * @return The timing controller associated with the current test thread. + */ + public TimingController getControllerForCurrentThread(); + + /** + * Suspends the test timer. + * + * @return The current time in nanoseconds. + */ + public long suspend(); + + /** + * Allows the test timer to continue running after a suspend. + * + * @return The current time in nanoseconds. + */ + public long resume(); + + /** + * Completely restarts the test timer from zero. + * + * @return The current time in nanoseconds. + */ + public long restart(); + + /** + * Register an additional pass/fail for the current test. The test result is assumed to apply to a test of + * 'size' parmeter 1. Use the {@link #completeTest(boolean, int)} method to register timings with parameters. + * + * @param testPassed Whether or not this timing is for a test pass or fail. + * + * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to + * indicate to the test method that it should stop immediately. + */ + public void completeTest(boolean testPassed) throws InterruptedException; + + /** + * Register an additional pass/fail for the current test. The test result is applies to a test of the specified + * 'size' parmeter. + * + * @param testPassed Whether or not this timing is for a test pass or fail. + * @param param The test parameter size for parameterized tests. + * + * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to + * indicate to the test method that it should stop immediately. + */ + public void completeTest(boolean testPassed, int param) throws InterruptedException; + + /** + * Register an additional pass/fail for the current test. The test result is applies to a test of the specified + * 'size' parmeter and allows the caller to sepecify the timing to log. + * + * @param testPassed Whether or not this timing is for a test pass or fail. + * @param param The test parameter size for parameterized tests. + * @param timeNanos The time in nano seconds to log the test result with. + * + * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to + * indicate to the test method that it should stop immediately. + */ + public void completeTest(boolean testPassed, int param, long timeNanos) throws InterruptedException; +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java index 1ccdc7dbad..11db87e073 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java @@ -1,43 +1,43 @@ -/* - * - * 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; - -/** - * TimingControllerAware is an interface that tests that manipulate the timing controller should implement. It enables - * the TK test runner to set the test up with a handle on the timing controller which the test can use to call back - * to the test runner to manage the timers. - * - *

- *
CRC Card
Responsibilities - *
Provide timing controller insertion point for tests. - *
- * - * @author Rupert Smith - */ -public interface TimingControllerAware -{ - /** - * Used by test runners that can supply a {@link TimingController} to set the controller on an aware test. - * - * @param controller The timing controller. - */ - public void setTimingController(TimingController controller); -} +/* + * + * 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; + +/** + * TimingControllerAware is an interface that tests that manipulate the timing controller should implement. It enables + * the TK test runner to set the test up with a handle on the timing controller which the test can use to call back + * to the test runner to manage the timers. + * + *

+ *
CRC Card
Responsibilities + *
Provide timing controller insertion point for tests. + *
+ * + * @author Rupert Smith + */ +public interface TimingControllerAware +{ + /** + * Used by test runners that can supply a {@link TimingController} to set the controller on an aware test. + * + * @param controller The timing controller. + */ + public void setTimingController(TimingController controller); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java index 7a1e537b1c..d5690fc24a 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java @@ -1,134 +1,134 @@ -/* - * - * 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.extensions.TestDecorator; - -import junit.framework.Test; -import junit.framework.TestSuite; - -import org.apache.log4j.Logger; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * WrappedSuiteTestDecorator is a test decorator that wraps a test suite, or another wrapped suite, but provides the - * same functionality for the {@link junit.extensions.TestDecorator#countTestCases()} and {@link TestSuite#testAt(int)} - * methods as the underlying suite. It returns the values that these methods provide, to enable classes using decorated - * tests to drill down to the underlying tests in the suite. That is to say that it indexes and reports the number of - * distinct tests in the suite, not the number of test runs that would result from, for example, wrapping the suite in a - * repeating decorator. - * - *

- *
CRC Card
Responsibilities - *
Provide access to the underlying tests in a suite. - *
- * - * @author Rupert Smith - */ -public class WrappedSuiteTestDecorator extends TestDecorator -{ - /** Used for logging. */ - private static Logger log = Logger.getLogger(WrappedSuiteTestDecorator.class); - - /** Holds the test suite that this supplies access to. */ - protected Test suite; - - /** - * Creates a wrappred suite test decorator from a test suite. - * - * @param suite The test suite. - */ - public WrappedSuiteTestDecorator(TestSuite suite) - { - super(suite); - this.suite = suite; - } - - /** - * Creates a wrapped suite test decorator from another one. - * - * @param suite The test suite. - */ - public WrappedSuiteTestDecorator(WrappedSuiteTestDecorator suite) - { - super(suite); - this.suite = suite; - } - - /** - * Returns the test count of the wrapped suite. - * - * @return The test count of the wrapped suite. - */ - public int countTestCases() - { - return suite.countTestCases(); - } - - /** - * Gets the ith test from the test suite. - * - * @param i The index of the test within the suite to get. - * - * @return The test with the specified index. - */ - public Test testAt(int i) - { - log.debug("public Test testAt(int i = " + i + "): called"); - - if (suite instanceof WrappedSuiteTestDecorator) - { - return ((WrappedSuiteTestDecorator) suite).testAt(i); - } - else if (suite instanceof TestSuite) - { - return ((TestSuite) suite).testAt(i); - } - - // This should never happen. - return null; - } - - /** - * Gets all the tests from the underlying test suite. - * - * @return All the tests from the underlying test suite. - */ - public Collection getAllUnderlyingTests() - { - log.debug("public Collection getAllUnderlyingTests(): called"); - - List tests = new ArrayList(); - - int numTests = countTestCases(); - log.debug("numTests = " + numTests); - - for (int i = 0; i < numTests; i++) - { - tests.add(testAt(i)); - } - - return tests; - } -} +/* + * + * 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.extensions.TestDecorator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.log4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * WrappedSuiteTestDecorator is a test decorator that wraps a test suite, or another wrapped suite, but provides the + * same functionality for the {@link junit.extensions.TestDecorator#countTestCases()} and {@link TestSuite#testAt(int)} + * methods as the underlying suite. It returns the values that these methods provide, to enable classes using decorated + * tests to drill down to the underlying tests in the suite. That is to say that it indexes and reports the number of + * distinct tests in the suite, not the number of test runs that would result from, for example, wrapping the suite in a + * repeating decorator. + * + *

+ *
CRC Card
Responsibilities + *
Provide access to the underlying tests in a suite. + *
+ * + * @author Rupert Smith + */ +public class WrappedSuiteTestDecorator extends TestDecorator +{ + /** Used for logging. */ + private static Logger log = Logger.getLogger(WrappedSuiteTestDecorator.class); + + /** Holds the test suite that this supplies access to. */ + protected Test suite; + + /** + * Creates a wrappred suite test decorator from a test suite. + * + * @param suite The test suite. + */ + public WrappedSuiteTestDecorator(TestSuite suite) + { + super(suite); + this.suite = suite; + } + + /** + * Creates a wrapped suite test decorator from another one. + * + * @param suite The test suite. + */ + public WrappedSuiteTestDecorator(WrappedSuiteTestDecorator suite) + { + super(suite); + this.suite = suite; + } + + /** + * Returns the test count of the wrapped suite. + * + * @return The test count of the wrapped suite. + */ + public int countTestCases() + { + return suite.countTestCases(); + } + + /** + * Gets the ith test from the test suite. + * + * @param i The index of the test within the suite to get. + * + * @return The test with the specified index. + */ + public Test testAt(int i) + { + log.debug("public Test testAt(int i = " + i + "): called"); + + if (suite instanceof WrappedSuiteTestDecorator) + { + return ((WrappedSuiteTestDecorator) suite).testAt(i); + } + else if (suite instanceof TestSuite) + { + return ((TestSuite) suite).testAt(i); + } + + // This should never happen. + return null; + } + + /** + * Gets all the tests from the underlying test suite. + * + * @return All the tests from the underlying test suite. + */ + public Collection getAllUnderlyingTests() + { + log.debug("public Collection getAllUnderlyingTests(): called"); + + List tests = new ArrayList(); + + int numTests = countTestCases(); + log.debug("numTests = " + numTests); + + for (int i = 0; i < numTests; i++) + { + tests.add(testAt(i)); + } + + return tests; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java index a771e08cf7..f93212e0c5 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java @@ -1,532 +1,532 @@ -/* - * - * 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.listeners; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestListener; - -import org.apache.log4j.Logger; - -import org.apache.qpid.junit.extensions.ShutdownHookable; -import org.apache.qpid.junit.extensions.util.TestContextProperties; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.TreeSet; - -/** - * CSVTestListener is both a test listener, a timings listener, a memory listener and a parameter listener. It listens for test completion events and - * then writes out all the data that it has listened to into a '.csv' (comma seperated values) file. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Listen to test events; start, end, fail, error. - *
Listen to test timings. - *
Listen to test memory usage. - *
Listen to parameterized test parameters. - *
Output all test data to a CSV file. - *
- * - * @author Rupert Smith - * - * @todo Write an XML output class. Write a transform to convert it into an HTML page with timings as graphs. - */ -public class CSVTestListener implements TestListener, TKTestListener, ShutdownHookable -{ - /** Used for logging. */ - private static final Logger log = Logger.getLogger(CSVTestListener.class); - - /** The timings file writer. */ - private Writer timingsWriter; - - /** - * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an - * explicit thread id must be used, where notifications come from different threads than the ones that called - * the test method. - */ - Map threadLocalResults = Collections.synchronizedMap(new HashMap()); - - /** Used to record the start time of a complete test run, for outputing statistics at the end of the test run. */ - private long batchStartTime; - - /** Used to record the number of errors accross a complete test run. */ - private int numError; - - /** Used to record the number of failures accross a complete test run. */ - private int numFailed; - - /** Used to record the number of passes accross a complete test run. */ - private int numPassed; - - /** Used to record the total tests run accross a complete test run. Always equal to passes + errors + fails. */ - private int totalTests; - - /** Used to recrod the current concurrency level for the test batch. */ - private int concurrencyLevel; - - /** - * Used to record the total 'size' of the tests run, this is the number run times the average value of the test - * size parameters. - */ - private int totalSize; - - /** - * Used to record the summation of all of the individual test timgings. Note that total time and summed time - * are unlikely to be in agreement, exception for a single threaded test (with no setup time). Total time is - * the time taken to run all the tests, summed time is the added up time that each individual test took. So if - * two tests run in parallel and take one second each, total time will be one seconds, summed time will be two - * seconds. - */ - private long summedTime; - - /** Flag to indicate when batch has been started but not ended to ensure end batch stats are output only once. */ - private boolean batchStarted = false; - - /** - * Creates a new CSVTestListener object. - * - * @param writer A writer where this CSV listener should write out its output to. - */ - public CSVTestListener(Writer writer) - { - // log.debug("public CSVTestListener(Writer writer): called"); - - // Keep the writer. - this.timingsWriter = writer; - } - - /** - * Resets the test results to the default state of time zero, memory usage zero, test passed. - * - * @param test The test to resest any results for. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void reset(Test test, Long threadId) - { - // log.debug("public void reset(Test test = \"" + test + "\", Long threadId = " + threadId + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testTime = 0L; - r.testStartMem = 0L; - r.testEndMem = 0L; - r.testState = "Pass"; - r.testParam = 0; - } - - /** - * Called when a test results in an error. - * - * @param test The test which is in error. - * @param t Any Throwable raised by the test in error. - */ - public void addError(Test test, Throwable t) - { - // log.debug("public void addError(Test test, Throwable t): called"); - - TestResult r = threadLocalResults.get(Thread.currentThread().getId()); - r.testState = "Error"; - } - - /** - * Called when a test results in a failure. - * - * @param test The test which failed. - * @param t The AssertionFailedError that encapsulates the test failure. - */ - public void addFailure(Test test, AssertionFailedError t) - { - // log.debug("public void addFailure(Test \"" + test + "\", AssertionFailedError t): called"); - - TestResult r = threadLocalResults.get(Thread.currentThread().getId()); - r.testState = "Failure"; - } - - /** - * Called when a test completes to mark it as a test fail. This method should be used when registering a - * failure from a different thread than the one that started the test. - * - * @param test The test which failed. - * @param e The assertion that failed the test. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void addFailure(Test test, AssertionFailedError e, Long threadId) - { - // log.debug("public void addFailure(Test test = \"" + test + "\", AssertionFailedError e, Long threadId = " + threadId - // + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testState = "Failure"; - } - - /** - * Called when a test completes. Success, failure and errors. - * - * @param test The test which completed. - */ - public void endTest(Test test) - { - // log.debug("public void endTest(Test \"" + test + "\"): called"); - - TestResult r = threadLocalResults.get(Thread.currentThread().getId()); - - writeTestResults(r, test); - - // Clear all the test results for the thread. - threadLocalResults.remove(Thread.currentThread().getId()); - } - - /** - * Called when a test starts. - * - * @param test The test wich has started. - */ - public void startTest(Test test) - { - // log.debug("public void startTest(Test \"" + test + "\"): called"); - - // Initialize the thread local test results. - threadLocalResults.put(Thread.currentThread().getId(), new TestResult()); - } - - /** - * Should be called every time a test completes with the run time of that test. - * - * @param test The name of the test. - * @param nanos The run time of the test in nanoseconds. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void timing(Test test, long nanos, Long threadId) - { - // log.debug("public void timing(String \"" + test + "\", long " + nanos + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testTime = nanos; - summedTime += nanos; - } - - /** - * Should be called every time a test completed with the amount of memory used before and after the test was run. - * - * @param test The test which memory was measured for. - * @param memStart The total JVM memory used before the test was run. - * @param memEnd The total JVM memory used after the test was run. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) - { - // log.debug("public void memoryUsed(Test \"" + test + "\", long " + memStart + ", long " + memEnd + ", Long " - // + threadId + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testStartMem = memStart; - r.testEndMem = memEnd; - } - - /** - * Should be called every time a parameterized test completed with the int value of its test parameter. - * - * @param test The test which memory was measured for. - * @param parameter The int parameter value. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void parameterValue(Test test, int parameter, Long threadId) - { - // log.debug("public void parameterValue(Test test = \"" + test + "\", int parameter = " + parameter + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testParam = parameter; - totalSize += parameter; - } - - /** - * Should be called every time a test completes with the current number of test threads running. This should not - * change within a test batch, therefore it is safe to take this as a batch level property value too. - * - * @param test The test for which the measurement is being generated. - * @param threads The number of tests being run concurrently. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void concurrencyLevel(Test test, int threads, Long threadId) - { - // log.debug("public void concurrencyLevel(Test test = \"" + test + "\", int threads = " + threads + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.testConcurrency = threads; - concurrencyLevel = threads; - - } - - /** - * Called when a test completes. Success, failure and errors. This method should be used when registering an - * end test from a different thread than the one that started the test. - * - * @param test The test which completed. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void endTest(Test test, Long threadId) - { - // log.debug("public void endTest(Test test = \"" + test + "\", Long threadId " + threadId + "): called"); - - TestResult r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - writeTestResults(r, test); - } - - /** - * Takes a time stamp for the beginning of the batch and resets stats counted for the batch. - */ - public synchronized void startBatch() - { - numError = 0; - numFailed = 0; - numPassed = 0; - totalTests = 0; - totalSize = 0; - batchStartTime = System.nanoTime(); - summedTime = 0; - batchStarted = true; - - // Write out the column headers for the batch. - writeColumnHeaders(); - } - - /** - * Takes a time stamp for the end of the batch to calculate the total run time. - * Write this and other stats out to the tail of the csv file. - * - * @param parameters The optional test parameters, may be null. - */ - public synchronized void endBatch(Properties parameters) - { - boolean noParams = (parameters == null) || (parameters.size() == 0); - - // Check that a batch has been started but not ended. - if (batchStarted) - { - long batchEndTime = System.nanoTime(); - float totalTimeMillis = ((float) (batchEndTime - batchStartTime)) / 1000000f; - float summedTimeMillis = ((float) summedTime) / 1000000f; - - // Write the stats for the batch out. - try - { - synchronized (this.getClass()) - { - timingsWriter.write("Total Tests:, " + totalTests + ", "); - timingsWriter.write("Total Passed:, " + numPassed + ", "); - timingsWriter.write("Total Failed:, " + numFailed + ", "); - timingsWriter.write("Total Error:, " + numError + ", "); - timingsWriter.write("Total Size:, " + totalSize + ", "); - timingsWriter.write("Summed Time:, " + summedTimeMillis + ", "); - timingsWriter.write("Concurrency Level:, " + concurrencyLevel + ", "); - timingsWriter.write("Total Time:, " + totalTimeMillis + ", "); - timingsWriter.write("Test Throughput:, " + (((float) totalTests) / totalTimeMillis) + ", "); - timingsWriter.write("Test * Size Throughput:, " + (((float) totalSize) / totalTimeMillis) - + (noParams ? "\n\n" : ", ")); - - // Write out the test parameters if there are any specified. - if (!noParams) - { - properties(parameters); - } - - timingsWriter.flush(); - } - } - catch (IOException e) - { - throw new RuntimeException("Unable to write out end batch statistics: " + e, e); - } - } - - // Reset the batch started flag to ensure stats are only output once. - batchStarted = false; - } - - /** - * Notifies listeners of the tests read/set properties. - * - * @param properties The tests read/set properties. - */ - public void properties(Properties properties) - { - // log.debug("public void properties(Properties properties): called"); - - // Write the properties out to the results file. - try - { - synchronized (this.getClass()) - { - Set keySet = new TreeSet(properties.keySet()); - - // timingsWriter.write("\n"); - - for (Object key : keySet) - { - timingsWriter.write(key + " = , " + properties.getProperty((String) key) + ", "); - } - - timingsWriter.write("\n\n"); - // timingsWriter.flush(); - } - } - catch (IOException e) - { - throw new RuntimeException("Unable to write out test parameters: " + e, e); - } - - // Write out the column headers after the properties. - // writeColumnHeaders(); - } - - /** - * Writes out and flushes the column headers for raw test data. - */ - private void writeColumnHeaders() - { - // Write the column headers for the CSV file. Any IO exceptions are ignored. - try - { - timingsWriter.write("Class, "); - timingsWriter.write("Method, "); - timingsWriter.write("Thread, "); - timingsWriter.write("Test Outcome, "); - timingsWriter.write("Time (milliseconds), "); - timingsWriter.write("Memory Used (bytes), "); - timingsWriter.write("Concurrency level, "); - timingsWriter.write("Test Size\n"); - - timingsWriter.flush(); - } - catch (IOException e) - { - throw new RuntimeException("Unable to write out column headers: " + e, e); - } - } - - /** - * Writes out the test results for the specified test. This outputs a single line of results to the csv file. - * - * @param r The test results to write out. - * @param test The test to write them out for. - */ - private void writeTestResults(TestResult r, Test test) - { - // Update the running stats for this batch. - if ("Error".equals(r.testState)) - { - numError++; - } - else if ("Failure".equals(r.testState)) - { - numFailed++; - } - else if ("Pass".equals(r.testState)) - { - numPassed++; - } - - totalTests++; - - // Write the test name and thread information plus all instrumenation a line of the CSV ouput. Any IO - // exceptions are ignored. - try - { - synchronized (this.getClass()) - { - timingsWriter.write(test.getClass().getName() + ", "); - timingsWriter.write(((test instanceof TestCase) ? ((TestCase) test).getName() : "") + ", "); - timingsWriter.write(Thread.currentThread().getName() + ", "); - timingsWriter.write(r.testState + ", "); - timingsWriter.write((((float) r.testTime) / 1000000f) + ", "); - timingsWriter.write((r.testEndMem - r.testStartMem) + ", "); - timingsWriter.write(r.testConcurrency + ", "); - timingsWriter.write(r.testParam + "\n"); - } - } - catch (IOException e) - { - throw new RuntimeException("Unable to write out test results: " + e, e); - } - } - - /** - * Supplies the shutdown hook. This attempts to flush the results in the event of the test runner being prematurely - * suspended before the end of the current test batch. - * - * @return The shut down hook. - */ - public Thread getShutdownHook() - { - return new Thread(new Runnable() - { - public void run() - { - log.debug("CSVTestListener::ShutdownHook: called"); - - // Complete the current test batch stats. - endBatch(TestContextProperties.getInstance()); - } - }); - } - - /** Captures test results packaged into a single object, so that it can be set up as a thread local. */ - private static class TestResult - { - /** Used to hold the test timing. */ - public long testTime; - - /** Used to hold the test start memory usage. */ - public long testStartMem; - - /** Used to hold the test end memory usage. */ - public long testEndMem; - - /** Used to hold the test pass/fail/error state. */ - public String testState = "Pass"; - - /** Used to hold the test parameter value. */ - public int testParam; - - /** Used to hold the concurrency level under which the test was run. */ - public int testConcurrency; - } -} +/* + * + * 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.listeners; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestListener; + +import org.apache.log4j.Logger; + +import org.apache.qpid.junit.extensions.ShutdownHookable; +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +import java.io.IOException; +import java.io.Writer; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; + +/** + * CSVTestListener is both a test listener, a timings listener, a memory listener and a parameter listener. It listens for test completion events and + * then writes out all the data that it has listened to into a '.csv' (comma seperated values) file. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Listen to test events; start, end, fail, error. + *
Listen to test timings. + *
Listen to test memory usage. + *
Listen to parameterized test parameters. + *
Output all test data to a CSV file. + *
+ * + * @author Rupert Smith + * + * @todo Write an XML output class. Write a transform to convert it into an HTML page with timings as graphs. + */ +public class CSVTestListener implements TestListener, TKTestListener, ShutdownHookable +{ + /** Used for logging. */ + private static final Logger log = Logger.getLogger(CSVTestListener.class); + + /** The timings file writer. */ + private Writer timingsWriter; + + /** + * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an + * explicit thread id must be used, where notifications come from different threads than the ones that called + * the test method. + */ + Map threadLocalResults = Collections.synchronizedMap(new HashMap()); + + /** Used to record the start time of a complete test run, for outputing statistics at the end of the test run. */ + private long batchStartTime; + + /** Used to record the number of errors accross a complete test run. */ + private int numError; + + /** Used to record the number of failures accross a complete test run. */ + private int numFailed; + + /** Used to record the number of passes accross a complete test run. */ + private int numPassed; + + /** Used to record the total tests run accross a complete test run. Always equal to passes + errors + fails. */ + private int totalTests; + + /** Used to recrod the current concurrency level for the test batch. */ + private int concurrencyLevel; + + /** + * Used to record the total 'size' of the tests run, this is the number run times the average value of the test + * size parameters. + */ + private int totalSize; + + /** + * Used to record the summation of all of the individual test timgings. Note that total time and summed time + * are unlikely to be in agreement, exception for a single threaded test (with no setup time). Total time is + * the time taken to run all the tests, summed time is the added up time that each individual test took. So if + * two tests run in parallel and take one second each, total time will be one seconds, summed time will be two + * seconds. + */ + private long summedTime; + + /** Flag to indicate when batch has been started but not ended to ensure end batch stats are output only once. */ + private boolean batchStarted = false; + + /** + * Creates a new CSVTestListener object. + * + * @param writer A writer where this CSV listener should write out its output to. + */ + public CSVTestListener(Writer writer) + { + // log.debug("public CSVTestListener(Writer writer): called"); + + // Keep the writer. + this.timingsWriter = writer; + } + + /** + * Resets the test results to the default state of time zero, memory usage zero, test passed. + * + * @param test The test to resest any results for. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void reset(Test test, Long threadId) + { + // log.debug("public void reset(Test test = \"" + test + "\", Long threadId = " + threadId + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testTime = 0L; + r.testStartMem = 0L; + r.testEndMem = 0L; + r.testState = "Pass"; + r.testParam = 0; + } + + /** + * Called when a test results in an error. + * + * @param test The test which is in error. + * @param t Any Throwable raised by the test in error. + */ + public void addError(Test test, Throwable t) + { + // log.debug("public void addError(Test test, Throwable t): called"); + + TestResult r = threadLocalResults.get(Thread.currentThread().getId()); + r.testState = "Error"; + } + + /** + * Called when a test results in a failure. + * + * @param test The test which failed. + * @param t The AssertionFailedError that encapsulates the test failure. + */ + public void addFailure(Test test, AssertionFailedError t) + { + // log.debug("public void addFailure(Test \"" + test + "\", AssertionFailedError t): called"); + + TestResult r = threadLocalResults.get(Thread.currentThread().getId()); + r.testState = "Failure"; + } + + /** + * Called when a test completes to mark it as a test fail. This method should be used when registering a + * failure from a different thread than the one that started the test. + * + * @param test The test which failed. + * @param e The assertion that failed the test. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void addFailure(Test test, AssertionFailedError e, Long threadId) + { + // log.debug("public void addFailure(Test test = \"" + test + "\", AssertionFailedError e, Long threadId = " + threadId + // + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testState = "Failure"; + } + + /** + * Called when a test completes. Success, failure and errors. + * + * @param test The test which completed. + */ + public void endTest(Test test) + { + // log.debug("public void endTest(Test \"" + test + "\"): called"); + + TestResult r = threadLocalResults.get(Thread.currentThread().getId()); + + writeTestResults(r, test); + + // Clear all the test results for the thread. + threadLocalResults.remove(Thread.currentThread().getId()); + } + + /** + * Called when a test starts. + * + * @param test The test wich has started. + */ + public void startTest(Test test) + { + // log.debug("public void startTest(Test \"" + test + "\"): called"); + + // Initialize the thread local test results. + threadLocalResults.put(Thread.currentThread().getId(), new TestResult()); + } + + /** + * Should be called every time a test completes with the run time of that test. + * + * @param test The name of the test. + * @param nanos The run time of the test in nanoseconds. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void timing(Test test, long nanos, Long threadId) + { + // log.debug("public void timing(String \"" + test + "\", long " + nanos + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testTime = nanos; + summedTime += nanos; + } + + /** + * Should be called every time a test completed with the amount of memory used before and after the test was run. + * + * @param test The test which memory was measured for. + * @param memStart The total JVM memory used before the test was run. + * @param memEnd The total JVM memory used after the test was run. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) + { + // log.debug("public void memoryUsed(Test \"" + test + "\", long " + memStart + ", long " + memEnd + ", Long " + // + threadId + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testStartMem = memStart; + r.testEndMem = memEnd; + } + + /** + * Should be called every time a parameterized test completed with the int value of its test parameter. + * + * @param test The test which memory was measured for. + * @param parameter The int parameter value. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void parameterValue(Test test, int parameter, Long threadId) + { + // log.debug("public void parameterValue(Test test = \"" + test + "\", int parameter = " + parameter + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testParam = parameter; + totalSize += parameter; + } + + /** + * Should be called every time a test completes with the current number of test threads running. This should not + * change within a test batch, therefore it is safe to take this as a batch level property value too. + * + * @param test The test for which the measurement is being generated. + * @param threads The number of tests being run concurrently. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void concurrencyLevel(Test test, int threads, Long threadId) + { + // log.debug("public void concurrencyLevel(Test test = \"" + test + "\", int threads = " + threads + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.testConcurrency = threads; + concurrencyLevel = threads; + + } + + /** + * Called when a test completes. Success, failure and errors. This method should be used when registering an + * end test from a different thread than the one that started the test. + * + * @param test The test which completed. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void endTest(Test test, Long threadId) + { + // log.debug("public void endTest(Test test = \"" + test + "\", Long threadId " + threadId + "): called"); + + TestResult r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + writeTestResults(r, test); + } + + /** + * Takes a time stamp for the beginning of the batch and resets stats counted for the batch. + */ + public synchronized void startBatch() + { + numError = 0; + numFailed = 0; + numPassed = 0; + totalTests = 0; + totalSize = 0; + batchStartTime = System.nanoTime(); + summedTime = 0; + batchStarted = true; + + // Write out the column headers for the batch. + writeColumnHeaders(); + } + + /** + * Takes a time stamp for the end of the batch to calculate the total run time. + * Write this and other stats out to the tail of the csv file. + * + * @param parameters The optional test parameters, may be null. + */ + public synchronized void endBatch(Properties parameters) + { + boolean noParams = (parameters == null) || (parameters.size() == 0); + + // Check that a batch has been started but not ended. + if (batchStarted) + { + long batchEndTime = System.nanoTime(); + float totalTimeMillis = ((float) (batchEndTime - batchStartTime)) / 1000000f; + float summedTimeMillis = ((float) summedTime) / 1000000f; + + // Write the stats for the batch out. + try + { + synchronized (this.getClass()) + { + timingsWriter.write("Total Tests:, " + totalTests + ", "); + timingsWriter.write("Total Passed:, " + numPassed + ", "); + timingsWriter.write("Total Failed:, " + numFailed + ", "); + timingsWriter.write("Total Error:, " + numError + ", "); + timingsWriter.write("Total Size:, " + totalSize + ", "); + timingsWriter.write("Summed Time:, " + summedTimeMillis + ", "); + timingsWriter.write("Concurrency Level:, " + concurrencyLevel + ", "); + timingsWriter.write("Total Time:, " + totalTimeMillis + ", "); + timingsWriter.write("Test Throughput:, " + (((float) totalTests) / totalTimeMillis) + ", "); + timingsWriter.write("Test * Size Throughput:, " + (((float) totalSize) / totalTimeMillis) + + (noParams ? "\n\n" : ", ")); + + // Write out the test parameters if there are any specified. + if (!noParams) + { + properties(parameters); + } + + timingsWriter.flush(); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to write out end batch statistics: " + e, e); + } + } + + // Reset the batch started flag to ensure stats are only output once. + batchStarted = false; + } + + /** + * Notifies listeners of the tests read/set properties. + * + * @param properties The tests read/set properties. + */ + public void properties(Properties properties) + { + // log.debug("public void properties(Properties properties): called"); + + // Write the properties out to the results file. + try + { + synchronized (this.getClass()) + { + Set keySet = new TreeSet(properties.keySet()); + + // timingsWriter.write("\n"); + + for (Object key : keySet) + { + timingsWriter.write(key + " = , " + properties.getProperty((String) key) + ", "); + } + + timingsWriter.write("\n\n"); + // timingsWriter.flush(); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to write out test parameters: " + e, e); + } + + // Write out the column headers after the properties. + // writeColumnHeaders(); + } + + /** + * Writes out and flushes the column headers for raw test data. + */ + private void writeColumnHeaders() + { + // Write the column headers for the CSV file. Any IO exceptions are ignored. + try + { + timingsWriter.write("Class, "); + timingsWriter.write("Method, "); + timingsWriter.write("Thread, "); + timingsWriter.write("Test Outcome, "); + timingsWriter.write("Time (milliseconds), "); + timingsWriter.write("Memory Used (bytes), "); + timingsWriter.write("Concurrency level, "); + timingsWriter.write("Test Size\n"); + + timingsWriter.flush(); + } + catch (IOException e) + { + throw new RuntimeException("Unable to write out column headers: " + e, e); + } + } + + /** + * Writes out the test results for the specified test. This outputs a single line of results to the csv file. + * + * @param r The test results to write out. + * @param test The test to write them out for. + */ + private void writeTestResults(TestResult r, Test test) + { + // Update the running stats for this batch. + if ("Error".equals(r.testState)) + { + numError++; + } + else if ("Failure".equals(r.testState)) + { + numFailed++; + } + else if ("Pass".equals(r.testState)) + { + numPassed++; + } + + totalTests++; + + // Write the test name and thread information plus all instrumenation a line of the CSV ouput. Any IO + // exceptions are ignored. + try + { + synchronized (this.getClass()) + { + timingsWriter.write(test.getClass().getName() + ", "); + timingsWriter.write(((test instanceof TestCase) ? ((TestCase) test).getName() : "") + ", "); + timingsWriter.write(Thread.currentThread().getName() + ", "); + timingsWriter.write(r.testState + ", "); + timingsWriter.write((((float) r.testTime) / 1000000f) + ", "); + timingsWriter.write((r.testEndMem - r.testStartMem) + ", "); + timingsWriter.write(r.testConcurrency + ", "); + timingsWriter.write(r.testParam + "\n"); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to write out test results: " + e, e); + } + } + + /** + * Supplies the shutdown hook. This attempts to flush the results in the event of the test runner being prematurely + * suspended before the end of the current test batch. + * + * @return The shut down hook. + */ + public Thread getShutdownHook() + { + return new Thread(new Runnable() + { + public void run() + { + log.debug("CSVTestListener::ShutdownHook: called"); + + // Complete the current test batch stats. + endBatch(TestContextProperties.getInstance()); + } + }); + } + + /** Captures test results packaged into a single object, so that it can be set up as a thread local. */ + private static class TestResult + { + /** Used to hold the test timing. */ + public long testTime; + + /** Used to hold the test start memory usage. */ + public long testStartMem; + + /** Used to hold the test end memory usage. */ + public long testEndMem; + + /** Used to hold the test pass/fail/error state. */ + public String testState = "Pass"; + + /** Used to hold the test parameter value. */ + public int testParam; + + /** Used to hold the concurrency level under which the test was run. */ + public int testConcurrency; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java index 5c328a8814..2955fba2bd 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java @@ -1,264 +1,264 @@ -/* - * - * 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.listeners; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestListener; - -import org.apache.qpid.junit.extensions.SleepThrottle; -import org.apache.qpid.junit.extensions.Throttle; - -import java.util.Properties; - -/** - * ConsoleTestListener provides feedback to the console, as test timings are taken, by drawing a '.', or an 'E', or an - * 'F', for each test that passes, is in error or fails. It does this for every test result registered with the framework, - * not just on the completion of each test method as the JUnit one does. It also uses a throttle to cap the rate of - * dot drawing, as exessively high rates can degrade test performance without providing much usefull feedback to the user. - * Unlike the JUnit dot drawing feedback, this one will correctly wrap lines when tests are run concurrently (the - * rate capping ensures that this does not become a hot-spot for thread contention). - * - *

Where rate capping causes the conflation of multiple requested dots into a single dot, the dot that is actually - * drawn will be the worst result within the conflation period, that is, error is worse than fail which is worse than pass. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Draw dots as each test result completes, at a capped rate. - *
- * - * @author Rupert Smith - */ -public class ConsoleTestListener implements TestListener, TKTestListener -{ - /** Used to indicate a test pass. */ - private static final int PASS = 1; - - /** Used to indicate a test failure. */ - private static final int FAIL = 2; - - /** Used to indicate a test error. */ - private static final int ERROR = 3; - - /** Defines the maximum number of columns of dots to print. */ - private static final int MAX_COLUMNS = 80; - - /** Used to throttle the dot writing rate. */ - Throttle throttle; - - /** Tracks the worst test result so far, when the throttled print method must conflate results due to throttling. */ - private int conflatedResult = 0; - - /** Tracks the column count as dots are printed, so that newlines can be inserted at the right margin. */ - private int columnCount = 0; - - /** Used as a monitor on the print method criticial section, to ensure that line ends always happen in the right place. */ - private final Object printMonitor = new Object(); - - /** - * Creates a dot drawing feedback test listener, set by default to 80 columns at 80 dots per second capped rate. - */ - public ConsoleTestListener() - { - throttle = new SleepThrottle(); - throttle.setRate(80f); - } - - /** - * Prints dots at a capped rate, conflating the requested type of dot to draw if this method is called at a rate - * higher than the capped rate. The conflation works by always printing the worst result that occurs within the - * conflation period, that is, error is worse than fail which is worse than a pass. - * - * @param result The type of dot to draw, {@link #PASS}, {@link #FAIL} or {@link #ERROR}. - */ - private void throttledPrint(int result) - { - conflatedResult = (result > conflatedResult) ? result : conflatedResult; - - if (throttle.checkThrottle()) - { - synchronized (printMonitor) - { - switch (conflatedResult) - { - default: - case PASS: - System.out.print('.'); - break; - - case FAIL: - System.out.print('F'); - break; - - case ERROR: - System.out.print('E'); - break; - } - - columnCount = (columnCount >= MAX_COLUMNS) ? 0 : (columnCount + 1); - - if (columnCount == 0) - { - System.out.print('\n'); - } - - conflatedResult = 0; - } - } - } - - /** - * An error occurred. - * - * @param test The test in error. Ignored. - * @param t The error that the test threw. Ignored. - */ - public void addError(Test test, Throwable t) - { - throttledPrint(ERROR); - } - - /** - * A failure occurred. - * - * @param test The test that failed. Ignored. - * @param t The assertion failure that the test threw. Ignored. - */ - public void addFailure(Test test, AssertionFailedError t) - { - throttledPrint(FAIL); - } - - /** - * A test ended. - * - * @param test The test that ended. Ignored. - */ - public void endTest(Test test) - { - throttledPrint(PASS); - } - - /** - * A test started. - * - * @param test The test that started. Ignored. - */ - public void startTest(Test test) - { } - - /** - * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. - * - * @param test The test to resest any results for. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void reset(Test test, Long threadId) - { } - - /** - * Should be called every time a test completes with the run time of that test. - * - * @param test The name of the test. - * @param nanos The run time of the test in nanoseconds. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void timing(Test test, long nanos, Long threadId) - { } - - /** - * Should be called every time a test completed with the amount of memory used before and after the test was run. - * - * @param test The test which memory was measured for. - * @param memStart The total JVM memory used before the test was run. - * @param memEnd The total JVM memory used after the test was run. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) - { } - - /** - * Should be called every time a parameterized test completed with the int value of its test parameter. - * - * @param test The test which memory was measured for. - * @param parameter The int parameter value. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void parameterValue(Test test, int parameter, Long threadId) - { } - - /** - * Should be called every time a test completes with the current number of test threads running. - * - * @param test The test for which the measurement is being generated. - * @param threads The number of tests being run concurrently. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void concurrencyLevel(Test test, int threads, Long threadId) - { } - - /** - * Called when a test completes. Success, failure and errors. This method should be used when registering an - * end test from a different thread than the one that started the test. - * - * @param test The test which completed. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void endTest(Test test, Long threadId) - { - throttledPrint(PASS); - } - - /** - * Called when a test completes to mark it as a test fail. This method should be used when registering a - * failure from a different thread than the one that started the test. - * - * @param test The test which failed. - * @param e The assertion that failed the test. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void addFailure(Test test, AssertionFailedError e, Long threadId) - { - throttledPrint(FAIL); - } - - /** - * Notifies listeners of the start of a complete run of tests. - */ - public void startBatch() - { } - - /** - * Notifies listeners of the end of a complete run of tests. - * - * @param parameters The optional test parameters to log out with the batch results. - */ - public void endBatch(Properties parameters) - { } - - /** - * Notifies listeners of the tests read/set properties. - * - * @param properties The tests read/set properties. - */ - public void properties(Properties properties) - { } -} +/* + * + * 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.listeners; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; + +import org.apache.qpid.junit.extensions.SleepThrottle; +import org.apache.qpid.junit.extensions.Throttle; + +import java.util.Properties; + +/** + * ConsoleTestListener provides feedback to the console, as test timings are taken, by drawing a '.', or an 'E', or an + * 'F', for each test that passes, is in error or fails. It does this for every test result registered with the framework, + * not just on the completion of each test method as the JUnit one does. It also uses a throttle to cap the rate of + * dot drawing, as exessively high rates can degrade test performance without providing much usefull feedback to the user. + * Unlike the JUnit dot drawing feedback, this one will correctly wrap lines when tests are run concurrently (the + * rate capping ensures that this does not become a hot-spot for thread contention). + * + *

Where rate capping causes the conflation of multiple requested dots into a single dot, the dot that is actually + * drawn will be the worst result within the conflation period, that is, error is worse than fail which is worse than pass. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Draw dots as each test result completes, at a capped rate. + *
+ * + * @author Rupert Smith + */ +public class ConsoleTestListener implements TestListener, TKTestListener +{ + /** Used to indicate a test pass. */ + private static final int PASS = 1; + + /** Used to indicate a test failure. */ + private static final int FAIL = 2; + + /** Used to indicate a test error. */ + private static final int ERROR = 3; + + /** Defines the maximum number of columns of dots to print. */ + private static final int MAX_COLUMNS = 80; + + /** Used to throttle the dot writing rate. */ + Throttle throttle; + + /** Tracks the worst test result so far, when the throttled print method must conflate results due to throttling. */ + private int conflatedResult = 0; + + /** Tracks the column count as dots are printed, so that newlines can be inserted at the right margin. */ + private int columnCount = 0; + + /** Used as a monitor on the print method criticial section, to ensure that line ends always happen in the right place. */ + private final Object printMonitor = new Object(); + + /** + * Creates a dot drawing feedback test listener, set by default to 80 columns at 80 dots per second capped rate. + */ + public ConsoleTestListener() + { + throttle = new SleepThrottle(); + throttle.setRate(80f); + } + + /** + * Prints dots at a capped rate, conflating the requested type of dot to draw if this method is called at a rate + * higher than the capped rate. The conflation works by always printing the worst result that occurs within the + * conflation period, that is, error is worse than fail which is worse than a pass. + * + * @param result The type of dot to draw, {@link #PASS}, {@link #FAIL} or {@link #ERROR}. + */ + private void throttledPrint(int result) + { + conflatedResult = (result > conflatedResult) ? result : conflatedResult; + + if (throttle.checkThrottle()) + { + synchronized (printMonitor) + { + switch (conflatedResult) + { + default: + case PASS: + System.out.print('.'); + break; + + case FAIL: + System.out.print('F'); + break; + + case ERROR: + System.out.print('E'); + break; + } + + columnCount = (columnCount >= MAX_COLUMNS) ? 0 : (columnCount + 1); + + if (columnCount == 0) + { + System.out.print('\n'); + } + + conflatedResult = 0; + } + } + } + + /** + * An error occurred. + * + * @param test The test in error. Ignored. + * @param t The error that the test threw. Ignored. + */ + public void addError(Test test, Throwable t) + { + throttledPrint(ERROR); + } + + /** + * A failure occurred. + * + * @param test The test that failed. Ignored. + * @param t The assertion failure that the test threw. Ignored. + */ + public void addFailure(Test test, AssertionFailedError t) + { + throttledPrint(FAIL); + } + + /** + * A test ended. + * + * @param test The test that ended. Ignored. + */ + public void endTest(Test test) + { + throttledPrint(PASS); + } + + /** + * A test started. + * + * @param test The test that started. Ignored. + */ + public void startTest(Test test) + { } + + /** + * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. + * + * @param test The test to resest any results for. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void reset(Test test, Long threadId) + { } + + /** + * Should be called every time a test completes with the run time of that test. + * + * @param test The name of the test. + * @param nanos The run time of the test in nanoseconds. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void timing(Test test, long nanos, Long threadId) + { } + + /** + * Should be called every time a test completed with the amount of memory used before and after the test was run. + * + * @param test The test which memory was measured for. + * @param memStart The total JVM memory used before the test was run. + * @param memEnd The total JVM memory used after the test was run. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) + { } + + /** + * Should be called every time a parameterized test completed with the int value of its test parameter. + * + * @param test The test which memory was measured for. + * @param parameter The int parameter value. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void parameterValue(Test test, int parameter, Long threadId) + { } + + /** + * Should be called every time a test completes with the current number of test threads running. + * + * @param test The test for which the measurement is being generated. + * @param threads The number of tests being run concurrently. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void concurrencyLevel(Test test, int threads, Long threadId) + { } + + /** + * Called when a test completes. Success, failure and errors. This method should be used when registering an + * end test from a different thread than the one that started the test. + * + * @param test The test which completed. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void endTest(Test test, Long threadId) + { + throttledPrint(PASS); + } + + /** + * Called when a test completes to mark it as a test fail. This method should be used when registering a + * failure from a different thread than the one that started the test. + * + * @param test The test which failed. + * @param e The assertion that failed the test. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void addFailure(Test test, AssertionFailedError e, Long threadId) + { + throttledPrint(FAIL); + } + + /** + * Notifies listeners of the start of a complete run of tests. + */ + public void startBatch() + { } + + /** + * Notifies listeners of the end of a complete run of tests. + * + * @param parameters The optional test parameters to log out with the batch results. + */ + public void endBatch(Properties parameters) + { } + + /** + * Notifies listeners of the tests read/set properties. + * + * @param properties The tests read/set properties. + */ + public void properties(Properties properties) + { } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java index 4f08e8bf2d..11fc6a7451 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java @@ -1,132 +1,132 @@ -/* - * - * 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.listeners; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestListener; - -import java.util.Properties; - -/** - * TKTestListener is a listener interface for listeners that want to be informed of the run times of tests, the memory - * usage of tests, the 'size' parameters of parameterized tests and the begin and end events of complete test runs. - * {@link org.apache.qpid.junit.extensions.TKTestResult} is an example of a test result class that listeners - * interested in these events can be attached to. - * - * The {@link #timing(junit.framework.Test, long, Long)}, {@link #memoryUsed(junit.framework.Test, long, long, Long)}, - * {@link #parameterValue(junit.framework.Test, int, Long)} and {@link #endTest(junit.framework.Test, Long)} methods - * all accept on optional thread id parameter. - * - *

- *
CRC Card
Responsibilities - *
Listen to test timings. - *
Listen to test memory usages. - *
Listen to parameterized test parameters. - *
- * - * @author Rupert Smith - */ -public interface TKTestListener extends TestListener -{ - /** - * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. - * - * @param test The test to resest any results for. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void reset(Test test, Long threadId); - - /** - * Should be called every time a test completes with the run time of that test. - * - * @param test The name of the test. - * @param nanos The run time of the test in nanoseconds. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void timing(Test test, long nanos, Long threadId); - - /** - * Should be called every time a test completed with the amount of memory used before and after the test was run. - * - * @param test The test which memory was measured for. - * @param memStart The total JVM memory used before the test was run. - * @param memEnd The total JVM memory used after the test was run. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void memoryUsed(Test test, long memStart, long memEnd, Long threadId); - - /** - * Should be called every time a parameterized test completed with the int value of its test parameter. - * - * @param test The test which memory was measured for. - * @param parameter The int parameter value. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void parameterValue(Test test, int parameter, Long threadId); - - /** - * Should be called every time a test completes with the current number of test threads running. - * - * @param test The test for which the measurement is being generated. - * @param threads The number of tests being run concurrently. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void concurrencyLevel(Test test, int threads, Long threadId); - - /** - * Called when a test completes. Success, failure and errors. This method should be used when registering an - * end test from a different thread than the one that started the test. - * - * @param test The test which completed. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void endTest(Test test, Long threadId); - - /** - * Called when a test completes to mark it as a test fail. This method should be used when registering a - * failure from a different thread than the one that started the test. - * - * @param test The test which failed. - * @param e The assertion that failed the test. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void addFailure(Test test, AssertionFailedError e, Long threadId); - - /** - * Notifies listeners of the start of a complete run of tests. - */ - public void startBatch(); - - /** - * Notifies listeners of the end of a complete run of tests. - * - * @param parameters The optional test parameters to log out with the batch results. - */ - public void endBatch(Properties parameters); - - /** - * Notifies listeners of the tests read/set properties. - * - * @param properties The tests read/set properties. - */ - public void properties(Properties properties); -} +/* + * + * 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.listeners; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; + +import java.util.Properties; + +/** + * TKTestListener is a listener interface for listeners that want to be informed of the run times of tests, the memory + * usage of tests, the 'size' parameters of parameterized tests and the begin and end events of complete test runs. + * {@link org.apache.qpid.junit.extensions.TKTestResult} is an example of a test result class that listeners + * interested in these events can be attached to. + * + * The {@link #timing(junit.framework.Test, long, Long)}, {@link #memoryUsed(junit.framework.Test, long, long, Long)}, + * {@link #parameterValue(junit.framework.Test, int, Long)} and {@link #endTest(junit.framework.Test, Long)} methods + * all accept on optional thread id parameter. + * + *

+ *
CRC Card
Responsibilities + *
Listen to test timings. + *
Listen to test memory usages. + *
Listen to parameterized test parameters. + *
+ * + * @author Rupert Smith + */ +public interface TKTestListener extends TestListener +{ + /** + * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. + * + * @param test The test to resest any results for. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void reset(Test test, Long threadId); + + /** + * Should be called every time a test completes with the run time of that test. + * + * @param test The name of the test. + * @param nanos The run time of the test in nanoseconds. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void timing(Test test, long nanos, Long threadId); + + /** + * Should be called every time a test completed with the amount of memory used before and after the test was run. + * + * @param test The test which memory was measured for. + * @param memStart The total JVM memory used before the test was run. + * @param memEnd The total JVM memory used after the test was run. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void memoryUsed(Test test, long memStart, long memEnd, Long threadId); + + /** + * Should be called every time a parameterized test completed with the int value of its test parameter. + * + * @param test The test which memory was measured for. + * @param parameter The int parameter value. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void parameterValue(Test test, int parameter, Long threadId); + + /** + * Should be called every time a test completes with the current number of test threads running. + * + * @param test The test for which the measurement is being generated. + * @param threads The number of tests being run concurrently. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void concurrencyLevel(Test test, int threads, Long threadId); + + /** + * Called when a test completes. Success, failure and errors. This method should be used when registering an + * end test from a different thread than the one that started the test. + * + * @param test The test which completed. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void endTest(Test test, Long threadId); + + /** + * Called when a test completes to mark it as a test fail. This method should be used when registering a + * failure from a different thread than the one that started the test. + * + * @param test The test which failed. + * @param e The assertion that failed the test. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void addFailure(Test test, AssertionFailedError e, Long threadId); + + /** + * Notifies listeners of the start of a complete run of tests. + */ + public void startBatch(); + + /** + * Notifies listeners of the end of a complete run of tests. + * + * @param parameters The optional test parameters to log out with the batch results. + */ + public void endBatch(Properties parameters); + + /** + * Notifies listeners of the tests read/set properties. + * + * @param properties The tests read/set properties. + */ + public void properties(Properties properties); +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java index a88837e323..ded07ef5bb 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java @@ -1,400 +1,400 @@ -/* - * - * 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.listeners; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestCase; - -import org.apache.log4j.Logger; - -import org.apache.qpid.junit.extensions.ShutdownHookable; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.*; - -/** - * Listens for test results for a named test and outputs these in the standard JUnit XML format to the specified - * writer. - * - *

The API for this listener accepts notifications about different aspects of a tests results through different - * methods, so some assumption needs to be made as to which test result a notification refers to. For example - * {@link #startTest} will be called, then possibly {@link #timing} will be called, even though the test instance is - * passed in both cases, it is not enough to distinguish a particular run of the test, as the test case instance may - * be being shared between multiple threads, or being run a repeated number of times, and can therfore be re-used - * between calls. The listeners make the assumption that, for every test, a unique thread will call {@link #startTest} - * and {@link #endTest} to delimit each test. All calls to set test parameters, timings, state and so on, will occur - * between the start and end and will be given with the same thread id as the start and end, so the thread id provides - * a unqiue value to identify a particular test run against. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Listen to test lifecycle notifications. - *
Listen to test errors and failures. - *
Listen to test timings. - *
Listen to test memory usages. - *
Listen to parameterized test parameters. - *
Responsibilities - *
- * - * @todo Merge this class with CSV test listener, making the collection of results common to both, and only factoring - * out the results printing code into sub-classes. Provide a simple XML results formatter with the same format as - * the ant XML formatter, and a more structured one for outputing results with timings and summaries from - * performance tests. - * - * @author Rupert Smith - */ -public class XMLTestListener implements TKTestListener, ShutdownHookable -{ - /** Used for debugging. */ - private static final Logger log = Logger.getLogger(XMLTestListener.class); - - /** The results file writer. */ - protected Writer writer; - - /** Holds the results for individual tests. */ - // protected Map results = new LinkedHashMap(); - // protected List results = new ArrayList(); - - /** - * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an - * explicit thread id must be used, where notifications come from different threads than the ones that called - * the test method. - */ - Map threadLocalResults = Collections.synchronizedMap(new LinkedHashMap()); - - /** - * Holds results for tests that have ended. Transferring these results here from the per-thread results map, means - * that the thread id is freed for the thread to generate more results. - */ - List results = new ArrayList(); - - /** Holds the overall error count. */ - protected int errors = 0; - - /** Holds the overall failure count. */ - protected int failures = 0; - - /** Holds the overall tests run count. */ - protected int runs = 0; - - /** Holds the name of the class that tests are being run for. */ - String testClassName; - - /** - * Creates a new XML results output listener that writes to the specified location. - * - * @param writer The location to write results to. - * @param testClassName The name of the test class to include in the test results. - */ - public XMLTestListener(Writer writer, String testClassName) - { - log.debug("public XMLTestListener(Writer writer, String testClassName = " + testClassName + "): called"); - - this.writer = writer; - this.testClassName = testClassName; - } - - /** - * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. - * - * @param test The test to resest any results for. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void reset(Test test, Long threadId) - { - log.debug("public void reset(Test test = " + test + ", Long threadId = " + threadId + "): called"); - - XMLTestListener.Result r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - - r.error = null; - r.failure = null; - - } - - /** - * Notification that a test started. - * - * @param test The test that started. - */ - public void startTest(Test test) - { - log.debug("public void startTest(Test test = " + test + "): called"); - - Result newResult = new Result(test.getClass().getName(), ((TestCase) test).getName()); - - // Initialize the thread local test results. - threadLocalResults.put(Thread.currentThread().getId(), newResult); - runs++; - } - - /** - * Should be called every time a test completes with the run time of that test. - * - * @param test The name of the test. - * @param nanos The run time of the test in nanoseconds. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void timing(Test test, long nanos, Long threadId) - { } - - /** - * Should be called every time a test completed with the amount of memory used before and after the test was run. - * - * @param test The test which memory was measured for. - * @param memStart The total JVM memory used before the test was run. - * @param memEnd The total JVM memory used after the test was run. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) - { } - - /** - * Should be called every time a parameterized test completed with the int value of its test parameter. - * - * @param test The test which memory was measured for. - * @param parameter The int parameter value. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void parameterValue(Test test, int parameter, Long threadId) - { } - - /** - * Should be called every time a test completes with the current number of test threads running. - * - * @param test The test for which the measurement is being generated. - * @param threads The number of tests being run concurrently. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void concurrencyLevel(Test test, int threads, Long threadId) - { } - - /** - * Notifies listeners of the tests read/set properties. - * - * @param properties The tests read/set properties. - */ - public void properties(Properties properties) - { } - - /** - * Notification that a test ended. - * - * @param test The test that ended. - */ - public void endTest(Test test) - { - log.debug("public void endTest(Test test = " + test + "): called"); - - // Move complete test results into the completed tests list. - Result r = threadLocalResults.get(Thread.currentThread().getId()); - results.add(r); - - // Clear all the test results for the thread. - threadLocalResults.remove(Thread.currentThread().getId()); - } - - /** - * Called when a test completes. Success, failure and errors. This method should be used when registering an - * end test from a different thread than the one that started the test. - * - * @param test The test which completed. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void endTest(Test test, Long threadId) - { - log.debug("public void endTest(Test test = " + test + ", Long threadId = " + threadId + "): called"); - - // Move complete test results into the completed tests list. - Result r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - results.add(r); - - // Clear all the test results for the thread. - threadLocalResults.remove(Thread.currentThread().getId()); - } - - /** - * An error occurred. - * - * @param test The test in which the error occurred. - * @param t The throwable that resulted from the error. - */ - public void addError(Test test, Throwable t) - { - log.debug("public void addError(Test test = " + test + ", Throwable t = " + t + "): called"); - - Result r = threadLocalResults.get(Thread.currentThread().getId()); - r.error = t; - errors++; - } - - /** - * A failure occurred. - * - * @param test The test in which the failure occurred. - * @param t The JUnit assertions that led to the failure. - */ - public void addFailure(Test test, AssertionFailedError t) - { - log.debug("public void addFailure(Test test = " + test + ", AssertionFailedError t = " + t + "): called"); - - Result r = threadLocalResults.get(Thread.currentThread().getId()); - r.failure = t; - failures++; - } - - /** - * Called when a test completes to mark it as a test fail. This method should be used when registering a - * failure from a different thread than the one that started the test. - * - * @param test The test which failed. - * @param e The assertion that failed the test. - * @param threadId Optional thread id if not calling from thread that started the test method. May be null. - */ - public void addFailure(Test test, AssertionFailedError e, Long threadId) - { - log.debug("public void addFailure(Test test, AssertionFailedError e, Long threadId): called"); - - Result r = - (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); - r.failure = e; - failures++; - } - - /** - * Notifies listeners of the start of a complete run of tests. - */ - public void startBatch() - { - log.debug("public void startBatch(): called"); - - // Reset all results counts. - threadLocalResults = Collections.synchronizedMap(new HashMap()); - errors = 0; - failures = 0; - runs = 0; - - // Write out the file header. - try - { - writer.write("\n"); - } - catch (IOException e) - { - throw new RuntimeException("Unable to write the test results.", e); - } - } - - /** - * Notifies listeners of the end of a complete run of tests. - * - * @param parameters The optional test parameters to log out with the batch results. - */ - public void endBatch(Properties parameters) - { - log.debug("public void endBatch(Properties parameters = " + parameters + "): called"); - - // Write out the results. - try - { - // writer.write("\n"); - writer.write("\n"); - - for (Result result : results) - { - writer.write(" \n"); - - if (result.error != null) - { - writer.write(" "); - result.error.printStackTrace(new PrintWriter(writer)); - writer.write(" "); - } - else if (result.failure != null) - { - writer.write(" "); - result.failure.printStackTrace(new PrintWriter(writer)); - writer.write(" "); - } - - writer.write(" \n"); - } - - writer.write("\n"); - writer.flush(); - } - catch (IOException e) - { - throw new RuntimeException("Unable to write the test results.", e); - } - } - - /** - * Supplies the shutdown hook. - * - * @return The shut down hook. - */ - public Thread getShutdownHook() - { - return new Thread(new Runnable() - { - public void run() - { - log.debug("XMLTestListener::ShutdownHook: called"); - } - }); - } - - /** - * Used to capture the results of a particular test run. - */ - protected static class Result - { - /** Holds the name of the test class. */ - public String testClass; - - /** Holds the name of the test method. */ - public String testName; - - /** Holds the exception that caused error in this test. */ - public Throwable error; - - /** Holds the assertion exception that caused failure in this test. */ - public AssertionFailedError failure; - - /** - * Creates a placeholder for the results of a test. - * - * @param testClass The test class. - * @param testName The name of the test that was run. - */ - public Result(String testClass, String testName) - { - this.testClass = testClass; - this.testName = testName; - } - } -} +/* + * + * 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.listeners; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; + +import org.apache.log4j.Logger; + +import org.apache.qpid.junit.extensions.ShutdownHookable; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.*; + +/** + * Listens for test results for a named test and outputs these in the standard JUnit XML format to the specified + * writer. + * + *

The API for this listener accepts notifications about different aspects of a tests results through different + * methods, so some assumption needs to be made as to which test result a notification refers to. For example + * {@link #startTest} will be called, then possibly {@link #timing} will be called, even though the test instance is + * passed in both cases, it is not enough to distinguish a particular run of the test, as the test case instance may + * be being shared between multiple threads, or being run a repeated number of times, and can therfore be re-used + * between calls. The listeners make the assumption that, for every test, a unique thread will call {@link #startTest} + * and {@link #endTest} to delimit each test. All calls to set test parameters, timings, state and so on, will occur + * between the start and end and will be given with the same thread id as the start and end, so the thread id provides + * a unqiue value to identify a particular test run against. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Listen to test lifecycle notifications. + *
Listen to test errors and failures. + *
Listen to test timings. + *
Listen to test memory usages. + *
Listen to parameterized test parameters. + *
Responsibilities + *
+ * + * @todo Merge this class with CSV test listener, making the collection of results common to both, and only factoring + * out the results printing code into sub-classes. Provide a simple XML results formatter with the same format as + * the ant XML formatter, and a more structured one for outputing results with timings and summaries from + * performance tests. + * + * @author Rupert Smith + */ +public class XMLTestListener implements TKTestListener, ShutdownHookable +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(XMLTestListener.class); + + /** The results file writer. */ + protected Writer writer; + + /** Holds the results for individual tests. */ + // protected Map results = new LinkedHashMap(); + // protected List results = new ArrayList(); + + /** + * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an + * explicit thread id must be used, where notifications come from different threads than the ones that called + * the test method. + */ + Map threadLocalResults = Collections.synchronizedMap(new LinkedHashMap()); + + /** + * Holds results for tests that have ended. Transferring these results here from the per-thread results map, means + * that the thread id is freed for the thread to generate more results. + */ + List results = new ArrayList(); + + /** Holds the overall error count. */ + protected int errors = 0; + + /** Holds the overall failure count. */ + protected int failures = 0; + + /** Holds the overall tests run count. */ + protected int runs = 0; + + /** Holds the name of the class that tests are being run for. */ + String testClassName; + + /** + * Creates a new XML results output listener that writes to the specified location. + * + * @param writer The location to write results to. + * @param testClassName The name of the test class to include in the test results. + */ + public XMLTestListener(Writer writer, String testClassName) + { + log.debug("public XMLTestListener(Writer writer, String testClassName = " + testClassName + "): called"); + + this.writer = writer; + this.testClassName = testClassName; + } + + /** + * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed. + * + * @param test The test to resest any results for. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void reset(Test test, Long threadId) + { + log.debug("public void reset(Test test = " + test + ", Long threadId = " + threadId + "): called"); + + XMLTestListener.Result r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + + r.error = null; + r.failure = null; + + } + + /** + * Notification that a test started. + * + * @param test The test that started. + */ + public void startTest(Test test) + { + log.debug("public void startTest(Test test = " + test + "): called"); + + Result newResult = new Result(test.getClass().getName(), ((TestCase) test).getName()); + + // Initialize the thread local test results. + threadLocalResults.put(Thread.currentThread().getId(), newResult); + runs++; + } + + /** + * Should be called every time a test completes with the run time of that test. + * + * @param test The name of the test. + * @param nanos The run time of the test in nanoseconds. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void timing(Test test, long nanos, Long threadId) + { } + + /** + * Should be called every time a test completed with the amount of memory used before and after the test was run. + * + * @param test The test which memory was measured for. + * @param memStart The total JVM memory used before the test was run. + * @param memEnd The total JVM memory used after the test was run. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void memoryUsed(Test test, long memStart, long memEnd, Long threadId) + { } + + /** + * Should be called every time a parameterized test completed with the int value of its test parameter. + * + * @param test The test which memory was measured for. + * @param parameter The int parameter value. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void parameterValue(Test test, int parameter, Long threadId) + { } + + /** + * Should be called every time a test completes with the current number of test threads running. + * + * @param test The test for which the measurement is being generated. + * @param threads The number of tests being run concurrently. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void concurrencyLevel(Test test, int threads, Long threadId) + { } + + /** + * Notifies listeners of the tests read/set properties. + * + * @param properties The tests read/set properties. + */ + public void properties(Properties properties) + { } + + /** + * Notification that a test ended. + * + * @param test The test that ended. + */ + public void endTest(Test test) + { + log.debug("public void endTest(Test test = " + test + "): called"); + + // Move complete test results into the completed tests list. + Result r = threadLocalResults.get(Thread.currentThread().getId()); + results.add(r); + + // Clear all the test results for the thread. + threadLocalResults.remove(Thread.currentThread().getId()); + } + + /** + * Called when a test completes. Success, failure and errors. This method should be used when registering an + * end test from a different thread than the one that started the test. + * + * @param test The test which completed. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void endTest(Test test, Long threadId) + { + log.debug("public void endTest(Test test = " + test + ", Long threadId = " + threadId + "): called"); + + // Move complete test results into the completed tests list. + Result r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + results.add(r); + + // Clear all the test results for the thread. + threadLocalResults.remove(Thread.currentThread().getId()); + } + + /** + * An error occurred. + * + * @param test The test in which the error occurred. + * @param t The throwable that resulted from the error. + */ + public void addError(Test test, Throwable t) + { + log.debug("public void addError(Test test = " + test + ", Throwable t = " + t + "): called"); + + Result r = threadLocalResults.get(Thread.currentThread().getId()); + r.error = t; + errors++; + } + + /** + * A failure occurred. + * + * @param test The test in which the failure occurred. + * @param t The JUnit assertions that led to the failure. + */ + public void addFailure(Test test, AssertionFailedError t) + { + log.debug("public void addFailure(Test test = " + test + ", AssertionFailedError t = " + t + "): called"); + + Result r = threadLocalResults.get(Thread.currentThread().getId()); + r.failure = t; + failures++; + } + + /** + * Called when a test completes to mark it as a test fail. This method should be used when registering a + * failure from a different thread than the one that started the test. + * + * @param test The test which failed. + * @param e The assertion that failed the test. + * @param threadId Optional thread id if not calling from thread that started the test method. May be null. + */ + public void addFailure(Test test, AssertionFailedError e, Long threadId) + { + log.debug("public void addFailure(Test test, AssertionFailedError e, Long threadId): called"); + + Result r = + (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId); + r.failure = e; + failures++; + } + + /** + * Notifies listeners of the start of a complete run of tests. + */ + public void startBatch() + { + log.debug("public void startBatch(): called"); + + // Reset all results counts. + threadLocalResults = Collections.synchronizedMap(new HashMap()); + errors = 0; + failures = 0; + runs = 0; + + // Write out the file header. + try + { + writer.write("\n"); + } + catch (IOException e) + { + throw new RuntimeException("Unable to write the test results.", e); + } + } + + /** + * Notifies listeners of the end of a complete run of tests. + * + * @param parameters The optional test parameters to log out with the batch results. + */ + public void endBatch(Properties parameters) + { + log.debug("public void endBatch(Properties parameters = " + parameters + "): called"); + + // Write out the results. + try + { + // writer.write("\n"); + writer.write("\n"); + + for (Result result : results) + { + writer.write(" \n"); + + if (result.error != null) + { + writer.write(" "); + result.error.printStackTrace(new PrintWriter(writer)); + writer.write(" "); + } + else if (result.failure != null) + { + writer.write(" "); + result.failure.printStackTrace(new PrintWriter(writer)); + writer.write(" "); + } + + writer.write(" \n"); + } + + writer.write("\n"); + writer.flush(); + } + catch (IOException e) + { + throw new RuntimeException("Unable to write the test results.", e); + } + } + + /** + * Supplies the shutdown hook. + * + * @return The shut down hook. + */ + public Thread getShutdownHook() + { + return new Thread(new Runnable() + { + public void run() + { + log.debug("XMLTestListener::ShutdownHook: called"); + } + }); + } + + /** + * Used to capture the results of a particular test run. + */ + protected static class Result + { + /** Holds the name of the test class. */ + public String testClass; + + /** Holds the name of the test method. */ + public String testName; + + /** Holds the exception that caused error in this test. */ + public Throwable error; + + /** Holds the assertion exception that caused failure in this test. */ + public AssertionFailedError failure; + + /** + * Creates a placeholder for the results of a test. + * + * @param testClass The test class. + * @param testName The name of the test that was run. + */ + public Result(String testClass, String testName) + { + this.testClass = testClass; + this.testName = testName; + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java index 61c58bf3ba..f158090e96 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java @@ -1,787 +1,787 @@ -/* - * - * 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.util; - -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * CommandLineParser provides a utility for specifying the format of a command line and parsing command lines to ensure - * that they fit their specified format. A command line is made up of flags and options, both may be refered to as - * options. A flag is an option that does not take an argument (specifying it means it has the value 'true' and not - * specifying it means it has the value 'false'). Options must take arguments but they can be set up with defaults so - * that they take a default value when not set. Options may be mandatory in wich case it is an error not to specify - * them on the command line. Flags are never mandatory because they are implicitly set to false when not specified. - * - *

Some examples command line are: - * - *

    - *
  • This one has two options that expect arguments: - *
    - * cruisecontrol -configfile cruisecontrol.xml -port 9000
    - * 
    - *
  • This has one no-arg flag and two 'free' arguments: - *
    - * zip -r project.zip project/*
    - * 
    - *
  • This one concatenates multiple flags into a single block with only one '-': - *
    - * jar -tvf mytar.tar
    - * 
    - * - *

    The parsing rules are: - * - *

      - *
    1. Flags may be combined after a single '-' because they never take arguments. Normally such flags are single letter - * flags but this is only a convention and not enforced. Flags of more than one letter are usually specified on their own. - *
    2. Options expecting arguments must always be on their own. - *
    3. The argument to an option may be seperated from it by whitespace or appended directly onto the option. - *
    4. The argument to an option may never begin with a '-' character. - *
    5. All other arguments not beginning with a '-' character are free arguments that do not belong to any option. - *
    6. The second or later of a set of duplicate or repeated flags override earlier ones. - *
    7. Options are matched up to the shortest matching option. This is because of the possibility of having no space - * between an option and its argument. This rules out the possibility of using two options where one is an opening - * substring of the other. For example, the options "foo" and "foobar" cannot be used on the same command line because - * it is not possible to distinguish the argument "-foobar" from being the "foobar" option or the "foo" option with - * the "bar" argument. - *
    - * - *

    By default, unknown options are simply ignored if specified on the command line. This behaviour may be changed - * so that the parser reports all unknowns as errors by using the {@link #setErrorsOnUnknowns} method. - * - *

    - *
    CRC Card
    Responsibilities Collaborations - *
    Accept a command line specification. - *
    Parse a command line into properties, validating it against its specification. - *
    Report all errors between a command line and its specification. - *
    Provide a formatted usage string for a command line. - *
    Provide a formatted options in force string for a command line. - *
    Allow errors on unknowns behaviour to be turned on or off. - *
    - * - * @author Rupert Smith - */ -public class CommandLineParser -{ - /** - * Holds a mapping from command line option names to detailed information about those options. - * Use of a tree map ensures that the options are easy to print in alphabetical order as a usage string. - * An alternative might be to use a LinkedHashMap to print them in the order they are specified. - */ - private Map optionMap = new TreeMap(); - - /** Holds a list of parsing errors. */ - private List parsingErrors = new ArrayList(); - - /** Holds the regular head matcher to match command line options with. */ - private Matcher optionMatcher = null; - - /** Holds the parsed command line properties after parsing. */ - private Properties parsedProperties = null; - - /** Holds any trailing name=value pairs specified in the free arguments. */ - private Properties trailingProperties = null; - - /** Flag used to indicate that errors should be created for unknown options. False by default. */ - private boolean errorsOnUnknowns = false; - - /** - * Creates a command line options parser from a command line specification. This is passed to this constructor - * as an array of arrays of strings. Each array of strings specifies the command line for a single option. A static - * array may therefore easily be used to configure the command line parser in a single method call with an easily - * readable format. - * - *

    Each array of strings must be 2, 3, 4 or 5 elements long. If any of the last three elements are missing they - * are assumed to be null. The elements specify the following parameters: - *

      - *
    1. The name of the option without the leading '-'. For example, "file". To specify the format of the 'free' - * arguments use the option names "1", "2", ... and so on. - *
    2. The option comment. A line of text describing the usage of the option. For example, "The file to be processed." - *
    3. The options argument. This is a very short description of the argument to the option, often a single word - * or a reminder as to the arguments format. When this element is null the option is a flag and does not - * accept any arguments. For example, "filename" or "(unix | windows)" or null. The actual text specified - * is only used to print in the usage message to remind the user of the usage of the option. - *
    4. The mandatory flag. When set to "true" an option must always be specified. Any other value, including null, - * means that the option is mandatory. Flags are always mandatory (see class javadoc for explanation of why) so - * this is ignored for flags. - *
    5. A regular head describing the format that the argument must take. Ignored if null. - *
    - *

    An example call to this constructor is: - * - *

    -     * CommandLineParser commandLine = new CommandLineParser(
    -     *     new String[][] {{"file", "The file to be processed. ", "filename", "true"},
    -     *                     {"dir", "Directory to store results in. Current dir used if not set.", "out dir"},
    -     *                     {"os", "Operating system EOL format to use.", "(windows | unix)", null, "windows\|unix"},
    -     *                     {"v", "Verbose mode. Prints information about the processing as it goes."},
    -     *                     {"1", "The processing command to run.", "command", "true", "add\|remove\|list"}});
    -     * 
    - * - * @param config The configuration as an array of arrays of strings. - */ - public CommandLineParser(String[][] config) - { - // Loop through all the command line option specifications creating details for each in the options map. - for (String[] nextOptionSpec : config) - { - addOption(nextOptionSpec[0], nextOptionSpec[1], (nextOptionSpec.length > 2) ? nextOptionSpec[2] : null, - (nextOptionSpec.length > 3) && ("true".equals(nextOptionSpec[3])), - (nextOptionSpec.length > 4) ? nextOptionSpec[4] : null); - } - } - - /** - * Extracts all name=value pairs from the command line, sets them all as system properties and also returns - * a map of properties containing them. - * - * @param args The command line. - * @param commandLine The command line parser. - * @param properties The properties object to inject all parsed properties into (optional may be null). - * - * @return A set of properties containing all name=value pairs from the command line. - */ - public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties) - { - // Capture the command line arguments or display errors and correct usage and then exit. - Properties options = null; - - try - { - options = commandLine.parseCommandLine(args); - - // Add all the command line options and trailing settings to properties if the optional properties object - // to copy them into has been set. - if (properties != null) - { - commandLine.addTrailingPairsToProperties(properties); - commandLine.addOptionsToProperties(properties); - } - } - catch (IllegalArgumentException e) - { - System.out.println(commandLine.getErrors()); - System.out.println(commandLine.getUsage()); - System.exit(1); - } - - return options; - } - - /** - * Lists all the parsing errors from the most recent parsing in a string. - * - * @return All the parsing errors from the most recent parsing. - */ - public String getErrors() - { - // Return the empty string if there are no errors. - if (parsingErrors.isEmpty()) - { - return ""; - } - - // Concatenate all the parsing errors together. - String result = ""; - - for (String s : parsingErrors) - { - result += s; - } - - return result; - } - - /** - * Lists the properties set from the most recent parsing or an empty string if no parsing has been done yet. - * - * @return The properties set from the most recent parsing or an empty string if no parsing has been done yet. - */ - public String getOptionsInForce() - { - // Check if there are no properties to report and return and empty string if so. - if (parsedProperties == null) - { - return ""; - } - - // List all the properties. - String result = "Options in force:\n"; - - for (Map.Entry property : parsedProperties.entrySet()) - { - result += property.getKey() + " = " + property.getValue() + "\n"; - } - - return result; - } - - /** - * Generates a usage string consisting of the name of each option and each options argument description and - * comment. - * - * @return A usage string for all the options. - */ - public String getUsage() - { - String result = "Options:\n"; - - int optionWidth = 0; - int argumentWidth = 0; - - // Calculate the column widths required for aligned layout. - for (CommandLineOption optionInfo : optionMap.values()) - { - int oWidth = optionInfo.option.length(); - int aWidth = (optionInfo.argument != null) ? (optionInfo.argument.length()) : 0; - - optionWidth = (oWidth > optionWidth) ? oWidth : optionWidth; - argumentWidth = (aWidth > argumentWidth) ? aWidth : argumentWidth; - } - - // Print usage on each of the command line options. - for (CommandLineOption optionInfo : optionMap.values()) - { - String argString = ((optionInfo.argument != null) ? (optionInfo.argument) : ""); - String optionString = optionInfo.option; - - argString = rightPad(argString, " ", argumentWidth); - optionString = rightPad(optionString, " ", optionWidth); - - result += "-" + optionString + " " + argString + " " + optionInfo.comment + "\n"; - } - - return result; - } - - /** - * Right pads a string with a given string to a given size. This method will repeat the padder string as many - * times as is necessary until the exact specified size is reached. If the specified size is less than the size - * of the original string then the original string is returned unchanged. - * - *
    -     * Example1 - original string "cat", padder string "white", size 8 gives "catwhite".
    -     * Example2 - original string "cat", padder string "white", size 15 gives "catwhitewhitewh".
    -     * Example3 - original string "cat", padder string "white", size 2 gives "cat".
    -     * 
    - * - * @param stringToPad The original string. - * @param padder The string to pad onto the original string. - * @param size The required size of the new string. - * - * @return The newly padded string. - */ - public static String rightPad(String stringToPad, String padder, int size) - { - if (padder.length() == 0) - { - return stringToPad; - } - - StringBuffer strb = new StringBuffer(stringToPad); - StringCharacterIterator sci = new StringCharacterIterator(padder); - - while (strb.length() < size) - { - for (char ch = sci.first(); ch != CharacterIterator.DONE; ch = sci.next()) - { - if (strb.length() < size) - { - strb.append(String.valueOf(ch)); - } - } - } - - return strb.toString(); - } - - /** - * Control the behaviour of the errors on unkowns reporting. When turned on this reports all unkowns options - * as errors. When turned off, all unknowns are simply ignored. - * - * @param errors The setting of the errors on unkown flag. True to turn it on. - */ - public void setErrorsOnUnknowns(boolean errors) - { - errorsOnUnknowns = errors; - } - - /** - * Parses a set of command line arguments into a set of properties, keyed by the argument flag. The free arguments - * are keyed by integers as strings starting at "1" and then "2", ... and so on. - * - *

    See the class level comment for a description of the parsing rules. - * - * @param args The command line arguments. - * - * @return The arguments as a set of properties. - * - * @throws IllegalArgumentException If the command line cannot be parsed against its specification. If this exception - * is thrown a call to {@link #getErrors} will provide a diagnostic of the command - * line errors. - */ - public Properties parseCommandLine(String[] args) throws IllegalArgumentException - { - Properties options = new Properties(); - - // Used to keep count of the current 'free' argument. - int free = 1; - - // Used to indicate that the most recently parsed option is expecting arguments. - boolean expectingArgs = false; - - // The option that is expecting arguments from the next element of the command line. - String optionExpectingArgs = null; - - // Used to indicate that the most recently parsed option is a duplicate and should be ignored. - // boolean ignore = false; - - // Create the regular head matcher for the command line options. - String regexp = "^("; - int optionsAdded = 0; - - for (Iterator i = optionMap.keySet().iterator(); i.hasNext();) - { - String nextOption = i.next(); - - // Check that the option is not a free argument definition. - boolean notFree = false; - - try - { - Integer.parseInt(nextOption); - } - catch (NumberFormatException e) - { - notFree = true; - } - - // Add the option to the regular head matcher if it is not a free argument definition. - if (notFree) - { - regexp += nextOption + (i.hasNext() ? "|" : ""); - optionsAdded++; - } - } - - // There has to be more that one option in the regular head or else the compiler complains that the close - // cannot be nullable if the '?' token is used to make the matched option string optional. - regexp += ")" + ((optionsAdded > 0) ? "?" : "") + "(.*)"; - Pattern pattern = Pattern.compile(regexp); - - // Loop through all the command line arguments. - for (String arg1 : args) - { - // Check if the next command line argument begins with a '-' character and is therefore the start of - // an option. - if (arg1.startsWith("-")) - { - // Extract the value of the option without the leading '-'. - String arg = arg1.substring(1); - - // Match up to the longest matching option. - optionMatcher = pattern.matcher(arg); - optionMatcher.matches(); - - String matchedOption = optionMatcher.group(1); - - // Match any argument directly appended onto the longest matching option. - String matchedArg = optionMatcher.group(2); - - // Check that a known option was matched. - if ((matchedOption != null) && !"".equals(matchedOption)) - { - // Get the command line option information for the matched option. - CommandLineOption optionInfo = optionMap.get(matchedOption); - - // Check if this option is expecting arguments. - if (optionInfo.expectsArgs) - { - // The option is expecting arguments so swallow the next command line argument as an - // argument to this option. - expectingArgs = true; - optionExpectingArgs = matchedOption; - - // In the mean time set this options argument to the empty string in case no argument is ever - // supplied. - // options.put(matchedOption, ""); - } - - // Check if the option was matched on its own and is a flag in which case set that flag. - if ("".equals(matchedArg) && !optionInfo.expectsArgs) - { - options.put(matchedOption, "true"); - } - // The option was matched as a substring with its argument appended to it or is a flag that is - // condensed together with other flags. - else if (!"".equals(matchedArg)) - { - // Check if the option is a flag and therefore is allowed to be condensed together - // with other flags. - if (!optionInfo.expectsArgs) - { - // Set the first matched flag. - options.put(matchedOption, "true"); - - // Repeat the longest matching process on the remainder but ensure that the remainder - // consists only of flags as only flags may be condensed together in this fashion. - do - { - // Match the remainder against the options. - optionMatcher = pattern.matcher(matchedArg); - optionMatcher.matches(); - - matchedOption = optionMatcher.group(1); - matchedArg = optionMatcher.group(2); - - // Check that an option was matched. - if (matchedOption != null) - { - // Get the command line option information for the next matched option. - optionInfo = optionMap.get(matchedOption); - - // Ensure that the next option is a flag or raise an error if not. - if (optionInfo.expectsArgs) - { - parsingErrors.add("Option " + matchedOption + " cannot be combined with flags.\n"); - } - - options.put(matchedOption, "true"); - } - // The remainder could not be matched against a flag it is either an unknown flag - // or an illegal argument to a flag. - else - { - parsingErrors.add("Illegal argument to a flag in the option " + arg + "\n"); - - break; - } - } - // Continue until the remainder of the argument has all been matched with flags. - while (!"".equals(matchedArg)); - } - // The option is expecting an argument, so store the unmatched portion against it - // as its argument. - else - { - // Check the arguments format is correct against any specified format. - checkArgumentFormat(optionInfo, matchedArg); - - // Store the argument against its option (regardless of its format). - options.put(matchedOption, matchedArg); - - // The argument to this flag has already been supplied to it. Do not swallow the - // next command line argument as an argument to this flag. - expectingArgs = false; - } - } - } - else // No matching option was found. - { - // Add this to the list of parsing errors if errors on unkowns is being used. - if (errorsOnUnknowns) - { - parsingErrors.add("Option " + matchedOption + " is not a recognized option.\n"); - } - } - } - // The command line argument did not being with a '-' so it is an argument to the previous flag or it - // is a free argument. - else - { - // Check if a previous flag is expecting to swallow this next argument as its argument. - if (expectingArgs) - { - // Get the option info for the option waiting for arguments. - CommandLineOption optionInfo = optionMap.get(optionExpectingArgs); - - // Check the arguments format is correct against any specified format. - checkArgumentFormat(optionInfo, arg1); - - // Store the argument against its option (regardless of its format). - options.put(optionExpectingArgs, arg1); - - // Clear the expecting args flag now that the argument has been swallowed. - expectingArgs = false; - optionExpectingArgs = null; - } - // This command line option is not an argument to any option. Add it to the set of 'free' options. - else - { - // Get the option info for the free option, if there is any. - CommandLineOption optionInfo = optionMap.get(Integer.toString(free)); - - if (optionInfo != null) - { - // Check the arguments format is correct against any specified format. - checkArgumentFormat(optionInfo, arg1); - } - - // Add to the list of free options. - options.put(Integer.toString(free), arg1); - - // Move on to the next free argument. - free++; - } - } - } - - // Scan through all the specified options to check that all mandatory options have been set and that all flags - // that were not set are set to false in the set of properties. - for (CommandLineOption optionInfo : optionMap.values()) - { - // Check if this is a flag. - if (!optionInfo.expectsArgs) - { - // Check if the flag is not set in the properties and set it to false if so. - if (!options.containsKey(optionInfo.option)) - { - options.put(optionInfo.option, "false"); - } - } - // Check if this is a mandatory option and was not set. - else if (optionInfo.mandatory && !options.containsKey(optionInfo.option)) - { - // Create an error for the missing option. - parsingErrors.add("Option " + optionInfo.option + " is mandatory but not was not specified.\n"); - } - } - - // Check if there were any errors. - if (!parsingErrors.isEmpty()) - { - // Throw an illegal argument exception to signify that there were parsing errors. - throw new IllegalArgumentException(); - } - - // Convert any name/value pairs in the free arguments into properties in the parsed options. - trailingProperties = takeFreeArgsAsProperties(options, 1); - - parsedProperties = options; - - return options; - } - - /** - * If a command line has been parsed, calling this method sets all of its free arguments that were name=value pairs - * on the specified properties. - * - * @param properties The property set to add the name=value pairs to. - */ - public void addTrailingPairsToProperties(Properties properties) - { - if (trailingProperties != null) - { - for (Object propKey : trailingProperties.keySet()) - { - String name = (String) propKey; - String value = trailingProperties.getProperty(name); - - properties.setProperty(name, value); - } - } - } - - /** - * If a command line has been parsed, calling this method sets all of its options that were set to the specified - * properties. - * - * @param properties The property set to the options to. - */ - public void addOptionsToProperties(Properties properties) - { - if (parsedProperties != null) - { - for (Object propKey : parsedProperties.keySet()) - { - String name = (String) propKey; - String value = parsedProperties.getProperty(name); - - // This filters out all trailing items. - if (!name.matches("^[0-9]+$")) - { - properties.setProperty(name, value); - } - } - } - } - - /** - * Resets this command line parser after it has been used to parse a command line. This method will only need - * to be called to use this parser a second time which is not likely seeing as a command line is usually only - * specified once. However, it is exposed as a public method for the rare case where this may be done. - * - *

    Cleans the internal state of this parser, removing all stored errors and information about the options in - * force. - */ - public void reset() - { - parsingErrors = new ArrayList(); - parsedProperties = null; - } - - /** - * Adds the option to list of available command line options. - * - * @param option The option to add as an available command line option. - * @param comment A comment for the option. - * @param argument The text that appears after the option in the usage string. - * @param mandatory When true, indicates that this option is mandatory. - * @param formatRegexp The format that the argument must take, defined as a regular head. - */ - protected void addOption(String option, String comment, String argument, boolean mandatory, String formatRegexp) - { - // Check if usage text has been set in which case this option is expecting arguments. - boolean expectsArgs = (!((argument == null) || argument.equals(""))); - - // Add the option to the map of command line options. - CommandLineOption opt = new CommandLineOption(option, expectsArgs, comment, argument, mandatory, formatRegexp); - optionMap.put(option, opt); - } - - /** - * Converts the free arguments into property declarations. After parsing the command line the free arguments - * are numbered from 1, such that the parsed properties contain values for the keys "1", "2", ... This method - * converts any free arguments declared using the 'name=value' syntax into properties with key 'name', value - * 'value'. - * - *

    For example the comand line: - *

    -     * ... debug=true
    -     * 
    - * - *

    After parsing has properties: - *

    [[1, debug=true]]
    - * - *

    After applying this method the properties are: - *

    [[1, debug=true], [debug, true]]
    - * - * @param properties The parsed command line properties. - * @param from The free argument index to convert to properties from. - * - * @return The parsed command line properties, with free argument name value pairs too. - */ - private Properties takeFreeArgsAsProperties(Properties properties, int from) - { - Properties result = new Properties(); - - for (int i = from; true; i++) - { - String nextFreeArg = properties.getProperty(Integer.toString(i)); - - // Terminate the loop once all free arguments have been consumed. - if (nextFreeArg == null) - { - break; - } - - // Split it on the =, strip any whitespace and set it as a system property. - String[] nameValuePair = nextFreeArg.split("="); - - if (nameValuePair.length == 2) - { - result.setProperty(nameValuePair[0], nameValuePair[1]); - } - } - - return result; - } - - /** - * Checks the format of an argument to an option against its specified regular head format if one has - * been set. Any errors are added to the list of parsing errors. - * - * @param optionInfo The command line option information for the option which is havings its argument checked. - * @param matchedArg The string argument to the option. - */ - private void checkArgumentFormat(CommandLineOption optionInfo, String matchedArg) - { - // Check if this option enforces a format for its argument. - if (optionInfo.argumentFormatRegexp != null) - { - Pattern pattern = Pattern.compile(optionInfo.argumentFormatRegexp); - Matcher argumentMatcher = pattern.matcher(matchedArg); - - // Check if the argument does not meet its required format. - if (!argumentMatcher.matches()) - { - // Create an error for this badly formed argument. - parsingErrors.add("The argument to option " + optionInfo.option + " does not meet its required format.\n"); - } - } - } - - /** - * Holds information about a command line options. This includes what its name is, whether or not it is a flag, - * whether or not it is mandatory, what its user comment is, what its argument reminder text is and what its - * regular head format is. - * - *

    - *
    CRC Card
    Responsibilities Collaborations - *
    Hold details of a command line option. - *
    - * - * @author Rupert Smith - */ - protected class CommandLineOption - { - /** Holds the text for the flag to match this argument with. */ - public String option = null; - - /** Holds a string describing how to use this command line argument. */ - public String argument = null; - - /** Flag that determines whether or not this command line argument can take arguments. */ - public boolean expectsArgs = false; - - /** Holds a short comment describing what this command line argument is for. */ - public String comment = null; - - /** Flag that determines whether or not this is an mandatory command line argument. */ - public boolean mandatory = false; - - /** A regular head describing what format the argument to this option muist have. */ - public String argumentFormatRegexp = null; - - /** - * Create a command line option object that holds specific information about a command line option. - * - * @param option The text that matches the option. - * @param expectsArgs Whether or not the option expects arguments. It is a flag if this is false. - * @param comment A comment explaining how to use this option. - * @param argument A short reminder of the format of the argument to this option/ - * @param mandatory Set to true if this option is mandatory. - * @param formatRegexp The regular head that the argument to this option must meet to be valid. - */ - public CommandLineOption(String option, boolean expectsArgs, String comment, String argument, boolean mandatory, - String formatRegexp) - { - this.option = option; - this.expectsArgs = expectsArgs; - this.comment = comment; - this.argument = argument; - this.mandatory = mandatory; - this.argumentFormatRegexp = formatRegexp; - } - } -} +/* + * + * 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.util; + +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * CommandLineParser provides a utility for specifying the format of a command line and parsing command lines to ensure + * that they fit their specified format. A command line is made up of flags and options, both may be refered to as + * options. A flag is an option that does not take an argument (specifying it means it has the value 'true' and not + * specifying it means it has the value 'false'). Options must take arguments but they can be set up with defaults so + * that they take a default value when not set. Options may be mandatory in wich case it is an error not to specify + * them on the command line. Flags are never mandatory because they are implicitly set to false when not specified. + * + *

    Some examples command line are: + * + *

      + *
    • This one has two options that expect arguments: + *
      + * cruisecontrol -configfile cruisecontrol.xml -port 9000
      + * 
      + *
    • This has one no-arg flag and two 'free' arguments: + *
      + * zip -r project.zip project/*
      + * 
      + *
    • This one concatenates multiple flags into a single block with only one '-': + *
      + * jar -tvf mytar.tar
      + * 
      + * + *

      The parsing rules are: + * + *

        + *
      1. Flags may be combined after a single '-' because they never take arguments. Normally such flags are single letter + * flags but this is only a convention and not enforced. Flags of more than one letter are usually specified on their own. + *
      2. Options expecting arguments must always be on their own. + *
      3. The argument to an option may be seperated from it by whitespace or appended directly onto the option. + *
      4. The argument to an option may never begin with a '-' character. + *
      5. All other arguments not beginning with a '-' character are free arguments that do not belong to any option. + *
      6. The second or later of a set of duplicate or repeated flags override earlier ones. + *
      7. Options are matched up to the shortest matching option. This is because of the possibility of having no space + * between an option and its argument. This rules out the possibility of using two options where one is an opening + * substring of the other. For example, the options "foo" and "foobar" cannot be used on the same command line because + * it is not possible to distinguish the argument "-foobar" from being the "foobar" option or the "foo" option with + * the "bar" argument. + *
      + * + *

      By default, unknown options are simply ignored if specified on the command line. This behaviour may be changed + * so that the parser reports all unknowns as errors by using the {@link #setErrorsOnUnknowns} method. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Accept a command line specification. + *
      Parse a command line into properties, validating it against its specification. + *
      Report all errors between a command line and its specification. + *
      Provide a formatted usage string for a command line. + *
      Provide a formatted options in force string for a command line. + *
      Allow errors on unknowns behaviour to be turned on or off. + *
      + * + * @author Rupert Smith + */ +public class CommandLineParser +{ + /** + * Holds a mapping from command line option names to detailed information about those options. + * Use of a tree map ensures that the options are easy to print in alphabetical order as a usage string. + * An alternative might be to use a LinkedHashMap to print them in the order they are specified. + */ + private Map optionMap = new TreeMap(); + + /** Holds a list of parsing errors. */ + private List parsingErrors = new ArrayList(); + + /** Holds the regular head matcher to match command line options with. */ + private Matcher optionMatcher = null; + + /** Holds the parsed command line properties after parsing. */ + private Properties parsedProperties = null; + + /** Holds any trailing name=value pairs specified in the free arguments. */ + private Properties trailingProperties = null; + + /** Flag used to indicate that errors should be created for unknown options. False by default. */ + private boolean errorsOnUnknowns = false; + + /** + * Creates a command line options parser from a command line specification. This is passed to this constructor + * as an array of arrays of strings. Each array of strings specifies the command line for a single option. A static + * array may therefore easily be used to configure the command line parser in a single method call with an easily + * readable format. + * + *

      Each array of strings must be 2, 3, 4 or 5 elements long. If any of the last three elements are missing they + * are assumed to be null. The elements specify the following parameters: + *

        + *
      1. The name of the option without the leading '-'. For example, "file". To specify the format of the 'free' + * arguments use the option names "1", "2", ... and so on. + *
      2. The option comment. A line of text describing the usage of the option. For example, "The file to be processed." + *
      3. The options argument. This is a very short description of the argument to the option, often a single word + * or a reminder as to the arguments format. When this element is null the option is a flag and does not + * accept any arguments. For example, "filename" or "(unix | windows)" or null. The actual text specified + * is only used to print in the usage message to remind the user of the usage of the option. + *
      4. The mandatory flag. When set to "true" an option must always be specified. Any other value, including null, + * means that the option is mandatory. Flags are always mandatory (see class javadoc for explanation of why) so + * this is ignored for flags. + *
      5. A regular head describing the format that the argument must take. Ignored if null. + *
      + *

      An example call to this constructor is: + * + *

      +     * CommandLineParser commandLine = new CommandLineParser(
      +     *     new String[][] {{"file", "The file to be processed. ", "filename", "true"},
      +     *                     {"dir", "Directory to store results in. Current dir used if not set.", "out dir"},
      +     *                     {"os", "Operating system EOL format to use.", "(windows | unix)", null, "windows\|unix"},
      +     *                     {"v", "Verbose mode. Prints information about the processing as it goes."},
      +     *                     {"1", "The processing command to run.", "command", "true", "add\|remove\|list"}});
      +     * 
      + * + * @param config The configuration as an array of arrays of strings. + */ + public CommandLineParser(String[][] config) + { + // Loop through all the command line option specifications creating details for each in the options map. + for (String[] nextOptionSpec : config) + { + addOption(nextOptionSpec[0], nextOptionSpec[1], (nextOptionSpec.length > 2) ? nextOptionSpec[2] : null, + (nextOptionSpec.length > 3) && ("true".equals(nextOptionSpec[3])), + (nextOptionSpec.length > 4) ? nextOptionSpec[4] : null); + } + } + + /** + * Extracts all name=value pairs from the command line, sets them all as system properties and also returns + * a map of properties containing them. + * + * @param args The command line. + * @param commandLine The command line parser. + * @param properties The properties object to inject all parsed properties into (optional may be null). + * + * @return A set of properties containing all name=value pairs from the command line. + */ + public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties) + { + // Capture the command line arguments or display errors and correct usage and then exit. + Properties options = null; + + try + { + options = commandLine.parseCommandLine(args); + + // Add all the command line options and trailing settings to properties if the optional properties object + // to copy them into has been set. + if (properties != null) + { + commandLine.addTrailingPairsToProperties(properties); + commandLine.addOptionsToProperties(properties); + } + } + catch (IllegalArgumentException e) + { + System.out.println(commandLine.getErrors()); + System.out.println(commandLine.getUsage()); + System.exit(1); + } + + return options; + } + + /** + * Lists all the parsing errors from the most recent parsing in a string. + * + * @return All the parsing errors from the most recent parsing. + */ + public String getErrors() + { + // Return the empty string if there are no errors. + if (parsingErrors.isEmpty()) + { + return ""; + } + + // Concatenate all the parsing errors together. + String result = ""; + + for (String s : parsingErrors) + { + result += s; + } + + return result; + } + + /** + * Lists the properties set from the most recent parsing or an empty string if no parsing has been done yet. + * + * @return The properties set from the most recent parsing or an empty string if no parsing has been done yet. + */ + public String getOptionsInForce() + { + // Check if there are no properties to report and return and empty string if so. + if (parsedProperties == null) + { + return ""; + } + + // List all the properties. + String result = "Options in force:\n"; + + for (Map.Entry property : parsedProperties.entrySet()) + { + result += property.getKey() + " = " + property.getValue() + "\n"; + } + + return result; + } + + /** + * Generates a usage string consisting of the name of each option and each options argument description and + * comment. + * + * @return A usage string for all the options. + */ + public String getUsage() + { + String result = "Options:\n"; + + int optionWidth = 0; + int argumentWidth = 0; + + // Calculate the column widths required for aligned layout. + for (CommandLineOption optionInfo : optionMap.values()) + { + int oWidth = optionInfo.option.length(); + int aWidth = (optionInfo.argument != null) ? (optionInfo.argument.length()) : 0; + + optionWidth = (oWidth > optionWidth) ? oWidth : optionWidth; + argumentWidth = (aWidth > argumentWidth) ? aWidth : argumentWidth; + } + + // Print usage on each of the command line options. + for (CommandLineOption optionInfo : optionMap.values()) + { + String argString = ((optionInfo.argument != null) ? (optionInfo.argument) : ""); + String optionString = optionInfo.option; + + argString = rightPad(argString, " ", argumentWidth); + optionString = rightPad(optionString, " ", optionWidth); + + result += "-" + optionString + " " + argString + " " + optionInfo.comment + "\n"; + } + + return result; + } + + /** + * Right pads a string with a given string to a given size. This method will repeat the padder string as many + * times as is necessary until the exact specified size is reached. If the specified size is less than the size + * of the original string then the original string is returned unchanged. + * + *
      +     * Example1 - original string "cat", padder string "white", size 8 gives "catwhite".
      +     * Example2 - original string "cat", padder string "white", size 15 gives "catwhitewhitewh".
      +     * Example3 - original string "cat", padder string "white", size 2 gives "cat".
      +     * 
      + * + * @param stringToPad The original string. + * @param padder The string to pad onto the original string. + * @param size The required size of the new string. + * + * @return The newly padded string. + */ + public static String rightPad(String stringToPad, String padder, int size) + { + if (padder.length() == 0) + { + return stringToPad; + } + + StringBuffer strb = new StringBuffer(stringToPad); + StringCharacterIterator sci = new StringCharacterIterator(padder); + + while (strb.length() < size) + { + for (char ch = sci.first(); ch != CharacterIterator.DONE; ch = sci.next()) + { + if (strb.length() < size) + { + strb.append(String.valueOf(ch)); + } + } + } + + return strb.toString(); + } + + /** + * Control the behaviour of the errors on unkowns reporting. When turned on this reports all unkowns options + * as errors. When turned off, all unknowns are simply ignored. + * + * @param errors The setting of the errors on unkown flag. True to turn it on. + */ + public void setErrorsOnUnknowns(boolean errors) + { + errorsOnUnknowns = errors; + } + + /** + * Parses a set of command line arguments into a set of properties, keyed by the argument flag. The free arguments + * are keyed by integers as strings starting at "1" and then "2", ... and so on. + * + *

      See the class level comment for a description of the parsing rules. + * + * @param args The command line arguments. + * + * @return The arguments as a set of properties. + * + * @throws IllegalArgumentException If the command line cannot be parsed against its specification. If this exception + * is thrown a call to {@link #getErrors} will provide a diagnostic of the command + * line errors. + */ + public Properties parseCommandLine(String[] args) throws IllegalArgumentException + { + Properties options = new Properties(); + + // Used to keep count of the current 'free' argument. + int free = 1; + + // Used to indicate that the most recently parsed option is expecting arguments. + boolean expectingArgs = false; + + // The option that is expecting arguments from the next element of the command line. + String optionExpectingArgs = null; + + // Used to indicate that the most recently parsed option is a duplicate and should be ignored. + // boolean ignore = false; + + // Create the regular head matcher for the command line options. + String regexp = "^("; + int optionsAdded = 0; + + for (Iterator i = optionMap.keySet().iterator(); i.hasNext();) + { + String nextOption = i.next(); + + // Check that the option is not a free argument definition. + boolean notFree = false; + + try + { + Integer.parseInt(nextOption); + } + catch (NumberFormatException e) + { + notFree = true; + } + + // Add the option to the regular head matcher if it is not a free argument definition. + if (notFree) + { + regexp += nextOption + (i.hasNext() ? "|" : ""); + optionsAdded++; + } + } + + // There has to be more that one option in the regular head or else the compiler complains that the close + // cannot be nullable if the '?' token is used to make the matched option string optional. + regexp += ")" + ((optionsAdded > 0) ? "?" : "") + "(.*)"; + Pattern pattern = Pattern.compile(regexp); + + // Loop through all the command line arguments. + for (String arg1 : args) + { + // Check if the next command line argument begins with a '-' character and is therefore the start of + // an option. + if (arg1.startsWith("-")) + { + // Extract the value of the option without the leading '-'. + String arg = arg1.substring(1); + + // Match up to the longest matching option. + optionMatcher = pattern.matcher(arg); + optionMatcher.matches(); + + String matchedOption = optionMatcher.group(1); + + // Match any argument directly appended onto the longest matching option. + String matchedArg = optionMatcher.group(2); + + // Check that a known option was matched. + if ((matchedOption != null) && !"".equals(matchedOption)) + { + // Get the command line option information for the matched option. + CommandLineOption optionInfo = optionMap.get(matchedOption); + + // Check if this option is expecting arguments. + if (optionInfo.expectsArgs) + { + // The option is expecting arguments so swallow the next command line argument as an + // argument to this option. + expectingArgs = true; + optionExpectingArgs = matchedOption; + + // In the mean time set this options argument to the empty string in case no argument is ever + // supplied. + // options.put(matchedOption, ""); + } + + // Check if the option was matched on its own and is a flag in which case set that flag. + if ("".equals(matchedArg) && !optionInfo.expectsArgs) + { + options.put(matchedOption, "true"); + } + // The option was matched as a substring with its argument appended to it or is a flag that is + // condensed together with other flags. + else if (!"".equals(matchedArg)) + { + // Check if the option is a flag and therefore is allowed to be condensed together + // with other flags. + if (!optionInfo.expectsArgs) + { + // Set the first matched flag. + options.put(matchedOption, "true"); + + // Repeat the longest matching process on the remainder but ensure that the remainder + // consists only of flags as only flags may be condensed together in this fashion. + do + { + // Match the remainder against the options. + optionMatcher = pattern.matcher(matchedArg); + optionMatcher.matches(); + + matchedOption = optionMatcher.group(1); + matchedArg = optionMatcher.group(2); + + // Check that an option was matched. + if (matchedOption != null) + { + // Get the command line option information for the next matched option. + optionInfo = optionMap.get(matchedOption); + + // Ensure that the next option is a flag or raise an error if not. + if (optionInfo.expectsArgs) + { + parsingErrors.add("Option " + matchedOption + " cannot be combined with flags.\n"); + } + + options.put(matchedOption, "true"); + } + // The remainder could not be matched against a flag it is either an unknown flag + // or an illegal argument to a flag. + else + { + parsingErrors.add("Illegal argument to a flag in the option " + arg + "\n"); + + break; + } + } + // Continue until the remainder of the argument has all been matched with flags. + while (!"".equals(matchedArg)); + } + // The option is expecting an argument, so store the unmatched portion against it + // as its argument. + else + { + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, matchedArg); + + // Store the argument against its option (regardless of its format). + options.put(matchedOption, matchedArg); + + // The argument to this flag has already been supplied to it. Do not swallow the + // next command line argument as an argument to this flag. + expectingArgs = false; + } + } + } + else // No matching option was found. + { + // Add this to the list of parsing errors if errors on unkowns is being used. + if (errorsOnUnknowns) + { + parsingErrors.add("Option " + matchedOption + " is not a recognized option.\n"); + } + } + } + // The command line argument did not being with a '-' so it is an argument to the previous flag or it + // is a free argument. + else + { + // Check if a previous flag is expecting to swallow this next argument as its argument. + if (expectingArgs) + { + // Get the option info for the option waiting for arguments. + CommandLineOption optionInfo = optionMap.get(optionExpectingArgs); + + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, arg1); + + // Store the argument against its option (regardless of its format). + options.put(optionExpectingArgs, arg1); + + // Clear the expecting args flag now that the argument has been swallowed. + expectingArgs = false; + optionExpectingArgs = null; + } + // This command line option is not an argument to any option. Add it to the set of 'free' options. + else + { + // Get the option info for the free option, if there is any. + CommandLineOption optionInfo = optionMap.get(Integer.toString(free)); + + if (optionInfo != null) + { + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, arg1); + } + + // Add to the list of free options. + options.put(Integer.toString(free), arg1); + + // Move on to the next free argument. + free++; + } + } + } + + // Scan through all the specified options to check that all mandatory options have been set and that all flags + // that were not set are set to false in the set of properties. + for (CommandLineOption optionInfo : optionMap.values()) + { + // Check if this is a flag. + if (!optionInfo.expectsArgs) + { + // Check if the flag is not set in the properties and set it to false if so. + if (!options.containsKey(optionInfo.option)) + { + options.put(optionInfo.option, "false"); + } + } + // Check if this is a mandatory option and was not set. + else if (optionInfo.mandatory && !options.containsKey(optionInfo.option)) + { + // Create an error for the missing option. + parsingErrors.add("Option " + optionInfo.option + " is mandatory but not was not specified.\n"); + } + } + + // Check if there were any errors. + if (!parsingErrors.isEmpty()) + { + // Throw an illegal argument exception to signify that there were parsing errors. + throw new IllegalArgumentException(); + } + + // Convert any name/value pairs in the free arguments into properties in the parsed options. + trailingProperties = takeFreeArgsAsProperties(options, 1); + + parsedProperties = options; + + return options; + } + + /** + * If a command line has been parsed, calling this method sets all of its free arguments that were name=value pairs + * on the specified properties. + * + * @param properties The property set to add the name=value pairs to. + */ + public void addTrailingPairsToProperties(Properties properties) + { + if (trailingProperties != null) + { + for (Object propKey : trailingProperties.keySet()) + { + String name = (String) propKey; + String value = trailingProperties.getProperty(name); + + properties.setProperty(name, value); + } + } + } + + /** + * If a command line has been parsed, calling this method sets all of its options that were set to the specified + * properties. + * + * @param properties The property set to the options to. + */ + public void addOptionsToProperties(Properties properties) + { + if (parsedProperties != null) + { + for (Object propKey : parsedProperties.keySet()) + { + String name = (String) propKey; + String value = parsedProperties.getProperty(name); + + // This filters out all trailing items. + if (!name.matches("^[0-9]+$")) + { + properties.setProperty(name, value); + } + } + } + } + + /** + * Resets this command line parser after it has been used to parse a command line. This method will only need + * to be called to use this parser a second time which is not likely seeing as a command line is usually only + * specified once. However, it is exposed as a public method for the rare case where this may be done. + * + *

      Cleans the internal state of this parser, removing all stored errors and information about the options in + * force. + */ + public void reset() + { + parsingErrors = new ArrayList(); + parsedProperties = null; + } + + /** + * Adds the option to list of available command line options. + * + * @param option The option to add as an available command line option. + * @param comment A comment for the option. + * @param argument The text that appears after the option in the usage string. + * @param mandatory When true, indicates that this option is mandatory. + * @param formatRegexp The format that the argument must take, defined as a regular head. + */ + protected void addOption(String option, String comment, String argument, boolean mandatory, String formatRegexp) + { + // Check if usage text has been set in which case this option is expecting arguments. + boolean expectsArgs = (!((argument == null) || argument.equals(""))); + + // Add the option to the map of command line options. + CommandLineOption opt = new CommandLineOption(option, expectsArgs, comment, argument, mandatory, formatRegexp); + optionMap.put(option, opt); + } + + /** + * Converts the free arguments into property declarations. After parsing the command line the free arguments + * are numbered from 1, such that the parsed properties contain values for the keys "1", "2", ... This method + * converts any free arguments declared using the 'name=value' syntax into properties with key 'name', value + * 'value'. + * + *

      For example the comand line: + *

      +     * ... debug=true
      +     * 
      + * + *

      After parsing has properties: + *

      [[1, debug=true]]
      + * + *

      After applying this method the properties are: + *

      [[1, debug=true], [debug, true]]
      + * + * @param properties The parsed command line properties. + * @param from The free argument index to convert to properties from. + * + * @return The parsed command line properties, with free argument name value pairs too. + */ + private Properties takeFreeArgsAsProperties(Properties properties, int from) + { + Properties result = new Properties(); + + for (int i = from; true; i++) + { + String nextFreeArg = properties.getProperty(Integer.toString(i)); + + // Terminate the loop once all free arguments have been consumed. + if (nextFreeArg == null) + { + break; + } + + // Split it on the =, strip any whitespace and set it as a system property. + String[] nameValuePair = nextFreeArg.split("="); + + if (nameValuePair.length == 2) + { + result.setProperty(nameValuePair[0], nameValuePair[1]); + } + } + + return result; + } + + /** + * Checks the format of an argument to an option against its specified regular head format if one has + * been set. Any errors are added to the list of parsing errors. + * + * @param optionInfo The command line option information for the option which is havings its argument checked. + * @param matchedArg The string argument to the option. + */ + private void checkArgumentFormat(CommandLineOption optionInfo, String matchedArg) + { + // Check if this option enforces a format for its argument. + if (optionInfo.argumentFormatRegexp != null) + { + Pattern pattern = Pattern.compile(optionInfo.argumentFormatRegexp); + Matcher argumentMatcher = pattern.matcher(matchedArg); + + // Check if the argument does not meet its required format. + if (!argumentMatcher.matches()) + { + // Create an error for this badly formed argument. + parsingErrors.add("The argument to option " + optionInfo.option + " does not meet its required format.\n"); + } + } + } + + /** + * Holds information about a command line options. This includes what its name is, whether or not it is a flag, + * whether or not it is mandatory, what its user comment is, what its argument reminder text is and what its + * regular head format is. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Hold details of a command line option. + *
      + * + * @author Rupert Smith + */ + protected class CommandLineOption + { + /** Holds the text for the flag to match this argument with. */ + public String option = null; + + /** Holds a string describing how to use this command line argument. */ + public String argument = null; + + /** Flag that determines whether or not this command line argument can take arguments. */ + public boolean expectsArgs = false; + + /** Holds a short comment describing what this command line argument is for. */ + public String comment = null; + + /** Flag that determines whether or not this is an mandatory command line argument. */ + public boolean mandatory = false; + + /** A regular head describing what format the argument to this option muist have. */ + public String argumentFormatRegexp = null; + + /** + * Create a command line option object that holds specific information about a command line option. + * + * @param option The text that matches the option. + * @param expectsArgs Whether or not the option expects arguments. It is a flag if this is false. + * @param comment A comment explaining how to use this option. + * @param argument A short reminder of the format of the argument to this option/ + * @param mandatory Set to true if this option is mandatory. + * @param formatRegexp The regular head that the argument to this option must meet to be valid. + */ + public CommandLineOption(String option, boolean expectsArgs, String comment, String argument, boolean mandatory, + String formatRegexp) + { + this.option = option; + this.expectsArgs = expectsArgs; + this.comment = comment; + this.argument = argument; + this.mandatory = mandatory; + this.argumentFormatRegexp = formatRegexp; + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ContextualProperties.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ContextualProperties.java index cabbf7869a..14de96d165 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ContextualProperties.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ContextualProperties.java @@ -1,494 +1,494 @@ -/* - * - * 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.util; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; - -/** - * ContextualProperties is an extension of {@link java.util.Properties} that automatically selects properties based on an - * environment parameter (defined by the system property {@link #ENV_SYS_PROPERTY}), the name of a class, plus a modifier - * (which can be used to name a method of a class) and a property key. It also supports the definition of arrays of - * property values using indexes. The properties are searched in the following order until a match is found: - * - *

        - *
      1. environment + class name with package name + modifier + key - *
      2. environment + class name with package name + key - *
      3. environment + key - *
      4. class name with package name + modifier + key - *
      5. class name with package name + key - *
      6. key - *
      - * - *

      To create arrays of property values add index numbers onto the end of the property keys. An array of string values - * will be created with the elements of the array set to the value of the property at the matching index. Ideally the - * index values will be contiguous, starting at 0. This does not need to be the case however. If an array definition - * begins at index n, and ends at index m, Then an array big enough to hold m + 1 elements will be created and populated - * with values from n to m. Values before n and any missing values between n and m will be null in the array. - * - *

      To give an example, suppose you have two different environments 'DEVELOPMENT' and 'PRODUCTION' and they each need - * the same properties but set to different values for each environment and some properties the same in both, you could - * create a properties file like: - * - *

      - * # Project configuration properties file.
      - *
      - * # These properties are environment specific.
      - * DEVELOPMENT.debug=true
      - * PRODUCTION.debug=false
      - *
      - * # Always debug MyClass in all environments but not the myMethod method.
      - * MyClass.debug=true
      - * MyClass.myMethod.debug=false
      - *
      - * # Set up an array of my ten favourite animals. Leave elements 3 to 8 as null as I haven't decided on them yet.
      - * animals.0=cat
      - * animals.1=dog
      - * animals.2=elephant
      - * animals.9=lion
      - *
      - * # This is a default value that will be used when the environment is not known.
      - * debug=false
      - *
      - * - *

      The most specific definition of a property is searched for first moving out to the most general. This allows - * general property defaults to be set and then overiden for specific uses by some classes and modifiers. - * - *

      A ContextualProperties object can be loaded in the same way as a java.utils.Properties. A recommended way to do - * this that does not assume that the properties file is a file (it could be in a jar) is to load the properties from the - * url for the resource lookup up on the classpath: - * - *

      - * Properties configProperties = new ContextualProperties();
      - * configProperties.load(this.getClass().getClassLoader().getResourceAsStream("config.properties"));
      - *
      - * - *

      EnvironmentProperties will load the 'DEVELOPMENT.debug' property or 'PROUCTION.debug' property based on the setting - * of the system environment property. If a matching property for the environment cannot be found then the simple property - * name without the environment pre-pended onto it will be used instead. This 'use of default environments' behaviour is - * turned on initially but it can be disabled by calling the {@link #useDefaultEnvironments} method. - * - *

      When a property matching a key cannot be found then the property accessor methods will always return null. If a - * default value for a property exists but the 'use of default environments' behavious prevents it being used then the - * accessor methods will return null. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      Automatically select properties dependant on environment, class name and modifier as well as property key. - *
      Convert indexed properties into arrays. - *
      - * - * @author Rupert Smith - */ -public class ContextualProperties extends ParsedProperties -{ - /** The name of the system property that is used to define the environment. */ - public static final String ENV_SYS_PROPERTY = "environment"; - - /** - *

      Holds the iteration count down order. - * - *

      If e = 4, b = 2, m = 1 then the iteration order or i is 7,6,4 and then if using environment defaults 3,2,0 - * where the accessor key is: - * (i & e != 0 ? environment : "") + (i & b != 0 ? base : "") + (1 + m != 0 ? modifier : "") + key - * - *

      In other words the presence or otherwise of the three least significant bits when counting down from 7 - * specifies which of the environment, base and modifier are to be included in the key where the environment, base - * and modifier stand for the bits in positions 2, 1 and 0. The numbers 5 and 1 are missed out of the count because - * they stand for the case where the modifier is used without the base which is not done. - */ - private static final int[] ORDER = new int[] { 7, 6, 4, 3, 2, 0 }; - - /** - * Defines the point in the iteration count order below which the 'use environment defaults' feature is being used. - */ - private static final int ENVIRONMENT_DEFAULTS_CUTOFF = 4; - - /** Defines the bit representation for the environment in the key ordering. See {@link #ORDER}. */ - private static final int E = 4; - - /** Defines the bit representation for the base in the key ordering. See {@link #ORDER}. */ - private static final int B = 2; - - /** Defines the bit representation for the modifier in the key ordering. See {@link #ORDER}. */ - private static final int M = 1; - - /** Used to hold the value of the environment system property. */ - private String environment; - - /** Used to indicate that the 'use of defaults' behaviour should be used. */ - private boolean useDefaults = true; - - /** Used to hold all the array properties. This is a mapping from property names to ArrayLists of Strings. */ - protected Map arrayProperties = new HashMap(); - - /** - * Default constructor that builds a ContextualProperties that uses environment defaults. - */ - public ContextualProperties() - { - super(); - - // Keep the value of the system environment property. - environment = System.getProperty(ENV_SYS_PROPERTY); - } - - /** - * Creates a ContextualProperties that uses environment defaults and is initialized with the specified properties. - * - * @param props The properties to initialize this with. - */ - public ContextualProperties(Properties props) - { - super(props); - - // Keep the value of the system environment property. - environment = System.getProperty(ENV_SYS_PROPERTY); - - // Extract any array properties as arrays. - createArrayProperties(); - } - - /** - * Parses an input stream as properties. - * - * @param inStream The input stream to read the properties from. - * - * @exception IOException If there is an IO error during reading from the input stream. - */ - public void load(InputStream inStream) throws IOException - { - super.load(inStream); - - // Extract any array properties as arrays. - createArrayProperties(); - } - - /** - * Tells this environment aware properties object whether it should use default environment properties without a - * pre-pended environment when a property for the current environment cannot be found. - * - * @param flag True to use defaults, false to not use defaults. - */ - public void useDefaultEnvironments(boolean flag) - { - useDefaults = flag; - } - - /** - * Looks up a property value relative to the environment, callers class and method. The default environment will be - * checked for a matching property if defaults are being used. In order to work out the callers class and method this - * method throws an exception and then searches one level up its stack frames. - * - * @param key The property key. - * - * @return The value of this property searching from the most specific definition (environment, class, method, key) - * to the most general (key only), unless use of default environments is turned off in which case the most general - * proeprty searched is (environment, key). - */ - public String getProperty(String key) - { - // Try to get the callers class name and method name by examing the stack. - String className = null; - String methodName = null; - - // Java 1.4 onwards only. - /*try - { - throw new Exception(); - } - catch (Exception e) - { - StackTraceElement[] stack = e.getStackTrace(); - - // Check that the stack trace contains at least two elements, one for this method and one for the caller. - if (stack.length >= 2) - { - className = stack[1].getClassName(); - methodName = stack[1].getMethodName(); - } - }*/ - - // Java 1.5 onwards only. - StackTraceElement[] stack = Thread.currentThread().getStackTrace(); - - // Check that the stack trace contains at least two elements, one for this method and one for the caller. - if (stack.length >= 2) - { - className = stack[1].getClassName(); - methodName = stack[1].getMethodName(); - } - - // Java 1.3 and before? Not sure, some horrible thing that parses the text spat out by printStackTrace? - - return getProperty(className, methodName, key); - } - - /** - * Looks up a property value relative to the environment, base class and modifier. The default environment will be - * checked for a matching property if defaults are being used. - * - * @param base An object of the class to retrieve properties relative to. - * @param modifier The modifier (which may stand for a method of the class). - * @param key The property key. - * - * @return The value of this property searching from the most specific definition (environment, class, modifier, key) - * to the most general (key only), unless use of default environments is turned off in which case the most general - * property searched is (environment, key). - */ - public String getProperty(Object base, String modifier, String key) - { - return getProperty(base.getClass().getName(), modifier, key); - } - - /** - * Looks up a property value relative to the environment, base class and modifier. The default environment will be - * checked for a matching property if defaults are being used. - * - * @param base The name of the class to retrieve properties relative to. - * @param modifier The modifier (which may stand for a method of the class). - * @param key The property key. - * - * @return The value of this property searching from the most specific definition (environment, class, modifier, key) - * to the most general (key only), unless use of default environments is turned off in which case the most general - * property searched is (environment, key). - */ - public String getProperty(String base, String modifier, String key) - { - String result = null; - - // Loop over the key orderings, from the most specific to the most general, until a matching value is found. - for (Iterator i = getKeyIterator(base, modifier, key); i.hasNext();) - { - String nextKey = (String) i.next(); - - result = super.getProperty(nextKey); - - if (result != null) - { - break; - } - } - - return result; - } - - /** - * Looks up an array property value relative to the environment, callers class and method. The default environment - * will be checked for a matching array property if defaults are being used. In order to work out the callers class - * and method this method throws an exception and then searches one level up its stack frames. - * - * @param key The property key. - * - * @return The array value of this indexed property searching from the most specific definition (environment, class, - * method, key) to the most general (key only), unless use of default environments is turned off in which - * case the most general proeprty searched is (environment, key). - */ - public String[] getProperties(String key) - { - // Try to get the callers class name and method name by throwing an exception an searching the stack frames. - String className = null; - String methodName = null; - - /* Java 1.4 onwards only. - try { - throw new Exception(); - } catch (Exception e) { - StackTraceElement[] stack = e.getStackTrace(); - // Check that the stack trace contains at least two elements, one for this method and one for the caller. - if (stack.length >= 2) { - className = stack[1].getClassName(); - methodName = stack[1].getMethodName(); - } - }*/ - return getProperties(className, methodName, key); - } - - /** - * Looks up an array property value relative to the environment, base class and modifier. The default environment will - * be checked for a matching array property if defaults are being used. - * - * @param base An object of the class to retrieve properties relative to. - * @param modifier The modifier (which may stand for a method of the class). - * @param key The property key. - * - * @return The array value of this indexed property searching from the most specific definition (environment, class, - * modifier, key) to the most general (key only), unless use of default environments is turned off in which - * case the most general proeprty searched is (environment, key). - */ - public String[] getProperties(Object base, String modifier, String key) - { - return getProperties(base.getClass().getName(), modifier, key); - } - - /** - * Looks up an array property value relative to the environment, base class and modifier. The default environment will - * be checked for a matching array property if defaults are being used. - * - * @param base The name of the class to retrieve properties relative to. - * @param modifier The modifier (which may stand for a method of the class). - * @param key The property key. - * - * @return The array value of this indexed property searching from the most specific definition (environment, class, - * modifier, key) to the most general (key only), unless use of default environments is turned off in which - * case the most general property searched is (environment, key). - */ - public String[] getProperties(String base, String modifier, String key) - { - String[] result = null; - - // Loop over the key orderings, from the most specific to the most general, until a matching value is found. - for (Iterator i = getKeyIterator(base, modifier, key); i.hasNext();) - { - String nextKey = (String) i.next(); - ArrayList arrayList = (ArrayList) arrayProperties.get(nextKey); - - if (arrayList != null) - { - result = (String[]) arrayList.toArray(new String[] {}); - - break; - } - } - - return result; - } - - /** - * For a given environment, base, modifier and key and setting of the use of default environments feature this - * generates an iterator that walks over the order in which to try and access properties. - * - *

      See the {@link #ORDER} constant for an explanation of how the key ordering is generated. - * - * @param base The name of the class to retrieve properties relative to. - * @param modifier The modifier (which may stand for a method of the class). - * @param key The property key. - * - * @return An Iterator over String keys defining the order in which properties should be accessed. - */ - protected Iterator getKeyIterator(final String base, final String modifier, final String key) - { - return new Iterator() - { - // The key ordering count always begins at the start of the ORDER array. - private int i = 0; - - public boolean hasNext() - { - return (useDefaults ? ((i < ORDER.length) && (ORDER[i] > ENVIRONMENT_DEFAULTS_CUTOFF)) - : (i < ORDER.length)); - } - - public Object next() - { - // Check that there is a next element and return null if not. - if (!hasNext()) - { - return null; - } - - // Get the next ordering count. - int o = ORDER[i]; - - // Do bit matching on the count to choose which elements to include in the key. - String result = - (((o & E) != 0) ? (environment + ".") : "") + (((o & B) != 0) ? (base + ".") : "") - + (((o & M) != 0) ? (modifier + ".") : "") + key; - - // Increment the iterator to get the next key on the next call. - i++; - - return result; - } - - public void remove() - { - // This method is not supported. - throw new UnsupportedOperationException("remove() is not supported on this key order iterator as " - + "the ordering cannot be changed"); - } - }; - } - - /** - * Scans all the properties in the parent Properties object and creates arrays for any array property definitions. - * - *

      Array properties are defined with indexes. For example: - * - *

      - * property.1=one
      - * property.2=two
      - * property.3=three
      - *
      - * - *

      Note that these properties will be stored as the 'empty string' or "" property array. - * - *

      - * .1=one
      - * 2=two
      - *
      - */ - protected void createArrayProperties() - { - // Scan through all defined properties. - for (Object o : keySet()) - { - String key = (String) o; - String value = super.getProperty(key); - - // Split the property key into everything before the last '.' and after it. - int lastDotIndex = key.lastIndexOf('.'); - String keyEnding = key.substring(lastDotIndex + 1, key.length()); - String keyStart = key.substring(0, (lastDotIndex == -1) ? 0 : lastDotIndex); - - // Check if the property key ends in an integer, in which case it is an array property. - int index = 0; - - try - { - index = Integer.parseInt(keyEnding); - } - // The ending is not an integer so its not an array. - catch (NumberFormatException e) - { - // Scan the next property. - continue; - } - - // Check if an array property already exists for this base name and create one if not. - ArrayList propArray = (ArrayList) arrayProperties.get(keyStart); - - if (propArray == null) - { - propArray = new ArrayList(); - arrayProperties.put(keyStart, propArray); - } - - // Add the new property value to the array property for the index. - propArray.set(index, value); - } - } -} +/* + * + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + * ContextualProperties is an extension of {@link java.util.Properties} that automatically selects properties based on an + * environment parameter (defined by the system property {@link #ENV_SYS_PROPERTY}), the name of a class, plus a modifier + * (which can be used to name a method of a class) and a property key. It also supports the definition of arrays of + * property values using indexes. The properties are searched in the following order until a match is found: + * + *

        + *
      1. environment + class name with package name + modifier + key + *
      2. environment + class name with package name + key + *
      3. environment + key + *
      4. class name with package name + modifier + key + *
      5. class name with package name + key + *
      6. key + *
      + * + *

      To create arrays of property values add index numbers onto the end of the property keys. An array of string values + * will be created with the elements of the array set to the value of the property at the matching index. Ideally the + * index values will be contiguous, starting at 0. This does not need to be the case however. If an array definition + * begins at index n, and ends at index m, Then an array big enough to hold m + 1 elements will be created and populated + * with values from n to m. Values before n and any missing values between n and m will be null in the array. + * + *

      To give an example, suppose you have two different environments 'DEVELOPMENT' and 'PRODUCTION' and they each need + * the same properties but set to different values for each environment and some properties the same in both, you could + * create a properties file like: + * + *

      + * # Project configuration properties file.
      + *
      + * # These properties are environment specific.
      + * DEVELOPMENT.debug=true
      + * PRODUCTION.debug=false
      + *
      + * # Always debug MyClass in all environments but not the myMethod method.
      + * MyClass.debug=true
      + * MyClass.myMethod.debug=false
      + *
      + * # Set up an array of my ten favourite animals. Leave elements 3 to 8 as null as I haven't decided on them yet.
      + * animals.0=cat
      + * animals.1=dog
      + * animals.2=elephant
      + * animals.9=lion
      + *
      + * # This is a default value that will be used when the environment is not known.
      + * debug=false
      + *
      + * + *

      The most specific definition of a property is searched for first moving out to the most general. This allows + * general property defaults to be set and then overiden for specific uses by some classes and modifiers. + * + *

      A ContextualProperties object can be loaded in the same way as a java.utils.Properties. A recommended way to do + * this that does not assume that the properties file is a file (it could be in a jar) is to load the properties from the + * url for the resource lookup up on the classpath: + * + *

      + * Properties configProperties = new ContextualProperties();
      + * configProperties.load(this.getClass().getClassLoader().getResourceAsStream("config.properties"));
      + *
      + * + *

      EnvironmentProperties will load the 'DEVELOPMENT.debug' property or 'PROUCTION.debug' property based on the setting + * of the system environment property. If a matching property for the environment cannot be found then the simple property + * name without the environment pre-pended onto it will be used instead. This 'use of default environments' behaviour is + * turned on initially but it can be disabled by calling the {@link #useDefaultEnvironments} method. + * + *

      When a property matching a key cannot be found then the property accessor methods will always return null. If a + * default value for a property exists but the 'use of default environments' behavious prevents it being used then the + * accessor methods will return null. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Automatically select properties dependant on environment, class name and modifier as well as property key. + *
      Convert indexed properties into arrays. + *
      + * + * @author Rupert Smith + */ +public class ContextualProperties extends ParsedProperties +{ + /** The name of the system property that is used to define the environment. */ + public static final String ENV_SYS_PROPERTY = "environment"; + + /** + *

      Holds the iteration count down order. + * + *

      If e = 4, b = 2, m = 1 then the iteration order or i is 7,6,4 and then if using environment defaults 3,2,0 + * where the accessor key is: + * (i & e != 0 ? environment : "") + (i & b != 0 ? base : "") + (1 + m != 0 ? modifier : "") + key + * + *

      In other words the presence or otherwise of the three least significant bits when counting down from 7 + * specifies which of the environment, base and modifier are to be included in the key where the environment, base + * and modifier stand for the bits in positions 2, 1 and 0. The numbers 5 and 1 are missed out of the count because + * they stand for the case where the modifier is used without the base which is not done. + */ + private static final int[] ORDER = new int[] { 7, 6, 4, 3, 2, 0 }; + + /** + * Defines the point in the iteration count order below which the 'use environment defaults' feature is being used. + */ + private static final int ENVIRONMENT_DEFAULTS_CUTOFF = 4; + + /** Defines the bit representation for the environment in the key ordering. See {@link #ORDER}. */ + private static final int E = 4; + + /** Defines the bit representation for the base in the key ordering. See {@link #ORDER}. */ + private static final int B = 2; + + /** Defines the bit representation for the modifier in the key ordering. See {@link #ORDER}. */ + private static final int M = 1; + + /** Used to hold the value of the environment system property. */ + private String environment; + + /** Used to indicate that the 'use of defaults' behaviour should be used. */ + private boolean useDefaults = true; + + /** Used to hold all the array properties. This is a mapping from property names to ArrayLists of Strings. */ + protected Map arrayProperties = new HashMap(); + + /** + * Default constructor that builds a ContextualProperties that uses environment defaults. + */ + public ContextualProperties() + { + super(); + + // Keep the value of the system environment property. + environment = System.getProperty(ENV_SYS_PROPERTY); + } + + /** + * Creates a ContextualProperties that uses environment defaults and is initialized with the specified properties. + * + * @param props The properties to initialize this with. + */ + public ContextualProperties(Properties props) + { + super(props); + + // Keep the value of the system environment property. + environment = System.getProperty(ENV_SYS_PROPERTY); + + // Extract any array properties as arrays. + createArrayProperties(); + } + + /** + * Parses an input stream as properties. + * + * @param inStream The input stream to read the properties from. + * + * @exception IOException If there is an IO error during reading from the input stream. + */ + public void load(InputStream inStream) throws IOException + { + super.load(inStream); + + // Extract any array properties as arrays. + createArrayProperties(); + } + + /** + * Tells this environment aware properties object whether it should use default environment properties without a + * pre-pended environment when a property for the current environment cannot be found. + * + * @param flag True to use defaults, false to not use defaults. + */ + public void useDefaultEnvironments(boolean flag) + { + useDefaults = flag; + } + + /** + * Looks up a property value relative to the environment, callers class and method. The default environment will be + * checked for a matching property if defaults are being used. In order to work out the callers class and method this + * method throws an exception and then searches one level up its stack frames. + * + * @param key The property key. + * + * @return The value of this property searching from the most specific definition (environment, class, method, key) + * to the most general (key only), unless use of default environments is turned off in which case the most general + * proeprty searched is (environment, key). + */ + public String getProperty(String key) + { + // Try to get the callers class name and method name by examing the stack. + String className = null; + String methodName = null; + + // Java 1.4 onwards only. + /*try + { + throw new Exception(); + } + catch (Exception e) + { + StackTraceElement[] stack = e.getStackTrace(); + + // Check that the stack trace contains at least two elements, one for this method and one for the caller. + if (stack.length >= 2) + { + className = stack[1].getClassName(); + methodName = stack[1].getMethodName(); + } + }*/ + + // Java 1.5 onwards only. + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + + // Check that the stack trace contains at least two elements, one for this method and one for the caller. + if (stack.length >= 2) + { + className = stack[1].getClassName(); + methodName = stack[1].getMethodName(); + } + + // Java 1.3 and before? Not sure, some horrible thing that parses the text spat out by printStackTrace? + + return getProperty(className, methodName, key); + } + + /** + * Looks up a property value relative to the environment, base class and modifier. The default environment will be + * checked for a matching property if defaults are being used. + * + * @param base An object of the class to retrieve properties relative to. + * @param modifier The modifier (which may stand for a method of the class). + * @param key The property key. + * + * @return The value of this property searching from the most specific definition (environment, class, modifier, key) + * to the most general (key only), unless use of default environments is turned off in which case the most general + * property searched is (environment, key). + */ + public String getProperty(Object base, String modifier, String key) + { + return getProperty(base.getClass().getName(), modifier, key); + } + + /** + * Looks up a property value relative to the environment, base class and modifier. The default environment will be + * checked for a matching property if defaults are being used. + * + * @param base The name of the class to retrieve properties relative to. + * @param modifier The modifier (which may stand for a method of the class). + * @param key The property key. + * + * @return The value of this property searching from the most specific definition (environment, class, modifier, key) + * to the most general (key only), unless use of default environments is turned off in which case the most general + * property searched is (environment, key). + */ + public String getProperty(String base, String modifier, String key) + { + String result = null; + + // Loop over the key orderings, from the most specific to the most general, until a matching value is found. + for (Iterator i = getKeyIterator(base, modifier, key); i.hasNext();) + { + String nextKey = (String) i.next(); + + result = super.getProperty(nextKey); + + if (result != null) + { + break; + } + } + + return result; + } + + /** + * Looks up an array property value relative to the environment, callers class and method. The default environment + * will be checked for a matching array property if defaults are being used. In order to work out the callers class + * and method this method throws an exception and then searches one level up its stack frames. + * + * @param key The property key. + * + * @return The array value of this indexed property searching from the most specific definition (environment, class, + * method, key) to the most general (key only), unless use of default environments is turned off in which + * case the most general proeprty searched is (environment, key). + */ + public String[] getProperties(String key) + { + // Try to get the callers class name and method name by throwing an exception an searching the stack frames. + String className = null; + String methodName = null; + + /* Java 1.4 onwards only. + try { + throw new Exception(); + } catch (Exception e) { + StackTraceElement[] stack = e.getStackTrace(); + // Check that the stack trace contains at least two elements, one for this method and one for the caller. + if (stack.length >= 2) { + className = stack[1].getClassName(); + methodName = stack[1].getMethodName(); + } + }*/ + return getProperties(className, methodName, key); + } + + /** + * Looks up an array property value relative to the environment, base class and modifier. The default environment will + * be checked for a matching array property if defaults are being used. + * + * @param base An object of the class to retrieve properties relative to. + * @param modifier The modifier (which may stand for a method of the class). + * @param key The property key. + * + * @return The array value of this indexed property searching from the most specific definition (environment, class, + * modifier, key) to the most general (key only), unless use of default environments is turned off in which + * case the most general proeprty searched is (environment, key). + */ + public String[] getProperties(Object base, String modifier, String key) + { + return getProperties(base.getClass().getName(), modifier, key); + } + + /** + * Looks up an array property value relative to the environment, base class and modifier. The default environment will + * be checked for a matching array property if defaults are being used. + * + * @param base The name of the class to retrieve properties relative to. + * @param modifier The modifier (which may stand for a method of the class). + * @param key The property key. + * + * @return The array value of this indexed property searching from the most specific definition (environment, class, + * modifier, key) to the most general (key only), unless use of default environments is turned off in which + * case the most general property searched is (environment, key). + */ + public String[] getProperties(String base, String modifier, String key) + { + String[] result = null; + + // Loop over the key orderings, from the most specific to the most general, until a matching value is found. + for (Iterator i = getKeyIterator(base, modifier, key); i.hasNext();) + { + String nextKey = (String) i.next(); + ArrayList arrayList = (ArrayList) arrayProperties.get(nextKey); + + if (arrayList != null) + { + result = (String[]) arrayList.toArray(new String[] {}); + + break; + } + } + + return result; + } + + /** + * For a given environment, base, modifier and key and setting of the use of default environments feature this + * generates an iterator that walks over the order in which to try and access properties. + * + *

      See the {@link #ORDER} constant for an explanation of how the key ordering is generated. + * + * @param base The name of the class to retrieve properties relative to. + * @param modifier The modifier (which may stand for a method of the class). + * @param key The property key. + * + * @return An Iterator over String keys defining the order in which properties should be accessed. + */ + protected Iterator getKeyIterator(final String base, final String modifier, final String key) + { + return new Iterator() + { + // The key ordering count always begins at the start of the ORDER array. + private int i = 0; + + public boolean hasNext() + { + return (useDefaults ? ((i < ORDER.length) && (ORDER[i] > ENVIRONMENT_DEFAULTS_CUTOFF)) + : (i < ORDER.length)); + } + + public Object next() + { + // Check that there is a next element and return null if not. + if (!hasNext()) + { + return null; + } + + // Get the next ordering count. + int o = ORDER[i]; + + // Do bit matching on the count to choose which elements to include in the key. + String result = + (((o & E) != 0) ? (environment + ".") : "") + (((o & B) != 0) ? (base + ".") : "") + + (((o & M) != 0) ? (modifier + ".") : "") + key; + + // Increment the iterator to get the next key on the next call. + i++; + + return result; + } + + public void remove() + { + // This method is not supported. + throw new UnsupportedOperationException("remove() is not supported on this key order iterator as " + + "the ordering cannot be changed"); + } + }; + } + + /** + * Scans all the properties in the parent Properties object and creates arrays for any array property definitions. + * + *

      Array properties are defined with indexes. For example: + * + *

      + * property.1=one
      + * property.2=two
      + * property.3=three
      + *
      + * + *

      Note that these properties will be stored as the 'empty string' or "" property array. + * + *

      + * .1=one
      + * 2=two
      + *
      + */ + protected void createArrayProperties() + { + // Scan through all defined properties. + for (Object o : keySet()) + { + String key = (String) o; + String value = super.getProperty(key); + + // Split the property key into everything before the last '.' and after it. + int lastDotIndex = key.lastIndexOf('.'); + String keyEnding = key.substring(lastDotIndex + 1, key.length()); + String keyStart = key.substring(0, (lastDotIndex == -1) ? 0 : lastDotIndex); + + // Check if the property key ends in an integer, in which case it is an array property. + int index = 0; + + try + { + index = Integer.parseInt(keyEnding); + } + // The ending is not an integer so its not an array. + catch (NumberFormatException e) + { + // Scan the next property. + continue; + } + + // Check if an array property already exists for this base name and create one if not. + ArrayList propArray = (ArrayList) arrayProperties.get(keyStart); + + if (propArray == null) + { + propArray = new ArrayList(); + arrayProperties.put(keyStart, propArray); + } + + // Add the new property value to the array property for the index. + propArray.set(index, value); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java index 7a45632643..7c803294f4 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java @@ -1,428 +1,428 @@ -/* - * - * 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.util; - -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Mathematical support methods for the toolkit. Caculating averages, variances, min/max for test latencies and - * generating linear/exponential sequences for test size/concurrency ramping up. - * - *

      The sequence specifications are of the form [lowest(, ...)(, highest)](,sample=s)(,exp), where round brackets - * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided - * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. - * - *

      The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least - * one of the optional values must be present. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      Generate a sequene of integers from a sequence specification. - *
      Parse an encoded duration into milliseconds. - *
      - * - * @author Rupert Smith - */ -public class MathUtils -{ - /** Used for debugging. */ - // private static final Logger log = Logger.getLogger(MathUtils.class); - - /** The sequence defintion matching regular expression. */ - public static final String SEQUENCE_REGEXP = "^(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; - - /** The regular expression that matches sequence definitions. */ - private static final Pattern SEQUENCE_PATTERN = Pattern.compile(SEQUENCE_REGEXP); - - /** The duration definition matching regular expression. */ - public static final String DURATION_REGEXP = "^(\\d+D)?(\\d+H)?(\\d+M)?(\\d+S)?$"; - - /** The regular expression that matches the duration expression. */ - public static final Pattern DURATION_PATTERN = Pattern.compile(DURATION_REGEXP); - - /** For matching name=value pairs. */ - public static final String NAME_VALUE_REGEXP = "^\\w+=\\w+$"; - - /** For matching name=[value1: value2: ...] variations. */ - public static final String NAME_VALUE_VARIATION_REGEXP = "^\\w+=\\[[\\w:]+\\]$"; - - /** For matching name=[n: ... :m](:sample=s)(:exp) sequences. */ - public static final String NAME_VALUE_SEQUENCE_REGEXP = "^\\w+=(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; - - /** The regular expression that matches name=value pairs and variations. */ - public static final Pattern NAME_VALUE_PATTERN = - Pattern.compile("(" + NAME_VALUE_REGEXP + ")|(" + NAME_VALUE_VARIATION_REGEXP + ")|(" + NAME_VALUE_SEQUENCE_REGEXP - + ")"); - - /** - * Runs a quick test of the sequence generation methods to confirm that they work as expected. - * - * @param args The command line parameters. - */ - public static void main(String[] args) - { - // Use the command line parser to evaluate the command line. - CommandLineParser commandLine = - new CommandLineParser( - new String[][] - { - { "s", "The sequence definition.", "[m:...:n](:sample=s)(:exp)", "true", MathUtils.SEQUENCE_REGEXP }, - { "d", "The duration definition.", "dDhHmMsS", "false", MathUtils.DURATION_REGEXP } - }); - - // Capture the command line arguments or display errors and correct usage and then exit. - ParsedProperties options = null; - - try - { - options = new ParsedProperties(commandLine.parseCommandLine(args)); - } - catch (IllegalArgumentException e) - { - System.out.println(commandLine.getErrors()); - System.out.println(commandLine.getUsage()); - System.exit(-1); - } - - // Extract the command line options. - String sequence = options.getProperty("s"); - String durationString = options.getProperty("d"); - - System.out.println("Sequence is: " + printArray(parseSequence(sequence))); - - if (durationString != null) - { - System.out.println("Duration is: " + parseDuration(durationString)); - } - } - - /** - * Given a start and end and a number of steps this method generates a sequence of evenly spaced integer - * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number - * of values in the sequence. The sequence returned may contain less than the specified number where the integer - * range between start and end is too small to contain that many. - * - *

      As the results are integers, they will not be perfectly evenly spaced but a best-fit. - * - * @param start The sequence start. - * @param end The sequence end. - * @param steps The number of steps. - * - * @return The sequence. - */ - public static int[] generateSequence(int start, int end, int steps) - { - // Check that there are at least two steps. - if (steps < 2) - { - throw new IllegalArgumentException("There must be at least 2 steps."); - } - - ArrayList result = new ArrayList(); - - // Calculate the sequence using floating point, then round into the results. - double fStart = start; - double fEnd = end; - double fCurrent = start; - - for (int i = 0; i < steps; i++) - { - fCurrent = (((fEnd - fStart) / (steps - 1)) * i) + fStart; - - roundAndAdd(result, fCurrent); - } - - // Return the results after converting to a primitive array. - return intListToPrimitiveArray(result); - } - - /** - * Given a start and end and a number of steps this method generates a sequence of expontentially spaced integer - * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number - * of values in the sequence. An exponentially spaced sequence is one where the ratio between any two consecutive - * numbers in the sequence remains constant. The sequence returned may contain less than the specified number where - * the difference between two consecutive values is too small (this is more likely at the start of the sequence, - * where the values are closer together). - * - *

      As the results are integers, they will not be perfectly exponentially spaced but a best-fit. - * - * @param start The sequence start. - * @param end The sequence end. - * @param steps The number of steps. - * - * @return The sequence. - */ - public static int[] generateExpSequence(int start, int end, int steps) - { - // Check that there are at least two steps. - if (steps < 2) - { - throw new IllegalArgumentException("There must be at least 2 steps."); - } - - ArrayList result = new ArrayList(); - - // Calculate the sequence using floating point, then round into the results. - double fStart = start; - double fEnd = end; - // float fCurrent = start; - double diff = fEnd - fStart; - double factor = java.lang.Math.pow(diff, (1.0f / (steps - 1))); - - for (int i = 0; i < steps; i++) - { - // This is a cheat to get the end exactly on and lose the accumulated rounding error. - if (i == (steps - 1)) - { - result.add(end); - } - else - { - roundAndAdd(result, fStart - 1.0f + java.lang.Math.pow(factor, i)); - } - } - - // Return the results after converting to a primitive array. - return intListToPrimitiveArray(result); - } - - /** - * Parses a string defintion of a sequence into an int array containing the sequence. The definition will conform - * to the regular expression: "^(\[[0-9,]+\])(,samples=[0-9]+)?(,exp)?$". This splits it into three parts, - * an array of integers, the optional sample count and the optional exponential flag. - * - * @param sequenceDef The sequence definition. - * - * @return The sequence as a fully expanded int array. - */ - public static int[] parseSequence(String sequenceDef) - { - // Match the sequence definition against the regular expression for sequences. - Matcher matcher = SEQUENCE_PATTERN.matcher(sequenceDef); - - // Check that the argument is of the right format accepted by this method. - if (!matcher.matches()) - { - throw new IllegalArgumentException("The sequence definition is not in the correct format."); - } - - // Get the total number of matching groups to see if either of the optional samples or exponential flag - // goups were set. - int numGroups = matcher.groupCount(); - - // Split the array of integers on commas. - String intArrayString = matcher.group(1); - - String[] intSplits = intArrayString.split("[:\\[\\]]"); - - int[] sequence = new int[intSplits.length - 1]; - - for (int i = 1; i < intSplits.length; i++) - { - sequence[i - 1] = Integer.parseInt(intSplits[i]); - } - - // Check for the optional samples count. - int samples = 0; - - if ((numGroups > 1) && (matcher.group(2) != null)) - { - String samplesGroup = matcher.group(2); - - String samplesString = samplesGroup.substring(",samples=".length()); - samples = Integer.parseInt(samplesString); - } - - // Check for the optional exponential flag. - boolean expFlag = false; - - if ((numGroups > 2) && (matcher.group(3) != null)) - { - expFlag = true; - } - - // If there is a sample count and 2 or more sequence values defined, then generate the sequence from the first - // and last sequence values. - if ((samples != 0) && (sequence.length >= 2)) - { - int start = sequence[0]; - int end = sequence[sequence.length - 1]; - - if (!expFlag) - { - sequence = generateSequence(start, end, samples); - } - else - { - sequence = generateExpSequence(start, end, samples); - } - } - - return sequence; - } - - /** - * Parses a duration defined as a string, giving a duration in days, hours, minutes and seconds into a number - * of milliseconds equal to that duration. - * - * @param duration The duration definition string. - * - * @return The duration in millliseconds. - */ - public static long parseDuration(String duration) - { - // Match the duration against the regular expression. - Matcher matcher = DURATION_PATTERN.matcher(duration); - - // Check that the argument is of the right format accepted by this method. - if (!matcher.matches()) - { - throw new IllegalArgumentException("The duration definition is not in the correct format."); - } - - // This accumulates the duration. - long result = 0; - - int numGroups = matcher.groupCount(); - - // Extract the days. - if (numGroups >= 1) - { - String daysString = matcher.group(1); - result += - (daysString == null) - ? 0 : (Long.parseLong(daysString.substring(0, daysString.length() - 1)) * 24 * 60 * 60 * 1000); - } - - // Extract the hours. - if (numGroups >= 2) - { - String hoursString = matcher.group(2); - result += - (hoursString == null) ? 0 - : (Long.parseLong(hoursString.substring(0, hoursString.length() - 1)) * 60 * 60 * 1000); - } - - // Extract the minutes. - if (numGroups >= 3) - { - String minutesString = matcher.group(3); - result += - (minutesString == null) - ? 0 : (Long.parseLong(minutesString.substring(0, minutesString.length() - 1)) * 60 * 1000); - } - - // Extract the seconds. - if (numGroups >= 4) - { - String secondsString = matcher.group(4); - result += - (secondsString == null) ? 0 : (Long.parseLong(secondsString.substring(0, secondsString.length() - 1)) * 1000); - } - - return result; - } - - /** - * Pretty prints an array of ints as a string. - * - * @param array The array to pretty print. - * - * @return The pretty printed string. - */ - public static String printArray(int[] array) - { - String result = "["; - for (int i = 0; i < array.length; i++) - { - result += array[i]; - result += (i < (array.length - 1)) ? ", " : ""; - } - - result += "]"; - - return result; - } - - /** - * Returns the maximum value in an array of integers. - * - * @param values The array to find the amx in. - * - * @return The max value. - */ - public static int maxInArray(int[] values) - { - if ((values == null) || (values.length == 0)) - { - throw new IllegalArgumentException("Cannot find the max of a null or empty array."); - } - - int max = values[0]; - - for (int value : values) - { - max = (max < value) ? value : max; - } - - return max; - } - - /** - * The #toArray methods of collections cannot be used with primitive arrays. This loops over and array list - * of Integers and outputs and array of int. - * - * @param result The array of Integers to convert. - * - * @return An array of int. - */ - private static int[] intListToPrimitiveArray(ArrayList result) - { - int[] resultArray = new int[result.size()]; - int index = 0; - for (int r : result) - { - resultArray[index] = result.get(index); - index++; - } - - return resultArray; - } - - /** - * Rounds the specified floating point value to the nearest integer and adds it to the specified list of - * integers, provided it is not already in the list. - * - * @param result The list of integers to add to. - * @param value The new candidate to round and add to the list. - */ - private static void roundAndAdd(ArrayList result, double value) - { - int roundedValue = (int) Math.round(value); - - if (!result.contains(roundedValue)) - { - result.add(roundedValue); - } - } -} +/* + * + * 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.util; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Mathematical support methods for the toolkit. Caculating averages, variances, min/max for test latencies and + * generating linear/exponential sequences for test size/concurrency ramping up. + * + *

      The sequence specifications are of the form [lowest(, ...)(, highest)](,sample=s)(,exp), where round brackets + * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided + * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. + * + *

      The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least + * one of the optional values must be present. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Generate a sequene of integers from a sequence specification. + *
      Parse an encoded duration into milliseconds. + *
      + * + * @author Rupert Smith + */ +public class MathUtils +{ + /** Used for debugging. */ + // private static final Logger log = Logger.getLogger(MathUtils.class); + + /** The sequence defintion matching regular expression. */ + public static final String SEQUENCE_REGEXP = "^(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; + + /** The regular expression that matches sequence definitions. */ + private static final Pattern SEQUENCE_PATTERN = Pattern.compile(SEQUENCE_REGEXP); + + /** The duration definition matching regular expression. */ + public static final String DURATION_REGEXP = "^(\\d+D)?(\\d+H)?(\\d+M)?(\\d+S)?$"; + + /** The regular expression that matches the duration expression. */ + public static final Pattern DURATION_PATTERN = Pattern.compile(DURATION_REGEXP); + + /** For matching name=value pairs. */ + public static final String NAME_VALUE_REGEXP = "^\\w+=\\w+$"; + + /** For matching name=[value1: value2: ...] variations. */ + public static final String NAME_VALUE_VARIATION_REGEXP = "^\\w+=\\[[\\w:]+\\]$"; + + /** For matching name=[n: ... :m](:sample=s)(:exp) sequences. */ + public static final String NAME_VALUE_SEQUENCE_REGEXP = "^\\w+=(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; + + /** The regular expression that matches name=value pairs and variations. */ + public static final Pattern NAME_VALUE_PATTERN = + Pattern.compile("(" + NAME_VALUE_REGEXP + ")|(" + NAME_VALUE_VARIATION_REGEXP + ")|(" + NAME_VALUE_SEQUENCE_REGEXP + + ")"); + + /** + * Runs a quick test of the sequence generation methods to confirm that they work as expected. + * + * @param args The command line parameters. + */ + public static void main(String[] args) + { + // Use the command line parser to evaluate the command line. + CommandLineParser commandLine = + new CommandLineParser( + new String[][] + { + { "s", "The sequence definition.", "[m:...:n](:sample=s)(:exp)", "true", MathUtils.SEQUENCE_REGEXP }, + { "d", "The duration definition.", "dDhHmMsS", "false", MathUtils.DURATION_REGEXP } + }); + + // Capture the command line arguments or display errors and correct usage and then exit. + ParsedProperties options = null; + + try + { + options = new ParsedProperties(commandLine.parseCommandLine(args)); + } + catch (IllegalArgumentException e) + { + System.out.println(commandLine.getErrors()); + System.out.println(commandLine.getUsage()); + System.exit(-1); + } + + // Extract the command line options. + String sequence = options.getProperty("s"); + String durationString = options.getProperty("d"); + + System.out.println("Sequence is: " + printArray(parseSequence(sequence))); + + if (durationString != null) + { + System.out.println("Duration is: " + parseDuration(durationString)); + } + } + + /** + * Given a start and end and a number of steps this method generates a sequence of evenly spaced integer + * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number + * of values in the sequence. The sequence returned may contain less than the specified number where the integer + * range between start and end is too small to contain that many. + * + *

      As the results are integers, they will not be perfectly evenly spaced but a best-fit. + * + * @param start The sequence start. + * @param end The sequence end. + * @param steps The number of steps. + * + * @return The sequence. + */ + public static int[] generateSequence(int start, int end, int steps) + { + // Check that there are at least two steps. + if (steps < 2) + { + throw new IllegalArgumentException("There must be at least 2 steps."); + } + + ArrayList result = new ArrayList(); + + // Calculate the sequence using floating point, then round into the results. + double fStart = start; + double fEnd = end; + double fCurrent = start; + + for (int i = 0; i < steps; i++) + { + fCurrent = (((fEnd - fStart) / (steps - 1)) * i) + fStart; + + roundAndAdd(result, fCurrent); + } + + // Return the results after converting to a primitive array. + return intListToPrimitiveArray(result); + } + + /** + * Given a start and end and a number of steps this method generates a sequence of expontentially spaced integer + * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number + * of values in the sequence. An exponentially spaced sequence is one where the ratio between any two consecutive + * numbers in the sequence remains constant. The sequence returned may contain less than the specified number where + * the difference between two consecutive values is too small (this is more likely at the start of the sequence, + * where the values are closer together). + * + *

      As the results are integers, they will not be perfectly exponentially spaced but a best-fit. + * + * @param start The sequence start. + * @param end The sequence end. + * @param steps The number of steps. + * + * @return The sequence. + */ + public static int[] generateExpSequence(int start, int end, int steps) + { + // Check that there are at least two steps. + if (steps < 2) + { + throw new IllegalArgumentException("There must be at least 2 steps."); + } + + ArrayList result = new ArrayList(); + + // Calculate the sequence using floating point, then round into the results. + double fStart = start; + double fEnd = end; + // float fCurrent = start; + double diff = fEnd - fStart; + double factor = java.lang.Math.pow(diff, (1.0f / (steps - 1))); + + for (int i = 0; i < steps; i++) + { + // This is a cheat to get the end exactly on and lose the accumulated rounding error. + if (i == (steps - 1)) + { + result.add(end); + } + else + { + roundAndAdd(result, fStart - 1.0f + java.lang.Math.pow(factor, i)); + } + } + + // Return the results after converting to a primitive array. + return intListToPrimitiveArray(result); + } + + /** + * Parses a string defintion of a sequence into an int array containing the sequence. The definition will conform + * to the regular expression: "^(\[[0-9,]+\])(,samples=[0-9]+)?(,exp)?$". This splits it into three parts, + * an array of integers, the optional sample count and the optional exponential flag. + * + * @param sequenceDef The sequence definition. + * + * @return The sequence as a fully expanded int array. + */ + public static int[] parseSequence(String sequenceDef) + { + // Match the sequence definition against the regular expression for sequences. + Matcher matcher = SEQUENCE_PATTERN.matcher(sequenceDef); + + // Check that the argument is of the right format accepted by this method. + if (!matcher.matches()) + { + throw new IllegalArgumentException("The sequence definition is not in the correct format."); + } + + // Get the total number of matching groups to see if either of the optional samples or exponential flag + // goups were set. + int numGroups = matcher.groupCount(); + + // Split the array of integers on commas. + String intArrayString = matcher.group(1); + + String[] intSplits = intArrayString.split("[:\\[\\]]"); + + int[] sequence = new int[intSplits.length - 1]; + + for (int i = 1; i < intSplits.length; i++) + { + sequence[i - 1] = Integer.parseInt(intSplits[i]); + } + + // Check for the optional samples count. + int samples = 0; + + if ((numGroups > 1) && (matcher.group(2) != null)) + { + String samplesGroup = matcher.group(2); + + String samplesString = samplesGroup.substring(",samples=".length()); + samples = Integer.parseInt(samplesString); + } + + // Check for the optional exponential flag. + boolean expFlag = false; + + if ((numGroups > 2) && (matcher.group(3) != null)) + { + expFlag = true; + } + + // If there is a sample count and 2 or more sequence values defined, then generate the sequence from the first + // and last sequence values. + if ((samples != 0) && (sequence.length >= 2)) + { + int start = sequence[0]; + int end = sequence[sequence.length - 1]; + + if (!expFlag) + { + sequence = generateSequence(start, end, samples); + } + else + { + sequence = generateExpSequence(start, end, samples); + } + } + + return sequence; + } + + /** + * Parses a duration defined as a string, giving a duration in days, hours, minutes and seconds into a number + * of milliseconds equal to that duration. + * + * @param duration The duration definition string. + * + * @return The duration in millliseconds. + */ + public static long parseDuration(String duration) + { + // Match the duration against the regular expression. + Matcher matcher = DURATION_PATTERN.matcher(duration); + + // Check that the argument is of the right format accepted by this method. + if (!matcher.matches()) + { + throw new IllegalArgumentException("The duration definition is not in the correct format."); + } + + // This accumulates the duration. + long result = 0; + + int numGroups = matcher.groupCount(); + + // Extract the days. + if (numGroups >= 1) + { + String daysString = matcher.group(1); + result += + (daysString == null) + ? 0 : (Long.parseLong(daysString.substring(0, daysString.length() - 1)) * 24 * 60 * 60 * 1000); + } + + // Extract the hours. + if (numGroups >= 2) + { + String hoursString = matcher.group(2); + result += + (hoursString == null) ? 0 + : (Long.parseLong(hoursString.substring(0, hoursString.length() - 1)) * 60 * 60 * 1000); + } + + // Extract the minutes. + if (numGroups >= 3) + { + String minutesString = matcher.group(3); + result += + (minutesString == null) + ? 0 : (Long.parseLong(minutesString.substring(0, minutesString.length() - 1)) * 60 * 1000); + } + + // Extract the seconds. + if (numGroups >= 4) + { + String secondsString = matcher.group(4); + result += + (secondsString == null) ? 0 : (Long.parseLong(secondsString.substring(0, secondsString.length() - 1)) * 1000); + } + + return result; + } + + /** + * Pretty prints an array of ints as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(int[] array) + { + String result = "["; + for (int i = 0; i < array.length; i++) + { + result += array[i]; + result += (i < (array.length - 1)) ? ", " : ""; + } + + result += "]"; + + return result; + } + + /** + * Returns the maximum value in an array of integers. + * + * @param values The array to find the amx in. + * + * @return The max value. + */ + public static int maxInArray(int[] values) + { + if ((values == null) || (values.length == 0)) + { + throw new IllegalArgumentException("Cannot find the max of a null or empty array."); + } + + int max = values[0]; + + for (int value : values) + { + max = (max < value) ? value : max; + } + + return max; + } + + /** + * The #toArray methods of collections cannot be used with primitive arrays. This loops over and array list + * of Integers and outputs and array of int. + * + * @param result The array of Integers to convert. + * + * @return An array of int. + */ + private static int[] intListToPrimitiveArray(ArrayList result) + { + int[] resultArray = new int[result.size()]; + int index = 0; + for (int r : result) + { + resultArray[index] = result.get(index); + index++; + } + + return resultArray; + } + + /** + * Rounds the specified floating point value to the nearest integer and adds it to the specified list of + * integers, provided it is not already in the list. + * + * @param result The list of integers to add to. + * @param value The new candidate to round and add to the list. + */ + private static void roundAndAdd(ArrayList result, double value) + { + int roundedValue = (int) Math.round(value); + + if (!result.contains(roundedValue)) + { + result.add(roundedValue); + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ParsedProperties.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ParsedProperties.java index 59c8cfbd3a..1cc6757675 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ParsedProperties.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/ParsedProperties.java @@ -1,390 +1,390 @@ -/* - * - * 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.util; - -import java.util.Properties; - -/** - * ParsedProperties extends the basic Properties class with methods to extract properties, not as strings but as strings - * parsed into basic types. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      - * - * @author Rupert Smith - */ -public class ParsedProperties extends Properties -{ - /** - * Creates an empty ParsedProperties. - */ - public ParsedProperties() - { - super(); - } - - /** - * Creates a ParsedProperties initialized with the specified properties. - * - * @param props The properties to initialize this with. - */ - public ParsedProperties(Properties props) - { - super(props); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static boolean setSysPropertyIfNull(String propname, boolean value) - { - return Boolean.parseBoolean(setSysPropertyIfNull(propname, Boolean.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static short setSysPropertyIfNull(String propname, short value) - { - return Short.parseShort(setSysPropertyIfNull(propname, Short.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static int setSysPropertyIfNull(String propname, int value) - { - return Integer.parseInt(setSysPropertyIfNull(propname, Integer.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static long setSysPropertyIfNull(String propname, long value) - { - return Long.parseLong(setSysPropertyIfNull(propname, Long.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static float setSysPropertyIfNull(String propname, float value) - { - return Float.parseFloat(setSysPropertyIfNull(propname, Float.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public static double setSysPropertyIfNull(String propname, double value) - { - return Double.parseDouble(setSysPropertyIfNull(propname, Double.toString(value))); - } - - /** - * Helper method for setting system properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the system property after this method call. - */ - public static String setSysPropertyIfNull(String propname, String value) - { - String property = System.getProperty(propname); - - if (property == null) - { - System.setProperty(propname, value); - - return value; - } - else - { - return property; - } - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public boolean setPropertyIfNull(String propname, boolean value) - { - return Boolean.parseBoolean(setPropertyIfNull(propname, Boolean.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public short setPropertyIfNull(String propname, short value) - { - return Short.parseShort(setPropertyIfNull(propname, Short.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public int setPropertyIfNull(String propname, int value) - { - return Integer.parseInt(setPropertyIfNull(propname, Integer.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public long setPropertyIfNull(String propname, long value) - { - return Long.parseLong(setPropertyIfNull(propname, Long.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public float setPropertyIfNull(String propname, float value) - { - return Float.parseFloat(setPropertyIfNull(propname, Float.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public double setPropertyIfNull(String propname, double value) - { - return Double.parseDouble(setPropertyIfNull(propname, Double.toString(value))); - } - - /** - * Helper method for setting properties to defaults when they are not already set. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public String setPropertyIfNull(String propname, String value) - { - String property = super.getProperty(propname); - - if (property == null) - { - super.setProperty(propname, value); - - return value; - } - else - { - return property; - } - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public boolean setProperty(String propname, boolean value) - { - setProperty(propname, Boolean.toString(value)); - - return value; - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public short setProperty(String propname, short value) - { - setProperty(propname, Short.toString(value)); - - return value; - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public int setProperty(String propname, int value) - { - setProperty(propname, Integer.toString(value)); - - return value; - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public long setProperty(String propname, long value) - { - setProperty(propname, Long.toString(value)); - - return value; - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public float setProperty(String propname, float value) - { - setProperty(propname, Float.toString(value)); - - return value; - } - - /** - * Helper method for setting properties. - * - * @param propname The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property after this method call. - */ - public double setProperty(String propname, double value) - { - setProperty(propname, Double.toString(value)); - - return value; - } - - /** - * Parses a property as a boolean. - * - * @param propName The property. - * - * @return The property as a boolean, or false if it does not exist. - */ - public boolean getPropertyAsBoolean(String propName) - { - String prop = getProperty(propName); - - return (prop != null) && Boolean.parseBoolean(prop); - } - - /** - * Parses a property as an integer. - * - * @param propName The property. - * - * @return The property as a integer, or null if it does not exist. - */ - public Integer getPropertyAsInteger(String propName) - { - String prop = getProperty(propName); - - return (prop != null) ? new Integer(prop) : null; - } - - /** - * Parses a property as a long. - * - * @param propName The property. - * - * @return The property as a long, or null if it does not exist. - */ - public Long getPropertyAsLong(String propName) - { - String prop = getProperty(propName); - - return (prop != null) ? new Long(prop) : null; - } -} +/* + * + * 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.util; + +import java.util.Properties; + +/** + * ParsedProperties extends the basic Properties class with methods to extract properties, not as strings but as strings + * parsed into basic types. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      + * + * @author Rupert Smith + */ +public class ParsedProperties extends Properties +{ + /** + * Creates an empty ParsedProperties. + */ + public ParsedProperties() + { + super(); + } + + /** + * Creates a ParsedProperties initialized with the specified properties. + * + * @param props The properties to initialize this with. + */ + public ParsedProperties(Properties props) + { + super(props); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static boolean setSysPropertyIfNull(String propname, boolean value) + { + return Boolean.parseBoolean(setSysPropertyIfNull(propname, Boolean.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static short setSysPropertyIfNull(String propname, short value) + { + return Short.parseShort(setSysPropertyIfNull(propname, Short.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static int setSysPropertyIfNull(String propname, int value) + { + return Integer.parseInt(setSysPropertyIfNull(propname, Integer.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static long setSysPropertyIfNull(String propname, long value) + { + return Long.parseLong(setSysPropertyIfNull(propname, Long.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static float setSysPropertyIfNull(String propname, float value) + { + return Float.parseFloat(setSysPropertyIfNull(propname, Float.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public static double setSysPropertyIfNull(String propname, double value) + { + return Double.parseDouble(setSysPropertyIfNull(propname, Double.toString(value))); + } + + /** + * Helper method for setting system properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the system property after this method call. + */ + public static String setSysPropertyIfNull(String propname, String value) + { + String property = System.getProperty(propname); + + if (property == null) + { + System.setProperty(propname, value); + + return value; + } + else + { + return property; + } + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public boolean setPropertyIfNull(String propname, boolean value) + { + return Boolean.parseBoolean(setPropertyIfNull(propname, Boolean.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public short setPropertyIfNull(String propname, short value) + { + return Short.parseShort(setPropertyIfNull(propname, Short.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public int setPropertyIfNull(String propname, int value) + { + return Integer.parseInt(setPropertyIfNull(propname, Integer.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public long setPropertyIfNull(String propname, long value) + { + return Long.parseLong(setPropertyIfNull(propname, Long.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public float setPropertyIfNull(String propname, float value) + { + return Float.parseFloat(setPropertyIfNull(propname, Float.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public double setPropertyIfNull(String propname, double value) + { + return Double.parseDouble(setPropertyIfNull(propname, Double.toString(value))); + } + + /** + * Helper method for setting properties to defaults when they are not already set. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public String setPropertyIfNull(String propname, String value) + { + String property = super.getProperty(propname); + + if (property == null) + { + super.setProperty(propname, value); + + return value; + } + else + { + return property; + } + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public boolean setProperty(String propname, boolean value) + { + setProperty(propname, Boolean.toString(value)); + + return value; + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public short setProperty(String propname, short value) + { + setProperty(propname, Short.toString(value)); + + return value; + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public int setProperty(String propname, int value) + { + setProperty(propname, Integer.toString(value)); + + return value; + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public long setProperty(String propname, long value) + { + setProperty(propname, Long.toString(value)); + + return value; + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public float setProperty(String propname, float value) + { + setProperty(propname, Float.toString(value)); + + return value; + } + + /** + * Helper method for setting properties. + * + * @param propname The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property after this method call. + */ + public double setProperty(String propname, double value) + { + setProperty(propname, Double.toString(value)); + + return value; + } + + /** + * Parses a property as a boolean. + * + * @param propName The property. + * + * @return The property as a boolean, or false if it does not exist. + */ + public boolean getPropertyAsBoolean(String propName) + { + String prop = getProperty(propName); + + return (prop != null) && Boolean.parseBoolean(prop); + } + + /** + * Parses a property as an integer. + * + * @param propName The property. + * + * @return The property as a integer, or null if it does not exist. + */ + public Integer getPropertyAsInteger(String propName) + { + String prop = getProperty(propName); + + return (prop != null) ? new Integer(prop) : null; + } + + /** + * Parses a property as a long. + * + * @param propName The property. + * + * @return The property as a long, or null if it does not exist. + */ + public Long getPropertyAsLong(String propName) + { + String prop = getProperty(propName); + + return (prop != null) ? new Long(prop) : null; + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/SizeOf.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/SizeOf.java index 5f3ebb4545..ecc08770a9 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/SizeOf.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/SizeOf.java @@ -1,94 +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.util; - -/** - * SizeOf provides a static method that does its best to return an accurate measure of the total amount of memory used by - * the virtual machine. This is calculated as the total memory available to the VM minus the actual amount used by it. - * Before this measurement is taken the garbage collector is run many times until the used memory calculation stabilizes. - * Generally, this trick works quite well to provide an accurate reading, however, it cannot be relied upon to be totally - * accurate. It is also quite slow. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      Calculate total memory used. - *
      - * - * @author Rupert Smith - */ -public class SizeOf -{ - /** Holds a reference to the runtime object. */ - private static final Runtime RUNTIME = Runtime.getRuntime(); - - /** - * Makes 4 calls the {@link #runGCTillStable} method. - */ - public static void runGCTillStableSeveralTimes() - { - // It helps to call Runtime.gc() using several method calls. - for (int r = 0; r < 4; ++r) - { - runGCTillStable(); - } - } - - /** - * Runs the garbage collector until the used memory reading stabilizes. It may run the garbage collector up - * to 500 times. - */ - public static void runGCTillStable() - { - long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE; - - for (int i = 0; (usedMem1 < usedMem2) && (i < 500); ++i) - { - RUNTIME.runFinalization(); - RUNTIME.gc(); - Thread.currentThread().yield(); - - usedMem2 = usedMem1; - usedMem1 = usedMemory(); - } - } - - /** - * Runs the garbage collector until the used memory stabilizes and then measures it. - * - * @return The amount of memory used by the virtual machine. - */ - public static long getUsedMemory() - { - runGCTillStableSeveralTimes(); - - return usedMemory(); - } - - /** - * Returns the amount of memory used by subtracting the free memory from the total available memory. - * - * @return The amount of memory used. - */ - private static long usedMemory() - { - return RUNTIME.totalMemory() - RUNTIME.freeMemory(); - } -} +/* + * + * 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.util; + +/** + * SizeOf provides a static method that does its best to return an accurate measure of the total amount of memory used by + * the virtual machine. This is calculated as the total memory available to the VM minus the actual amount used by it. + * Before this measurement is taken the garbage collector is run many times until the used memory calculation stabilizes. + * Generally, this trick works quite well to provide an accurate reading, however, it cannot be relied upon to be totally + * accurate. It is also quite slow. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Calculate total memory used. + *
      + * + * @author Rupert Smith + */ +public class SizeOf +{ + /** Holds a reference to the runtime object. */ + private static final Runtime RUNTIME = Runtime.getRuntime(); + + /** + * Makes 4 calls the {@link #runGCTillStable} method. + */ + public static void runGCTillStableSeveralTimes() + { + // It helps to call Runtime.gc() using several method calls. + for (int r = 0; r < 4; ++r) + { + runGCTillStable(); + } + } + + /** + * Runs the garbage collector until the used memory reading stabilizes. It may run the garbage collector up + * to 500 times. + */ + public static void runGCTillStable() + { + long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE; + + for (int i = 0; (usedMem1 < usedMem2) && (i < 500); ++i) + { + RUNTIME.runFinalization(); + RUNTIME.gc(); + Thread.currentThread().yield(); + + usedMem2 = usedMem1; + usedMem1 = usedMemory(); + } + } + + /** + * Runs the garbage collector until the used memory stabilizes and then measures it. + * + * @return The amount of memory used by the virtual machine. + */ + public static long getUsedMemory() + { + runGCTillStableSeveralTimes(); + + return usedMemory(); + } + + /** + * Returns the amount of memory used by subtracting the free memory from the total available memory. + * + * @return The amount of memory used. + */ + private static long usedMemory() + { + return RUNTIME.totalMemory() - RUNTIME.freeMemory(); + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/StackQueue.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/StackQueue.java index 9078c0e247..acc1e2c218 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/StackQueue.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/StackQueue.java @@ -1,131 +1,131 @@ -/* - * - * 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.util; - -import java.util.EmptyStackException; -import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.Stack; - -/** - * The Stack class in java.util (most unhelpfully) does not implement the Queue interface. This is an adaption of that - * class as a queue. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      Turn a stack into a queue. - *
      - * - * @todo Need to override the add method, and iterator and consider other methods too. They work like LIFO but - * really wany FIFO behaviour accross the whole data structure. - * - * @author Rupert Smith - */ -public class StackQueue extends Stack implements Queue -{ - /** - * Retrieves, but does not remove, the head of this queue. - * - * @return The element at the top of the stack. - */ - public E element() - { - try - { - return super.peek(); - } - catch (EmptyStackException e) - { - NoSuchElementException t = new NoSuchElementException(); - t.initCause(e); - throw t; - } - } - - /** - * Inserts the specified element into this queue, if possible. - * - * @param o The data element to push onto the stack. - * - * @return True if it was added to the stack, false otherwise (this implementation always returns true). - */ - public boolean offer(E o) - { - push(o); - - return true; - } - - /** - * Retrieves, but does not remove, the head of this queue, returning null if this queue is empty. - * - * @return The top element from the stack, or null if the stack is empty. - */ - public E peek() - { - try - { - return super.peek(); - } - catch (EmptyStackException e) - { - return null; - } - } - - /** - * Retrieves and removes the head of this queue, or null if this queue is empty. - * - * @return The top element from the stack, or null if the stack is empty. - */ - public E poll() - { - try - { - return super.pop(); - } - catch (EmptyStackException e) - { - return null; - } - } - - /** - * Retrieves and removes the head of this queue. - * - * @return The top element from the stack, or null if the stack is empty. - * - * @throws NoSuchElementException If the stack is empty so no element can be removed from it. - */ - public E remove() - { - try - { - return super.pop(); - } - catch (EmptyStackException e) - { - NoSuchElementException t = new NoSuchElementException(); - t.initCause(e); - throw t; - } - } -} +/* + * + * 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.util; + +import java.util.EmptyStackException; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Stack; + +/** + * The Stack class in java.util (most unhelpfully) does not implement the Queue interface. This is an adaption of that + * class as a queue. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Turn a stack into a queue. + *
      + * + * @todo Need to override the add method, and iterator and consider other methods too. They work like LIFO but + * really wany FIFO behaviour accross the whole data structure. + * + * @author Rupert Smith + */ +public class StackQueue extends Stack implements Queue +{ + /** + * Retrieves, but does not remove, the head of this queue. + * + * @return The element at the top of the stack. + */ + public E element() + { + try + { + return super.peek(); + } + catch (EmptyStackException e) + { + NoSuchElementException t = new NoSuchElementException(); + t.initCause(e); + throw t; + } + } + + /** + * Inserts the specified element into this queue, if possible. + * + * @param o The data element to push onto the stack. + * + * @return True if it was added to the stack, false otherwise (this implementation always returns true). + */ + public boolean offer(E o) + { + push(o); + + return true; + } + + /** + * Retrieves, but does not remove, the head of this queue, returning null if this queue is empty. + * + * @return The top element from the stack, or null if the stack is empty. + */ + public E peek() + { + try + { + return super.peek(); + } + catch (EmptyStackException e) + { + return null; + } + } + + /** + * Retrieves and removes the head of this queue, or null if this queue is empty. + * + * @return The top element from the stack, or null if the stack is empty. + */ + public E poll() + { + try + { + return super.pop(); + } + catch (EmptyStackException e) + { + return null; + } + } + + /** + * Retrieves and removes the head of this queue. + * + * @return The top element from the stack, or null if the stack is empty. + * + * @throws NoSuchElementException If the stack is empty so no element can be removed from it. + */ + public E remove() + { + try + { + return super.pop(); + } + catch (EmptyStackException e) + { + NoSuchElementException t = new NoSuchElementException(); + t.initCause(e); + throw t; + } + } +} diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/TestContextProperties.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/TestContextProperties.java index edb7b6d73a..d402077963 100644 --- a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/TestContextProperties.java +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/TestContextProperties.java @@ -1,202 +1,202 @@ -/* - * - * 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.util; - -import java.util.Properties; - -/** - * TestContextProperties is an extension of {@link ParsedProperties} that keeps track of property key/value pairs - * that are used by tests being run under the {@link org.apache.qpid.junit.extensions.TKTestRunner}. To keep the - * test runner notified of configurable test parameters, tests should establish their required property values by - * initiliazing fields or statics or in the constructor, through this class. The tk test runner automatically places - * any additional properties specified on the command line into the this class, and these are held statically. - * - *

      Here is an example: - * - *

      - * public class MyTestClass extends TestCase {
      - *     ParsedProperties testProps = TestContextProperties.getInstance();
      - *     private int testParam = testProps.setPropertyIfNull("testParam", 1);
      - * ...
      - * 
      - * - *

      This has the effect of setting up the field testParam with the default value of 1, unless it is overridden - * by values passed to the tk test runner. It also notifies the tk test runner of the name and value of the test - * parameter actually used for the test, so that this can be logged in the test output file. - * - *

      - *
      CRC Card
      Responsibilities Collaborations - *
      Log all name/value pairs read or written. - *
      - * - * @author Rupert Smith - */ -public class TestContextProperties extends ParsedProperties -{ - /** Used for debugging. */ - // Logger log = Logger.getLogger(TestContextProperties.class); - - /** Holds all properties set or read through this property extension class. */ - private Properties accessedProps = new Properties(); - - /** The singleton instance of the test context properties. */ - private static TestContextProperties singleton = null; - - /** - * Default constructor that builds a ContextualProperties that uses environment defaults. - */ - private TestContextProperties() - { - super(); - } - - /** - * Gets the singleton instance of the test context properties. - * - * @return The singleton instance of the test context properties. - */ - public static synchronized ParsedProperties getInstance() - { - if (singleton == null) - { - singleton = new TestContextProperties(); - } - - return singleton; - } - - /** - * Gets the singleton instance of the test context properties, applying a specified set of default properties to - * it, if they are not already set. - * - * @param defaults The defaults to apply for properties not already set. - * - * @return The singleton instance of the test context properties. - */ - public static synchronized ParsedProperties getInstance(Properties defaults) - { - ParsedProperties props = getInstance(); - - for (Object key : defaults.keySet()) - { - String stringKey = (String) key; - String value = defaults.getProperty(stringKey); - - props.setPropertyIfNull(stringKey, value); - } - - return props; - } - - /* - * Creates a ContextualProperties that uses environment defaults and is initialized with the specified properties. - * - * @param props The properties to initialize this with. - */ - /*public TestContextProperties(Properties props) - { - super(); - }*/ - - /** - * Gets all of the properties (with their most recent values) that have been set or read through this class. - * - * @return All of the properties accessed through this class. - */ - public static Properties getAccessedProps() - { - return (singleton == null) ? new Properties() : singleton; - // return accessedProps; - } - - /** - * Looks up a property value relative to the environment, callers class and method. The default environment will be - * checked for a matching property if defaults are being used. The property key/value pair is remembered and made - * available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. - * - * @param key The property key. - * - * @return The value of this property searching from the most specific definition (environment, class, method, key) - * to the most general (key only), unless use of default environments is turned off in which case the most general - * proeprty searched is (environment, key). - */ - public String getProperty(String key) - { - // log.debug("public String getProperty(String key = " + key + "): called"); - - String value = super.getProperty(key); - - if (value != null) - { - accessedProps.setProperty(key, value); - } - - // log.debug("value = " + value); - - return value; - } - - /** - * Calls the Hashtable method put. Provided for parallelism with the getProperty - * method. Enforces use of strings for property keys and values. The value returned is the result of the - * Hashtable call to put. The property key/value pair is remembered and made - * available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. - * - * @param key The key to be placed into this property list. - * @param value The value corresponding to key. - * - * @return The previous value of the specified key in this property list, or null if it did not have one. - */ - public synchronized Object setProperty(String key, String value) - { - // log.debug("public synchronized Object setProperty(String key = " + key + ", String value = " + value + "): called"); - - Object result = super.setProperty(key, value); - accessedProps.setProperty(key, value); - - return result; - } - - /** - * Helper method for setting properties to defaults when they are not already set. The property key/value pair is - * remembered and made available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. - * - * @param key The name of the system property to set. - * @param value The value to set it to. - * - * @return The value of the property, which will be the value passed in if it was null, or the existing value otherwise. - */ - public String setPropertyIfNull(String key, String value) - { - // log.debug("public String setPropertyIfNull(String key = " + key + ", String value = " + value + "): called"); - - String result = super.setPropertyIfNull(key, value); - - if (value != null) - { - accessedProps.setProperty(key, result); - } - - // log.debug("result = " + result); - - 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.extensions.util; + +import java.util.Properties; + +/** + * TestContextProperties is an extension of {@link ParsedProperties} that keeps track of property key/value pairs + * that are used by tests being run under the {@link org.apache.qpid.junit.extensions.TKTestRunner}. To keep the + * test runner notified of configurable test parameters, tests should establish their required property values by + * initiliazing fields or statics or in the constructor, through this class. The tk test runner automatically places + * any additional properties specified on the command line into the this class, and these are held statically. + * + *

      Here is an example: + * + *

      + * public class MyTestClass extends TestCase {
      + *     ParsedProperties testProps = TestContextProperties.getInstance();
      + *     private int testParam = testProps.setPropertyIfNull("testParam", 1);
      + * ...
      + * 
      + * + *

      This has the effect of setting up the field testParam with the default value of 1, unless it is overridden + * by values passed to the tk test runner. It also notifies the tk test runner of the name and value of the test + * parameter actually used for the test, so that this can be logged in the test output file. + * + *

      + *
      CRC Card
      Responsibilities Collaborations + *
      Log all name/value pairs read or written. + *
      + * + * @author Rupert Smith + */ +public class TestContextProperties extends ParsedProperties +{ + /** Used for debugging. */ + // Logger log = Logger.getLogger(TestContextProperties.class); + + /** Holds all properties set or read through this property extension class. */ + private Properties accessedProps = new Properties(); + + /** The singleton instance of the test context properties. */ + private static TestContextProperties singleton = null; + + /** + * Default constructor that builds a ContextualProperties that uses environment defaults. + */ + private TestContextProperties() + { + super(); + } + + /** + * Gets the singleton instance of the test context properties. + * + * @return The singleton instance of the test context properties. + */ + public static synchronized ParsedProperties getInstance() + { + if (singleton == null) + { + singleton = new TestContextProperties(); + } + + return singleton; + } + + /** + * Gets the singleton instance of the test context properties, applying a specified set of default properties to + * it, if they are not already set. + * + * @param defaults The defaults to apply for properties not already set. + * + * @return The singleton instance of the test context properties. + */ + public static synchronized ParsedProperties getInstance(Properties defaults) + { + ParsedProperties props = getInstance(); + + for (Object key : defaults.keySet()) + { + String stringKey = (String) key; + String value = defaults.getProperty(stringKey); + + props.setPropertyIfNull(stringKey, value); + } + + return props; + } + + /* + * Creates a ContextualProperties that uses environment defaults and is initialized with the specified properties. + * + * @param props The properties to initialize this with. + */ + /*public TestContextProperties(Properties props) + { + super(); + }*/ + + /** + * Gets all of the properties (with their most recent values) that have been set or read through this class. + * + * @return All of the properties accessed through this class. + */ + public static Properties getAccessedProps() + { + return (singleton == null) ? new Properties() : singleton; + // return accessedProps; + } + + /** + * Looks up a property value relative to the environment, callers class and method. The default environment will be + * checked for a matching property if defaults are being used. The property key/value pair is remembered and made + * available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. + * + * @param key The property key. + * + * @return The value of this property searching from the most specific definition (environment, class, method, key) + * to the most general (key only), unless use of default environments is turned off in which case the most general + * proeprty searched is (environment, key). + */ + public String getProperty(String key) + { + // log.debug("public String getProperty(String key = " + key + "): called"); + + String value = super.getProperty(key); + + if (value != null) + { + accessedProps.setProperty(key, value); + } + + // log.debug("value = " + value); + + return value; + } + + /** + * Calls the Hashtable method put. Provided for parallelism with the getProperty + * method. Enforces use of strings for property keys and values. The value returned is the result of the + * Hashtable call to put. The property key/value pair is remembered and made + * available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. + * + * @param key The key to be placed into this property list. + * @param value The value corresponding to key. + * + * @return The previous value of the specified key in this property list, or null if it did not have one. + */ + public synchronized Object setProperty(String key, String value) + { + // log.debug("public synchronized Object setProperty(String key = " + key + ", String value = " + value + "): called"); + + Object result = super.setProperty(key, value); + accessedProps.setProperty(key, value); + + return result; + } + + /** + * Helper method for setting properties to defaults when they are not already set. The property key/value pair is + * remembered and made available to {@link org.apache.qpid.junit.extensions.TKTestRunner}. + * + * @param key The name of the system property to set. + * @param value The value to set it to. + * + * @return The value of the property, which will be the value passed in if it was null, or the existing value otherwise. + */ + public String setPropertyIfNull(String key, String value) + { + // log.debug("public String setPropertyIfNull(String key = " + key + ", String value = " + value + "): called"); + + String result = super.setPropertyIfNull(key, value); + + if (value != null) + { + accessedProps.setProperty(key, result); + } + + // log.debug("result = " + result); + + return result; + } +} -- cgit v1.2.1