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