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