1 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 ; 40 41 42 64 public class MultiThreadedTestRunner 65 { 66 private static final Class THIS_CLASS = MultiThreadedTestRunner.class; 67 private static final String 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 synch = new Object (); 75 private boolean threadsFinished = false; 76 private ThreadGroup threadGroup; 77 private Thread coreThread; 78 private Throwable 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 91 public MultiThreadedTestRunner( TestRunnable tr[] ) 92 { 93 this( tr, null ); 94 } 95 96 97 106 public MultiThreadedTestRunner( TestRunnable runners[], 107 TestRunnable monitors[] ) 108 { 109 if (runners == null) 110 { 111 throw new IllegalArgumentException ("no null runners"); 112 } 113 int len = runners.length; 114 if (len <= 0) 115 { 116 throw new IllegalArgumentException ( 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 147 public void runTestRunnables() 148 throws Throwable 149 { 150 runTestRunnables( -1 ); 151 } 152 153 154 168 public void runTestRunnables( long maxTime ) 169 throws Throwable 170 { 171 Thread.interrupted(); 176 177 this.exception = null; 179 this.coreThread = Thread.currentThread(); 180 this.threadGroup = new ThreadGroup ( THIS_CLASS_NAME ); 181 this.threadsFinished = false; 182 183 Thread monitorThreads[] = setupThreads( 185 this.threadGroup, this.monitors ); 186 Thread runnerThreads[] = setupThreads( 187 this.threadGroup, this.runners ); 188 189 boolean threadsStillRunning; 192 try 193 { 194 threadsStillRunning = joinThreads( runnerThreads, maxTime ); 195 } 196 catch (InterruptedException ie) 197 { 198 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 setTimeoutError( maxTime ); 225 226 try 228 { 229 joinThreads( runnerThreads, 231 maxFinalJoinTime ); 232 } 233 catch (InterruptedException ie) 234 { 235 } 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 LOG.debug("Halting the monitor threads."); 247 try 248 { 249 joinThreads( monitorThreads, maxFinalJoinTime ); 250 } 251 catch (InterruptedException ex) 252 { 253 } 255 killThreads( monitorThreads ); 256 257 258 if (this.exception != null) 259 { 260 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 274 void handleException( Throwable 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 ) 292 { 293 throw (ThreadDeath )t; 296 } 297 } 298 299 300 303 void interruptThreads() 304 { 305 LOG.debug("Forcing all test threads to stop."); 306 synchronized (this.synch) 307 { 308 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 t[] = new Thread [ 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 336 boolean areThreadsFinished() 337 { 338 return this.threadsFinished; 339 } 340 341 342 345 private Thread [] setupThreads( ThreadGroup tg, TestRunnable tr[] ) 346 { 347 int len = tr.length; 348 Thread threads[] = new Thread [ len ]; 349 for (int i = 0; i < len; ++i) 350 { 351 tr[i].setTestRunner( this ); 352 threads[i] = new Thread ( tg, tr[i] ); 353 threads[i].setDaemon( true ); 354 } 355 for (int i = 0; i < len; ++i) 356 { 357 threads[i].start(); 358 359 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 389 private boolean joinThreads( Thread t[], long waitTime ) 390 throws InterruptedException 391 { 392 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 boolean threadsRunning = true; 409 InterruptedException 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 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 t[i].join( MIN_WAIT_TIME ); 438 } 439 catch (InterruptedException 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 threadsRunning = threadsRunning || !enteredLoop; 461 } 462 if (iex != null) 463 { 464 throw iex; 465 } 466 return threadsRunning; 467 } 468 469 470 475 private int killThreads( Thread [] 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 int count = 0; 489 boolean isAlive = t[i].isAlive(); 490 while (isAlive && count < 10) 491 { 492 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 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 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 createTimeoutError( long maxTime ) 541 { 542 Throwable ret = null; 543 try 545 { 546 Assert.fail( "Threads did not finish within " + 547 maxTime + " milliseconds."); 548 } 549 catch (ThreadDeath td) 550 { 551 throw td; 553 } 554 catch (Throwable t) 555 { 556 t.fillInStackTrace(); 557 ret = t; 558 } 559 return ret; 560 } 561 562 563 566 public static final class TestDeathException extends RuntimeException 567 { 568 private TestDeathException( String msg ) 569 { 570 super( msg ); 571 } 572 } 573 } 574 | Popular Tags |