/* * The Spring Framework is published under the terms * of the Apache Software License. */ package org.springframework.load; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.util.ResponseTimeMonitor; import org.springframework.util.ResponseTimeMonitorImpl; import org.springframework.util.StopWatch; /** * Convenient superclass that makes it very easy to implement Tests. * * Uses the Template Method design pattern. Concrete subclasses have * only to implement the abstract runPass(i) method to execute each test. * * This class exposes bean properties controlling test execution. * * @author Rod Johnson * @since February 9, 2001 */ public abstract class AbstractTest implements Test, BeanNameAware { //--------------------------------------------------------------------- // Instance variables //--------------------------------------------------------------------- /** Used to calculate random delays if a subclass wants this behavior */ private static Random rand = new Random(); protected final Log logger = LogFactory.getLog(getClass()); private long maxPause = -1L; private int nbPasses; private StopWatch runningTimer; private StopWatch pauseTimer; private StopWatch elapsedTimer; /** * List of failures encountered by this test so far */ private List testFailedExceptions; /** * Whether toString should generate a verbose format. * This property is inherited from the managing TestSuite unless * it's overridden as a bean property. */ private boolean longReports; /** Number of tests completed so far */ private int completedTests; /** Number of instances of this test. Use for weighting */ private int instances = 1; /** Descriptive name of this test */ private String name; /** * Helper object used to capture performance information */ private ResponseTimeMonitorImpl responseTimeMonitorImpl = new ResponseTimeMonitorImpl(); /** Suite that manages this test */ private AbstractTestSuite suite; //--------------------------------------------------------------------- // Constructor //--------------------------------------------------------------------- /** * Construct a new AbstractTest. The object must be further configured * by its JavaBean properties and/or by inheritance of properties * from the TestSuite it runs it. The setTestSuite() method must * be invoked by the managing AbstractTestSuite. */ protected AbstractTest() { testFailedExceptions = new LinkedList(); runningTimer = new StopWatch(); runningTimer.setKeepTaskList(false); pauseTimer = new StopWatch(); pauseTimer.setKeepTaskList(false); elapsedTimer = new StopWatch(); elapsedTimer.setKeepTaskList(false); } /** * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) */ public void setBeanName(String name) { setName(name); } //--------------------------------------------------------------------- // Implementation of Test //--------------------------------------------------------------------- /** * Subclasses can override this to save a test * fixture to run application-specific tests against. * This implementation doesn't know anything about fixtures, so * it always throws an exception. * @param context application-specific object to test */ public void setFixture(Object context) { throw new UnsupportedOperationException("AbstractTest.setFixture"); } /** * @return the test fixture used by this object. * Subclasses that require test fixtures must override this method, * which always throws UnsupportedOperationException */ public Object getFixture() { throw new UnsupportedOperationException("AbstractTest.getFixture"); } /** * Set the number of instances of this Test. This enables weighting * of tests. */ public void setInstances(int instances) { this.instances = instances; } public int getInstances() { return instances; } /** * The TestSuite object that manages this test thread invokes this * method. It is used to enable properties to be inherited from the * test suite unless overriden by bean properties on this class. */ public void setTestSuite(AbstractTestSuite suite) { this.suite = suite; // Set defaults if not set via this class's bean properties if (getPasses() == 0) this.setPasses(suite.getPasses()); if (getMaxPause() < 0L) this.setMaxPause(suite.getMaxPause()); this.setLongReports(suite.getLongReports()); } public final void setName(String name) { this.name = name; } public final void setLongReports(boolean longReports) { this.longReports = longReports; } public final void setPasses(int nbPasses) { this.nbPasses = nbPasses; } public final void setMaxPause(long maxPause) { this.maxPause = maxPause; } public final long getMaxPause() { return this.maxPause; } public final int getPasses() { return nbPasses; } public final int getErrorCount() { return testFailedExceptions.size(); } public final String getName() { return name; } public final int getTestsCompletedCount() { return completedTests; } public final boolean isComplete() { return getPasses() == getTestsCompletedCount(); } /** * @see org.springframework.load.TestStatus#getAverageResponseTime() */ public int getAverageResponseTime() { return this.responseTimeMonitorImpl.getAverageResponseTimeMillis(); } /** * @see org.springframework.load.TestStatus#getTestsPerSecondCount() */ public final double getTestsPerSecondCount() { double res = 0.0; double totalTime = runningTimer.getTotalTimeMillis(); double testCompleted = getTestsCompletedCount(); if (testCompleted == 0.0) return 0.0; if (totalTime != 0.0) res = 1000.0 / (totalTime / testCompleted); else { // No time!!!! //return testCompleted; return Double.POSITIVE_INFINITY; } return res; } /** * @see org.springframework.load.TestStatus#getTotalWorkingTime() */ public final long getTotalWorkingTime() { return runningTimer.getTotalTimeMillis(); } /** * @see org.springframework.load.TestStatus#getElapsedTime() */ public final long getElapsedTime() { return elapsedTimer.getTotalTimeMillis(); } /** * @see org.springframework.load.TestStatus#getTotalPauseTime() */ public final long getTotalPauseTime() { return pauseTimer.getTotalTimeMillis(); } public final TestFailedException[] getFailureExceptions() { TestFailedException[] fails = new TestFailedException[testFailedExceptions.size()]; for (int i = 0; i < fails.length; i++) fails[i] = (TestFailedException) testFailedExceptions.get(i); return fails; } /** * @see org.springframework.load.Test#reset() */ public void reset() { elapsedTimer = new StopWatch("elapsed timer"); completedTests = 0; runningTimer = new StopWatch("running timer"); pauseTimer = new StopWatch("pause timer"); testFailedExceptions.clear(); } /** * @return additional stats information. */ public ResponseTimeMonitor getTargetResponse() { return responseTimeMonitorImpl; } /** * Run all the tests in this thread * @see java.lang.Runnable#run() */ public final void run() { elapsedTimer.start(null);//"run"); for (int i = 0; i < getPasses(); i++) { try { pause(); runningTimer.start(null);//"run"); runPass(i); } catch (AbortTestException ex) { // Terminate the for loop to end the work of this test thread System.err.println("Abortion!: " + ex); break; } catch (TestFailedException ex) { // We don't need to wrap this testFailedExceptions.add(ex); onTestPassFailed(ex); } catch (Exception ex) { // Wrap the uncaught exception in a new TestFailedException TestFailedException tfe = new TestFailedException("Uncaught exception: " + ex.getMessage(), ex); testFailedExceptions.add(tfe); onTestPassFailed(ex); } finally { ++completedTests; runningTimer.stop(); responseTimeMonitorImpl.recordResponseTime(runningTimer.getLastTaskTimeMillis()); } } // for each test elapsedTimer.stop(); } // run /** * @return diagnostic information about this Test. * The verbosity of the format will depend on the value of the * longReports bean property. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getName() + "\t"); sb.append(getTestsCompletedCount() + "/" + getPasses()); sb.append("\terrs=" + getErrorCount()); sb.append("\t" + suite.getDecimalFormat().format(getTestsPerSecondCount()) + "hps"); sb.append("\tavg=" + responseTimeMonitorImpl.getAverageResponseTimeMillis() + "ms"); if (longReports) { sb.append("\tworkt=" + getTotalWorkingTime()); sb.append("\telt=" + getElapsedTime()); sb.append("\best=" + responseTimeMonitorImpl.getBestResponseTimeMillis() + "ms"); sb.append("\tworst=" + responseTimeMonitorImpl.getWorstResponseTimeMillis() + "ms"); sb.append("\tpause=" + getTotalPauseTime()); } if (isComplete() && this.longReports) { sb.append("\tCOMPLETED\n"); sb.append(getErrorReport()); } return sb.toString(); } // toString /** * @return an error report string */ public final String getErrorReport() { String s = ""; TestFailedException[] fails = getFailureExceptions(); if (fails == null || fails.length == 0) return "No errors\n"; else { for (int i = 0; i < fails.length; i++) { s += fails[i] + "\n"; } } return s; } public String getGroup() { return null; } /** * Subclasses must implement this method * @throws TestFailedException if the test failed * @throws AbortTestException if the test run should abort * @throws Exception if there's any other unspecified error. * This will not cause the test run to abort. * @param i index of test pass, indexed from 0 */ protected abstract void runPass(int i) throws TestFailedException, AbortTestException, Exception; /** * For handling any exceptions thrown by a Test pass. Override as desired. * This implementation does nothing. * This method is invoked on test failures, whether caused by an uncaught exception * or a TestAssertionFailure */ protected void onTestPassFailed(Exception ex) { ex.printStackTrace(); } //--------------------------------------------------------------------- // Implementation methods and convenience methods for subclasses //--------------------------------------------------------------------- /** * Pause for up to maxPause milliseconds */ private void pause() { if (this.maxPause > 0L) { try { pauseTimer.start(null);//"pause"); long p = Math.abs(rand.nextLong() % this.maxPause); Thread.sleep(p); } catch (InterruptedException ex) { ex.printStackTrace(); } finally { pauseTimer.stop(); } } } // pause /** * Convenience method to simulate delay for between min and max ms * @param min minimum number of milliseconds to delay * @param max maximum number of milliseconds to delay */ public static void simulateDelay(long min, long max) { if (max - min > 0L) { try { long p = Math.abs(min + rand.nextLong() % (max - min)); //System.out.println("delay for " + p + "ms"); Thread.sleep(p); } catch (InterruptedException ex) { // Ignore it } } } // simulateDelay /** * Convenience method for subclasses * @param sz size of array to index * @return a random array of list index from 0 up to sz-1 */ public static int randomIndex(int sz) { return Math.abs(rand.nextInt(sz)); } /** * Like JUnit assertion * @param s */ protected void assertTrue(String s, boolean condition) throws TestFailedException { if (!condition) throw new TestFailedException(s); } protected void assertEquals(String s, Object a, Object b) throws TestFailedException { if (a == b) return; if (a == null) { if (b != null) throw new TestFailedException(s); } else if (b == null) { // a isn't null throw new TestFailedException(s); } if (!a.equals(b)) { throw new TestFailedException(s); } } protected void assertEquals(String s, int a, int b) throws TestFailedException { if (a != b) throw new TestFailedException(s + ": expected " + a + " but found " + b); } protected void assertEquals(String s, long a, long b) throws TestFailedException { if (a != b) throw new TestFailedException(s); } } // class AbstractTest