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