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.Executors;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.ScheduledFuture;
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 MasterProcessReader 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 );
143 }
144 catch ( Throwable t )
145 {
146
147
148 t.printStackTrace( System.err );
149
150 exit( 1, EXIT, reader );
151 }
152 finally
153 {
154 pingScheduler.cancel( true );
155 }
156 }
157
158 private static MasterProcessReader startupMasterProcessReader()
159 {
160 return MasterProcessReader.getReader();
161 }
162
163 private static ScheduledFuture<?> listenToShutdownCommands( MasterProcessReader 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 MasterProcessListener createPingHandler( final AtomicBoolean pingDone )
173 {
174 return new MasterProcessListener()
175 {
176 public void update( Command command )
177 {
178 pingDone.set( true );
179 }
180 };
181 }
182
183 private static MasterProcessListener createExitHandler( final MasterProcessReader reader )
184 {
185 return new MasterProcessListener()
186 {
187 public void update( Command command )
188 {
189 exit( 1, command.toShutdownData(), reader );
190 }
191 };
192 }
193
194 private static Runnable createPingJob( final AtomicBoolean pingDone, final MasterProcessReader 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 );
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, MasterProcessReader reader )
216 {
217 switch ( shutdownType )
218 {
219 case KILL:
220 reader.stop();
221 Runtime.getRuntime().halt( returnCode );
222 case EXIT:
223 reader.stop();
224 launchLastDitchDaemonShutdownThread( returnCode );
225 System.exit( returnCode );
226 case DEFAULT:
227
228 default:
229 break;
230 }
231 }
232
233 private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
234 ProviderConfiguration providerConfiguration,
235 PrintStream originalSystemOut )
236 throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
237 {
238 final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );
239
240 return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
241 false );
242 }
243
244 private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
245 PrintStream originalSystemOut )
246 {
247 final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
248 return SurefireReflector.createForkingReporterFactoryInCurrentClassLoader( trimStackTrace, originalSystemOut );
249 }
250
251 private static ScheduledExecutorService createJvmTerminator()
252 {
253 ThreadFactory threadFactory = newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-"
254 + SYSTEM_EXIT_TIMEOUT_IN_SECONDS
255 + "sec" );
256
257 return Executors.newScheduledThreadPool( 1, threadFactory );
258 }
259
260 @SuppressWarnings( "checkstyle:emptyblock" )
261 private static void launchLastDitchDaemonShutdownThread( final int returnCode )
262 {
263 JVM_TERMINATOR.schedule( new Runnable()
264 {
265 public void run()
266 {
267 Runtime.getRuntime().halt( returnCode );
268 }
269 }, SYSTEM_EXIT_TIMEOUT_IN_SECONDS, SECONDS );
270 }
271
272 private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory,
273 ProviderConfiguration providerConfiguration,
274 boolean insideFork,
275 StartupConfiguration startupConfig,
276 boolean restoreStreams )
277 throws TestSetFailedException, InvocationTargetException
278 {
279 final PrintStream orgSystemOut = System.out;
280 final PrintStream orgSystemErr = System.err;
281
282
283
284 try
285 {
286 return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfiguration, factory )
287 .invoke( testSet );
288 }
289 finally
290 {
291 if ( restoreStreams && System.getSecurityManager() == null )
292 {
293 System.setOut( orgSystemOut );
294 System.setErr( orgSystemErr );
295 }
296 }
297 }
298
299 private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration1,
300 boolean isInsideFork,
301 ProviderConfiguration providerConfiguration,
302 Object reporterManagerFactory1 )
303 {
304 BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory1, isInsideFork );
305 bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
306 bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
307 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
308 bpf.setClassLoaders( classLoader );
309 bpf.setTestArtifactInfo( providerConfiguration.getTestArtifact() );
310 bpf.setProviderProperties( providerConfiguration.getProviderProperties() );
311 bpf.setRunOrderParameters( providerConfiguration.getRunOrderParameters() );
312 bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
313 bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
314 bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
315 bpf.setShutdown( providerConfiguration.getShutdown() );
316 String providerClass = startupConfiguration1.getActualClassName();
317 return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
318 }
319 }