All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.thebadgerset.junit.extensions.TKTestResult Maven / Gradle / Ivy

Go to download

JUnit Toolkit enhances JUnit with performance testing, asymptotic behaviour analysis, and concurrency testing.

The newest version!
/*
 * Copyright 2007 Rupert Smith.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package uk.co.thebadgerset.junit.extensions;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;

import uk.co.thebadgerset.junit.extensions.listeners.TKTestListener;

/**
 * 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); /** Used for verbose output. */ private PrintStream writer; /** The delay between two tests. */ private int delay = 0; /** Verbose output flag. */ private boolean verbose = false; /** * 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 writer The writer to output verbose information about which test is being run. * @param delay A delay in milliseconds to introduce before every test start. * @param verbose A flag to indicate that verbose test information should be output. * @param testCaseName The name of the test case that this is the TestResult object for. */ public TKTestResult(PrintStream writer, int delay, boolean verbose, 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.verbose = verbose; this.writer = writer; 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) { } } // 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; } // Output verbose information about the test if verbose mode is on. if (this.verbose) { this.writer.println("Test started: " + getTestInfo(test)); } // 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() { 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: [ 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 { // 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."); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy