View Javadoc
1   package org.apache.maven.surefire.booter;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * The part of the booter that is unique to a forked vm.
66   * <p/>
67   * Deals with deserialization of the booter wire-level protocol
68   * <p/>
69   *
70   * @author Jason van Zyl
71   * @author Emmanuel Venisse
72   * @author Kristian Rosenvold
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       * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
85       * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
86       *
87       * @param args Commandline arguments
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             // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
168             // noinspection UseOfSystemOutOrSystemErr
169             t.printStackTrace( err );
170             cancelPingScheduler( pingScheduler );
171             // noinspection ProhibitedExceptionThrown,CallToSystemExit
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                 // ignore
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                 // else refers to shutdown=testset, but not used now, keeping reader open
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         //noinspection SynchronizationOnLocalVariableOrMethodParameter
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         // Note that System.out/System.err are also read in the "ReporterConfiguration" instantiation
382         // in createProvider below. These are the same values as here.
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 }