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.Classpath;
39  import org.apache.maven.surefire.booter.ClasspathConfiguration;
40  import org.apache.maven.surefire.booter.KeyValueSource;
41  import org.apache.maven.surefire.booter.PropertiesWrapper;
42  import org.apache.maven.surefire.booter.ProviderConfiguration;
43  import org.apache.maven.surefire.booter.ProviderFactory;
44  import org.apache.maven.surefire.booter.Shutdown;
45  import org.apache.maven.surefire.booter.StartupConfiguration;
46  import org.apache.maven.surefire.booter.SurefireBooterForkException;
47  import org.apache.maven.surefire.booter.SurefireExecutionException;
48  import org.apache.maven.surefire.providerapi.SurefireProvider;
49  import org.apache.maven.surefire.report.StackTraceWriter;
50  import org.apache.maven.surefire.suite.RunResult;
51  import org.apache.maven.surefire.testset.TestRequest;
52  import org.apache.maven.surefire.util.DefaultScanResult;
53  
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.Classpath.join;
92  import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
93  import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
94  import static org.apache.maven.surefire.suite.RunResult.failure;
95  import static org.apache.maven.surefire.suite.RunResult.timeout;
96  import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
97  import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
98  import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
99  import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
100 import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
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         public CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
167         {
168             this.jvmRun = jvmRun;
169             this.testProvidingInputStream = new ConcurrentLinkedQueue<Closeable>();
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 dump = InPluginProcessDumpSingleton.getSingleton()
202                                         .dumpException( e, msg, defaultReporterFactory, 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( SurefireProperties effectiveSystemProperties, 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         DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration, log );
269         defaultReporterFactories.add( forkedReporterFactory );
270         TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
271         PropertiesWrapper props = new PropertiesWrapper( providerProperties );
272         TestLessInputStream stream = builder.build();
273         ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, log, forkConfiguration.isDebug(),
274                 new AtomicBoolean() );
275         Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
276         ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
277         try
278         {
279             addShutDownHook( shutdown );
280             return fork( null, props, forkClient, effectiveSystemProperties, stream, false );
281         }
282         finally
283         {
284             removeShutdownHook( shutdown );
285             ping.cancel( true );
286             builder.removeStream( stream );
287         }
288     }
289 
290     private RunResult run( SurefireProperties effectiveSystemProperties )
291             throws SurefireBooterForkException
292     {
293         return forkConfiguration.isReuseForks()
294                 ? runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() )
295                 : runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
296     }
297 
298     private boolean isForkOnce()
299     {
300         return forkConfiguration.isReuseForks() && ( forkConfiguration.getForkCount() == 1 || hasSuiteXmlFiles() );
301     }
302 
303     private boolean hasSuiteXmlFiles()
304     {
305         TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
306         return testSuiteDefinition != null && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
307     }
308 
309     @SuppressWarnings( "checkstyle:magicnumber" )
310     private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
311         throws SurefireBooterForkException
312     {
313         ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS,
314                                                                   new ArrayBlockingQueue<Runnable>( forkCount ) );
315         executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
316 
317         final Queue<String> tests = new ConcurrentLinkedQueue<String>();
318 
319         for ( Class<?> clazz : getSuitesIterator() )
320         {
321             tests.add( clazz.getName() );
322         }
323 
324         final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<TestProvidingInputStream>();
325 
326         for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ )
327         {
328             testStreams.add( new TestProvidingInputStream( tests ) );
329         }
330 
331         ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams );
332         Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() );
333 
334         try
335         {
336             addShutDownHook( shutdown );
337             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
338             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
339             final Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
340             final AtomicBoolean printedErrorStream = new AtomicBoolean();
341             for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
342             {
343                 Callable<RunResult> pf = new Callable<RunResult>()
344                 {
345                     @Override
346                     public RunResult call()
347                         throws Exception
348                     {
349                         DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration, log );
350                         defaultReporterFactories.add( reporter );
351                         ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
352                                 forkConfiguration.isDebug(), printedErrorStream )
353                         {
354                             @Override
355                             protected void stopOnNextTest()
356                             {
357                                 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
358                                 {
359                                     notifyStreamsToSkipTests( testStreams );
360                                 }
361                             }
362                         };
363 
364                         return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
365                                  forkClient, effectiveSystemProperties, testProvidingInputStream, true );
366                     }
367                 };
368                 results.add( executorService.submit( pf ) );
369             }
370             return awaitResultsDone( results, executorService );
371         }
372         finally
373         {
374             removeShutdownHook( shutdown );
375             ping.cancel( true );
376             closeExecutor( executorService );
377         }
378     }
379 
380     private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams )
381     {
382         for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams )
383         {
384             notifiableTestStream.skipSinceNextTest();
385         }
386     }
387 
388     @SuppressWarnings( "checkstyle:magicnumber" )
389     private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount )
390         throws SurefireBooterForkException
391     {
392         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
393         ThreadPoolExecutor executorService =
394             new ThreadPoolExecutor( forkCount, forkCount, 60, SECONDS, new LinkedBlockingQueue<Runnable>() );
395         executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY );
396         final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
397         ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
398         Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() );
399         try
400         {
401             addShutDownHook( shutdown );
402             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
403             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
404             final AtomicBoolean printedErrorStream = new AtomicBoolean();
405             for ( final Object testSet : getSuitesIterator() )
406             {
407                 Callable<RunResult> pf = new Callable<RunResult>()
408                 {
409                     @Override
410                     public RunResult call()
411                         throws Exception
412                     {
413                         DefaultReporterFactory forkedReporterFactory =
414                             new DefaultReporterFactory( startupReportConfiguration, log );
415                         defaultReporterFactories.add( forkedReporterFactory );
416                         ForkClient forkClient = new ForkClient( forkedReporterFactory, builder.getImmediateCommands(),
417                                 log, forkConfiguration.isDebug(), printedErrorStream )
418                         {
419                             @Override
420                             protected void stopOnNextTest()
421                             {
422                                 if ( countDownToZero( notifyStreamsToSkipTestsJustNow ) )
423                                 {
424                                     builder.getCachableCommands().skipSinceNextTest();
425                                 }
426                             }
427                         };
428                         TestLessInputStream stream = builder.build();
429                         try
430                         {
431                             return fork( testSet,
432                                          new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
433                                          forkClient, effectiveSystemProperties, stream, false );
434                         }
435                         finally
436                         {
437                             builder.removeStream( stream );
438                         }
439                     }
440                 };
441                 results.add( executorService.submit( pf ) );
442             }
443             return awaitResultsDone( results, executorService );
444         }
445         finally
446         {
447             removeShutdownHook( shutdown );
448             ping.cancel( true );
449             closeExecutor( executorService );
450         }
451     }
452 
453     private static RunResult awaitResultsDone( Collection<Future<RunResult>> results, ExecutorService executorService )
454         throws SurefireBooterForkException
455     {
456         RunResult globalResult = new RunResult( 0, 0, 0, 0 );
457         SurefireBooterForkException exception = null;
458         for ( Future<RunResult> result : results )
459         {
460             try
461             {
462                 RunResult cur = result.get();
463                 if ( cur != null )
464                 {
465                     globalResult = globalResult.aggregate( cur );
466                 }
467                 else
468                 {
469                     throw new SurefireBooterForkException( "No results for " + result.toString() );
470                 }
471             }
472             catch ( InterruptedException e )
473             {
474                 executorService.shutdownNow();
475                 currentThread().interrupt();
476                 throw new SurefireBooterForkException( "Interrupted", e );
477             }
478             catch ( ExecutionException e )
479             {
480                 Throwable realException = e.getCause();
481                 if ( realException == null )
482                 {
483                     if ( exception == null )
484                     {
485                         exception = new SurefireBooterForkException( EXECUTION_EXCEPTION );
486                     }
487                 }
488                 else
489                 {
490                     String previousError = "";
491                     if ( exception != null && !EXECUTION_EXCEPTION.equals( exception.getLocalizedMessage().trim() ) )
492                     {
493                         previousError = exception.getLocalizedMessage() + "\n";
494                     }
495                     String error = previousError + EXECUTION_EXCEPTION + " " + realException.getLocalizedMessage();
496                     exception = new SurefireBooterForkException( error, realException );
497                 }
498             }
499         }
500 
501         if ( exception != null )
502         {
503             throw exception;
504         }
505 
506         return globalResult;
507     }
508 
509     @SuppressWarnings( "checkstyle:magicnumber" )
510     private void closeExecutor( ExecutorService executorService )
511         throws SurefireBooterForkException
512     {
513         executorService.shutdown();
514         try
515         {
516             // Should stop immediately, as we got all the results if we are here
517             executorService.awaitTermination( 60 * 60, SECONDS );
518         }
519         catch ( InterruptedException e )
520         {
521             currentThread().interrupt();
522             throw new SurefireBooterForkException( "Interrupted", e );
523         }
524     }
525 
526     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
527                             SurefireProperties effectiveSystemProperties,
528                             AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
529         throws SurefireBooterForkException
530     {
531         int forkNumber = drawNumber();
532         forkClient.setForkNumber( forkNumber );
533         try
534         {
535             return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
536                          testProvidingInputStream, readTestsFromInStream );
537         }
538         finally
539         {
540             returnNumber( forkNumber );
541         }
542     }
543 
544     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
545                             SurefireProperties effectiveSystemProperties, int forkNumber,
546                             AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
547         throws SurefireBooterForkException
548     {
549         final String tempDir;
550         final File surefireProperties;
551         final File systPropsFile;
552         try
553         {
554             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
555             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
556 
557             surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
558                                                                    startupConfiguration, testSet,
559                                                                    readTestsFromInStream,
560                                                                    forkConfiguration.getPluginPlatform().getPid() );
561 
562             log.debug( "Determined Maven Process ID " + forkConfiguration.getPluginPlatform().getPid() );
563 
564             if ( effectiveSystemProperties != null )
565             {
566                 SurefireProperties filteredProperties =
567                     createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
568 
569                 systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
570                                                      "surefire_" + SYSTEM_PROPERTIES_FILE_COUNTER.getAndIncrement(),
571                                                      forkConfiguration.isDebug() );
572             }
573             else
574             {
575                 systPropsFile = null;
576             }
577         }
578         catch ( IOException e )
579         {
580             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
581         }
582 
583         // this could probably be simplified further
584         final Classpath bootClasspathConfiguration = startupConfiguration.isProviderMainClass()
585             ? startupConfiguration.getClasspathConfiguration().getProviderClasspath()
586             : forkConfiguration.getBootClasspath();
587 
588         Classpath bootClasspath = join(
589             join( bootClasspathConfiguration, startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
590             startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
591 
592         log.debug( bootClasspath.getLogMessage( "boot" ) );
593         log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
594 
595         OutputStreamFlushableCommandline cli =
596             forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber );
597 
598         if ( testProvidingInputStream != null )
599         {
600             testProvidingInputStream.setFlushReceiverProvider( cli );
601         }
602 
603         cli.createArg().setValue( tempDir );
604         cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
605         cli.createArg().setValue( surefireProperties.getName() );
606         if ( systPropsFile != null )
607         {
608             cli.createArg().setValue( systPropsFile.getName() );
609         }
610 
611         final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
612         final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
613                                                             requireNonNull( testProvidingInputStream, "null param" ) );
614 
615         log.debug( "Forking command line: " + cli );
616 
617         Integer result = null;
618         RunResult runResult = null;
619         SurefireBooterForkException booterForkException = null;
620         try
621         {
622             NativeStdErrStreamConsumer stdErrConsumer =
623                     new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
624 
625             CommandLineCallable future =
626                     executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
627                                                         stdErrConsumer, 0, closer, ISO_8859_1 );
628 
629             currentForkClients.add( forkClient );
630 
631             result = future.call();
632 
633             if ( forkClient.hadTimeout() )
634             {
635                 runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
636             }
637             else if ( result != SUCCESS )
638             {
639                 booterForkException =
640                         new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
641             }
642         }
643         catch ( CommandLineException e )
644         {
645             runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
646             String cliErr = e.getLocalizedMessage();
647             Throwable cause = e.getCause();
648             booterForkException =
649                     new SurefireBooterForkException( "Error while executing forked tests.", cliErr, cause, runResult );
650         }
651         finally
652         {
653             currentForkClients.remove( forkClient );
654             closer.close();
655             if ( runResult == null )
656             {
657                 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
658             }
659             forkClient.close( runResult.isTimeout() );
660 
661             if ( !runResult.isTimeout() )
662             {
663                 Throwable cause = booterForkException == null ? null : booterForkException.getCause();
664                 String detail = booterForkException == null ? "" : "\n" + booterForkException.getMessage();
665 
666                 if ( forkClient.isErrorInFork() )
667                 {
668                     StackTraceWriter errorInFork = forkClient.getErrorInFork();
669                     // noinspection ThrowFromFinallyBlock
670                     throw new SurefireBooterForkException( "There was an error in the forked process"
671                                                         + detail
672                                                         + '\n'
673                                                         + errorInFork.getThrowable().getLocalizedMessage(), cause );
674                 }
675                 if ( !forkClient.isSaidGoodBye() )
676                 {
677                     String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
678                     String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
679                     for ( String test : forkClient.testsInProgress() )
680                     {
681                         testsInProgress += "\n" + test;
682                     }
683                     // noinspection ThrowFromFinallyBlock
684                     throw new SurefireBooterForkException(
685                         "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
686                             + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
687                 }
688             }
689 
690             if ( booterForkException != null )
691             {
692                 // noinspection ThrowFromFinallyBlock
693                 throw booterForkException;
694             }
695         }
696 
697         return runResult;
698     }
699 
700     private Iterable<Class<?>> getSuitesIterator()
701         throws SurefireBooterForkException
702     {
703         try
704         {
705             final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
706             ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
707 
708             CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
709             Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration, log );
710 
711             ProviderFactory providerFactory =
712                 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory );
713             SurefireProvider surefireProvider = providerFactory.createProvider( false );
714             return surefireProvider.getSuites();
715         }
716         catch ( SurefireExecutionException e )
717         {
718             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
719         }
720     }
721 
722     private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder,
723                                                              final Shutdown shutdownType )
724     {
725         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
726         {
727             @Override
728             public void run()
729             {
730                 builder.getImmediateCommands().shutdown( shutdownType );
731             }
732         } );
733     }
734 
735     private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder,
736                                                             final Shutdown shutdownType )
737     {
738         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
739         {
740             @Override
741             public void run()
742             {
743                 builder.getCachableCommands().shutdown( shutdownType );
744             }
745         } );
746     }
747 
748     private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams,
749                                                     final Shutdown shutdownType )
750     {
751         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
752         {
753             @Override
754             public void run()
755             {
756                 for ( TestProvidingInputStream stream : streams )
757                 {
758                     stream.shutdown( shutdownType );
759                 }
760             }
761         } );
762     }
763 
764     private static ScheduledExecutorService createPingScheduler()
765     {
766         ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "s" );
767         return newScheduledThreadPool( 1, threadFactory );
768     }
769 
770     private static ScheduledExecutorService createTimeoutCheckScheduler()
771     {
772         ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" );
773         return newScheduledThreadPool( 1, threadFactory );
774     }
775 
776     private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder )
777     {
778         return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
779         {
780             @Override
781             public void run()
782             {
783                 builder.getImmediateCommands().noop();
784             }
785         }, 0, PING_IN_SECONDS, SECONDS );
786     }
787 
788     private ScheduledFuture<?> triggerPingTimerForShutdown( final Iterable<TestProvidingInputStream> streams )
789     {
790         return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
791         {
792             @Override
793             public void run()
794             {
795                 for ( TestProvidingInputStream stream : streams )
796                 {
797                     stream.noop();
798                 }
799             }
800         }, 0, PING_IN_SECONDS, SECONDS );
801     }
802 
803     private ScheduledFuture<?> triggerTimeoutCheck()
804     {
805         return timeoutCheckScheduler.scheduleAtFixedRate( new Runnable()
806         {
807             @Override
808             public void run()
809             {
810                 long systemTime = currentTimeMillis();
811                 for ( ForkClient forkClient : currentForkClients )
812                 {
813                     forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds );
814                 }
815             }
816         }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS );
817     }
818 }