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