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