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