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.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
70
71
72
73
74
75
76
77
78
79
80
81
82 public class ForkStarter
83 {
84
85
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
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
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
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
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
396
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
475 throw new RuntimeException( "There was an error in the forked process\n"
476 + errorInFork.writeTraceToString() );
477 }
478 if ( !forkClient.isSaidGoodBye() )
479 {
480
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 }