View Javadoc
1   package org.apache.maven.plugin.surefire.booterclient;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.plugin.surefire.CommonReflector;
23  import org.apache.maven.plugin.surefire.StartupReportConfiguration;
24  import org.apache.maven.plugin.surefire.SurefireProperties;
25  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream;
26  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
27  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
28  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
29  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
30  import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
31  import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
32  import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamConsumer;
33  import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
34  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
35  import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
36  import org.apache.maven.shared.utils.cli.CommandLineCallable;
37  import org.apache.maven.shared.utils.cli.CommandLineException;
38  import org.apache.maven.surefire.booter.AbstractPathConfiguration;
39  import org.apache.maven.surefire.booter.KeyValueSource;
40  import org.apache.maven.surefire.booter.PropertiesWrapper;
41  import org.apache.maven.surefire.booter.ProviderConfiguration;
42  import org.apache.maven.surefire.booter.ProviderFactory;
43  import org.apache.maven.surefire.booter.Shutdown;
44  import org.apache.maven.surefire.booter.StartupConfiguration;
45  import org.apache.maven.surefire.booter.SurefireBooterForkException;
46  import org.apache.maven.surefire.booter.SurefireExecutionException;
47  import org.apache.maven.surefire.providerapi.SurefireProvider;
48  import org.apache.maven.surefire.report.StackTraceWriter;
49  import org.apache.maven.surefire.suite.RunResult;
50  import org.apache.maven.surefire.testset.TestRequest;
51  import org.apache.maven.surefire.util.DefaultScanResult;
52  
53  import javax.annotation.Nonnull;
54  import java.io.Closeable;
55  import java.io.File;
56  import java.io.IOException;
57  import java.util.ArrayList;
58  import java.util.Collection;
59  import java.util.Map;
60  import java.util.Queue;
61  import java.util.concurrent.ArrayBlockingQueue;
62  import java.util.concurrent.Callable;
63  import java.util.concurrent.ConcurrentLinkedQueue;
64  import java.util.concurrent.ExecutionException;
65  import java.util.concurrent.ExecutorService;
66  import java.util.concurrent.Future;
67  import java.util.concurrent.LinkedBlockingQueue;
68  import java.util.concurrent.ScheduledExecutorService;
69  import java.util.concurrent.ScheduledFuture;
70  import java.util.concurrent.ThreadFactory;
71  import java.util.concurrent.ThreadPoolExecutor;
72  import java.util.concurrent.atomic.AtomicBoolean;
73  import java.util.concurrent.atomic.AtomicInteger;
74  
75  import static java.lang.StrictMath.min;
76  import static java.lang.System.currentTimeMillis;
77  import static java.lang.Thread.currentThread;
78  import static java.nio.charset.StandardCharsets.ISO_8859_1;
79  import static java.util.Collections.addAll;
80  import static java.util.Objects.requireNonNull;
81  import static java.util.concurrent.Executors.newScheduledThreadPool;
82  import static java.util.concurrent.TimeUnit.MILLISECONDS;
83  import static java.util.concurrent.TimeUnit.SECONDS;
84  import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder;
85  import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
86  import static org.apache.maven.plugin.surefire.SurefireHelper.replaceForkThreadsInPath;
87  import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
88  import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
89  import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
90                        .TestLessInputStreamBuilder;
91  import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable;
92  import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
93  import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
94  import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
95  import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
96  import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
97  import static org.apache.maven.surefire.suite.RunResult.failure;
98  import static org.apache.maven.surefire.suite.RunResult.timeout;
99  import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
100 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
101 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
102 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
103 
104 /**
105  * Starts the fork or runs in-process.
106  * <br>
107  * Lives only on the plugin-side (not present in remote vms)
108  * <br>
109  * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
110  *
111  * @author Jason van Zyl
112  * @author Emmanuel Venisse
113  * @author Brett Porter
114  * @author Dan Fabulich
115  * @author Carlos Sanchez
116  * @author Kristian Rosenvold
117  */
118 public class ForkStarter
119 {
120     private static final String EXECUTION_EXCEPTION = "ExecutionException";
121 
122     private static final long PING_IN_SECONDS = 10;
123 
124     private static final int TIMEOUT_CHECK_PERIOD_MILLIS = 100;
125 
126     private static final ThreadFactory FORKED_JVM_DAEMON_THREAD_FACTORY
127         = newDaemonThreadFactory( "surefire-fork-starter" );
128 
129     private static final ThreadFactory SHUTDOWN_HOOK_THREAD_FACTORY
130         = newDaemonThreadFactory( "surefire-jvm-killer-shutdownhook" );
131 
132     private static final AtomicInteger SYSTEM_PROPERTIES_FILE_COUNTER = new AtomicInteger();
133 
134     private final ScheduledExecutorService pingThreadScheduler = createPingScheduler();
135 
136     private final ScheduledExecutorService timeoutCheckScheduler;
137 
138     private final Queue<ForkClient> currentForkClients;
139 
140     private final int forkedProcessTimeoutInSeconds;
141 
142     private final ProviderConfiguration providerConfiguration;
143 
144     private final StartupConfiguration startupConfiguration;
145 
146     private final ForkConfiguration forkConfiguration;
147 
148     private final StartupReportConfiguration startupReportConfiguration;
149 
150     private final ConsoleLogger log;
151 
152     private final DefaultReporterFactory defaultReporterFactory;
153 
154     private final Collection<DefaultReporterFactory> defaultReporterFactories;
155 
156     /**
157      * Closes stuff, with a shutdown hook to make sure things really get closed.
158      */
159     private final class CloseableCloser
160         implements Runnable, Closeable
161     {
162         private final int jvmRun;
163 
164         private final Queue<Closeable> testProvidingInputStream;
165 
166         private final Thread inputStreamCloserHook;
167 
168         CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
169         {
170             this.jvmRun = jvmRun;
171             this.testProvidingInputStream = new ConcurrentLinkedQueue<>();
172             addAll( this.testProvidingInputStream, testProvidingInputStream );
173             if ( this.testProvidingInputStream.isEmpty() )
174             {
175                 inputStreamCloserHook = null;
176             }
177             else
178             {
179                 inputStreamCloserHook = newDaemonThread( this, "closer-shutdown-hook" );
180                 addShutDownHook( inputStreamCloserHook );
181             }
182         }
183 
184         @Override
185         @SuppressWarnings( "checkstyle:innerassignment" )
186         public void run()
187         {
188             for ( Closeable closeable; ( closeable = testProvidingInputStream.poll() ) != null; )
189             {
190                 try
191                 {
192                     closeable.close();
193                 }
194                 catch ( IOException e )
195                 {
196                     // This error does not fail a test and does not necessarily mean that the forked JVM std/out stream
197                     // was not closed, see ThreadedStreamConsumer. This error means that JVM wrote messages to a native
198                     // stream which could not be parsed or report failed. The tests may still correctly run nevertheless
199                     // this exception happened => warning on console. The user would see hint to check dump file only
200                     // if tests failed, but if this does not happen then printing warning to console is the only way to
201                     // inform the users.
202                     String msg = "ForkStarter IOException: " + e.getLocalizedMessage() + ".";
203                     File reportsDir = defaultReporterFactory.getReportsDirectory();
204                     File dump = InPluginProcessDumpSingleton.getSingleton()
205                                         .dumpStreamException( e, msg, reportsDir, jvmRun );
206                     log.warning( msg + " See the dump file " + dump.getAbsolutePath() );
207                 }
208             }
209         }
210 
211         @Override
212         public void close()
213         {
214             run();
215             if ( inputStreamCloserHook != null )
216             {
217                 removeShutdownHook( inputStreamCloserHook );
218             }
219         }
220     }
221 
222     public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
223                         ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
224                         StartupReportConfiguration startupReportConfiguration, ConsoleLogger log )
225     {
226         this.forkConfiguration = forkConfiguration;
227         this.providerConfiguration = providerConfiguration;
228         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
229         this.startupConfiguration = startupConfiguration;
230         this.startupReportConfiguration = startupReportConfiguration;
231         this.log = log;
232         defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
233         defaultReporterFactory.runStarting();
234         defaultReporterFactories = new ConcurrentLinkedQueue<>();
235         currentForkClients = new ConcurrentLinkedQueue<>();
236         timeoutCheckScheduler = createTimeoutCheckScheduler();
237         triggerTimeoutCheck();
238     }
239 
240     public RunResult run( @Nonnull SurefireProperties effectiveSystemProperties, @Nonnull DefaultScanResult scanResult )
241         throws SurefireBooterForkException
242     {
243         try
244         {
245             Map<String, String> providerProperties = providerConfiguration.getProviderProperties();
246             scanResult.writeTo( providerProperties );
247             return isForkOnce()
248                     ? run( effectiveSystemProperties, providerProperties )
249                     : run( effectiveSystemProperties );
250         }
251         finally
252         {
253             defaultReporterFactory.mergeFromOtherFactories( defaultReporterFactories );
254             defaultReporterFactory.close();
255             pingThreadScheduler.shutdownNow();
256             timeoutCheckScheduler.shutdownNow();
257         }
258     }
259 
260     public void killOrphanForks()
261     {
262         for ( ForkClient fork : currentForkClients )
263         {
264             fork.kill();
265         }
266     }
267 
268     private RunResult run( SurefireProperties effectiveSystemProperties, Map<String, String> providerProperties )
269             throws SurefireBooterForkException
270     {
271         TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
272         PropertiesWrapper props = new PropertiesWrapper( providerProperties );
273         TestLessInputStream stream = builder.build();
274         Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
275         ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
276         int forkNumber = drawNumber();
277         try
278         {
279             addShutDownHook( shutdown );
280             DefaultReporterFactory forkedReporterFactory =
281                     new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
282             defaultReporterFactories.add( forkedReporterFactory );
283             ForkClient forkClient =
284                     new ForkClient( forkedReporterFactory, stream, log, new AtomicBoolean(), forkNumber );
285             return fork( null, props, forkClient, effectiveSystemProperties, forkNumber, stream, false );
286         }
287         finally
288         {
289             returnNumber( forkNumber );
290             removeShutdownHook( shutdown );
291             ping.cancel( true );
292             builder.removeStream( stream );
293         }
294     }
295 
296     private RunResult run( SurefireProperties effectiveSystemProperties )
297             throws SurefireBooterForkException
298     {
299         return forkConfiguration.isReuseForks()
300                 ? runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() )
301                 : runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
302     }
303 
304     private boolean isForkOnce()
305     {
306         return forkConfiguration.isReuseForks() && ( forkConfiguration.getForkCount() == 1 || hasSuiteXmlFiles() );
307     }
308 
309     private boolean hasSuiteXmlFiles()
310     {
311         TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
312         return testSuiteDefinition != null && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
313     }
314 
315     @SuppressWarnings( "checkstyle:magicnumber" )
316     private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
317         throws SurefireBooterForkException
318     {
319         ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS,
320                                                                   new ArrayBlockingQueue<Runnable>( forkCount ) );
321         executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
322 
323         final Queue<String> tests = new ConcurrentLinkedQueue<>();
324 
325         for ( Class<?> clazz : getSuitesIterator() )
326         {
327             tests.add( clazz.getName() );
328         }
329 
330         final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<>();
331 
332         for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ )
333         {
334             testStreams.add( new TestProvidingInputStream( tests ) );
335         }
336 
337         ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams );
338         Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() );
339 
340         try
341         {
342             addShutDownHook( shutdown );
343             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
344             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
345             final Collection<Future<RunResult>> results = new ArrayList<>( forkCount );
346             final AtomicBoolean printedErrorStream = new AtomicBoolean();
347             for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
348             {
349                 Callable<RunResult> pf = new Callable<RunResult>()
350                 {
351                     @Override
352                     public RunResult call()
353                         throws Exception
354                     {
355                         int forkNumber = drawNumber();
356                         DefaultReporterFactory reporter =
357                                 new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
358                         defaultReporterFactories.add( reporter );
359                         ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
360                                 printedErrorStream, forkNumber )
361                         {
362                             @Override
363                             protected void stopOnNextTest()
364                             {
365                                 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
366                                 {
367                                     notifyStreamsToSkipTests( testStreams );
368                                 }
369                             }
370                         };
371                         Map<String, String> providerProperties = providerConfiguration.getProviderProperties();
372                         try
373                         {
374                             return fork( null, new PropertiesWrapper( providerProperties ), forkClient,
375                                     effectiveSystemProperties, forkNumber, testProvidingInputStream, true );
376                         }
377                         finally
378                         {
379                             returnNumber( forkNumber );
380                         }
381                     }
382                 };
383                 results.add( executorService.submit( pf ) );
384             }
385             return awaitResultsDone( results, executorService );
386         }
387         finally
388         {
389             removeShutdownHook( shutdown );
390             ping.cancel( true );
391             closeExecutor( executorService );
392         }
393     }
394 
395     private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams )
396     {
397         for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams )
398         {
399             notifiableTestStream.skipSinceNextTest();
400         }
401     }
402 
403     @SuppressWarnings( "checkstyle:magicnumber" )
404     private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount )
405         throws SurefireBooterForkException
406     {
407         ArrayList<Future<RunResult>> results = new ArrayList<>( 500 );
408         ThreadPoolExecutor executorService =
409             new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS, new LinkedBlockingQueue<Runnable>() );
410         executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
411         final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
412         ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
413         Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() );
414         try
415         {
416             addShutDownHook( shutdown );
417             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
418             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
419             final AtomicBoolean printedErrorStream = new AtomicBoolean();
420             for ( final Object testSet : getSuitesIterator() )
421             {
422                 Callable<RunResult> pf = new Callable<RunResult>()
423                 {
424                     @Override
425                     public RunResult call()
426                         throws Exception
427                     {
428                         int forkNumber = drawNumber();
429                         DefaultReporterFactory forkedReporterFactory =
430                             new DefaultReporterFactory( startupReportConfiguration, log, forkNumber );
431                         defaultReporterFactories.add( forkedReporterFactory );
432                         ForkClient forkClient = new ForkClient( forkedReporterFactory, builder.getImmediateCommands(),
433                                 log, printedErrorStream, forkNumber )
434                         {
435                             @Override
436                             protected void stopOnNextTest()
437                             {
438                                 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
439                                 {
440                                     builder.getCachableCommands().skipSinceNextTest();
441                                 }
442                             }
443                         };
444                         TestLessInputStream stream = builder.build();
445                         try
446                         {
447                             return fork( testSet,
448                                          new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
449                                          forkClient, effectiveSystemProperties, forkNumber, stream, false );
450                         }
451                         finally
452                         {
453                             returnNumber( forkNumber );
454                             builder.removeStream( stream );
455                         }
456                     }
457                 };
458                 results.add( executorService.submit( pf ) );
459             }
460             return awaitResultsDone( results, executorService );
461         }
462         finally
463         {
464             removeShutdownHook( shutdown );
465             ping.cancel( true );
466             closeExecutor( executorService );
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             // Should stop immediately, as we got all the results if we are here
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, KeyValueSource providerProperties, ForkClient forkClient,
544                             SurefireProperties effectiveSystemProperties, int forkNumber,
545                             AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
546         throws SurefireBooterForkException
547     {
548         final String tempDir;
549         final File surefireProperties;
550         final File systPropsFile;
551         try
552         {
553             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
554             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
555             Long pluginPid = forkConfiguration.getPluginPlatform().getPluginPid();
556             surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
557                     startupConfiguration, testSet, readTestsFromInStream, pluginPid, forkNumber );
558 
559             log.debug( "Determined Maven Process ID " + pluginPid );
560 
561             if ( effectiveSystemProperties != null )
562             {
563                 SurefireProperties filteredProperties =
564                     createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
565 
566                 systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
567                                                      "surefire_" + SYSTEM_PROPERTIES_FILE_COUNTER.getAndIncrement(),
568                                                      forkConfiguration.isDebug() );
569             }
570             else
571             {
572                 systPropsFile = null;
573             }
574         }
575         catch ( IOException e )
576         {
577             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
578         }
579 
580         File dumpLogDir = replaceForkThreadsInPath( startupReportConfiguration.getReportsDirectory(), forkNumber );
581         OutputStreamFlushableCommandline cli =
582                 forkConfiguration.createCommandLine( startupConfiguration, forkNumber, dumpLogDir );
583 
584         if ( testProvidingInputStream != null )
585         {
586             testProvidingInputStream.setFlushReceiverProvider( cli );
587         }
588 
589         cli.createArg().setValue( tempDir );
590         cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
591         cli.createArg().setValue( surefireProperties.getName() );
592         if ( systPropsFile != null )
593         {
594             cli.createArg().setValue( systPropsFile.getName() );
595         }
596 
597         final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
598         final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
599                                                             requireNonNull( testProvidingInputStream, "null param" ) );
600 
601         log.debug( "Forking command line: " + cli );
602 
603         Integer result = null;
604         RunResult runResult = null;
605         SurefireBooterForkException booterForkException = null;
606         try
607         {
608             NativeStdErrStreamConsumer stdErrConsumer =
609                     new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
610 
611             CommandLineCallable future =
612                     executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
613                                                         stdErrConsumer, 0, closer, ISO_8859_1 );
614 
615             currentForkClients.add( forkClient );
616 
617             result = future.call();
618 
619             if ( forkClient.hadTimeout() )
620             {
621                 runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
622             }
623             else if ( result == null || result != SUCCESS )
624             {
625                 booterForkException =
626                         new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
627             }
628         }
629         catch ( CommandLineException e )
630         {
631             runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
632             String cliErr = e.getLocalizedMessage();
633             Throwable cause = e.getCause();
634             booterForkException =
635                     new SurefireBooterForkException( "Error while executing forked tests.", cliErr, cause, runResult );
636         }
637         finally
638         {
639             currentForkClients.remove( forkClient );
640             closer.close();
641             if ( runResult == null )
642             {
643                 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
644             }
645             forkClient.close( runResult.isTimeout() );
646 
647             if ( !runResult.isTimeout() )
648             {
649                 Throwable cause = booterForkException == null ? null : booterForkException.getCause();
650                 String detail = booterForkException == null ? "" : "\n" + booterForkException.getMessage();
651 
652                 if ( forkClient.isErrorInFork() )
653                 {
654                     StackTraceWriter errorInFork = forkClient.getErrorInFork();
655                     String errorInForkMessage =
656                             errorInFork == null ? null : errorInFork.getThrowable().getLocalizedMessage();
657                     boolean showStackTrace = providerConfiguration.getMainCliOptions().contains( SHOW_ERRORS );
658                     String stackTrace = errorInForkMessage;
659                     if ( showStackTrace )
660                     {
661                         if ( errorInFork != null )
662                         {
663                             if ( stackTrace == null )
664                             {
665                                 stackTrace = "";
666                             }
667                             else
668                             {
669                                 stackTrace += NL;
670                             }
671                             stackTrace += errorInFork.writeTrimmedTraceToString();
672                         }
673                     }
674                     //noinspection ThrowFromFinallyBlock
675                     throw new SurefireBooterForkException( "There was an error in the forked process"
676                                                         + detail
677                                                         + ( stackTrace == null ? "" : stackTrace ), cause );
678                 }
679                 if ( !forkClient.isSaidGoodBye() )
680                 {
681                     String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
682                     String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
683                     for ( String test : forkClient.testsInProgress() )
684                     {
685                         testsInProgress += "\n" + test;
686                     }
687                     // noinspection ThrowFromFinallyBlock
688                     throw new SurefireBooterForkException(
689                         "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
690                             + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
691                 }
692             }
693 
694             if ( booterForkException != null )
695             {
696                 // noinspection ThrowFromFinallyBlock
697                 throw booterForkException;
698             }
699         }
700 
701         return runResult;
702     }
703 
704     private Iterable<Class<?>> getSuitesIterator()
705         throws SurefireBooterForkException
706     {
707         try
708         {
709             AbstractPathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
710             ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
711 
712             CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
713             Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration, log );
714 
715             ProviderFactory providerFactory =
716                 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory );
717             SurefireProvider surefireProvider = providerFactory.createProvider( false );
718             return surefireProvider.getSuites();
719         }
720         catch ( SurefireExecutionException e )
721         {
722             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
723         }
724     }
725 
726     private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder,
727                                                              final Shutdown shutdownType )
728     {
729         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
730         {
731             @Override
732             public void run()
733             {
734                 builder.getImmediateCommands().shutdown( shutdownType );
735             }
736         } );
737     }
738 
739     private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder,
740                                                             final Shutdown shutdownType )
741     {
742         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
743         {
744             @Override
745             public void run()
746             {
747                 builder.getCachableCommands().shutdown( shutdownType );
748             }
749         } );
750     }
751 
752     private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams,
753                                                     final Shutdown shutdownType )
754     {
755         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
756         {
757             @Override
758             public void run()
759             {
760                 for ( TestProvidingInputStream stream : streams )
761                 {
762                     stream.shutdown( shutdownType );
763                 }
764             }
765         } );
766     }
767 
768     private static ScheduledExecutorService createPingScheduler()
769     {
770         ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "s" );
771         return newScheduledThreadPool( 1, threadFactory );
772     }
773 
774     private static ScheduledExecutorService createTimeoutCheckScheduler()
775     {
776         ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" );
777         return newScheduledThreadPool( 1, threadFactory );
778     }
779 
780     private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder )
781     {
782         return pingThreadScheduler.scheduleWithFixedDelay( new Runnable()
783         {
784             @Override
785             public void run()
786             {
787                 builder.getImmediateCommands().noop();
788             }
789         }, 0, PING_IN_SECONDS, SECONDS );
790     }
791 
792     private ScheduledFuture<?> triggerPingTimerForShutdown( final Iterable<TestProvidingInputStream> streams )
793     {
794         return pingThreadScheduler.scheduleWithFixedDelay( new Runnable()
795         {
796             @Override
797             public void run()
798             {
799                 for ( TestProvidingInputStream stream : streams )
800                 {
801                     stream.noop();
802                 }
803             }
804         }, 0, PING_IN_SECONDS, SECONDS );
805     }
806 
807     private ScheduledFuture<?> triggerTimeoutCheck()
808     {
809         return timeoutCheckScheduler.scheduleWithFixedDelay( new Runnable()
810         {
811             @Override
812             public void run()
813             {
814                 long systemTime = currentTimeMillis();
815                 for ( ForkClient forkClient : currentForkClients )
816                 {
817                     forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds );
818                 }
819             }
820         }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS );
821     }
822 }