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.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
73
74
75
76
77
78
79
80
81
82
83
84
85 public class ForkStarter
86 {
87
88
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
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
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
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
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
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
479 throw new RuntimeException(
480 "There was an error in the forked process\n" + errorInFork.writeTraceToString() );
481 }
482 if ( !forkClient.isSaidGoodBye() )
483 {
484
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 }