KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > groboutils > junit > v1 > MultiThreadedTestRunner


1 /*
2  * @(#)MultiThreadedTestRunner.java
3  *
4  * The basics are taken from an article by Andy Schneider
5  * andrew.schneider@javaworld.com
6  * The article is "JUnit Best Practices"
7  * http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit_p.html
8  *
9  * Part of the GroboUtils package at:
10  * http://groboutils.sourceforge.net
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included in
20  * all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  */

30
31 package net.sourceforge.groboutils.junit.v1;
32
33 import org.apache.log4j.Logger;
34 import junit.framework.TestCase;
35 import junit.framework.TestResult;
36 import junit.framework.AssertionFailedError;
37 import junit.framework.Assert;
38
39 import java.lang.reflect.Method JavaDoc;
40
41
42 /**
43  * A framework which allows for an array of tests to be
44  * run asynchronously. TestCases should reference this class in a test
45  * method.
46  * <P>
47  * <B>Update for July 9, 2003:</B> now, you can also register
48  * <tt>TestRunner</tt> instances as monitors (request 771008); these run
49  * parallel with the standard <tt>TestRunner</tt> instances, but they only quit
50  * when all of the standard <tt>TestRunner</tt> instances end.
51  * <P>
52  * Fixed bugs 771000 and 771001: spawned threads are now Daemon threads,
53  * and all "wild threads" (threads that just won't stop) are
54  * <tt>Thread.stop()</tt>ed.
55  * <P>
56  * All these changes have made this class rather fragile, as there are
57  * many threaded timing issues to deal with. Expect future refactoring
58  * with backwards compatibility.
59  *
60  * @author Matt Albrecht <a HREF="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
61  * @since Jan 14, 2002
62  * @version $Date: 2003/10/03 14:26:45 $
63  */

64 public class MultiThreadedTestRunner
65 {
66     private static final Class JavaDoc THIS_CLASS = MultiThreadedTestRunner.class;
67     private static final String JavaDoc THIS_CLASS_NAME = THIS_CLASS.getName();
68     private static final Logger LOG = Logger.getLogger( THIS_CLASS );
69     
70     private static final long DEFAULT_MAX_FINAL_JOIN_TIME = 30l * 1000l;
71     private static final long DEFAULT_MAX_WAIT_TIME = 24l * 60l * 60l * 1000l;
72     private static final long MIN_WAIT_TIME = 10l;
73     
74     private Object JavaDoc synch = new Object JavaDoc();
75     private boolean threadsFinished = false;
76     private ThreadGroup JavaDoc threadGroup;
77     private Thread JavaDoc coreThread;
78     private Throwable JavaDoc exception;
79     private TestRunnable runners[];
80     private TestRunnable monitors[];
81     private long maxFinalJoinTime = DEFAULT_MAX_FINAL_JOIN_TIME;
82     private long maxWaitTime = DEFAULT_MAX_WAIT_TIME;
83     private boolean performKills = true;
84     
85     
86     /**
87      * Create a new utility instance with the given set of parallel runners.
88      * All runners passed into this method must end on their own, else it's
89      * an error.
90      */

91     public MultiThreadedTestRunner( TestRunnable tr[] )
92     {
93         this( tr, null );
94     }
95     
96     
97     /**
98      * Create a new utility instance with the given set of parallel runners
99      * and a set of monitors. The runners must end on their own, but the
100      * monitors can run until told to stop.
101      *
102      * @param runners a non-null, non-empty collection of test runners.
103      * @param monitors a list of monitor runners, which may be <tt>null</tt> or
104      * empty.
105      */

106     public MultiThreadedTestRunner( TestRunnable runners[],
107             TestRunnable monitors[] )
108     {
109         if (runners == null)
110         {
111             throw new IllegalArgumentException JavaDoc("no null runners");
112         }
113         int len = runners.length;
114         if (len <= 0)
115         {
116             throw new IllegalArgumentException JavaDoc(
117                 "must have at least one runnable");
118         }
119         this.runners = new TestRunnable[ len ];
120         System.arraycopy( runners, 0, this.runners, 0, len );
121         
122         if (monitors != null)
123         {
124             len = monitors.length;
125             this.monitors = new TestRunnable[ len ];
126             System.arraycopy( monitors, 0, this.monitors, 0, len );
127         }
128         else
129         {
130             this.monitors = new TestRunnable[ 0 ];
131         }
132     }
133     
134     
135     /**
136      * Run each test given in a separate thread. Wait for each thread
137      * to finish running, then return.
138      * <P>
139      * As of July 9, 2003, this method will not wait forever, but rather
140      * will wait for the internal maximum run time, which is by default
141      * 24 hours; for most unit testing scenarios, this is more than
142      * sufficient.
143      *
144      * @exception Throwable thrown on a test run if a threaded task
145      * throws an exception.
146      */

147     public void runTestRunnables()
148             throws Throwable JavaDoc
149     {
150         runTestRunnables( -1 );
151     }
152     
153     
154     /**
155      * Runs each test given in a separate thread. Waits for each thread
156      * to finish running (possibly killing them), then returns.
157      *
158      * @param runnables the list of TestCaseRunnable objects to run
159      * asynchronously
160      * @param maxTime the maximum amount of milliseconds to wait for
161      * the tests to run. If the time is &lt;= 0, then the tests
162      * will run until they are complete. Otherwise, any threads that
163      * don't complete by the given number of milliseconds will be killed,
164      * and a failure will be thrown.
165      * @exception Throwable thrown from the underlying tests if they happen
166      * to cause an error.
167      */

168     public void runTestRunnables( long maxTime )
169             throws Throwable JavaDoc
170     {
171         // Ensure we aren't interrupted.
172
// This can happen from one test execution to the next, if an
173
// interrupt was poorly timed on the core thread. Calling
174
// Thread.interrupted() will clear the interrupted status flag.
175
Thread.interrupted();
176         
177         // initialize the data.
178
this.exception = null;
179         this.coreThread = Thread.currentThread();
180         this.threadGroup = new ThreadGroup JavaDoc( THIS_CLASS_NAME );
181         this.threadsFinished = false;
182         
183         // start the monitors before the runners
184
Thread JavaDoc monitorThreads[] = setupThreads(
185             this.threadGroup, this.monitors );
186         Thread JavaDoc runnerThreads[] = setupThreads(
187             this.threadGroup, this.runners );
188         
189         // catch the IE exception outside the loop so that an exception
190
// thrown in a thread will kill all the other threads.
191
boolean threadsStillRunning;
192         try
193         {
194             threadsStillRunning = joinThreads( runnerThreads, maxTime );
195         }
196         catch (InterruptedException JavaDoc ie)
197         {
198             // Thread join interrupted: some runner or monitor caused an
199
// exception. Note that this is NOT a timeout!
200
threadsStillRunning = true;
201         }
202         finally
203         {
204             synchronized (this.synch)
205             {
206                 if (!this.threadsFinished)
207                 {
208                     interruptThreads();
209                 }
210                 else
211                 {
212                     LOG.debug( "All threads finished within timeframe." );
213                 }
214             }
215         }
216         
217         if (threadsStillRunning)
218         {
219             LOG.debug( "Halting the test threads." );
220             
221             // threads are still running. If no exception was generated,
222
// then set a timeout error to indicate some threads didn't
223
// end in time.
224
setTimeoutError( maxTime );
225             
226             // kill any remaining threads
227
try
228             {
229                 // but give them one last chance!
230
joinThreads( runnerThreads,
231                     maxFinalJoinTime );
232             }
233             catch (InterruptedException JavaDoc ie)
234             {
235                 // someone caused a real exception. This is NOT a timeout!
236
}
237             int killCount = killThreads( runnerThreads );
238             if (killCount > 0)
239             {
240                 LOG.fatal( killCount+" thread(s) did not stop themselves." );
241                 setTimeoutError( maxFinalJoinTime );
242             }
243         }
244         
245         // Stop the monitor threads - they have a time limit!
246
LOG.debug("Halting the monitor threads.");
247         try
248         {
249             joinThreads( monitorThreads, maxFinalJoinTime );
250         }
251         catch (InterruptedException JavaDoc ex)
252         {
253             // don't cause a timeout error with monitor threads.
254
}
255         killThreads( monitorThreads );
256         
257         
258         if (this.exception != null)
259         {
260             // an exception/error occurred during the test, so throw
261
// the exception so it is reported by the owning test
262
// correctly.
263
LOG.debug( "Exception occurred during testing.", this.exception );
264             throw this.exception;
265         }
266         LOG.debug( "No exceptions caused during execution." );
267     }
268     
269     
270     /**
271      * Handles an exception by sending them to the test results. Called by
272      * runner or monitor threads.
273      */

274     void handleException( Throwable JavaDoc t )
275     {
276         LOG.warn( "A test thread caused an exception.", t );
277         synchronized( this.synch )
278         {
279             if (this.exception == null)
280             {
281                 LOG.debug("Setting the exception to:",t);
282                 this.exception = t;
283             }
284             
285             if (!this.threadsFinished)
286             {
287                 interruptThreads();
288             }
289         }
290         
291         if (t instanceof ThreadDeath JavaDoc)
292         {
293             // rethrow ThreadDeath after they have been registered
294
// and the threads have been signaled to halt.
295
throw (ThreadDeath JavaDoc)t;
296         }
297     }
298     
299     
300     /**
301      * Stops all running test threads. Called by runner or monitor threads.
302      */

303     void interruptThreads()
304     {
305         LOG.debug("Forcing all test threads to stop.");
306         synchronized (this.synch)
307         {
308             // interrupt the core thread (that might be doing a join)
309
// first, so that it doesn't accidentally do a join on
310
// other threads that were interrupted.
311
if (Thread.currentThread() != this.coreThread)
312             {
313                 this.coreThread.interrupt();
314             }
315             
316             this.threadsFinished = true;
317             
318             int count = this.threadGroup.activeCount();
319             Thread JavaDoc t[] = new Thread JavaDoc[ count ];
320             this.threadGroup.enumerate( t );
321             for (int i = t.length; --i >= 0;)
322             {
323                 if (t[i] != null && t[i].isAlive())
324                 {
325                     t[i].interrupt();
326                 }
327             }
328         }
329     }
330     
331     
332     /**
333      * Used by the TestRunnable instances to tell if the parallel execution
334      * has stopped or is stopping.
335      */

336     boolean areThreadsFinished()
337     {
338         return this.threadsFinished;
339     }
340     
341     
342     /**
343      * Sets up the threads for the given runnables and starts them.
344      */

345     private Thread JavaDoc[] setupThreads( ThreadGroup JavaDoc tg, TestRunnable tr[] )
346     {
347         int len = tr.length;
348         Thread JavaDoc threads[] = new Thread JavaDoc[ len ];
349         for (int i = 0; i < len; ++i)
350         {
351             tr[i].setTestRunner( this );
352             threads[i] = new Thread JavaDoc( tg, tr[i] );
353             threads[i].setDaemon( true );
354         }
355         for (int i = 0; i < len; ++i)
356         {
357             threads[i].start();
358             
359             // wait for the threads to actually start. If we wait 10
360
// times and still no dice, I expect the test already started
361
// and finished.
362
int count = 0;
363             while (!threads[i].isAlive() && count < 10)
364             {
365                 LOG.debug("Waiting for thread at index "+i+" to start.");
366                 Thread.yield();
367                 ++count;
368             }
369             if (count >= 10)
370             {
371                 LOG.debug("Assuming thread at index "+i+" already finished.");
372             }
373         }
374         return threads;
375     }
376     
377     
378     /**
379      * This joins all the threads together. If the max time is exceeded,
380      * then <tt>true</tt> is returned. This method is only called by the core
381      * thread. The thread array will be altered at return time to only contain
382      * threads which are still active (all other slots will be <tt>null</tt>).
383      * <P>
384      * This routine allows us to attempt to collect all the halted threads
385      * together, while not waiting forever on threads that poorly don't
386      * respond to outside stimuli (and thus require a stop() on the
387      * thread).
388      */

389     private boolean joinThreads( Thread JavaDoc t[], long waitTime )
390             throws InterruptedException JavaDoc
391     {
392         // check the arguments
393
if (t == null)
394         {
395             return false;
396         }
397         int len = t.length;
398         if (len <= 0)
399         {
400             return false;
401         }
402         if (waitTime < 0 || waitTime > maxWaitTime)
403         {
404             waitTime = DEFAULT_MAX_WAIT_TIME;
405         }
406         
407         // slowly halt the threads.
408
boolean threadsRunning = true;
409         InterruptedException JavaDoc iex = null;
410         long finalTime = System.currentTimeMillis() + waitTime;
411         while (threadsRunning && System.currentTimeMillis() < finalTime &&
412             iex == null)
413         {
414             LOG.debug("Time = "+System.currentTimeMillis()+"; final = "+finalTime);
415             threadsRunning = false;
416             
417             // There might be circumstances where
418
// the time between entering the while loop and entering the
419
// for loop exceeds the final time, which can cause an incorrect
420
// threadsRunning value. That's why this boolean exists. Note
421
// that since we put in the (len <= 0) test above, we don't
422
// have to worry about another edge case where the length prevents
423
// the loop from being entered.
424
boolean enteredLoop = false;
425             
426             for (int i = 0;
427                 i < len && System.currentTimeMillis() < finalTime;
428                 ++i)
429             {
430                 enteredLoop = true;
431                 if (t[i] != null)
432                 {
433                     try
434                     {
435                         // this will yield our time, so we don't
436
// need any explicit yield statement.
437
t[i].join( MIN_WAIT_TIME );
438                     }
439                     catch (InterruptedException JavaDoc ex)
440                     {
441                         LOG.debug("Join for thread at index "+i+
442                             " was interrupted.");
443                         iex = ex;
444                     }
445                     if (!t[i].isAlive())
446                     {
447                         LOG.debug("Joined thread at index "+i);
448                         t[i] = null;
449                     }
450                     else
451                     {
452                         LOG.debug("Thread at index "+i+" still running.");
453                         threadsRunning = true;
454                     }
455                 }
456             }
457             
458             // If the threadsRunning is true, it remains true. If
459
// the enteredLoop is false, this will be true.
460
threadsRunning = threadsRunning || !enteredLoop;
461         }
462         if (iex != null)
463         {
464             throw iex;
465         }
466         return threadsRunning;
467     }
468     
469     
470     /**
471      * This will execute a stop() on all non-null, alive threads in the list.
472      *
473      * @return the number of threads killed
474      */

475     private int killThreads( Thread JavaDoc[] t )
476     {
477         int killCount = 0;
478         for (int i = 0; i < t.length; ++i)
479         {
480             if (t[i] != null && t[i].isAlive())
481             {
482                 LOG.debug("Stopping thread at index "+i);
483                 ++killCount;
484                 if (this.performKills)
485                 {
486                     // Yes, this is deprecated API, but we give the threads
487
// "sufficient" warning to stop themselves.
488
int count = 0;
489                     boolean isAlive = t[i].isAlive();
490                     while (isAlive && count < 10)
491                     {
492                         // send an InterruptedException, as this is handled
493
// specially in the TestRunnable.
494
t[i].stop(
495                             new TestDeathException(
496                             "Thread "+i+" did not die on its own" ) );
497                         LOG.debug( "Waiting for thread at index "+i+
498                             " to stop.");
499                         Thread.yield();
500                         isAlive = t[i].isAlive();
501                         
502                         if (isAlive)
503                         {
504                             // it may have been in a sleep state, so
505
// make it shake a leg!
506
t[i].interrupt();
507                         }
508                         ++count;
509                     }
510                     if (count >= 10)
511                     {
512                         LOG.fatal("Thread at index "+i+" did not stop!" );
513                     }
514                     t[i] = null;
515                 }
516                 else
517                 {
518                     LOG.fatal( "Did not stop thread "+t[i] );
519                 }
520             }
521         }
522         return killCount;
523     }
524     
525     
526     private void setTimeoutError( long maxTime )
527     {
528         Throwable JavaDoc t = createTimeoutError( maxTime );
529         synchronized (this.synch)
530         {
531             if (this.exception == null)
532             {
533                 LOG.debug("Setting the exception to a timeout exception.");
534                 this.exception = t;
535             }
536         }
537     }
538     
539     
540     private Throwable JavaDoc createTimeoutError( long maxTime )
541     {
542         Throwable JavaDoc ret = null;
543         // need to set the exception to a timeout
544
try
545         {
546             Assert.fail( "Threads did not finish within " +
547                 maxTime + " milliseconds.");
548         }
549         catch (ThreadDeath JavaDoc td)
550         {
551             // never trap these
552
throw td;
553         }
554         catch (Throwable JavaDoc t)
555         {
556             t.fillInStackTrace();
557             ret = t;
558         }
559         return ret;
560     }
561     
562     
563     /**
564      * An exception that declares that the test has been stop()ed.
565      */

566     public static final class TestDeathException extends RuntimeException JavaDoc
567     {
568         private TestDeathException( String JavaDoc msg )
569         {
570             super( msg );
571         }
572     }
573 }
574
Popular Tags