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 java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Properties;
30  import java.util.Queue;
31  import java.util.concurrent.ArrayBlockingQueue;
32  import java.util.concurrent.Callable;
33  import java.util.concurrent.ConcurrentLinkedQueue;
34  import java.util.concurrent.ExecutionException;
35  import java.util.concurrent.ExecutorService;
36  import java.util.concurrent.Future;
37  import java.util.concurrent.LinkedBlockingQueue;
38  import java.util.concurrent.ThreadPoolExecutor;
39  import java.util.concurrent.TimeUnit;
40  import java.util.concurrent.atomic.AtomicReference;
41  
42  import org.apache.maven.plugin.logging.Log;
43  import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
44  import org.apache.maven.plugin.surefire.CommonReflector;
45  import org.apache.maven.plugin.surefire.StartupReportConfiguration;
46  import org.apache.maven.plugin.surefire.SurefireProperties;
47  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
48  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
49  import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
50  import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
51  import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
52  import org.apache.maven.shared.utils.cli.CommandLineException;
53  import org.apache.maven.shared.utils.cli.CommandLineTimeOutException;
54  import org.apache.maven.shared.utils.cli.CommandLineUtils;
55  import org.apache.maven.shared.utils.cli.ShutdownHookUtils;
56  import org.apache.maven.surefire.booter.Classpath;
57  import org.apache.maven.surefire.booter.ClasspathConfiguration;
58  import org.apache.maven.surefire.booter.KeyValueSource;
59  import org.apache.maven.surefire.booter.PropertiesWrapper;
60  import org.apache.maven.surefire.booter.ProviderConfiguration;
61  import org.apache.maven.surefire.booter.ProviderFactory;
62  import org.apache.maven.surefire.booter.StartupConfiguration;
63  import org.apache.maven.surefire.booter.SurefireBooterForkException;
64  import org.apache.maven.surefire.booter.SurefireExecutionException;
65  import org.apache.maven.surefire.booter.SystemPropertyManager;
66  import org.apache.maven.surefire.providerapi.SurefireProvider;
67  import org.apache.maven.surefire.report.StackTraceWriter;
68  import org.apache.maven.surefire.suite.RunResult;
69  import org.apache.maven.surefire.testset.TestRequest;
70  import org.apache.maven.surefire.util.DefaultScanResult;
71  
72  import static org.apache.maven.surefire.booter.Classpath.join;
73  
74  /**
75   * Starts the fork or runs in-process.
76   * <p/>
77   * Lives only on the plugin-side (not present in remote vms)
78   * <p/>
79   * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
80   *
81   * @author Jason van Zyl
82   * @author Emmanuel Venisse
83   * @author Brett Porter
84   * @author Dan Fabulich
85   * @author Carlos Sanchez
86   * @author Kristian Rosenvold
87   */
88  public class ForkStarter
89  {
90      /**
91       * Closes an InputStream
92       */
93      private static class InputStreamCloser
94          implements Runnable
95      {
96          private final AtomicReference<InputStream> testProvidingInputStream;
97  
98          public InputStreamCloser( InputStream testProvidingInputStream )
99          {
100             this.testProvidingInputStream = new AtomicReference<InputStream>( testProvidingInputStream );
101         }
102 
103         public void run()
104         {
105             InputStream stream = testProvidingInputStream.getAndSet( null );
106             if ( stream != null )
107             {
108                 try
109                 {
110                     stream.close();
111                 }
112                 catch ( IOException e )
113                 {
114                     // ignore
115                 }
116             }
117         }
118     }
119 
120     private final int forkedProcessTimeoutInSeconds;
121 
122     private final ProviderConfiguration providerConfiguration;
123 
124     private final StartupConfiguration startupConfiguration;
125 
126     private final ForkConfiguration forkConfiguration;
127 
128     private final StartupReportConfiguration startupReportConfiguration;
129 
130     private final Log log;
131 
132     private final DefaultReporterFactory defaultReporterFactory;
133 
134     private final Collection<DefaultReporterFactory> defaultReporterFactoryList;
135 
136     private static volatile int systemPropertiesFileCounter = 0;
137 
138     public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
139                         ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
140                         StartupReportConfiguration startupReportConfiguration, Log log )
141     {
142         this.forkConfiguration = forkConfiguration;
143         this.providerConfiguration = providerConfiguration;
144         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
145         this.startupConfiguration = startupConfiguration;
146         this.startupReportConfiguration = startupReportConfiguration;
147         this.log = log;
148         defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration );
149         defaultReporterFactory.runStarting();
150         defaultReporterFactoryList = new ConcurrentLinkedQueue<DefaultReporterFactory>();
151     }
152 
153     public RunResult run( SurefireProperties effectiveSystemProperties, DefaultScanResult scanResult )
154         throws SurefireBooterForkException, SurefireExecutionException
155     {
156         final RunResult result;
157         try
158         {
159             Properties providerProperties = providerConfiguration.getProviderProperties();
160             scanResult.writeTo( providerProperties );
161             if ( isForkOnce() )
162             {
163                 DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration );
164                 defaultReporterFactoryList.add( forkedReporterFactory );
165                 final ForkClient forkClient =
166                     new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties() );
167                 result = fork( null, new PropertiesWrapper( providerProperties ), forkClient, effectiveSystemProperties,
168                                null );
169             }
170             else
171             {
172                 if ( forkConfiguration.isReuseForks() )
173                 {
174                     result = runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() );
175                 }
176                 else
177                 {
178                     result = runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
179                 }
180             }
181         }
182         finally
183         {
184             defaultReporterFactory.mergeFromOtherFactories( defaultReporterFactoryList );
185             defaultReporterFactory.close();
186         }
187         return result;
188     }
189 
190     private boolean isForkOnce()
191     {
192         return forkConfiguration.isReuseForks() && ( 1 == forkConfiguration.getForkCount() || hasSuiteXmlFiles() );
193     }
194 
195     private boolean hasSuiteXmlFiles()
196     {
197         TestRequest testSuiteDefinition = providerConfiguration.getTestSuiteDefinition();
198         return testSuiteDefinition != null && testSuiteDefinition.getSuiteXmlFiles() != null
199             && !testSuiteDefinition.getSuiteXmlFiles().isEmpty();
200     }
201 
202     @SuppressWarnings( "checkstyle:magicnumber" )
203     private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
204         throws SurefireBooterForkException
205     {
206 
207         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
208         ExecutorService executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS,
209                                                                   new ArrayBlockingQueue<Runnable>( forkCount ) );
210 
211         try
212         {
213             // Ask to the executorService to run all tasks
214             RunResult globalResult = new RunResult( 0, 0, 0, 0 );
215 
216             List<Class<?>> suites = new ArrayList<Class<?>>();
217             Iterator<Class<?>> suitesIterator = getSuitesIterator();
218             while ( suitesIterator.hasNext() )
219             {
220                 suites.add( suitesIterator.next() );
221             }
222             final Queue<String> messageQueue = new ConcurrentLinkedQueue<String>();
223             for ( Class<?> clazz : suites )
224             {
225                 messageQueue.add( clazz.getName() );
226             }
227 
228 
229             for ( int forkNum = 0; forkNum < forkCount && forkNum < suites.size(); forkNum++ )
230             {
231                 Callable<RunResult> pf = new Callable<RunResult>()
232                 {
233                     public RunResult call()
234                         throws Exception
235                     {
236                         TestProvidingInputStream testProvidingInputStream =
237                             new TestProvidingInputStream( messageQueue );
238 
239                         DefaultReporterFactory forkedReporterFactory =
240                             new DefaultReporterFactory( startupReportConfiguration );
241                         defaultReporterFactoryList.add( forkedReporterFactory );
242                         ForkClient forkClient = new ForkClient( forkedReporterFactory,
243                                                                 startupReportConfiguration.getTestVmSystemProperties(),
244                                                                 testProvidingInputStream );
245 
246                         return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
247                                      forkClient, effectiveSystemProperties, testProvidingInputStream );
248                     }
249                 };
250 
251                 results.add( executorService.submit( pf ) );
252             }
253 
254             for ( Future<RunResult> result : results )
255             {
256                 try
257                 {
258                     RunResult cur = result.get();
259                     if ( cur != null )
260                     {
261                         globalResult = globalResult.aggregate( cur );
262                     }
263                     else
264                     {
265                         throw new SurefireBooterForkException( "No results for " + result.toString() );
266                     }
267                 }
268                 catch ( InterruptedException e )
269                 {
270                     throw new SurefireBooterForkException( "Interrupted", e );
271                 }
272                 catch ( ExecutionException e )
273                 {
274                     throw new SurefireBooterForkException( "ExecutionException", e );
275                 }
276             }
277             return globalResult;
278 
279         }
280         finally
281         {
282             closeExecutor( executorService );
283         }
284 
285     }
286 
287     @SuppressWarnings( "checkstyle:magicnumber" )
288     private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, final int forkCount )
289         throws SurefireBooterForkException
290     {
291 
292         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
293         ExecutorService executorService =
294             new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
295 
296         try
297         {
298             // Ask to the executorService to run all tasks
299             RunResult globalResult = new RunResult( 0, 0, 0, 0 );
300             final Iterator<Class<?>> suites = getSuitesIterator();
301             while ( suites.hasNext() )
302             {
303                 final Object testSet = suites.next();
304                 Callable<RunResult> pf = new Callable<RunResult>()
305                 {
306                     public RunResult call()
307                         throws Exception
308                     {
309                         DefaultReporterFactory forkedReporterFactory =
310                             new DefaultReporterFactory( startupReportConfiguration );
311                         defaultReporterFactoryList.add( forkedReporterFactory );
312                         ForkClient forkClient =
313                                         new ForkClient( forkedReporterFactory,
314                                                         startupReportConfiguration.getTestVmSystemProperties() );
315                         return fork( testSet, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
316                                      forkClient, effectiveSystemProperties, null );
317                     }
318                 };
319                 results.add( executorService.submit( pf ) );
320 
321             }
322 
323             for ( Future<RunResult> result : results )
324             {
325                 try
326                 {
327                     RunResult cur = result.get();
328                     if ( cur != null )
329                     {
330                         globalResult = globalResult.aggregate( cur );
331                     }
332                     else
333                     {
334                         throw new SurefireBooterForkException( "No results for " + result.toString() );
335                     }
336                 }
337                 catch ( InterruptedException e )
338                 {
339                     throw new SurefireBooterForkException( "Interrupted", e );
340                 }
341                 catch ( ExecutionException e )
342                 {
343                     throw new SurefireBooterForkException( "ExecutionException", e );
344                 }
345             }
346             return globalResult;
347 
348         }
349         finally
350         {
351             closeExecutor( executorService );
352         }
353 
354     }
355 
356     @SuppressWarnings( "checkstyle:magicnumber" )
357     private void closeExecutor( ExecutorService executorService )
358         throws SurefireBooterForkException
359     {
360         executorService.shutdown();
361         try
362         {
363             // Should stop immediately, as we got all the results if we are here
364             executorService.awaitTermination( 60 * 60, TimeUnit.SECONDS );
365         }
366         catch ( InterruptedException e )
367         {
368             throw new SurefireBooterForkException( "Interrupted", e );
369         }
370     }
371 
372     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
373                             SurefireProperties effectiveSystemProperties,
374                             TestProvidingInputStream testProvidingInputStream )
375         throws SurefireBooterForkException
376     {
377         int forkNumber = ForkNumberBucket.drawNumber();
378         try
379         {
380             return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
381                          testProvidingInputStream );
382         }
383         finally
384         {
385             ForkNumberBucket.returnNumber( forkNumber );
386         }
387     }
388 
389     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
390                             SurefireProperties effectiveSystemProperties, int forkNumber,
391                             TestProvidingInputStream testProvidingInputStream )
392         throws SurefireBooterForkException
393     {
394         File surefireProperties;
395         File systPropsFile = null;
396         try
397         {
398             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
399 
400             surefireProperties =
401                 booterSerializer.serialize( providerProperties, providerConfiguration, startupConfiguration, testSet,
402                                             null != testProvidingInputStream );
403 
404             if ( effectiveSystemProperties != null )
405             {
406                 SurefireProperties filteredProperties =
407                     AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties,
408                                                                                  forkNumber );
409                 systPropsFile =
410                     SystemPropertyManager.writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
411                                                                "surefire_" + systemPropertiesFileCounter++,
412                                                                forkConfiguration.isDebug() );
413             }
414         }
415         catch ( IOException e )
416         {
417             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
418         }
419 
420         // this could probably be simplified further
421         final Classpath bootClasspathConfiguration = startupConfiguration.isProviderMainClass()
422             ? startupConfiguration.getClasspathConfiguration().getProviderClasspath()
423             : forkConfiguration.getBootClasspath();
424 
425         Classpath bootClasspath = join(
426             join( bootClasspathConfiguration, startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
427             startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
428 
429         if ( log.isDebugEnabled() )
430         {
431             log.debug( bootClasspath.getLogMessage( "boot" ) );
432             log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
433         }
434         OutputStreamFlushableCommandline cli =
435             forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber );
436 
437         final InputStreamCloser inputStreamCloser;
438         final Thread inputStreamCloserHook;
439         if ( testProvidingInputStream != null )
440         {
441             testProvidingInputStream.setFlushReceiverProvider( cli );
442             inputStreamCloser = new InputStreamCloser( testProvidingInputStream );
443             inputStreamCloserHook = new Thread( inputStreamCloser );
444             ShutdownHookUtils.addShutDownHook( inputStreamCloserHook );
445         }
446         else
447         {
448             inputStreamCloser = null;
449             inputStreamCloserHook = null;
450         }
451 
452         cli.createArg().setFile( surefireProperties );
453 
454         if ( systPropsFile != null )
455         {
456             cli.createArg().setFile( systPropsFile );
457         }
458 
459         ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
460 
461         if ( forkConfiguration.isDebug() )
462         {
463             System.out.println( "Forking command line: " + cli );
464         }
465 
466         RunResult runResult = null;
467 
468         try
469         {
470             final int timeout = forkedProcessTimeoutInSeconds > 0 ? forkedProcessTimeoutInSeconds : 0;
471             final int result =
472                 CommandLineUtils.executeCommandLine( cli, testProvidingInputStream, threadedStreamConsumer,
473                                                      threadedStreamConsumer, timeout, inputStreamCloser );
474             if ( result != RunResult.SUCCESS )
475             {
476                 throw new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
477             }
478 
479         }
480         catch ( CommandLineTimeOutException e )
481         {
482             runResult = RunResult.timeout(
483                 forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() );
484         }
485         catch ( CommandLineException e )
486         {
487             runResult =
488                 RunResult.failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e );
489             throw new SurefireBooterForkException( "Error while executing forked tests.", e.getCause() );
490         }
491         finally
492         {
493             threadedStreamConsumer.close();
494             if ( inputStreamCloser != null )
495             {
496                 inputStreamCloser.run();
497                 ShutdownHookUtils.removeShutdownHook( inputStreamCloserHook );
498             }
499             if ( runResult == null )
500             {
501                 runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult();
502             }
503             if ( !runResult.isTimeout() )
504             {
505                 StackTraceWriter errorInFork = forkClient.getErrorInFork();
506                 if ( errorInFork != null )
507                 {
508                     // noinspection ThrowFromFinallyBlock
509                     throw new RuntimeException(
510                         "There was an error in the forked process\n" + errorInFork.writeTraceToString() );
511                 }
512                 if ( !forkClient.isSaidGoodBye() )
513                 {
514                     // noinspection ThrowFromFinallyBlock
515                     throw new RuntimeException(
516                         "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
517                             + "\nCommand was " + cli.toString() );
518                 }
519 
520             }
521             forkClient.close( runResult.isTimeout() );
522         }
523 
524         return runResult;
525     }
526 
527     @SuppressWarnings( "unchecked" )
528     private Iterator<Class<?>> getSuitesIterator()
529         throws SurefireBooterForkException
530     {
531         try
532         {
533             final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
534             ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
535 
536             CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
537             Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration );
538 
539             final ProviderFactory providerFactory =
540                 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader,
541                                      reporterFactory );
542             SurefireProvider surefireProvider = providerFactory.createProvider( false );
543             return surefireProvider.getSuites();
544         }
545         catch ( SurefireExecutionException e )
546         {
547             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
548         }
549     }
550 }