001: package com.clarkware.junitperf;
002:
003: import junit.framework.Test;
004: import junit.framework.TestResult;
005: import junit.extensions.RepeatedTest;
006:
007: /**
008: * The <code>LoadTest</code> is a test decorator that runs
009: * a test with a simulated number of concurrent users and
010: * iterations.
011: * <p>
012: * In its simplest form, a <code>LoadTest</code> is constructed
013: * with a test to decorate and the number of concurrent users.
014: * </p>
015: * <p>
016: * For example, to create a load test of 10 concurrent users
017: * with each user running <code>ExampleTest</code> once and
018: * all users started simultaneously, use:
019: * <blockquote>
020: * <pre>
021: * Test loadTest = new LoadTest(new TestSuite(ExampleTest.class), 10);
022: * </pre>
023: * </blockquote>
024: * or, to load test a single test method, use:
025: * <blockquote>
026: * <pre>
027: * Test loadTest = new LoadTest(new ExampleTest("testSomething"), 10);
028: * </pre>
029: * </blockquote>
030: * </p>
031: * <p>
032: * The load can be ramped by specifying a pluggable
033: * <code>Timer</code> instance which prescribes the delay
034: * between the addition of each concurrent user. A
035: * <code>ConstantTimer</code> has a constant delay, with
036: * a zero value indicating that all users will be started
037: * simultaneously. A <code>RandomTimer</code> has a random
038: * delay with a uniformly distributed variation.
039: * </p>
040: * <p>
041: * For example, to create a load test of 10 concurrent users
042: * with each user running <code>ExampleTest.testSomething()</code> once and
043: * with a one second delay between the addition of users, use:
044: * <blockquote>
045: * <pre>
046: * Timer timer = new ConstantTimer(1000);
047: * Test loadTest = new LoadTest(new ExampleTest("testSomething"), 10, timer);
048: * </pre>
049: * </blockquote>
050: * </p>
051: * <p>
052: * In order to simulate each concurrent user running a test for a
053: * specified number of iterations, a <code>LoadTest</code> can be
054: * constructed to decorate a <code>RepeatedTest</code>.
055: * Alternatively, a <code>LoadTest</code> convenience constructor
056: * specifying the number of iterations is provided which creates a
057: * <code>RepeatedTest</code>.
058: * </p>
059: * <p>
060: * For example, to create a load test of 10 concurrent users
061: * with each user running <code>ExampleTest.testSomething()</code> for 20 iterations,
062: * and with a one second delay between the addition of users, use:
063: * <blockquote>
064: * <pre>
065: * Timer timer = new ConstantTimer(1000);
066: * Test repeatedTest = new RepeatedTest(new ExampleTest("testSomething"), 20);
067: * Test loadTest = new LoadTest(repeatedTest, 10, timer);
068: * </pre>
069: * </blockquote>
070: * or, alternatively, use:
071: * <blockquote>
072: * <pre>
073: * Timer timer = new ConstantTimer(1000);
074: * Test loadTest = new LoadTest(new ExampleTest("testSomething"), 10, 20, timer);
075: * </pre>
076: * </blockquote>
077: * A <code>LoadTest</code> can be decorated as a <code>TimedTest</code>
078: * to test the elapsed time of the load test. For example, to decorate
079: * the load test constructed above as a timed test with a maximum elapsed
080: * time of 2 seconds, use:
081: * <blockquote>
082: * <pre>
083: * Test timedTest = new TimedTest(loadTest, 2000);
084: * </pre>
085: * </blockquote>
086: * </p>
087: * <p>
088: * By default, a <code>LoadTest</code> does not enforce test
089: * atomicity (as defined in transaction processing) if its decorated
090: * test spawns threads, either directly or indirectly. In other words,
091: * if a decorated test spawns a thread and then returns control without
092: * waiting for its spawned thread to complete, then the test is assumed
093: * to be transactionally complete.
094: * </p>
095: * <p>
096: * If threads are integral to the successful completion of
097: * a decorated test, meaning that the decorated test should not be
098: * treated as complete until all of its threads complete, then
099: * <code>setEnforceTestAtomicity(true)</code> should be invoked to
100: * enforce test atomicity. This effectively causes the load test to
101: * wait for the completion of all threads belonging to the same
102: * <code>ThreadGroup</code> as the thread running the decorated test.
103: * </p>
104: * @author <b>Mike Clark</b>
105: * @author Clarkware Consulting, Inc.
106: * @author Ervin Varga
107: */
108:
109: public class LoadTest implements Test {
110:
111: private final int users;
112: private final Timer timer;
113: private final ThreadedTest test;
114: private final ThreadedTestGroup group;
115: private final ThreadBarrier barrier;
116: private boolean enforceTestAtomicity;
117:
118: /**
119: * Constructs a <code>LoadTest</code> to decorate
120: * the specified test using the specified number
121: * of concurrent users starting simultaneously.
122: *
123: * @param test Test to decorate.
124: * @param users Number of concurrent users.
125: */
126: public LoadTest(Test test, int users) {
127: this (test, users, new ConstantTimer(0));
128: }
129:
130: /**
131: * Constructs a <code>LoadTest</code> to decorate
132: * the specified test using the specified number
133: * of concurrent users starting simultaneously and
134: * the number of iterations per user.
135: *
136: * @param test Test to decorate.
137: * @param users Number of concurrent users.
138: * @param iterations Number of iterations per user.
139: */
140: public LoadTest(Test test, int users, int iterations) {
141: this (test, users, iterations, new ConstantTimer(0));
142: }
143:
144: /**
145: * Constructs a <code>LoadTest</code> to decorate
146: * the specified test using the specified number
147: * of concurrent users, number of iterations per
148: * user, and delay timer.
149: *
150: * @param test Test to decorate.
151: * @param users Number of concurrent users.
152: * @param iterations Number of iterations per user.
153: * @param timer Delay timer.
154: */
155: public LoadTest(Test test, int users, int iterations, Timer timer) {
156: this (new RepeatedTest(test, iterations), users, timer);
157: }
158:
159: /**
160: * Constructs a <code>LoadTest</code> to decorate
161: * the specified test using the specified number
162: * of concurrent users and delay timer.
163: *
164: * @param test Test to decorate.
165: * @param users Number of concurrent users.
166: * @param timer Delay timer.
167: */
168: public LoadTest(Test test, int users, Timer timer) {
169:
170: if (users < 1) {
171: throw new IllegalArgumentException(
172: "Number of users must be > 0");
173: } else if (timer == null) {
174: throw new IllegalArgumentException("Delay timer is null");
175: } else if (test == null) {
176: throw new IllegalArgumentException("Decorated test is null");
177: }
178:
179: this .users = users;
180: this .timer = timer;
181: setEnforceTestAtomicity(false);
182: this .barrier = new ThreadBarrier(users);
183: this .group = new ThreadedTestGroup(this );
184: this .test = new ThreadedTest(test, group, barrier);
185: }
186:
187: /**
188: * Indicates whether test atomicity should be enforced.
189: * <p>
190: * If threads are integral to the successful completion of
191: * a decorated test, meaning that the decorated test should not be
192: * treated as complete until all of its threads complete, then
193: * <code>setEnforceTestAtomicity(true)</code> should be invoked to
194: * enforce test atomicity. This effectively causes the load test to
195: * wait for the completion of all threads belonging to the same
196: * <code>ThreadGroup</code> as the thread running the decorated test.
197: *
198: * @param isAtomic <code>true</code> to enforce test atomicity;
199: * <code>false</code> otherwise.
200: */
201: public void setEnforceTestAtomicity(boolean isAtomic) {
202: enforceTestAtomicity = isAtomic;
203: }
204:
205: /**
206: * Returns the number of tests in this load test.
207: *
208: * @return Number of tests.
209: */
210: public int countTestCases() {
211: return test.countTestCases() * users;
212: }
213:
214: /**
215: * Runs the test.
216: *
217: * @param result Test result.
218: */
219: public void run(TestResult result) {
220:
221: group.setTestResult(result);
222:
223: for (int i = 0; i < users; i++) {
224:
225: if (result.shouldStop()) {
226: barrier.cancelThreads(users - i);
227: break;
228: }
229:
230: test.run(result);
231:
232: sleep(getDelay());
233: }
234:
235: waitForTestCompletion();
236:
237: cleanup();
238: }
239:
240: protected void waitForTestCompletion() {
241: //
242: // TODO: May require a strategy pattern
243: // if other algorithms emerge.
244: //
245: if (enforceTestAtomicity) {
246: waitForAllThreadsToComplete();
247: } else {
248: waitForThreadedTestThreadsToComplete();
249: }
250: }
251:
252: protected void waitForThreadedTestThreadsToComplete() {
253: while (!barrier.isReached()) {
254: sleep(50);
255: }
256: }
257:
258: protected void waitForAllThreadsToComplete() {
259: while (group.activeCount() > 0) {
260: sleep(50);
261: }
262: }
263:
264: protected void sleep(long time) {
265: try {
266: Thread.sleep(time);
267: } catch (Exception ignored) {
268: }
269: }
270:
271: protected void cleanup() {
272: try {
273: group.destroy();
274: } catch (Throwable ignored) {
275: }
276: }
277:
278: public String toString() {
279: if (enforceTestAtomicity) {
280: return "LoadTest (ATOMIC): " + test.toString();
281: } else {
282: return "LoadTest (NON-ATOMIC): " + test.toString();
283: }
284: }
285:
286: protected long getDelay() {
287: return timer.getDelay();
288: }
289: }
|