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