1 package org.apache.maven.plugin.surefire.booterclient;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.surefire.CommonReflector;
23 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
24 import org.apache.maven.plugin.surefire.SurefireProperties;
25 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
26 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
27 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
28 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
29 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
30 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
31 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
32 import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamConsumer;
33 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
34 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
35 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
36 import org.apache.maven.shared.utils.cli.CommandLineCallable;
37 import org.apache.maven.shared.utils.cli.CommandLineException;
38 import org.apache.maven.surefire.booter.Classpath;
39 import org.apache.maven.surefire.booter.ClasspathConfiguration;
40 import org.apache.maven.surefire.booter.KeyValueSource;
41 import org.apache.maven.surefire.booter.PropertiesWrapper;
42 import org.apache.maven.surefire.booter.ProviderConfiguration;
43 import org.apache.maven.surefire.booter.ProviderFactory;
44 import org.apache.maven.surefire.booter.Shutdown;
45 import org.apache.maven.surefire.booter.StartupConfiguration;
46 import org.apache.maven.surefire.booter.SurefireBooterForkException;
47 import org.apache.maven.surefire.booter.SurefireExecutionException;
48 import org.apache.maven.surefire.providerapi.SurefireProvider;
49 import org.apache.maven.surefire.report.StackTraceWriter;
50 import org.apache.maven.surefire.suite.RunResult;
51 import org.apache.maven.surefire.testset.TestRequest;
52 import org.apache.maven.surefire.util.DefaultScanResult;
53
54 import java.io.Closeable;
55 import java.io.File;
56 import java.io.IOException;
57 import java.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.Classpath.join;
92 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
93 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
94 import static org.apache.maven.surefire.suite.RunResult.failure;
95 import static org.apache.maven.surefire.suite.RunResult.timeout;
96 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
97 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
98 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
99 import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
100 import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
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 public CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
167 {
168 this.jvmRun = jvmRun;
169 this.testProvidingInputStream = new ConcurrentLinkedQueue<Closeable>();
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 dump = InPluginProcessDumpSingleton.getSingleton()
202 .dumpException( e, msg, defaultReporterFactory, 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( SurefireProperties effectiveSystemProperties, 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 DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
269 defaultReporterFactories.add( forkedReporterFactory );
270 TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
271 PropertiesWrapper props = new PropertiesWrapper( providerProperties );
272 TestLessInputStream stream = builder.build();
273 ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, log, forkConfiguration.isDebug(),
274 new AtomicBoolean() );
275 Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
276 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
277 try
278 {
279 addShutDownHook( shutdown );
280 return fork( null, props, forkClient, effectiveSystemProperties, stream, false );
281 }
282 finally
283 {
284 removeShutdownHook( shutdown );
285 ping.cancel( true );
286 builder.removeStream( stream );
287 }
288 }
289
290 private RunResult run( SurefireProperties effectiveSystemProperties )
291 throws SurefireBooterForkException
292 {
293 return forkConfiguration.isReuseForks()
294 ? runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() )
295 : runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
296 }
297
298 private boolean isForkOnce()
299 {
300 return forkConfiguration.isReuseForks() && ( forkConfiguration.getForkCount() == 1 || hasSuiteXmlFiles() );
301 }
302
303 private boolean hasSuiteXmlFiles()
304 {
305 TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
306 return testSuiteDefinition != null && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
307 }
308
309 @SuppressWarnings( "checkstyle:magicnumber" )
310 private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
311 throws SurefireBooterForkException
312 {
313 ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS,
314 new ArrayBlockingQueue<Runnable>( forkCount ) );
315 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
316
317 final Queue<String> tests = new ConcurrentLinkedQueue<String>();
318
319 for ( Class<?> clazz : getSuitesIterator() )
320 {
321 tests.add( clazz.getName() );
322 }
323
324 final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<TestProvidingInputStream>();
325
326 for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ )
327 {
328 testStreams.add( new TestProvidingInputStream( tests ) );
329 }
330
331 ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams );
332 Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() );
333
334 try
335 {
336 addShutDownHook( shutdown );
337 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
338 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
339 final Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
340 final AtomicBoolean printedErrorStream = new AtomicBoolean();
341 for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
342 {
343 Callable<RunResult> pf = new Callable<RunResult>()
344 {
345 @Override
346 public RunResult call()
347 throws Exception
348 {
349 DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration, log );
350 defaultReporterFactories.add( reporter );
351 ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
352 forkConfiguration.isDebug(), printedErrorStream )
353 {
354 @Override
355 protected void stopOnNextTest()
356 {
357 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
358 {
359 notifyStreamsToSkipTests( testStreams );
360 }
361 }
362 };
363
364 return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
365 forkClient, effectiveSystemProperties, testProvidingInputStream, true );
366 }
367 };
368 results.add( executorService.submit( pf ) );
369 }
370 return awaitResultsDone( results, executorService );
371 }
372 finally
373 {
374 removeShutdownHook( shutdown );
375 ping.cancel( true );
376 closeExecutor( executorService );
377 }
378 }
379
380 private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams )
381 {
382 for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams )
383 {
384 notifiableTestStream.skipSinceNextTest();
385 }
386 }
387
388 @SuppressWarnings( "checkstyle:magicnumber" )
389 private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount )
390 throws SurefireBooterForkException
391 {
392 ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
393 ThreadPoolExecutor executorService =
394 new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS, new LinkedBlockingQueue<Runnable>() );
395 executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
396 final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
397 ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
398 Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() );
399 try
400 {
401 addShutDownHook( shutdown );
402 int failFastCount = providerConfiguration.getSkipAfterFailureCount();
403 final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
404 final AtomicBoolean printedErrorStream = new AtomicBoolean();
405 for ( final Object testSet : getSuitesIterator() )
406 {
407 Callable<RunResult> pf = new Callable<RunResult>()
408 {
409 @Override
410 public RunResult call()
411 throws Exception
412 {
413 DefaultReporterFactory forkedReporterFactory =
414 new DefaultReporterFactory( startupReportConfiguration, log );
415 defaultReporterFactories.add( forkedReporterFactory );
416 ForkClient forkClient = new ForkClient( forkedReporterFactory, builder.getImmediateCommands(),
417 log, forkConfiguration.isDebug(), printedErrorStream )
418 {
419 @Override
420 protected void stopOnNextTest()
421 {
422 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
423 {
424 builder.getCachableCommands().skipSinceNextTest();
425 }
426 }
427 };
428 TestLessInputStream stream = builder.build();
429 try
430 {
431 return fork( testSet,
432 new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
433 forkClient, effectiveSystemProperties, stream, false );
434 }
435 finally
436 {
437 builder.removeStream( stream );
438 }
439 }
440 };
441 results.add( executorService.submit( pf ) );
442 }
443 return awaitResultsDone( results, executorService );
444 }
445 finally
446 {
447 removeShutdownHook( shutdown );
448 ping.cancel( true );
449 closeExecutor( executorService );
450 }
451 }
452
453 private static RunResult awaitResultsDone( Collection<Future<RunResult>> results, ExecutorService executorService )
454 throws SurefireBooterForkException
455 {
456 RunResult globalResult = new RunResult( 0, 0, 0, 0 );
457 SurefireBooterForkException exception = null;
458 for ( Future<RunResult> result : results )
459 {
460 try
461 {
462 RunResult cur = result.get();
463 if ( cur != null )
464 {
465 globalResult = globalResult.aggregate( cur );
466 }
467 else
468 {
469 throw new SurefireBooterForkException( "No results for " + result.toString() );
470 }
471 }
472 catch ( InterruptedException e )
473 {
474 executorService.shutdownNow();
475 currentThread().interrupt();
476 throw new SurefireBooterForkException( "Interrupted", e );
477 }
478 catch ( ExecutionException e )
479 {
480 Throwable realException = e.getCause();
481 if ( realException == null )
482 {
483 if ( exception == null )
484 {
485 exception = new SurefireBooterForkException( EXECUTION_EXCEPTION );
486 }
487 }
488 else
489 {
490 String previousError = "";
491 if ( exception != null && !EXECUTION_EXCEPTION.equals( exception.getLocalizedMessage().trim() ) )
492 {
493 previousError = exception.getLocalizedMessage() + "\n";
494 }
495 String error = previousError + EXECUTION_EXCEPTION + " " + realException.getLocalizedMessage();
496 exception = new SurefireBooterForkException( error, realException );
497 }
498 }
499 }
500
501 if ( exception != null )
502 {
503 throw exception;
504 }
505
506 return globalResult;
507 }
508
509 @SuppressWarnings( "checkstyle:magicnumber" )
510 private void closeExecutor( ExecutorService executorService )
511 throws SurefireBooterForkException
512 {
513 executorService.shutdown();
514 try
515 {
516
517 executorService.awaitTermination( 60 * 60, SECONDS );
518 }
519 catch ( InterruptedException e )
520 {
521 currentThread().interrupt();
522 throw new SurefireBooterForkException( "Interrupted", e );
523 }
524 }
525
526 private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
527 SurefireProperties effectiveSystemProperties,
528 AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
529 throws SurefireBooterForkException
530 {
531 int forkNumber = drawNumber();
532 forkClient.setForkNumber( forkNumber );
533 try
534 {
535 return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
536 testProvidingInputStream, readTestsFromInStream );
537 }
538 finally
539 {
540 returnNumber( forkNumber );
541 }
542 }
543
544 private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
545 SurefireProperties effectiveSystemProperties, int forkNumber,
546 AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
547 throws SurefireBooterForkException
548 {
549 final String tempDir;
550 final File surefireProperties;
551 final File systPropsFile;
552 try
553 {
554 tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
555 BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
556
557 surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
558 startupConfiguration, testSet,
559 readTestsFromInStream,
560 forkConfiguration.getPluginPlatform().getPid() );
561
562 log.debug( "Determined Maven Process ID " + forkConfiguration.getPluginPlatform().getPid() );
563
564 if ( effectiveSystemProperties != null )
565 {
566 SurefireProperties filteredProperties =
567 createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
568
569 systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
570 "surefire_" + SYSTEM_PROPERTIES_FILE_COUNTER.getAndIncrement(),
571 forkConfiguration.isDebug() );
572 }
573 else
574 {
575 systPropsFile = null;
576 }
577 }
578 catch ( IOException e )
579 {
580 throw new SurefireBooterForkException( "Error creating properties files for forking", e );
581 }
582
583
584 final Classpath bootClasspathConfiguration = startupConfiguration.isProviderMainClass()
585 ? startupConfiguration.getClasspathConfiguration().getProviderClasspath()
586 : forkConfiguration.getBootClasspath();
587
588 Classpath bootClasspath = join(
589 join( bootClasspathConfiguration, startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
590 startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
591
592 log.debug( bootClasspath.getLogMessage( "boot" ) );
593 log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
594
595 OutputStreamFlushableCommandline cli =
596 forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber );
597
598 if ( testProvidingInputStream != null )
599 {
600 testProvidingInputStream.setFlushReceiverProvider( cli );
601 }
602
603 cli.createArg().setValue( tempDir );
604 cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
605 cli.createArg().setValue( surefireProperties.getName() );
606 if ( systPropsFile != null )
607 {
608 cli.createArg().setValue( systPropsFile.getName() );
609 }
610
611 final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
612 final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
613 requireNonNull( testProvidingInputStream, "null param" ) );
614
615 log.debug( "Forking command line: " + cli );
616
617 Integer result = null;
618 RunResult runResult = null;
619 SurefireBooterForkException booterForkException = null;
620 try
621 {
622 NativeStdErrStreamConsumer stdErrConsumer =
623 new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
624
625 CommandLineCallable future =
626 executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
627 stdErrConsumer, 0, closer, ISO_8859_1 );
628
629 currentForkClients.add( forkClient );
630
631 result = future.call();
632
633 if ( forkClient.hadTimeout() )
634 {
635 runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
636 }
637 else if ( result != SUCCESS )
638 {
639 booterForkException =
640 new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
641 }
642 }
643 catch ( CommandLineException e )
644 {
645 runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
646 String cliErr = e.getLocalizedMessage();
647 Throwable cause = e.getCause();
648 booterForkException =
649 new SurefireBooterForkException( "Error while executing forked tests.", cliErr, cause, runResult );
650 }
651 finally
652 {
653 currentForkClients.remove( forkClient );
654 closer.close();
655 if ( runResult == null )
656 {
657 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
658 }
659 forkClient.close( runResult.isTimeout() );
660
661 if ( !runResult.isTimeout() )
662 {
663 Throwable cause = booterForkException == null ? null : booterForkException.getCause();
664 String detail = booterForkException == null ? "" : "\n" + booterForkException.getMessage();
665
666 if ( forkClient.isErrorInFork() )
667 {
668 StackTraceWriter errorInFork = forkClient.getErrorInFork();
669
670 throw new SurefireBooterForkException( "There was an error in the forked process"
671 + detail
672 + '\n'
673 + errorInFork.getThrowable().getLocalizedMessage(), cause );
674 }
675 if ( !forkClient.isSaidGoodBye() )
676 {
677 String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
678 String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
679 for ( String test : forkClient.testsInProgress() )
680 {
681 testsInProgress += "\n" + test;
682 }
683
684 throw new SurefireBooterForkException(
685 "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
686 + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
687 }
688 }
689
690 if ( booterForkException != null )
691 {
692
693 throw booterForkException;
694 }
695 }
696
697 return runResult;
698 }
699
700 private Iterable<Class<?>> getSuitesIterator()
701 throws SurefireBooterForkException
702 {
703 try
704 {
705 final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
706 ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
707
708 CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
709 Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration, log );
710
711 ProviderFactory providerFactory =
712 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory );
713 SurefireProvider surefireProvider = providerFactory.createProvider( false );
714 return surefireProvider.getSuites();
715 }
716 catch ( SurefireExecutionException e )
717 {
718 throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
719 }
720 }
721
722 private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder,
723 final Shutdown shutdownType )
724 {
725 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
726 {
727 @Override
728 public void run()
729 {
730 builder.getImmediateCommands().shutdown( shutdownType );
731 }
732 } );
733 }
734
735 private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder,
736 final Shutdown shutdownType )
737 {
738 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
739 {
740 @Override
741 public void run()
742 {
743 builder.getCachableCommands().shutdown( shutdownType );
744 }
745 } );
746 }
747
748 private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams,
749 final Shutdown shutdownType )
750 {
751 return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
752 {
753 @Override
754 public void run()
755 {
756 for ( TestProvidingInputStream stream : streams )
757 {
758 stream.shutdown( shutdownType );
759 }
760 }
761 } );
762 }
763
764 private static ScheduledExecutorService createPingScheduler()
765 {
766 ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "s" );
767 return newScheduledThreadPool( 1, threadFactory );
768 }
769
770 private static ScheduledExecutorService createTimeoutCheckScheduler()
771 {
772 ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" );
773 return newScheduledThreadPool( 1, threadFactory );
774 }
775
776 private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder )
777 {
778 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
779 {
780 @Override
781 public void run()
782 {
783 builder.getImmediateCommands().noop();
784 }
785 }, 0, PING_IN_SECONDS, SECONDS );
786 }
787
788 private ScheduledFuture<?> triggerPingTimerForShutdown( final Iterable<TestProvidingInputStream> streams )
789 {
790 return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
791 {
792 @Override
793 public void run()
794 {
795 for ( TestProvidingInputStream stream : streams )
796 {
797 stream.noop();
798 }
799 }
800 }, 0, PING_IN_SECONDS, SECONDS );
801 }
802
803 private ScheduledFuture<?> triggerTimeoutCheck()
804 {
805 return timeoutCheckScheduler.scheduleAtFixedRate( new Runnable()
806 {
807 @Override
808 public void run()
809 {
810 long systemTime = currentTimeMillis();
811 for ( ForkClient forkClient : currentForkClients )
812 {
813 forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds );
814 }
815 }
816 }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS );
817 }
818 }