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 java.io.File;
23 import java.io.FileInputStream;
24 import java.io.InputStream;
25 import java.io.PrintStream;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.concurrent.ScheduledExecutorService;
28 import java.util.concurrent.ScheduledFuture;
29 import java.util.concurrent.ScheduledThreadPoolExecutor;
30 import java.util.concurrent.ThreadFactory;
31 import java.util.concurrent.atomic.AtomicBoolean;
32
33 import org.apache.maven.surefire.providerapi.ProviderParameters;
34 import org.apache.maven.surefire.providerapi.SurefireProvider;
35 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
36 import org.apache.maven.surefire.report.ReporterFactory;
37 import org.apache.maven.surefire.report.StackTraceWriter;
38 import org.apache.maven.surefire.suite.RunResult;
39 import org.apache.maven.surefire.testset.TestSetFailedException;
40
41 import static org.apache.maven.surefire.booter.Shutdown.EXIT;
42 import static org.apache.maven.surefire.booter.Shutdown.KILL;
43 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
44 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR;
45 import static org.apache.maven.surefire.booter.ForkingRunListener.encode;
46 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg;
47 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
48 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
49 import static java.util.concurrent.TimeUnit.SECONDS;
50
51
52
53
54
55
56
57
58
59
60
61 public final class ForkedBooter
62 {
63 private static final long SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30;
64 private static final long PING_TIMEOUT_IN_SECONDS = 20;
65
66 private static final ScheduledExecutorService JVM_TERMINATOR = createJvmTerminator();
67
68
69
70
71
72
73
74 public static void main( String... args )
75 {
76 final CommandReader reader = startupMasterProcessReader();
77 final ScheduledFuture<?> pingScheduler = listenToShutdownCommands( reader );
78 final PrintStream originalOut = System.out;
79 try
80 {
81 if ( args.length > 1 )
82 {
83 SystemPropertyManager.setSystemProperties( new File( args[1] ) );
84 }
85
86 File surefirePropertiesFile = new File( args[0] );
87 InputStream stream = surefirePropertiesFile.exists() ? new FileInputStream( surefirePropertiesFile ) : null;
88 BooterDeserializer booterDeserializer = new BooterDeserializer( stream );
89 ProviderConfiguration providerConfiguration = booterDeserializer.deserialize();
90 final StartupConfiguration startupConfiguration = booterDeserializer.getProviderConfiguration();
91
92 TypeEncodedValue forkedTestSet = providerConfiguration.getTestForFork();
93 boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream();
94
95 final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
96 if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
97 {
98 classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
99 }
100
101 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
102 classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
103 startupConfiguration.writeSurefireTestClasspathProperty();
104
105 final Object testSet;
106 if ( forkedTestSet != null )
107 {
108 testSet = forkedTestSet.getDecodedValue( classLoader );
109 }
110 else if ( readTestsFromInputStream )
111 {
112 testSet = new LazyTestsToRun( originalOut );
113 }
114 else
115 {
116 testSet = null;
117 }
118
119 try
120 {
121 runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut );
122 }
123 catch ( InvocationTargetException t )
124 {
125 LegacyPojoStackTraceWriter stackTraceWriter =
126 new LegacyPojoStackTraceWriter( "test subystem", "no method", t.getTargetException() );
127 StringBuilder stringBuilder = new StringBuilder();
128 encode( stringBuilder, stackTraceWriter, false );
129 encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut );
130 }
131 catch ( Throwable t )
132 {
133 StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subystem", "no method", t );
134 StringBuilder stringBuilder = new StringBuilder();
135 encode( stringBuilder, stackTraceWriter, false );
136 encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut );
137 }
138
139 encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
140 originalOut.flush();
141
142 exit( 0, EXIT, reader, false );
143 }
144 catch ( Throwable t )
145 {
146
147
148 t.printStackTrace( System.err );
149
150 exit( 1, EXIT, reader, false );
151 }
152 finally
153 {
154 pingScheduler.cancel( true );
155 }
156 }
157
158 private static CommandReader startupMasterProcessReader()
159 {
160 return CommandReader.getReader();
161 }
162
163 private static ScheduledFuture<?> listenToShutdownCommands( CommandReader reader )
164 {
165 reader.addShutdownListener( createExitHandler( reader ) );
166 AtomicBoolean pingDone = new AtomicBoolean( true );
167 reader.addNoopListener( createPingHandler( pingDone ) );
168 return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone, reader ),
169 0, PING_TIMEOUT_IN_SECONDS, SECONDS );
170 }
171
172 private static CommandListener createPingHandler( final AtomicBoolean pingDone )
173 {
174 return new CommandListener()
175 {
176 public void update( Command command )
177 {
178 pingDone.set( true );
179 }
180 };
181 }
182
183 private static CommandListener createExitHandler( final CommandReader reader )
184 {
185 return new CommandListener()
186 {
187 public void update( Command command )
188 {
189 exit( 1, command.toShutdownData(), reader, true );
190 }
191 };
192 }
193
194 private static Runnable createPingJob( final AtomicBoolean pingDone, final CommandReader reader )
195 {
196 return new Runnable()
197 {
198 public void run()
199 {
200 boolean hasPing = pingDone.getAndSet( false );
201 if ( !hasPing )
202 {
203 exit( 1, KILL, reader, true );
204 }
205 }
206 };
207 }
208
209 private static void encodeAndWriteToOutput( String string, PrintStream out )
210 {
211 byte[] encodeBytes = encodeStringForForkCommunication( string );
212 out.write( encodeBytes, 0, encodeBytes.length );
213 }
214
215 private static void exit( int returnCode, Shutdown shutdownType, CommandReader reader, boolean stopReaderOnExit )
216 {
217 switch ( shutdownType )
218 {
219 case KILL:
220 Runtime.getRuntime().halt( returnCode );
221 case EXIT:
222 if ( stopReaderOnExit )
223 {
224 reader.stop();
225 }
226 launchLastDitchDaemonShutdownThread( returnCode );
227 System.exit( returnCode );
228 case DEFAULT:
229
230 default:
231 break;
232 }
233 }
234
235 private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
236 ProviderConfiguration providerConfiguration,
237 PrintStream originalSystemOut )
238 throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
239 {
240 final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );
241
242 return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
243 false );
244 }
245
246 private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
247 PrintStream originalSystemOut )
248 {
249 final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
250 return SurefireReflector.createForkingReporterFactoryInCurrentClassLoader( trimStackTrace, originalSystemOut );
251 }
252
253 private static ScheduledExecutorService createJvmTerminator()
254 {
255 ThreadFactory threadFactory = newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-"
256 + SYSTEM_EXIT_TIMEOUT_IN_SECONDS
257 + "sec" );
258 ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
259 executor.setMaximumPoolSize( 1 );
260 executor.prestartCoreThread();
261 return executor;
262 }
263
264 @SuppressWarnings( "checkstyle:emptyblock" )
265 private static void launchLastDitchDaemonShutdownThread( final int returnCode )
266 {
267 JVM_TERMINATOR.schedule( new Runnable()
268 {
269 public void run()
270 {
271 Runtime.getRuntime().halt( returnCode );
272 }
273 }, SYSTEM_EXIT_TIMEOUT_IN_SECONDS, SECONDS );
274 }
275
276 private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory,
277 ProviderConfiguration providerConfiguration,
278 boolean insideFork,
279 StartupConfiguration startupConfig,
280 boolean restoreStreams )
281 throws TestSetFailedException, InvocationTargetException
282 {
283 final PrintStream orgSystemOut = System.out;
284 final PrintStream orgSystemErr = System.err;
285
286
287
288 try
289 {
290 return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfiguration, factory )
291 .invoke( testSet );
292 }
293 finally
294 {
295 if ( restoreStreams && System.getSecurityManager() == null )
296 {
297 System.setOut( orgSystemOut );
298 System.setErr( orgSystemErr );
299 }
300 }
301 }
302
303 private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration1,
304 boolean isInsideFork,
305 ProviderConfiguration providerConfiguration,
306 Object reporterManagerFactory1 )
307 {
308 BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory1, isInsideFork );
309 bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
310 bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
311 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
312 bpf.setClassLoaders( classLoader );
313 bpf.setTestArtifactInfo( providerConfiguration.getTestArtifact() );
314 bpf.setProviderProperties( providerConfiguration.getProviderProperties() );
315 bpf.setRunOrderParameters( providerConfiguration.getRunOrderParameters() );
316 bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
317 bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
318 bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
319 bpf.setShutdown( providerConfiguration.getShutdown() );
320 String providerClass = startupConfiguration1.getActualClassName();
321 return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
322 }
323 }