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