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