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.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   * 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 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             // Say bye.
139             encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
140             originalOut.flush();
141             // noinspection CallToSystemExit
142             exit( 0, EXIT, reader );
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 );
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                 // refers to shutdown=testset, but not used now, keeping reader open
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         // Note that System.out/System.err are also read in the "ReporterConfiguration" instatiation
282         // in createProvider below. These are the same values as here.
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 }