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