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