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