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 java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.util.Enumeration;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.jar.JarEntry;
30  import java.util.jar.JarOutputStream;
31  import java.util.jar.Manifest;
32  
33  import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
34  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
35  import org.apache.maven.plugin.surefire.util.Relocator;
36  import org.apache.maven.shared.utils.StringUtils;
37  import org.apache.maven.surefire.booter.Classpath;
38  import org.apache.maven.surefire.booter.ForkedBooter;
39  import org.apache.maven.surefire.booter.StartupConfiguration;
40  import org.apache.maven.surefire.booter.SurefireBooterForkException;
41  import org.apache.maven.surefire.util.UrlUtils;
42  
43  /**
44   * Configuration for forking tests.
45   *
46   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
47   * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
48   * @author <a href="mailto:krosenvold@apache.org">Kristian Rosenvold</a>
49   */
50  public class ForkConfiguration
51  {
52      public static final String FORK_ONCE = "once";
53  
54      public static final String FORK_ALWAYS = "always";
55  
56      public static final String FORK_NEVER = "never";
57  
58      public static final String FORK_PERTHREAD = "perthread";
59  
60      private final int forkCount;
61  
62      private final boolean reuseForks;
63  
64      private final Classpath bootClasspathConfiguration;
65  
66      private final String jvmExecutable;
67  
68      private Properties modelProperties;
69  
70      private final String argLine;
71  
72      private final Map<String, String> environmentVariables;
73  
74      private final File workingDirectory;
75  
76      private final File tempDirectory;
77  
78      private final boolean debug;
79  
80      private final String debugLine;
81  
82      @SuppressWarnings( "checkstyle:parameternumber" )
83      public ForkConfiguration( Classpath bootClasspathConfiguration, File tmpDir, String debugLine,
84                                String jvmExecutable, File workingDirectory, Properties modelProperties, String argLine,
85                                Map<String, String> environmentVariables, boolean debugEnabled, int forkCount,
86                                boolean reuseForks )
87      {
88          this.bootClasspathConfiguration = bootClasspathConfiguration;
89          this.tempDirectory = tmpDir;
90          this.debugLine = debugLine;
91          this.jvmExecutable = jvmExecutable;
92          this.workingDirectory = workingDirectory;
93          this.modelProperties = modelProperties;
94          this.argLine = argLine;
95          this.environmentVariables = environmentVariables;
96          this.debug = debugEnabled;
97          this.forkCount = forkCount;
98          this.reuseForks = reuseForks;
99      }
100 
101     public Classpath getBootClasspath()
102     {
103         return bootClasspathConfiguration;
104     }
105 
106     public static String getEffectiveForkMode( String forkMode )
107     {
108         if ( "pertest".equalsIgnoreCase( forkMode ) )
109         {
110             return FORK_ALWAYS;
111         }
112         else if ( "none".equalsIgnoreCase( forkMode ) )
113         {
114             return FORK_NEVER;
115         }
116         else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
117                || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( FORK_PERTHREAD ) )
118         {
119             return forkMode;
120         }
121         else
122         {
123             throw new IllegalArgumentException( "Fork mode " + forkMode + " is not a legal value" );
124         }
125     }
126 
127     /**
128      * @param classPath            cla the classpath arguments
129      * @param startupConfiguration The startup configuration
130      * @param threadNumber         the thread number, to be the replacement in the argLine   @return A commandline
131      * @throws org.apache.maven.surefire.booter.SurefireBooterForkException
132      *          when unable to perform the fork
133      */
134     public OutputStreamFlushableCommandline createCommandLine( List<String> classPath,
135                                                                StartupConfiguration startupConfiguration,
136                                                                int threadNumber )
137         throws SurefireBooterForkException
138     {
139         return createCommandLine( classPath,
140                                   startupConfiguration.getClassLoaderConfiguration()
141                                       .isManifestOnlyJarRequestedAndUsable(),
142                                   startupConfiguration.isShadefire(), startupConfiguration.isProviderMainClass()
143             ? startupConfiguration.getActualClassName()
144             : ForkedBooter.class.getName(), threadNumber );
145     }
146 
147     OutputStreamFlushableCommandline createCommandLine( List<String> classPath, boolean useJar, boolean shadefire,
148                                                         String providerThatHasMainMethod, int threadNumber )
149         throws SurefireBooterForkException
150     {
151         OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
152 
153         cli.setExecutable( jvmExecutable );
154 
155         if ( argLine != null )
156         {
157             cli.createArg().setLine(
158                    replaceThreadNumberPlaceholder( stripNewLines( replacePropertyExpressions( argLine ) ),
159                                                    threadNumber ) );
160         }
161 
162         if ( environmentVariables != null )
163         {
164 
165             for ( String key : environmentVariables.keySet() )
166             {
167                 String value = environmentVariables.get( key );
168 
169                 cli.addEnvironment( key, value == null ? "" : value );
170             }
171         }
172 
173         if ( getDebugLine() != null && !"".equals( getDebugLine() ) )
174         {
175             cli.createArg().setLine( getDebugLine() );
176         }
177 
178         if ( useJar )
179         {
180             File jarFile;
181             try
182             {
183                 jarFile = createJar( classPath, providerThatHasMainMethod );
184             }
185             catch ( IOException e )
186             {
187                 throw new SurefireBooterForkException( "Error creating archive file", e );
188             }
189 
190             cli.createArg().setValue( "-jar" );
191 
192             cli.createArg().setValue( jarFile.getAbsolutePath() );
193         }
194         else
195         {
196             cli.addEnvironment( "CLASSPATH", StringUtils.join( classPath.iterator(), File.pathSeparator ) );
197 
198             final String forkedBooter =
199                 providerThatHasMainMethod != null ? providerThatHasMainMethod : ForkedBooter.class.getName();
200 
201             cli.createArg().setValue( shadefire ? new Relocator().relocate( forkedBooter ) : forkedBooter );
202         }
203 
204         cli.setWorkingDirectory( workingDirectory.getAbsolutePath() );
205 
206         return cli;
207     }
208 
209     private String replaceThreadNumberPlaceholder( String argLine, int threadNumber )
210     {
211         return argLine.replace( AbstractSurefireMojo.THREAD_NUMBER_PLACEHOLDER,
212                                 String.valueOf( threadNumber ) ).replace( AbstractSurefireMojo.FORK_NUMBER_PLACEHOLDER,
213                                                                           String.valueOf( threadNumber ) );
214     }
215 
216     /**
217      * Replaces expressions <pre>@{property-name}</pre> with the corresponding properties
218      * from the model. This allows late evaluation of property values when the plugin is executed (as compared
219      * to evaluation when the pom is parsed as is done with <pre>${property-name}</pre> expressions).
220      *
221      * This allows other plugins to modify or set properties with the changes getting picked up by surefire.
222      */
223     private String replacePropertyExpressions( String argLine )
224     {
225         if ( argLine == null )
226         {
227             return null;
228         }
229 
230         for ( Enumeration<?> e = modelProperties.propertyNames(); e.hasMoreElements(); )
231         {
232             String key = e.nextElement().toString();
233             String field = "@{" + key + "}";
234             if ( argLine.contains( field ) )
235             {
236                 argLine = argLine.replace( field, modelProperties.getProperty( key, "" ) );
237             }
238         }
239 
240         return argLine;
241     }
242 
243     /**
244      * Create a jar with just a manifest containing a Main-Class entry for BooterConfiguration and a Class-Path entry
245      * for all classpath elements.
246      *
247      * @param classPath      List&lt;String> of all classpath elements.
248      * @param startClassName  The classname to start (main-class)
249      * @return The file pointint to the jar
250      * @throws java.io.IOException When a file operation fails.
251      */
252     private File createJar( List<String> classPath, String startClassName )
253         throws IOException
254     {
255         File file = File.createTempFile( "surefirebooter", ".jar", tempDirectory );
256         if ( !debug )
257         {
258             file.deleteOnExit();
259         }
260         FileOutputStream fos = new FileOutputStream( file );
261         JarOutputStream jos = new JarOutputStream( fos );
262         jos.setLevel( JarOutputStream.STORED );
263         JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
264         jos.putNextEntry( je );
265 
266         Manifest man = new Manifest();
267 
268         // we can't use StringUtils.join here since we need to add a '/' to
269         // the end of directory entries - otherwise the jvm will ignore them.
270         String cp = "";
271         for ( String el : classPath )
272         {
273             // NOTE: if File points to a directory, this entry MUST end in '/'.
274             cp += UrlUtils.getURL( new File( el ) ).toExternalForm() + " ";
275         }
276 
277         man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
278         man.getMainAttributes().putValue( "Class-Path", cp.trim() );
279         man.getMainAttributes().putValue( "Main-Class", startClassName );
280 
281         man.write( jos );
282         jos.close();
283 
284         return file;
285     }
286 
287     public boolean isDebug()
288     {
289         return debug;
290     }
291 
292     public String stripNewLines( String argline )
293     {
294         return argline.replace( "\n", " " ).replace( "\r", " " );
295     }
296 
297     public String getDebugLine()
298     {
299         return debugLine;
300     }
301 
302     public File getTempDirectory()
303     {
304         return tempDirectory;
305     }
306 
307     public int getForkCount()
308     {
309         return forkCount;
310     }
311 
312 
313     public boolean isReuseForks()
314     {
315         return reuseForks;
316     }
317 }