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