1 package org.apache.maven.plugin.surefire.booterclient;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
76
77
78
79
80
81
82
83
84
85
86
87
88 public class ForkStarter
89 {
90
91
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
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
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
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
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
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
509 throw new RuntimeException(
510 "There was an error in the forked process\n" + errorInFork.writeTraceToString() );
511 }
512 if ( !forkClient.isSaidGoodBye() )
513 {
514
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 }