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.AbstractPathConfiguration;
39 import org.apache.maven.surefire.booter.KeyValueSource;
40 import org.apache.maven.surefire.booter.PropertiesWrapper;
41 import org.apache.maven.surefire.booter.ProviderConfiguration;
42 import org.apache.maven.surefire.booter.ProviderFactory;
43 import org.apache.maven.surefire.booter.Shutdown;
44 import org.apache.maven.surefire.booter.StartupConfiguration;
45 import org.apache.maven.surefire.booter.SurefireBooterForkException;
46 import org.apache.maven.surefire.booter.SurefireExecutionException;
47 import org.apache.maven.surefire.providerapi.SurefireProvider;
48 import org.apache.maven.surefire.report.StackTraceWriter;
49 import org.apache.maven.surefire.suite.RunResult;
50 import org.apache.maven.surefire.testset.TestRequest;
51 import org.apache.maven.surefire.util.DefaultScanResult;
52
53 import javax.annotation.Nonnull;
54 import java.io.Closeable;
55 import java.io.File;
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Map;
60 import java.util.Queue;
61 import java.util.concurrent.ArrayBlockingQueue;
62 import java.util.concurrent.Callable;
63 import java.util.concurrent.ConcurrentLinkedQueue;
64 import java.util.concurrent.ExecutionException;
65 import java.util.concurrent.ExecutorService;
66 import java.util.concurrent.Future;
67 import java.util.concurrent.LinkedBlockingQueue;
68 import java.util.concurrent.ScheduledExecutorService;
69 import java.util.concurrent.ScheduledFuture;
70 import java.util.concurrent.ThreadFactory;
71 import java.util.concurrent.ThreadPoolExecutor;
72 import java.util.concurrent.atomic.AtomicBoolean;
73 import java.util.concurrent.atomic.AtomicInteger;
74
75 import static java.lang.StrictMath.min;
76 import static java.lang.System.currentTimeMillis;
77 import static java.lang.Thread.currentThread;
78 import static java.util.Collections.addAll;
79 import static java.util.concurrent.Executors.newScheduledThreadPool;
80 import static java.util.concurrent.TimeUnit.MILLISECONDS;
81 import static java.util.concurrent.TimeUnit.SECONDS;
82 import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder;
83 import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
84 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
85 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
86 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
87 .TestLessInputStreamBuilder;
88 import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable;
89 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
90 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
91 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
92 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
93 import static org.apache.maven.surefire.suite.RunResult.failure;
94 import static org.apache.maven.surefire.suite.RunResult.timeout;
95 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
96 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
97 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
98 import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
99 import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 public class ForkStarter
116 {
117 private static final String EXECUTION_EXCEPTION = "ExecutionException";
118
119 private static final long PING_IN_SECONDS = 10;
120
121 private static final int TIMEOUT_CHECK_PERIOD_MILLIS = 100;
122
123 private static final ThreadFactory FORKED_JVM_DAEMON_THREAD_FACTORY
124 = newDaemonThreadFactory( "surefire-fork-starter" );
125
126 private static final ThreadFactory SHUTDOWN_HOOK_THREAD_FACTORY
127 = newDaemonThreadFactory( "surefire-jvm-killer-shutdownhook" );
128
129 private static final AtomicInteger SYSTEM_PROPERTIES_FILE_COUNTER = new AtomicInteger();
130
131 private final ScheduledExecutorService pingThreadScheduler = createPingScheduler();
132
133 private final ScheduledExecutorService timeoutCheckScheduler;
134
135 private final Queue<ForkClient> currentForkClients;
136
137 private final int forkedProcessTimeoutInSeconds;
138
139 private final ProviderConfiguration providerConfiguration;
140
141 private final StartupConfiguration startupConfiguration;
142
143 private final ForkConfiguration forkConfiguration;
144
145 private final StartupReportConfiguration startupReportConfiguration;
146
147 private final ConsoleLogger log;
148
149 private final DefaultReporterFactory defaultReporterFactory;
150
151 private final Collection<DefaultReporterFactory> defaultReporterFactories;
152
153
154
155
156 private final class CloseableCloser
157 implements Runnable, Closeable
158 {
159 private final int jvmRun;
160
161 private final Queue<Closeable> testProvidingInputStream;
162
163 private final Thread inputStreamCloserHook;
164
165 CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
166 {
167 this.jvmRun = jvmRun;
168 this.testProvidingInputStream = new ConcurrentLinkedQueue<Closeable>();
169 addAll( this.testProvidingInputStream, testProvidingInputStream );
170 if ( this.testProvidingInputStream.isEmpty() )
171 {
172 inputStreamCloserHook = null;
173 }
174 else
175 {
176 inputStreamCloserHook = newDaemonThread( this, "closer-shutdown-hook" );
177 addShutDownHook( inputStreamCloserHook );
178 }
179 }
180
181 @Override
182 @SuppressWarnings( "checkstyle:innerassignment" )
183 public void run()
184 {
185 for ( Closeable closeable; ( closeable = testProvidingInputStream.poll() ) != null; )
186 {
187 try
188 {
189 closeable.close();
190 }
191 catch ( IOException e )
192 {
193
194
195
196
197
198
199 String msg = "ForkStarter IOException: " + e.getLocalizedMessage() + ".";
200 File dump = InPluginProcessDumpSingleton.getSingleton()
201 .dumpException( e, msg, defaultReporterFactory, jvmRun );
202 log.warning( msg + " See the dump file " + dump.getAbsolutePath() );
203 }
204 }
205 }
206
207 @Override
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( @Nonnull SurefireProperties effectiveSystemProperties, @Nonnull 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 ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean() );
273 Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
274 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
275 try
276 {
277 addShutDownHook( shutdown );
278 return fork( null, props, forkClient, effectiveSystemProperties, stream, false );
279 }
280 finally
281 {
282 removeShutdownHook( shutdown );
283 ping.cancel( true );
284 builder.removeStream( stream );
285 }
286 }
287
288 private RunResult run( SurefireProperties effectiveSystemProperties )
289 throws SurefireBooterForkException
290 {
291 return forkConfiguration.isReuseForks()
292 ? runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() )
293 : runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
294 }
295
296 private boolean isForkOnce()
297 {
298 return forkConfiguration.isReuseForks() && ( forkConfiguration.getForkCount() == 1 || hasSuiteXmlFiles() );
299 }
300
301 private boolean hasSuiteXmlFiles()
302 {
303 TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
304 return testSuiteDefinition != null && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
305 }
306
307 @SuppressWarnings( "checkstyle:magicnumber" )
308 private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
309 throws SurefireBooterForkException
310 {
311 ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS,
312 new ArrayBlockingQueue<Runnable>( forkCount ) );
313 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
314
315 final Queue<String> tests = new ConcurrentLinkedQueue<String>();
316
317 for ( Class<?> clazz : getSuitesIterator() )
318 {
319 tests.add( clazz.getName() );
320 }
321
322 final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<TestProvidingInputStream>();
323
324 for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ )
325 {
326 testStreams.add( new TestProvidingInputStream( tests ) );
327 }
328
329 ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams );
330 Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() );
331
332 try
333 {
334 addShutDownHook( shutdown );
335 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
336 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
337 final Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
338 final AtomicBoolean printedErrorStream = new AtomicBoolean();
339 for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
340 {
341 Callable<RunResult> pf = new Callable<RunResult>()
342 {
343 @Override
344 public RunResult call()
345 throws Exception
346 {
347 DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration, log );
348 defaultReporterFactories.add( reporter );
349 ForkClient forkClient =
350 new ForkClient( reporter, testProvidingInputStream, log, printedErrorStream )
351 {
352 @Override
353 protected void stopOnNextTest()
354 {
355 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
356 {
357 notifyStreamsToSkipTests( testStreams );
358 }
359 }
360 };
361
362 return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
363 forkClient, effectiveSystemProperties, testProvidingInputStream, true );
364 }
365 };
366 results.add( executorService.submit( pf ) );
367 }
368 return awaitResultsDone( results, executorService );
369 }
370 finally
371 {
372 removeShutdownHook( shutdown );
373 ping.cancel( true );
374 closeExecutor( executorService );
375 }
376 }
377
378 private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams )
379 {
380 for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams )
381 {
382 notifiableTestStream.skipSinceNextTest();
383 }
384 }
385
386 @SuppressWarnings( "checkstyle:magicnumber" )
387 private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount )
388 throws SurefireBooterForkException
389 {
390 ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
391 ThreadPoolExecutor executorService =
392 new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS, new LinkedBlockingQueue<Runnable>() );
393 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
394 final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
395 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
396 Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() );
397 try
398 {
399 addShutDownHook( shutdown );
400 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
401 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
402 final AtomicBoolean printedErrorStream = new AtomicBoolean();
403 for ( final Object testSet : getSuitesIterator() )
404 {
405 Callable<RunResult> pf = new Callable<RunResult>()
406 {
407 @Override
408 public RunResult call()
409 throws Exception
410 {
411 DefaultReporterFactory forkedReporterFactory =
412 new DefaultReporterFactory( startupReportConfiguration, log );
413 defaultReporterFactories.add( forkedReporterFactory );
414 ForkClient forkClient = new ForkClient( forkedReporterFactory, builder.getImmediateCommands(),
415 log, printedErrorStream )
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 Long pluginPid = forkConfiguration.getPluginPlatform().getPluginPid();
555 surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
556 startupConfiguration, testSet, readTestsFromInStream, pluginPid );
557
558 log.debug( "Determined Maven Process ID " + pluginPid );
559
560 if ( effectiveSystemProperties != null )
561 {
562 SurefireProperties filteredProperties =
563 createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
564
565 systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
566 "surefire_" + SYSTEM_PROPERTIES_FILE_COUNTER.getAndIncrement(),
567 forkConfiguration.isDebug() );
568 }
569 else
570 {
571 systPropsFile = null;
572 }
573 }
574 catch ( IOException e )
575 {
576 throw new SurefireBooterForkException( "Error creating properties files for forking", e );
577 }
578
579
580
581 OutputStreamFlushableCommandline cli = forkConfiguration.createCommandLine( startupConfiguration, forkNumber );
582
583 if ( testProvidingInputStream != null )
584 {
585 testProvidingInputStream.setFlushReceiverProvider( cli );
586 }
587
588 cli.createArg().setValue( tempDir );
589 cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
590 cli.createArg().setValue( surefireProperties.getName() );
591 if ( systPropsFile != null )
592 {
593 cli.createArg().setValue( systPropsFile.getName() );
594 }
595
596 final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
597 final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
598 requireNonNull( testProvidingInputStream, "null param" ) );
599
600 log.debug( "Forking command line: " + cli );
601
602 Integer result = null;
603 RunResult runResult = null;
604 SurefireBooterForkException booterForkException = null;
605 try
606 {
607 NativeStdErrStreamConsumer stdErrConsumer =
608 new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
609
610 CommandLineCallable future =
611 executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
612 stdErrConsumer, 0, closer, ISO_8859_1 );
613
614 currentForkClients.add( forkClient );
615
616 result = future.call();
617
618 if ( forkClient.hadTimeout() )
619 {
620 runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
621 }
622 else if ( result == null || result != SUCCESS )
623 {
624 booterForkException =
625 new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
626 }
627 }
628 catch ( CommandLineException e )
629 {
630 runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
631 String cliErr = e.getLocalizedMessage();
632 Throwable cause = e.getCause();
633 booterForkException =
634 new SurefireBooterForkException( "Error while executing forked tests.", cliErr, cause, runResult );
635 }
636 finally
637 {
638 currentForkClients.remove( forkClient );
639 closer.close();
640 if ( runResult == null )
641 {
642 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
643 }
644 forkClient.close( runResult.isTimeout() );
645
646 if ( !runResult.isTimeout() )
647 {
648 Throwable cause = booterForkException == null ? null : booterForkException.getCause();
649 String detail = booterForkException == null ? "" : "\n" + booterForkException.getMessage();
650
651 if ( forkClient.isErrorInFork() )
652 {
653 StackTraceWriter errorInFork = forkClient.getErrorInFork();
654
655 throw new SurefireBooterForkException( "There was an error in the forked process"
656 + detail
657 + '\n'
658 + errorInFork.getThrowable().getLocalizedMessage(), cause );
659 }
660 if ( !forkClient.isSaidGoodBye() )
661 {
662 String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
663 String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
664 for ( String test : forkClient.testsInProgress() )
665 {
666 testsInProgress += "\n" + test;
667 }
668
669 throw new SurefireBooterForkException(
670 "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
671 + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
672 }
673 }
674
675 if ( booterForkException != null )
676 {
677
678 throw booterForkException;
679 }
680 }
681
682 return runResult;
683 }
684
685 private Iterable<Class<?>> getSuitesIterator()
686 throws SurefireBooterForkException
687 {
688 try
689 {
690 AbstractPathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
691 ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
692
693 CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
694 Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration, log );
695
696 ProviderFactory providerFactory =
697 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory );
698 SurefireProvider surefireProvider = providerFactory.createProvider( false );
699 return surefireProvider.getSuites();
700 }
701 catch ( SurefireExecutionException e )
702 {
703 throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
704 }
705 }
706
707 private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder,
708 final Shutdown shutdownType )
709 {
710 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
711 {
712 @Override
713 public void run()
714 {
715 builder.getImmediateCommands().shutdown( shutdownType );
716 }
717 } );
718 }
719
720 private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder,
721 final Shutdown shutdownType )
722 {
723 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
724 {
725 @Override
726 public void run()
727 {
728 builder.getCachableCommands().shutdown( shutdownType );
729 }
730 } );
731 }
732
733 private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams,
734 final Shutdown shutdownType )
735 {
736 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
737 {
738 @Override
739 public void run()
740 {
741 for ( TestProvidingInputStream stream : streams )
742 {
743 stream.shutdown( shutdownType );
744 }
745 }
746 } );
747 }
748
749 private static ScheduledExecutorService createPingScheduler()
750 {
751 ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "s" );
752 return newScheduledThreadPool( 1, threadFactory );
753 }
754
755 private static ScheduledExecutorService createTimeoutCheckScheduler()
756 {
757 ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" );
758 return newScheduledThreadPool( 1, threadFactory );
759 }
760
761 private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder )
762 {
763 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
764 {
765 @Override
766 public void run()
767 {
768 builder.getImmediateCommands().noop();
769 }
770 }, 0, PING_IN_SECONDS, SECONDS );
771 }
772
773 private ScheduledFuture<?> triggerPingTimerForShutdown( final Iterable<TestProvidingInputStream> streams )
774 {
775 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
776 {
777 @Override
778 public void run()
779 {
780 for ( TestProvidingInputStream stream : streams )
781 {
782 stream.noop();
783 }
784 }
785 }, 0, PING_IN_SECONDS, SECONDS );
786 }
787
788 private ScheduledFuture<?> triggerTimeoutCheck()
789 {
790 return timeoutCheckScheduler.scheduleAtFixedRate( new Runnable()
791 {
792 @Override
793 public void run()
794 {
795 long systemTime = currentTimeMillis();
796 for ( ForkClient forkClient : currentForkClients )
797 {
798 forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds );
799 }
800 }
801 }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS );
802 }
803 }