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