001: /*
002: * @(#)MultiThreadedTestRunner.java
003: *
004: * The basics are taken from an article by Andy Schneider
005: * andrew.schneider@javaworld.com
006: * The article is "JUnit Best Practices"
007: * http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit_p.html
008: *
009: * Part of the GroboUtils package at:
010: * http://groboutils.sourceforge.net
011: *
012: * Permission is hereby granted, free of charge, to any person obtaining a
013: * copy of this software and associated documentation files (the "Software"),
014: * to deal in the Software without restriction, including without limitation
015: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
016: * and/or sell copies of the Software, and to permit persons to whom the
017: * Software is furnished to do so, subject to the following conditions:
018: *
019: * The above copyright notice and this permission notice shall be included in
020: * all copies or substantial portions of the Software.
021: *
022: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
023: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
024: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
025: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
026: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
027: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
028: * DEALINGS IN THE SOFTWARE.
029: */
030:
031: package net.sourceforge.groboutils.junit.v1;
032:
033: import org.apache.log4j.Logger;
034: import junit.framework.TestCase;
035: import junit.framework.TestResult;
036: import junit.framework.AssertionFailedError;
037: import junit.framework.Assert;
038:
039: import java.lang.reflect.Method;
040:
041: /**
042: * A framework which allows for an array of tests to be
043: * run asynchronously. TestCases should reference this class in a test
044: * method.
045: * <P>
046: * <B>Update for July 9, 2003:</B> now, you can also register
047: * <tt>TestRunner</tt> instances as monitors (request 771008); these run
048: * parallel with the standard <tt>TestRunner</tt> instances, but they only quit
049: * when all of the standard <tt>TestRunner</tt> instances end.
050: * <P>
051: * Fixed bugs 771000 and 771001: spawned threads are now Daemon threads,
052: * and all "wild threads" (threads that just won't stop) are
053: * <tt>Thread.stop()</tt>ed.
054: * <P>
055: * All these changes have made this class rather fragile, as there are
056: * many threaded timing issues to deal with. Expect future refactoring
057: * with backwards compatibility.
058: *
059: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
060: * @since Jan 14, 2002
061: * @version $Date: 2003/10/03 14:26:45 $
062: */
063: public class MultiThreadedTestRunner {
064: private static final Class THIS_CLASS = MultiThreadedTestRunner.class;
065: private static final String THIS_CLASS_NAME = THIS_CLASS.getName();
066: private static final Logger LOG = Logger.getLogger(THIS_CLASS);
067:
068: private static final long DEFAULT_MAX_FINAL_JOIN_TIME = 30l * 1000l;
069: private static final long DEFAULT_MAX_WAIT_TIME = 24l * 60l * 60l * 1000l;
070: private static final long MIN_WAIT_TIME = 10l;
071:
072: private Object synch = new Object();
073: private boolean threadsFinished = false;
074: private ThreadGroup threadGroup;
075: private Thread coreThread;
076: private Throwable exception;
077: private TestRunnable runners[];
078: private TestRunnable monitors[];
079: private long maxFinalJoinTime = DEFAULT_MAX_FINAL_JOIN_TIME;
080: private long maxWaitTime = DEFAULT_MAX_WAIT_TIME;
081: private boolean performKills = true;
082:
083: /**
084: * Create a new utility instance with the given set of parallel runners.
085: * All runners passed into this method must end on their own, else it's
086: * an error.
087: */
088: public MultiThreadedTestRunner(TestRunnable tr[]) {
089: this (tr, null);
090: }
091:
092: /**
093: * Create a new utility instance with the given set of parallel runners
094: * and a set of monitors. The runners must end on their own, but the
095: * monitors can run until told to stop.
096: *
097: * @param runners a non-null, non-empty collection of test runners.
098: * @param monitors a list of monitor runners, which may be <tt>null</tt> or
099: * empty.
100: */
101: public MultiThreadedTestRunner(TestRunnable runners[],
102: TestRunnable monitors[]) {
103: if (runners == null) {
104: throw new IllegalArgumentException("no null runners");
105: }
106: int len = runners.length;
107: if (len <= 0) {
108: throw new IllegalArgumentException(
109: "must have at least one runnable");
110: }
111: this .runners = new TestRunnable[len];
112: System.arraycopy(runners, 0, this .runners, 0, len);
113:
114: if (monitors != null) {
115: len = monitors.length;
116: this .monitors = new TestRunnable[len];
117: System.arraycopy(monitors, 0, this .monitors, 0, len);
118: } else {
119: this .monitors = new TestRunnable[0];
120: }
121: }
122:
123: /**
124: * Run each test given in a separate thread. Wait for each thread
125: * to finish running, then return.
126: * <P>
127: * As of July 9, 2003, this method will not wait forever, but rather
128: * will wait for the internal maximum run time, which is by default
129: * 24 hours; for most unit testing scenarios, this is more than
130: * sufficient.
131: *
132: * @exception Throwable thrown on a test run if a threaded task
133: * throws an exception.
134: */
135: public void runTestRunnables() throws Throwable {
136: runTestRunnables(-1);
137: }
138:
139: /**
140: * Runs each test given in a separate thread. Waits for each thread
141: * to finish running (possibly killing them), then returns.
142: *
143: * @param runnables the list of TestCaseRunnable objects to run
144: * asynchronously
145: * @param maxTime the maximum amount of milliseconds to wait for
146: * the tests to run. If the time is <= 0, then the tests
147: * will run until they are complete. Otherwise, any threads that
148: * don't complete by the given number of milliseconds will be killed,
149: * and a failure will be thrown.
150: * @exception Throwable thrown from the underlying tests if they happen
151: * to cause an error.
152: */
153: public void runTestRunnables(long maxTime) throws Throwable {
154: // Ensure we aren't interrupted.
155: // This can happen from one test execution to the next, if an
156: // interrupt was poorly timed on the core thread. Calling
157: // Thread.interrupted() will clear the interrupted status flag.
158: Thread.interrupted();
159:
160: // initialize the data.
161: this .exception = null;
162: this .coreThread = Thread.currentThread();
163: this .threadGroup = new ThreadGroup(THIS_CLASS_NAME);
164: this .threadsFinished = false;
165:
166: // start the monitors before the runners
167: Thread monitorThreads[] = setupThreads(this .threadGroup,
168: this .monitors);
169: Thread runnerThreads[] = setupThreads(this .threadGroup,
170: this .runners);
171:
172: // catch the IE exception outside the loop so that an exception
173: // thrown in a thread will kill all the other threads.
174: boolean threadsStillRunning;
175: try {
176: threadsStillRunning = joinThreads(runnerThreads, maxTime);
177: } catch (InterruptedException ie) {
178: // Thread join interrupted: some runner or monitor caused an
179: // exception. Note that this is NOT a timeout!
180: threadsStillRunning = true;
181: } finally {
182: synchronized (this .synch) {
183: if (!this .threadsFinished) {
184: interruptThreads();
185: } else {
186: LOG.debug("All threads finished within timeframe.");
187: }
188: }
189: }
190:
191: if (threadsStillRunning) {
192: LOG.debug("Halting the test threads.");
193:
194: // threads are still running. If no exception was generated,
195: // then set a timeout error to indicate some threads didn't
196: // end in time.
197: setTimeoutError(maxTime);
198:
199: // kill any remaining threads
200: try {
201: // but give them one last chance!
202: joinThreads(runnerThreads, maxFinalJoinTime);
203: } catch (InterruptedException ie) {
204: // someone caused a real exception. This is NOT a timeout!
205: }
206: int killCount = killThreads(runnerThreads);
207: if (killCount > 0) {
208: LOG.fatal(killCount
209: + " thread(s) did not stop themselves.");
210: setTimeoutError(maxFinalJoinTime);
211: }
212: }
213:
214: // Stop the monitor threads - they have a time limit!
215: LOG.debug("Halting the monitor threads.");
216: try {
217: joinThreads(monitorThreads, maxFinalJoinTime);
218: } catch (InterruptedException ex) {
219: // don't cause a timeout error with monitor threads.
220: }
221: killThreads(monitorThreads);
222:
223: if (this .exception != null) {
224: // an exception/error occurred during the test, so throw
225: // the exception so it is reported by the owning test
226: // correctly.
227: LOG.debug("Exception occurred during testing.",
228: this .exception);
229: throw this .exception;
230: }
231: LOG.debug("No exceptions caused during execution.");
232: }
233:
234: /**
235: * Handles an exception by sending them to the test results. Called by
236: * runner or monitor threads.
237: */
238: void handleException(Throwable t) {
239: LOG.warn("A test thread caused an exception.", t);
240: synchronized (this .synch) {
241: if (this .exception == null) {
242: LOG.debug("Setting the exception to:", t);
243: this .exception = t;
244: }
245:
246: if (!this .threadsFinished) {
247: interruptThreads();
248: }
249: }
250:
251: if (t instanceof ThreadDeath) {
252: // rethrow ThreadDeath after they have been registered
253: // and the threads have been signaled to halt.
254: throw (ThreadDeath) t;
255: }
256: }
257:
258: /**
259: * Stops all running test threads. Called by runner or monitor threads.
260: */
261: void interruptThreads() {
262: LOG.debug("Forcing all test threads to stop.");
263: synchronized (this .synch) {
264: // interrupt the core thread (that might be doing a join)
265: // first, so that it doesn't accidentally do a join on
266: // other threads that were interrupted.
267: if (Thread.currentThread() != this .coreThread) {
268: this .coreThread.interrupt();
269: }
270:
271: this .threadsFinished = true;
272:
273: int count = this .threadGroup.activeCount();
274: Thread t[] = new Thread[count];
275: this .threadGroup.enumerate(t);
276: for (int i = t.length; --i >= 0;) {
277: if (t[i] != null && t[i].isAlive()) {
278: t[i].interrupt();
279: }
280: }
281: }
282: }
283:
284: /**
285: * Used by the TestRunnable instances to tell if the parallel execution
286: * has stopped or is stopping.
287: */
288: boolean areThreadsFinished() {
289: return this .threadsFinished;
290: }
291:
292: /**
293: * Sets up the threads for the given runnables and starts them.
294: */
295: private Thread[] setupThreads(ThreadGroup tg, TestRunnable tr[]) {
296: int len = tr.length;
297: Thread threads[] = new Thread[len];
298: for (int i = 0; i < len; ++i) {
299: tr[i].setTestRunner(this );
300: threads[i] = new Thread(tg, tr[i]);
301: threads[i].setDaemon(true);
302: }
303: for (int i = 0; i < len; ++i) {
304: threads[i].start();
305:
306: // wait for the threads to actually start. If we wait 10
307: // times and still no dice, I expect the test already started
308: // and finished.
309: int count = 0;
310: while (!threads[i].isAlive() && count < 10) {
311: LOG.debug("Waiting for thread at index " + i
312: + " to start.");
313: Thread.yield();
314: ++count;
315: }
316: if (count >= 10) {
317: LOG.debug("Assuming thread at index " + i
318: + " already finished.");
319: }
320: }
321: return threads;
322: }
323:
324: /**
325: * This joins all the threads together. If the max time is exceeded,
326: * then <tt>true</tt> is returned. This method is only called by the core
327: * thread. The thread array will be altered at return time to only contain
328: * threads which are still active (all other slots will be <tt>null</tt>).
329: * <P>
330: * This routine allows us to attempt to collect all the halted threads
331: * together, while not waiting forever on threads that poorly don't
332: * respond to outside stimuli (and thus require a stop() on the
333: * thread).
334: */
335: private boolean joinThreads(Thread t[], long waitTime)
336: throws InterruptedException {
337: // check the arguments
338: if (t == null) {
339: return false;
340: }
341: int len = t.length;
342: if (len <= 0) {
343: return false;
344: }
345: if (waitTime < 0 || waitTime > maxWaitTime) {
346: waitTime = DEFAULT_MAX_WAIT_TIME;
347: }
348:
349: // slowly halt the threads.
350: boolean threadsRunning = true;
351: InterruptedException iex = null;
352: long finalTime = System.currentTimeMillis() + waitTime;
353: while (threadsRunning && System.currentTimeMillis() < finalTime
354: && iex == null) {
355: LOG.debug("Time = " + System.currentTimeMillis()
356: + "; final = " + finalTime);
357: threadsRunning = false;
358:
359: // There might be circumstances where
360: // the time between entering the while loop and entering the
361: // for loop exceeds the final time, which can cause an incorrect
362: // threadsRunning value. That's why this boolean exists. Note
363: // that since we put in the (len <= 0) test above, we don't
364: // have to worry about another edge case where the length prevents
365: // the loop from being entered.
366: boolean enteredLoop = false;
367:
368: for (int i = 0; i < len
369: && System.currentTimeMillis() < finalTime; ++i) {
370: enteredLoop = true;
371: if (t[i] != null) {
372: try {
373: // this will yield our time, so we don't
374: // need any explicit yield statement.
375: t[i].join(MIN_WAIT_TIME);
376: } catch (InterruptedException ex) {
377: LOG.debug("Join for thread at index " + i
378: + " was interrupted.");
379: iex = ex;
380: }
381: if (!t[i].isAlive()) {
382: LOG.debug("Joined thread at index " + i);
383: t[i] = null;
384: } else {
385: LOG.debug("Thread at index " + i
386: + " still running.");
387: threadsRunning = true;
388: }
389: }
390: }
391:
392: // If the threadsRunning is true, it remains true. If
393: // the enteredLoop is false, this will be true.
394: threadsRunning = threadsRunning || !enteredLoop;
395: }
396: if (iex != null) {
397: throw iex;
398: }
399: return threadsRunning;
400: }
401:
402: /**
403: * This will execute a stop() on all non-null, alive threads in the list.
404: *
405: * @return the number of threads killed
406: */
407: private int killThreads(Thread[] t) {
408: int killCount = 0;
409: for (int i = 0; i < t.length; ++i) {
410: if (t[i] != null && t[i].isAlive()) {
411: LOG.debug("Stopping thread at index " + i);
412: ++killCount;
413: if (this .performKills) {
414: // Yes, this is deprecated API, but we give the threads
415: // "sufficient" warning to stop themselves.
416: int count = 0;
417: boolean isAlive = t[i].isAlive();
418: while (isAlive && count < 10) {
419: // send an InterruptedException, as this is handled
420: // specially in the TestRunnable.
421: t[i].stop(new TestDeathException("Thread " + i
422: + " did not die on its own"));
423: LOG.debug("Waiting for thread at index " + i
424: + " to stop.");
425: Thread.yield();
426: isAlive = t[i].isAlive();
427:
428: if (isAlive) {
429: // it may have been in a sleep state, so
430: // make it shake a leg!
431: t[i].interrupt();
432: }
433: ++count;
434: }
435: if (count >= 10) {
436: LOG.fatal("Thread at index " + i
437: + " did not stop!");
438: }
439: t[i] = null;
440: } else {
441: LOG.fatal("Did not stop thread " + t[i]);
442: }
443: }
444: }
445: return killCount;
446: }
447:
448: private void setTimeoutError(long maxTime) {
449: Throwable t = createTimeoutError(maxTime);
450: synchronized (this .synch) {
451: if (this .exception == null) {
452: LOG
453: .debug("Setting the exception to a timeout exception.");
454: this .exception = t;
455: }
456: }
457: }
458:
459: private Throwable createTimeoutError(long maxTime) {
460: Throwable ret = null;
461: // need to set the exception to a timeout
462: try {
463: Assert.fail("Threads did not finish within " + maxTime
464: + " milliseconds.");
465: } catch (ThreadDeath td) {
466: // never trap these
467: throw td;
468: } catch (Throwable t) {
469: t.fillInStackTrace();
470: ret = t;
471: }
472: return ret;
473: }
474:
475: /**
476: * An exception that declares that the test has been stop()ed.
477: */
478: public static final class TestDeathException extends
479: RuntimeException {
480: private TestDeathException(String msg) {
481: super(msg);
482: }
483: }
484: }
|