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