View Javadoc

1   package org.apache.maven.plugin.surefire.booterclient;
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.plugin.surefire.booterclient.output.FileOutputConsumerProxy;
23  import org.apache.maven.plugin.surefire.booterclient.output.NullOutputConsumer;
24  import org.apache.maven.plugin.surefire.booterclient.output.OutputConsumer;
25  import org.apache.maven.plugin.surefire.booterclient.output.StandardOutputConsumer;
26  import org.apache.maven.plugin.surefire.booterclient.output.SupressFooterOutputConsumerProxy;
27  import org.apache.maven.plugin.surefire.booterclient.output.SupressHeaderOutputConsumerProxy;
28  import org.apache.maven.plugin.surefire.booterclient.output.SynchronizedOutputConsumer;
29  import org.apache.maven.surefire.booter.Classpath;
30  import org.apache.maven.surefire.booter.ProviderConfiguration;
31  import org.apache.maven.surefire.booter.ProviderFactory;
32  import org.apache.maven.surefire.booter.StartupConfiguration;
33  import org.apache.maven.surefire.booter.SurefireBooterForkException;
34  import org.apache.maven.surefire.booter.SurefireExecutionException;
35  import org.apache.maven.surefire.booter.SurefireStarter;
36  import org.apache.maven.surefire.booter.SystemPropertyManager;
37  import org.apache.maven.surefire.providerapi.SurefireProvider;
38  import org.apache.maven.surefire.suite.RunResult;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.cli.CommandLineException;
41  import org.codehaus.plexus.util.cli.CommandLineTimeOutException;
42  import org.codehaus.plexus.util.cli.CommandLineUtils;
43  import org.codehaus.plexus.util.cli.Commandline;
44  import org.codehaus.plexus.util.cli.StreamConsumer;
45  
46  import java.io.File;
47  import java.io.FileInputStream;
48  import java.io.FileNotFoundException;
49  import java.io.IOException;
50  import java.util.Iterator;
51  import java.util.Properties;
52  
53  
54  /**
55   * Starts the fork or runs in-process.
56   * <p/>
57   * Lives only on the plugin-side (not present in remote vms)
58   * <p/>
59   * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
60   *
61   * @author Jason van Zyl
62   * @author Emmanuel Venisse
63   * @author Brett Porter
64   * @author Dan Fabulich
65   * @author Carlos Sanchez
66   * @author Kristian Rosenvold
67   * @version $Id: ForkStarter.java 1076367 2011-03-02 20:30:49Z krosenvold $
68   */
69  public class ForkStarter
70  {
71      private final int forkedProcessTimeoutInSeconds;
72  
73      private final ProviderConfiguration providerConfiguration;
74  
75      private final StartupConfiguration startupConfiguration;
76  
77      private final ForkConfiguration forkConfiguration;
78  
79      private final File reportsDirectory;
80  
81      private final boolean printSummary;
82  
83      public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
84                          File reportsDirectory, ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
85                          boolean printSummary )
86      {
87          this.forkConfiguration = forkConfiguration;
88          this.providerConfiguration = providerConfiguration;
89          this.reportsDirectory = reportsDirectory;
90          this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
91          this.startupConfiguration = startupConfiguration;
92          this.printSummary = printSummary;
93      }
94  
95      public int run()
96          throws SurefireBooterForkException, SurefireExecutionException
97      {
98          final int result;
99  
100         final String requestedForkMode = forkConfiguration.getForkMode();
101         if ( ForkConfiguration.FORK_NEVER.equals( requestedForkMode ) )
102         {
103             SurefireStarter surefireStarter = new SurefireStarter( startupConfiguration, providerConfiguration );
104             result = surefireStarter.runSuitesInProcess();
105         }
106         else if ( ForkConfiguration.FORK_ONCE.equals( requestedForkMode ) )
107         {
108             result = runSuitesForkOnce();
109         }
110         else if ( ForkConfiguration.FORK_ALWAYS.equals( requestedForkMode ) )
111         {
112             result = runSuitesForkPerTestSet();
113         }
114         else
115         {
116             throw new SurefireExecutionException( "Unknown forkmode: " + requestedForkMode, null );
117         }
118         return result;
119     }
120 
121     private int runSuitesForkOnce()
122         throws SurefireBooterForkException
123     {
124         return fork( null, providerConfiguration.getProviderProperties(), true, true );
125     }
126 
127     private int runSuitesForkPerTestSet()
128         throws SurefireBooterForkException
129     {
130         int globalResult = 0;
131 
132         ClassLoader testsClassLoader;
133         ClassLoader surefireClassLoader;
134         try
135         {
136             testsClassLoader = startupConfiguration.getClasspathConfiguration().createTestClassLoader( false );
137             // TODO: assertions = true shouldn't be required if we had proper separation (see TestNG)
138             surefireClassLoader =
139                 startupConfiguration.getClasspathConfiguration().createSurefireClassLoader( testsClassLoader );
140         }
141         catch ( SurefireExecutionException e )
142         {
143             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
144         }
145 
146         boolean showHeading = true;
147         final ProviderFactory providerFactory =
148             new ProviderFactory( startupConfiguration, providerConfiguration, surefireClassLoader, testsClassLoader );
149         SurefireProvider surefireProvider = providerFactory.createProvider();
150 
151         Properties properties = new Properties();
152 
153         final Iterator suites = surefireProvider.getSuites();
154         while ( suites.hasNext() )
155         {
156             Object testSet = suites.next();
157             boolean showFooter = !suites.hasNext();
158             int result = fork( testSet, properties, showHeading, showFooter );
159 
160             if ( result > globalResult )
161             {
162                 globalResult = result;
163             }
164             showHeading = false;
165         }
166         // At this place, show aggregated results ?
167         return globalResult;
168     }
169 
170     private int fork( Object testSet, Properties properties, boolean showHeading, boolean showFooter )
171         throws SurefireBooterForkException
172     {
173         File surefireProperties;
174         File systemProperties = null;
175         try
176         {
177             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration, properties );
178 
179             surefireProperties = booterSerializer.serialize( providerConfiguration, startupConfiguration, testSet );
180 
181             if ( forkConfiguration.getSystemProperties() != null )
182             {
183                 systemProperties = SystemPropertyManager.writePropertiesFile( forkConfiguration.getSystemProperties(),
184                                                                               forkConfiguration.getTempDirectory(),
185                                                                               "surefire", forkConfiguration.isDebug() );
186             }
187         }
188         catch ( IOException e )
189         {
190             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
191         }
192 
193         final Classpath bootClasspathConfiguration = forkConfiguration.getBootClasspath();
194         final Classpath additionlClassPathUrls = startupConfiguration.useSystemClassLoader()
195             ? startupConfiguration.getClasspathConfiguration().getTestClasspath()
196             : null;
197 
198         Classpath bootClasspath = Classpath.join( bootClasspathConfiguration, additionlClassPathUrls );
199 
200         Commandline cli = forkConfiguration.createCommandLine( bootClasspath.getClassPath(),
201                                                                startupConfiguration.getClassLoaderConfiguration(),
202                                                                startupConfiguration.isShadefire() );
203 
204         cli.createArg().setFile( surefireProperties );
205 
206         if ( systemProperties != null )
207         {
208             cli.createArg().setFile( systemProperties );
209         }
210 
211         final boolean willBeSharingConsumer = startupConfiguration.isRedirectTestOutputToFile();
212 
213         ForkingStreamConsumer out =
214             getForkingStreamConsumer( showHeading, showFooter, startupConfiguration.isRedirectTestOutputToFile(),
215                                       willBeSharingConsumer, printSummary );
216 
217         StreamConsumer err = willBeSharingConsumer
218             ? out
219             : getForkingStreamConsumer( showHeading, showFooter, startupConfiguration.isRedirectTestOutputToFile(),
220                                         false, printSummary );
221 
222         if ( forkConfiguration.isDebug() )
223         {
224             System.out.println( "Forking command line: " + cli );
225         }
226 
227         int returnCode;
228 
229         try
230         {
231             returnCode = CommandLineUtils.executeCommandLine( cli, out, err, forkedProcessTimeoutInSeconds > 0 ?
232                 forkedProcessTimeoutInSeconds: 0 );
233         }
234         catch ( CommandLineTimeOutException e )
235         {
236             returnCode = RunResult.FAILURE;
237         }
238         catch ( CommandLineException e )
239         {
240             throw new SurefireBooterForkException( "Error while executing forked tests.", e.getCause() );
241         }
242 
243        /*if ( !providerConfiguration.isSurefireForkReturnCode( returnCode ) )
244         {
245             throw new SurefireBooterForkException( "Uncontrolled error while forking surefire."
246                                                        + "You need to inspect log files (with reportFormat=plain and redirectTestOutputToFile=true )"
247                                                        + " to see stacktrace" );
248         }  */
249 
250         if ( startupConfiguration.isRedirectTestOutputToFile() )
251         {
252             // ensure the FileOutputConsumerProxy flushes/closes the output file
253             try
254             {
255                 out.getOutputConsumer().testSetCompleted();
256             }
257             catch ( Exception e )
258             {
259                 // the FileOutputConsumerProxy might throw an IllegalStateException but that's not of interest now
260             }
261         }
262 
263         if ( surefireProperties != null && surefireProperties.exists() )
264         {
265             FileInputStream inStream = null;
266             try
267             {
268                 inStream = new FileInputStream( surefireProperties );
269 
270                 properties.load( inStream );
271             }
272             catch ( FileNotFoundException e )
273             {
274                 throw new SurefireBooterForkException( "Unable to reload properties file from forked process", e );
275             }
276             catch ( IOException e )
277             {
278                 throw new SurefireBooterForkException( "Unable to reload properties file from forked process", e );
279             }
280             finally
281             {
282                 IOUtil.close( inStream );
283             }
284         }
285 
286         return returnCode;
287     }
288 
289 
290     private ForkingStreamConsumer getForkingStreamConsumer( boolean showHeading, boolean showFooter,
291                                                             boolean redirectTestOutputToFile, boolean mustBeThreadSafe,
292                                                             boolean printSummary )
293     {
294         OutputConsumer outputConsumer =
295             printSummary ? new StandardOutputConsumer() : (OutputConsumer) new NullOutputConsumer();
296 
297         if ( redirectTestOutputToFile )
298         {
299             outputConsumer = new FileOutputConsumerProxy( outputConsumer, reportsDirectory );
300         }
301 
302         if ( !showHeading )
303         {
304             outputConsumer = new SupressHeaderOutputConsumerProxy( outputConsumer );
305         }
306 
307         if ( !showFooter )
308         {
309             outputConsumer = new SupressFooterOutputConsumerProxy( outputConsumer );
310         }
311 
312         if ( mustBeThreadSafe )
313         {
314             outputConsumer = new SynchronizedOutputConsumer( outputConsumer );
315         }
316 
317         return new ForkingStreamConsumer( outputConsumer );
318     }
319 }