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