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 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   * The part of the booter that is unique to a forked vm.
53   * <p/>
54   * Deals with deserialization of the booter wire-level protocol
55   * <p/>
56   *
57   * @author Jason van Zyl
58   * @author Emmanuel Venisse
59   * @author Kristian Rosenvold
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       * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
70       * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
71       *
72       * @param args Commandline arguments
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             // Say bye.
139             encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
140             originalOut.flush();
141             // noinspection CallToSystemExit
142             exit( 0, EXIT, reader, false );
143         }
144         catch ( Throwable t )
145         {
146             // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
147             // noinspection UseOfSystemOutOrSystemErr
148             t.printStackTrace( System.err );
149             // noinspection ProhibitedExceptionThrown,CallToSystemExit
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                 // refers to shutdown=testset, but not used now, keeping reader open
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         // Note that System.out/System.err are also read in the "ReporterConfiguration" instatiation
286         // in createProvider below. These are the same values as here.
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 }