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