1 package org.apache.maven.plugin.surefire.booterclient;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.surefire.CommonReflector;
23 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
24 import org.apache.maven.plugin.surefire.SurefireProperties;
25 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
26 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
27 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
28 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
29 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
30 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
31 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
32 import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamConsumer;
33 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
34 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
35 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
36 import org.apache.maven.shared.utils.cli.CommandLineCallable;
37 import org.apache.maven.shared.utils.cli.CommandLineException;
38 import org.apache.maven.surefire.booter.Classpath;
39 import org.apache.maven.surefire.booter.ClasspathConfiguration;
40 import org.apache.maven.surefire.booter.KeyValueSource;
41 import org.apache.maven.surefire.booter.PropertiesWrapper;
42 import org.apache.maven.surefire.booter.ProviderConfiguration;
43 import org.apache.maven.surefire.booter.ProviderFactory;
44 import org.apache.maven.surefire.booter.Shutdown;
45 import org.apache.maven.surefire.booter.StartupConfiguration;
46 import org.apache.maven.surefire.booter.SurefireBooterForkException;
47 import org.apache.maven.surefire.booter.SurefireExecutionException;
48 import org.apache.maven.surefire.providerapi.SurefireProvider;
49 import org.apache.maven.surefire.report.StackTraceWriter;
50 import org.apache.maven.surefire.suite.RunResult;
51 import org.apache.maven.surefire.testset.TestRequest;
52 import org.apache.maven.surefire.util.DefaultScanResult;
53
54 import java.io.Closeable;
55 import java.io.File;
56 import java.io.IOException;
57 import java.nio.charset.Charset;
58 import java.util.ArrayList;
59 import java.util.Collection;
60 import java.util.Map;
61 import java.util.Properties;
62 import java.util.Queue;
63 import java.util.concurrent.ArrayBlockingQueue;
64 import java.util.concurrent.Callable;
65 import java.util.concurrent.ConcurrentLinkedQueue;
66 import java.util.concurrent.ExecutionException;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Future;
69 import java.util.concurrent.LinkedBlockingQueue;
70 import java.util.concurrent.ScheduledExecutorService;
71 import java.util.concurrent.ScheduledFuture;
72 import java.util.concurrent.ThreadFactory;
73 import java.util.concurrent.ThreadPoolExecutor;
74 import java.util.concurrent.atomic.AtomicInteger;
75
76 import static java.lang.StrictMath.min;
77 import static java.lang.System.currentTimeMillis;
78 import static java.lang.Thread.currentThread;
79 import static java.util.Collections.addAll;
80 import static java.util.concurrent.Executors.newScheduledThreadPool;
81 import static java.util.concurrent.TimeUnit.MILLISECONDS;
82 import static java.util.concurrent.TimeUnit.SECONDS;
83 import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder;
84 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
85 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
86 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
87 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
88 .TestLessInputStreamBuilder;
89 import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable;
90 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
91 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
92 import static org.apache.maven.surefire.booter.Classpath.join;
93 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
94 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
95 import static org.apache.maven.surefire.suite.RunResult.failure;
96 import static org.apache.maven.surefire.suite.RunResult.timeout;
97 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
98 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
99 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
100 import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
101 import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 public class ForkStarter
118 {
119 private static final String EXECUTION_EXCEPTION = "ExecutionException";
120
121 private static final long PING_IN_SECONDS = 10;
122
123 private static final int TIMEOUT_CHECK_PERIOD_MILLIS = 100;
124
125 private static final ThreadFactory FORKED_JVM_DAEMON_THREAD_FACTORY
126 = newDaemonThreadFactory( "surefire-fork-starter" );
127
128 private static final ThreadFactory SHUTDOWN_HOOK_THREAD_FACTORY
129 = newDaemonThreadFactory( "surefire-jvm-killer-shutdownhook" );
130
131 private static final AtomicInteger SYSTEM_PROPERTIES_FILE_COUNTER = new AtomicInteger();
132
133 private final ScheduledExecutorService pingThreadScheduler = createPingScheduler();
134
135 private final ScheduledExecutorService timeoutCheckScheduler;
136
137 private final Queue<ForkClient> currentForkClients;
138
139 private final int forkedProcessTimeoutInSeconds;
140
141 private final ProviderConfiguration providerConfiguration;
142
143 private final StartupConfiguration startupConfiguration;
144
145 private final ForkConfiguration forkConfiguration;
146
147 private final StartupReportConfiguration startupReportConfiguration;
148
149 private final ConsoleLogger log;
150
151 private final DefaultReporterFactory defaultReporterFactory;
152
153 private final Collection<DefaultReporterFactory> defaultReporterFactories;
154
155
156
157
158 private final class CloseableCloser
159 implements Runnable, Closeable
160 {
161 private final int jvmRun;
162
163 private final Queue<Closeable> testProvidingInputStream;
164
165 private final Thread inputStreamCloserHook;
166
167 public CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
168 {
169 this.jvmRun = jvmRun;
170 this.testProvidingInputStream = new ConcurrentLinkedQueue<Closeable>();
171 addAll( this.testProvidingInputStream, testProvidingInputStream );
172 if ( this.testProvidingInputStream.isEmpty() )
173 {
174 inputStreamCloserHook = null;
175 }
176 else
177 {
178 inputStreamCloserHook = newDaemonThread( this, "closer-shutdown-hook" );
179 addShutDownHook( inputStreamCloserHook );
180 }
181 }
182
183 @SuppressWarnings( "checkstyle:innerassignment" )
184 public void run()
185 {
186 for ( Closeable closeable; ( closeable = testProvidingInputStream.poll() ) != null; )
187 {
188 try
189 {
190 closeable.close();
191 }
192 catch ( IOException e )
193 {
194
195
196
197
198
199
200 String msg = "ForkStarter IOException: " + e.getLocalizedMessage() + ".";
201 File dump = InPluginProcessDumpSingleton.getSingleton()
202 .dumpException( e, msg, defaultReporterFactory, jvmRun );
203 log.warning( msg + " See the dump file " + dump.getAbsolutePath() );
204 }
205 }
206 }
207
208 public void close()
209 {
210 run();
211 if ( inputStreamCloserHook != null )
212 {
213 removeShutdownHook( inputStreamCloserHook );
214 }
215 }
216 }
217
218 public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
219 ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
220 StartupReportConfiguration startupReportConfiguration, ConsoleLogger log )
221 {
222 this.forkConfiguration = forkConfiguration;
223 this.providerConfiguration = providerConfiguration;
224 this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
225 this.startupConfiguration = startupConfiguration;
226 this.startupReportConfiguration = startupReportConfiguration;
227 this.log = log;
228 defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
229 defaultReporterFactory.runStarting();
230 defaultReporterFactories = new ConcurrentLinkedQueue<DefaultReporterFactory>();
231 currentForkClients = new ConcurrentLinkedQueue<ForkClient>();
232 timeoutCheckScheduler = createTimeoutCheckScheduler();
233 triggerTimeoutCheck();
234 }
235
236 public RunResult run( SurefireProperties effectiveSystemProperties, DefaultScanResult scanResult )
237 throws SurefireBooterForkException, SurefireExecutionException
238 {
239 try
240 {
241 Map<String, String> providerProperties = providerConfiguration.getProviderProperties();
242 scanResult.writeTo( providerProperties );
243 return isForkOnce()
244 ? run( effectiveSystemProperties, providerProperties )
245 : run( effectiveSystemProperties );
246 }
247 finally
248 {
249 defaultReporterFactory.mergeFromOtherFactories( defaultReporterFactories );
250 defaultReporterFactory.close();
251 pingThreadScheduler.shutdownNow();
252 timeoutCheckScheduler.shutdownNow();
253 }
254 }
255
256 public void killOrphanForks()
257 {
258 for ( ForkClient fork : currentForkClients )
259 {
260 fork.kill();
261 }
262 }
263
264 private RunResult run( SurefireProperties effectiveSystemProperties, Map<String, String> providerProperties )
265 throws SurefireBooterForkException
266 {
267 DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
268 defaultReporterFactories.add( forkedReporterFactory );
269 TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
270 PropertiesWrapper props = new PropertiesWrapper( providerProperties );
271 TestLessInputStream stream = builder.build();
272 Properties sysProps = startupReportConfiguration.getTestVmSystemProperties();
273 ForkClient forkClient = new ForkClient( forkedReporterFactory, sysProps, stream, log );
274 Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
275 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
276 try
277 {
278 addShutDownHook( shutdown );
279 return fork( null, props, forkClient, effectiveSystemProperties, stream, false );
280 }
281 finally
282 {
283 removeShutdownHook( shutdown );
284 ping.cancel( true );
285 builder.removeStream( stream );
286 }
287 }
288
289 private RunResult run( SurefireProperties effectiveSystemProperties )
290 throws SurefireBooterForkException
291 {
292 return forkConfiguration.isReuseForks()
293 ? runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() )
294 : runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
295 }
296
297 private boolean isForkOnce()
298 {
299 return forkConfiguration.isReuseForks() && ( forkConfiguration.getForkCount() == 1 || hasSuiteXmlFiles() );
300 }
301
302 private boolean hasSuiteXmlFiles()
303 {
304 TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
305 return testSuiteDefinition != null && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
306 }
307
308 @SuppressWarnings( "checkstyle:magicnumber" )
309 private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
310 throws SurefireBooterForkException
311 {
312 ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS,
313 new ArrayBlockingQueue<Runnable>( forkCount ) );
314 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
315
316 final Queue<String> tests = new ConcurrentLinkedQueue<String>();
317
318 for ( Class<?> clazz : getSuitesIterator() )
319 {
320 tests.add( clazz.getName() );
321 }
322
323 final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<TestProvidingInputStream>();
324
325 for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ )
326 {
327 testStreams.add( new TestProvidingInputStream( tests ) );
328 }
329
330 ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams );
331 Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() );
332
333 try
334 {
335 addShutDownHook( shutdown );
336 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
337 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
338 Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
339 for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
340 {
341 Callable<RunResult> pf = new Callable<RunResult>()
342 {
343 public RunResult call()
344 throws Exception
345 {
346 DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration, log );
347 defaultReporterFactories.add( reporter );
348
349 Properties vmProps = startupReportConfiguration.getTestVmSystemProperties();
350
351 ForkClient forkClient = new ForkClient( reporter, vmProps, testProvidingInputStream, log )
352 {
353 @Override
354 protected void stopOnNextTest()
355 {
356 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
357 {
358 notifyStreamsToSkipTests( testStreams );
359 }
360 }
361 };
362
363 return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
364 forkClient, effectiveSystemProperties, testProvidingInputStream, true );
365 }
366 };
367 results.add( executorService.submit( pf ) );
368 }
369 return awaitResultsDone( results, executorService );
370 }
371 finally
372 {
373 removeShutdownHook( shutdown );
374 ping.cancel( true );
375 closeExecutor( executorService );
376 }
377 }
378
379 private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams )
380 {
381 for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams )
382 {
383 notifiableTestStream.skipSinceNextTest();
384 }
385 }
386
387 @SuppressWarnings( "checkstyle:magicnumber" )
388 private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount )
389 throws SurefireBooterForkException
390 {
391 ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
392 ThreadPoolExecutor executorService =
393 new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS, new LinkedBlockingQueue<Runnable>() );
394 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
395 final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
396 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
397 Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() );
398 try
399 {
400 addShutDownHook( shutdown );
401 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
402 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
403 for ( final Object testSet : getSuitesIterator() )
404 {
405 Callable<RunResult> pf = new Callable<RunResult>()
406 {
407 public RunResult call()
408 throws Exception
409 {
410 DefaultReporterFactory forkedReporterFactory =
411 new DefaultReporterFactory( startupReportConfiguration, log );
412 defaultReporterFactories.add( forkedReporterFactory );
413 Properties vmProps = startupReportConfiguration.getTestVmSystemProperties();
414 ForkClient forkClient = new ForkClient( forkedReporterFactory, vmProps,
415 builder.getImmediateCommands(), log )
416 {
417 @Override
418 protected void stopOnNextTest()
419 {
420 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
421 {
422 builder.getCachableCommands().skipSinceNextTest();
423 }
424 }
425 };
426 TestLessInputStream stream = builder.build();
427 try
428 {
429 return fork( testSet,
430 new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
431 forkClient, effectiveSystemProperties, stream, false );
432 }
433 finally
434 {
435 builder.removeStream( stream );
436 }
437 }
438 };
439 results.add( executorService.submit( pf ) );
440 }
441 return awaitResultsDone( results, executorService );
442 }
443 finally
444 {
445 removeShutdownHook( shutdown );
446 ping.cancel( true );
447 closeExecutor( executorService );
448 }
449 }
450
451 private static RunResult awaitResultsDone( Collection<Future<RunResult>> results, ExecutorService executorService )
452 throws SurefireBooterForkException
453 {
454 RunResult globalResult = new RunResult( 0, 0, 0, 0 );
455 SurefireBooterForkException exception = null;
456 for ( Future<RunResult> result : results )
457 {
458 try
459 {
460 RunResult cur = result.get();
461 if ( cur != null )
462 {
463 globalResult = globalResult.aggregate( cur );
464 }
465 else
466 {
467 throw new SurefireBooterForkException( "No results for " + result.toString() );
468 }
469 }
470 catch ( InterruptedException e )
471 {
472 executorService.shutdownNow();
473 currentThread().interrupt();
474 throw new SurefireBooterForkException( "Interrupted", e );
475 }
476 catch ( ExecutionException e )
477 {
478 Throwable realException = e.getCause();
479 if ( realException == null )
480 {
481 if ( exception == null )
482 {
483 exception = new SurefireBooterForkException( EXECUTION_EXCEPTION );
484 }
485 }
486 else
487 {
488 String previousError = "";
489 if ( exception != null && !EXECUTION_EXCEPTION.equals( exception.getLocalizedMessage().trim() ) )
490 {
491 previousError = exception.getLocalizedMessage() + "\n";
492 }
493 String error = previousError + EXECUTION_EXCEPTION + " " + realException.getLocalizedMessage();
494 exception = new SurefireBooterForkException( error, realException );
495 }
496 }
497 }
498
499 if ( exception != null )
500 {
501 throw exception;
502 }
503
504 return globalResult;
505 }
506
507 @SuppressWarnings( "checkstyle:magicnumber" )
508 private void closeExecutor( ExecutorService executorService )
509 throws SurefireBooterForkException
510 {
511 executorService.shutdown();
512 try
513 {
514
515 executorService.awaitTermination( 60 * 60, SECONDS );
516 }
517 catch ( InterruptedException e )
518 {
519 currentThread().interrupt();
520 throw new SurefireBooterForkException( "Interrupted", e );
521 }
522 }
523
524 private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
525 SurefireProperties effectiveSystemProperties,
526 AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
527 throws SurefireBooterForkException
528 {
529 int forkNumber = drawNumber();
530 forkClient.setForkNumber( forkNumber );
531 try
532 {
533 return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
534 testProvidingInputStream, readTestsFromInStream );
535 }
536 finally
537 {
538 returnNumber( forkNumber );
539 }
540 }
541
542 private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
543 SurefireProperties effectiveSystemProperties, int forkNumber,
544 AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
545 throws SurefireBooterForkException
546 {
547 final String tempDir;
548 final File surefireProperties;
549 final File systPropsFile;
550 try
551 {
552 tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
553 BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
554
555 surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
556 startupConfiguration, testSet, readTestsFromInStream );
557
558 if ( effectiveSystemProperties != null )
559 {
560 SurefireProperties filteredProperties =
561 createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
562
563 systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
564 "surefire_" + SYSTEM_PROPERTIES_FILE_COUNTER.getAndIncrement(),
565 forkConfiguration.isDebug() );
566 }
567 else
568 {
569 systPropsFile = null;
570 }
571 }
572 catch ( IOException e )
573 {
574 throw new SurefireBooterForkException( "Error creating properties files for forking", e );
575 }
576
577
578 final Classpath bootClasspathConfiguration = startupConfiguration.isProviderMainClass()
579 ? startupConfiguration.getClasspathConfiguration().getProviderClasspath()
580 : forkConfiguration.getBootClasspath();
581
582 Classpath bootClasspath = join(
583 join( bootClasspathConfiguration, startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
584 startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
585
586 log.debug( bootClasspath.getLogMessage( "boot" ) );
587 log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
588
589 OutputStreamFlushableCommandline cli =
590 forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber );
591
592 if ( testProvidingInputStream != null )
593 {
594 testProvidingInputStream.setFlushReceiverProvider( cli );
595 }
596
597 cli.createArg().setValue( tempDir );
598 cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
599 cli.createArg().setValue( surefireProperties.getName() );
600 if ( systPropsFile != null )
601 {
602 cli.createArg().setValue( systPropsFile.getName() );
603 }
604
605 final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
606 final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
607 requireNonNull( testProvidingInputStream, "null param" ) );
608
609 log.debug( "Forking command line: " + cli );
610
611 Integer result = null;
612 RunResult runResult = null;
613 SurefireBooterForkException booterForkException = null;
614 try
615 {
616 NativeStdErrStreamConsumer stdErrConsumer =
617 new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
618
619 CommandLineCallable future =
620 executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
621 stdErrConsumer, 0, closer,
622 Charset.forName( FORK_STREAM_CHARSET_NAME ) );
623
624 currentForkClients.add( forkClient );
625
626 result = future.call();
627
628 if ( forkClient.hadTimeout() )
629 {
630 runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
631 }
632 else if ( result != SUCCESS )
633 {
634 booterForkException =
635 new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
636 }
637 }
638 catch ( CommandLineException e )
639 {
640 runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
641 String cliErr = e.getLocalizedMessage();
642 Throwable cause = e.getCause();
643 booterForkException =
644 new SurefireBooterForkException( "Error while executing forked tests.", cliErr, cause, runResult );
645 }
646 finally
647 {
648 currentForkClients.remove( forkClient );
649 closer.close();
650 if ( runResult == null )
651 {
652 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
653 }
654 forkClient.close( runResult.isTimeout() );
655
656 if ( !runResult.isTimeout() )
657 {
658 Throwable cause = booterForkException == null ? null : booterForkException.getCause();
659 String detail = booterForkException == null ? "" : "\n" + booterForkException.getMessage();
660
661 if ( forkClient.isErrorInFork() )
662 {
663 StackTraceWriter errorInFork = forkClient.getErrorInFork();
664
665 throw new SurefireBooterForkException( "There was an error in the forked process"
666 + detail
667 + '\n'
668 + errorInFork.getThrowable().getLocalizedMessage(), cause );
669 }
670 if ( !forkClient.isSaidGoodBye() )
671 {
672 String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
673 String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
674 for ( String test : forkClient.testsInProgress() )
675 {
676 testsInProgress += "\n" + test;
677 }
678
679 throw new SurefireBooterForkException(
680 "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
681 + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
682 }
683 }
684
685 if ( booterForkException != null )
686 {
687
688 throw booterForkException;
689 }
690 }
691
692 return runResult;
693 }
694
695 private Iterable<Class<?>> getSuitesIterator()
696 throws SurefireBooterForkException
697 {
698 try
699 {
700 final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
701 ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
702
703 CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
704 Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration, log );
705
706 ProviderFactory providerFactory =
707 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory );
708 SurefireProvider surefireProvider = providerFactory.createProvider( false );
709 return surefireProvider.getSuites();
710 }
711 catch ( SurefireExecutionException e )
712 {
713 throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
714 }
715 }
716
717 private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder,
718 final Shutdown shutdownType )
719 {
720 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
721 {
722 public void run()
723 {
724 builder.getImmediateCommands().shutdown( shutdownType );
725 }
726 } );
727 }
728
729 private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder,
730 final Shutdown shutdownType )
731 {
732 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
733 {
734 public void run()
735 {
736 builder.getCachableCommands().shutdown( shutdownType );
737 }
738 } );
739 }
740
741 private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams,
742 final Shutdown shutdownType )
743 {
744 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
745 {
746 public void run()
747 {
748 for ( TestProvidingInputStream stream : streams )
749 {
750 stream.shutdown( shutdownType );
751 }
752 }
753 } );
754 }
755
756 private static ScheduledExecutorService createPingScheduler()
757 {
758 ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "s" );
759 return newScheduledThreadPool( 1, threadFactory );
760 }
761
762 private static ScheduledExecutorService createTimeoutCheckScheduler()
763 {
764 ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" );
765 return newScheduledThreadPool( 1, threadFactory );
766 }
767
768 private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder )
769 {
770 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
771 {
772 public void run()
773 {
774 builder.getImmediateCommands().noop();
775 }
776 }, 0, PING_IN_SECONDS, SECONDS );
777 }
778
779 private ScheduledFuture<?> triggerPingTimerForShutdown( final Iterable<TestProvidingInputStream> streams )
780 {
781 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
782 {
783 public void run()
784 {
785 for ( TestProvidingInputStream stream : streams )
786 {
787 stream.noop();
788 }
789 }
790 }, 0, PING_IN_SECONDS, SECONDS );
791 }
792
793 private ScheduledFuture<?> triggerTimeoutCheck()
794 {
795 return timeoutCheckScheduler.scheduleAtFixedRate( new Runnable()
796 {
797 public void run()
798 {
799 long systemTime = currentTimeMillis();
800 for ( ForkClient forkClient : currentForkClients )
801 {
802 forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds );
803 }
804 }
805 }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS );
806 }
807 }