1 package org.apache.maven.surefire.booter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.surefire.providerapi.ProviderParameters;
23 import org.apache.maven.surefire.providerapi.SurefireProvider;
24 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
25 import org.apache.maven.surefire.report.StackTraceWriter;
26 import org.apache.maven.surefire.suite.RunResult;
27 import org.apache.maven.surefire.testset.TestSetFailedException;
28
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.PrintStream;
35 import java.lang.management.ManagementFactory;
36 import java.lang.reflect.InvocationTargetException;
37 import java.security.AccessControlException;
38 import java.security.AccessController;
39 import java.security.PrivilegedAction;
40 import java.util.concurrent.ScheduledExecutorService;
41 import java.util.concurrent.ScheduledThreadPoolExecutor;
42 import java.util.concurrent.Semaphore;
43 import java.util.concurrent.ThreadFactory;
44 import java.util.concurrent.atomic.AtomicBoolean;
45
46 import static java.lang.Math.max;
47 import static java.lang.Thread.currentThread;
48 import static java.util.concurrent.TimeUnit.MILLISECONDS;
49 import static java.util.concurrent.TimeUnit.SECONDS;
50 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
51 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR;
52 import static org.apache.maven.surefire.booter.ForkingRunListener.encode;
53 import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties;
54 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg;
55 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
56 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
57
58
59
60
61
62
63
64
65
66
67
68 public final class ForkedBooter
69 {
70 private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30L;
71 private static final long PING_TIMEOUT_IN_SECONDS = 30L;
72 private static final long ONE_SECOND_IN_MILLIS = 1000L;
73 private static final String LAST_DITCH_SHUTDOWN_THREAD = "surefire-forkedjvm-last-ditch-daemon-shutdown-thread-";
74 private static final String PING_THREAD = "surefire-forkedjvm-ping-";
75
76 private final CommandReader commandReader = CommandReader.getReader();
77 private final PrintStream originalOut = System.out;
78
79 private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
80 private volatile PingScheduler pingScheduler;
81
82 private ScheduledThreadPoolExecutor jvmTerminator;
83 private ProviderConfiguration providerConfiguration;
84 private StartupConfiguration startupConfiguration;
85 private Object testSet;
86
87 private void setupBooter( String tmpDir, String dumpFileName, String surefirePropsFileName,
88 String effectiveSystemPropertiesFileName )
89 throws IOException, SurefireExecutionException
90 {
91 BooterDeserializer booterDeserializer =
92 new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) );
93
94 pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() );
95 setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) );
96
97 providerConfiguration = booterDeserializer.deserialize();
98 DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() );
99
100 startupConfiguration = booterDeserializer.getProviderConfiguration();
101 systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
102
103 AbstractPathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
104
105 if ( classpathConfiguration.isClassPathConfig() )
106 {
107 if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
108 {
109 classpathConfiguration.toRealPath( ClasspathConfiguration.class )
110 .trickClassPathWhenManifestOnlyClasspath();
111 }
112 startupConfiguration.writeSurefireTestClasspathProperty();
113 }
114
115 ClassLoader classLoader = currentThread().getContextClassLoader();
116 classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
117 boolean readTestsFromCommandReader = providerConfiguration.isReadTestsFromInStream();
118 testSet = createTestSet( providerConfiguration.getTestForFork(), readTestsFromCommandReader, classLoader );
119 }
120
121 private void execute()
122 {
123 try
124 {
125 runSuitesInProcess();
126 }
127 catch ( InvocationTargetException t )
128 {
129 DumpErrorSingleton.getSingleton().dumpException( t );
130 StackTraceWriter stackTraceWriter =
131 new LegacyPojoStackTraceWriter( "test subsystem", "no method", t.getTargetException() );
132 StringBuilder stringBuilder = new StringBuilder();
133 encode( stringBuilder, stackTraceWriter, false );
134 encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
135 }
136 catch ( Throwable t )
137 {
138 DumpErrorSingleton.getSingleton().dumpException( t );
139 StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
140 StringBuilder stringBuilder = new StringBuilder();
141 encode( stringBuilder, stackTraceWriter, false );
142 encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
143 }
144 acknowledgedExit();
145 }
146
147 private Object createTestSet( TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl )
148 {
149 if ( forkedTestSet != null )
150 {
151 return forkedTestSet.getDecodedValue( cl );
152 }
153 else if ( readTestsFromCommandReader )
154 {
155 return new LazyTestsToRun( originalOut );
156 }
157 return null;
158 }
159
160 private void cancelPingScheduler()
161 {
162 if ( pingScheduler != null )
163 {
164 try
165 {
166 AccessController.doPrivileged( new PrivilegedAction<Object>()
167 {
168 @Override
169 public Object run()
170 {
171 pingScheduler.shutdown();
172 return null;
173 }
174 }
175 );
176 }
177 catch ( AccessControlException e )
178 {
179
180 }
181 }
182 }
183
184 private PingScheduler listenToShutdownCommands( Long pluginPid )
185 {
186 commandReader.addShutdownListener( createExitHandler() );
187 AtomicBoolean pingDone = new AtomicBoolean( true );
188 commandReader.addNoopListener( createPingHandler( pingDone ) );
189
190 PingScheduler pingMechanisms = new PingScheduler( createPingScheduler(),
191 pluginPid == null ? null : new PpidChecker( pluginPid ) );
192 if ( pingMechanisms.pluginProcessChecker != null )
193 {
194 Runnable checkerJob = processCheckerJob( pingMechanisms );
195 pingMechanisms.pingScheduler.scheduleWithFixedDelay( checkerJob, 0L, 1L, SECONDS );
196 }
197 Runnable pingJob = createPingJob( pingDone, pingMechanisms.pluginProcessChecker );
198 pingMechanisms.pingScheduler.scheduleAtFixedRate( pingJob, 0L, PING_TIMEOUT_IN_SECONDS, SECONDS );
199
200 return pingMechanisms;
201 }
202
203 private Runnable processCheckerJob( final PingScheduler pingMechanism )
204 {
205 return new Runnable()
206 {
207 @Override
208 public void run()
209 {
210 try
211 {
212 if ( pingMechanism.pluginProcessChecker.canUse()
213 && !pingMechanism.pluginProcessChecker.isProcessAlive()
214 && !pingMechanism.pingScheduler.isShutdown() )
215 {
216 DumpErrorSingleton.getSingleton().dumpText( "Killing self fork JVM. Maven process died." );
217 kill();
218 }
219 }
220 catch ( Exception e )
221 {
222 DumpErrorSingleton.getSingleton()
223 .dumpText( "System.exit() or native command error interrupted process checker." );
224 }
225 }
226 };
227 }
228
229 private CommandListener createPingHandler( final AtomicBoolean pingDone )
230 {
231 return new CommandListener()
232 {
233 @Override
234 public void update( Command command )
235 {
236 pingDone.set( true );
237 }
238 };
239 }
240
241 private CommandListener createExitHandler()
242 {
243 return new CommandListener()
244 {
245 @Override
246 public void update( Command command )
247 {
248 Shutdown shutdown = command.toShutdownData();
249 if ( shutdown.isKill() )
250 {
251 DumpErrorSingleton.getSingleton()
252 .dumpText( "Killing self fork JVM. Received SHUTDOWN command from Maven shutdown hook." );
253 kill();
254 }
255 else if ( shutdown.isExit() )
256 {
257 cancelPingScheduler();
258 DumpErrorSingleton.getSingleton()
259 .dumpText( "Exiting self fork JVM. Received SHUTDOWN command from Maven shutdown hook." );
260 exit( 1 );
261 }
262
263 }
264 };
265 }
266
267 private Runnable createPingJob( final AtomicBoolean pingDone, final PpidChecker pluginProcessChecker )
268 {
269 return new Runnable()
270 {
271 @Override
272 public void run()
273 {
274 if ( !canUseNewPingMechanism( pluginProcessChecker ) )
275 {
276 boolean hasPing = pingDone.getAndSet( false );
277 if ( !hasPing )
278 {
279 DumpErrorSingleton.getSingleton().dumpText( "Killing self fork JVM. PING timeout elapsed." );
280 kill();
281 }
282 }
283 }
284 };
285 }
286
287 private void encodeAndWriteToOutput( String string )
288 {
289 byte[] encodeBytes = encodeStringForForkCommunication( string );
290
291 synchronized ( originalOut )
292 {
293 originalOut.write( encodeBytes, 0, encodeBytes.length );
294 originalOut.flush();
295 }
296 }
297
298 private void kill()
299 {
300 kill( 1 );
301 }
302
303 private void kill( int returnCode )
304 {
305 commandReader.stop();
306 Runtime.getRuntime().halt( returnCode );
307 }
308
309 private void exit( int returnCode )
310 {
311 launchLastDitchDaemonShutdownThread( returnCode );
312 System.exit( returnCode );
313 }
314
315 private void acknowledgedExit()
316 {
317 final Semaphore barrier = new Semaphore( 0 );
318 commandReader.addByeAckListener( new CommandListener()
319 {
320 @Override
321 public void update( Command command )
322 {
323 barrier.release();
324 }
325 }
326 );
327 encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n" );
328 launchLastDitchDaemonShutdownThread( 0 );
329 long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
330 acquireOnePermit( barrier, timeoutMillis );
331 cancelPingScheduler();
332 commandReader.stop();
333 System.exit( 0 );
334 }
335
336 private RunResult runSuitesInProcess()
337 throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
338 {
339 ForkingReporterFactory factory = createForkingReporterFactory();
340 return invokeProviderInSameClassLoader( factory );
341 }
342
343 private ForkingReporterFactory createForkingReporterFactory()
344 {
345 final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
346 return new ForkingReporterFactory( trimStackTrace, originalOut );
347 }
348
349 private synchronized ScheduledThreadPoolExecutor getJvmTerminator()
350 {
351 if ( jvmTerminator == null )
352 {
353 ThreadFactory threadFactory =
354 newDaemonThreadFactory( LAST_DITCH_SHUTDOWN_THREAD + systemExitTimeoutInSeconds + "s" );
355 jvmTerminator = new ScheduledThreadPoolExecutor( 1, threadFactory );
356 jvmTerminator.setMaximumPoolSize( 1 );
357 }
358 return jvmTerminator;
359 }
360
361 @SuppressWarnings( "checkstyle:emptyblock" )
362 private void launchLastDitchDaemonShutdownThread( final int returnCode )
363 {
364 getJvmTerminator().schedule( new Runnable()
365 {
366 @Override
367 public void run()
368 {
369 kill( returnCode );
370 }
371 }, systemExitTimeoutInSeconds, SECONDS
372 );
373 }
374
375 private RunResult invokeProviderInSameClassLoader( ForkingReporterFactory factory )
376 throws TestSetFailedException, InvocationTargetException
377 {
378 return createProviderInCurrentClassloader( factory )
379 .invoke( testSet );
380 }
381
382 private SurefireProvider createProviderInCurrentClassloader( ForkingReporterFactory reporterManagerFactory )
383 {
384 BaseProviderFactory bpf = new BaseProviderFactory( reporterManagerFactory, true );
385 bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
386 bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
387 ClassLoader classLoader = currentThread().getContextClassLoader();
388 bpf.setClassLoaders( classLoader );
389 bpf.setTestArtifactInfo( providerConfiguration.getTestArtifact() );
390 bpf.setProviderProperties( providerConfiguration.getProviderProperties() );
391 bpf.setRunOrderParameters( providerConfiguration.getRunOrderParameters() );
392 bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
393 bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
394 bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
395 bpf.setShutdown( providerConfiguration.getShutdown() );
396 bpf.setSystemExitTimeout( providerConfiguration.getSystemExitTimeout() );
397 String providerClass = startupConfiguration.getActualClassName();
398 return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
399 }
400
401
402
403
404
405
406
407 public static void main( String... args )
408 {
409 ForkedBooter booter = new ForkedBooter();
410 try
411 {
412 booter.setupBooter( args[0], args[1], args[2], args.length > 3 ? args[3] : null );
413 booter.execute();
414 }
415 catch ( Throwable t )
416 {
417 DumpErrorSingleton.getSingleton().dumpException( t );
418 t.printStackTrace();
419 booter.cancelPingScheduler();
420 booter.exit( 1 );
421 }
422 }
423
424 private static boolean canUseNewPingMechanism( PpidChecker pluginProcessChecker )
425 {
426 return pluginProcessChecker != null && pluginProcessChecker.canUse();
427 }
428
429 private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
430 {
431 try
432 {
433 return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
434 }
435 catch ( InterruptedException e )
436 {
437 return true;
438 }
439 }
440
441 private static ScheduledExecutorService createPingScheduler()
442 {
443 ThreadFactory threadFactory = newDaemonThreadFactory( PING_THREAD + PING_TIMEOUT_IN_SECONDS + "s" );
444 ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
445 executor.setKeepAliveTime( 3L, SECONDS );
446 executor.setMaximumPoolSize( 2 );
447 return executor;
448 }
449
450 private static InputStream createSurefirePropertiesIfFileExists( String tmpDir, String propFileName )
451 throws FileNotFoundException
452 {
453 File surefirePropertiesFile = new File( tmpDir, propFileName );
454 return surefirePropertiesFile.exists() ? new FileInputStream( surefirePropertiesFile ) : null;
455 }
456
457 private static boolean isDebugging()
458 {
459 for ( String argument : ManagementFactory.getRuntimeMXBean().getInputArguments() )
460 {
461 if ( "-Xdebug".equals( argument ) || argument.startsWith( "-agentlib:jdwp" ) )
462 {
463 return true;
464 }
465 }
466 return false;
467 }
468
469 private static class PingScheduler
470 {
471 private final ScheduledExecutorService pingScheduler;
472 private final PpidChecker pluginProcessChecker;
473
474 PingScheduler( ScheduledExecutorService pingScheduler, PpidChecker pluginProcessChecker )
475 {
476 this.pingScheduler = pingScheduler;
477 this.pluginProcessChecker = pluginProcessChecker;
478 }
479
480 void shutdown()
481 {
482 pingScheduler.shutdown();
483 if ( pluginProcessChecker != null )
484 {
485 pluginProcessChecker.destroyActiveCommands();
486 }
487 }
488
489 boolean isShutdown()
490 {
491 return pingScheduler.isShutdown();
492 }
493 }
494 }