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