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