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