1 package org.apache.maven.plugin.surefire.booterclient;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
23 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
24 import org.apache.maven.plugin.surefire.util.ImmutableMap;
25 import org.apache.maven.plugin.surefire.util.Relocator;
26 import org.apache.maven.shared.utils.StringUtils;
27 import org.apache.maven.surefire.booter.Classpath;
28 import org.apache.maven.surefire.booter.ForkedBooter;
29 import org.apache.maven.surefire.booter.StartupConfiguration;
30 import org.apache.maven.surefire.booter.SurefireBooterForkException;
31
32 import java.io.File;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Properties;
40 import java.util.jar.JarEntry;
41 import java.util.jar.JarOutputStream;
42 import java.util.jar.Manifest;
43
44
45
46
47
48
49
50
51 public class ForkConfiguration
52 {
53 public static final String FORK_ONCE = "once";
54
55 public static final String FORK_ALWAYS = "always";
56
57 public static final String FORK_NEVER = "never";
58
59 public static final String FORK_PERTHREAD = "perthread";
60
61 private final int forkCount;
62
63 private final boolean reuseForks;
64
65 private final Classpath bootClasspathConfiguration;
66
67 private final String jvmExecutable;
68
69 private final Properties modelProperties;
70
71 private final String argLine;
72
73 private final Map<String, String> environmentVariables;
74
75 private final File workingDirectory;
76
77 private final File tempDirectory;
78
79 private final boolean debug;
80
81 private final String debugLine;
82
83 @SuppressWarnings( "checkstyle:parameternumber" )
84 public ForkConfiguration( Classpath bootClasspathConfiguration, File tmpDir, String debugLine,
85 String jvmExecutable, File workingDirectory, Properties modelProperties, String argLine,
86 Map<String, String> environmentVariables, boolean debugEnabled, int forkCount,
87 boolean reuseForks )
88 {
89 this.bootClasspathConfiguration = bootClasspathConfiguration;
90 this.tempDirectory = tmpDir;
91 this.debugLine = debugLine;
92 this.jvmExecutable = jvmExecutable;
93 this.workingDirectory = workingDirectory;
94 this.modelProperties = modelProperties;
95 this.argLine = argLine;
96 this.environmentVariables = toImmutable( environmentVariables );
97 this.debug = debugEnabled;
98 this.forkCount = forkCount;
99 this.reuseForks = reuseForks;
100 }
101
102 public Classpath getBootClasspath()
103 {
104 return bootClasspathConfiguration;
105 }
106
107 public static String getEffectiveForkMode( String forkMode )
108 {
109 if ( "pertest".equalsIgnoreCase( forkMode ) )
110 {
111 return FORK_ALWAYS;
112 }
113 else if ( "none".equalsIgnoreCase( forkMode ) )
114 {
115 return FORK_NEVER;
116 }
117 else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
118 || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( FORK_PERTHREAD ) )
119 {
120 return forkMode;
121 }
122 else
123 {
124 throw new IllegalArgumentException( "Fork mode " + forkMode + " is not a legal value" );
125 }
126 }
127
128
129
130
131
132
133
134
135 public OutputStreamFlushableCommandline createCommandLine( List<String> classPath,
136 StartupConfiguration startupConfiguration,
137 int threadNumber )
138 throws SurefireBooterForkException
139 {
140 return createCommandLine( classPath,
141 startupConfiguration.getClassLoaderConfiguration()
142 .isManifestOnlyJarRequestedAndUsable(),
143 startupConfiguration.isShadefire(), startupConfiguration.isProviderMainClass()
144 ? startupConfiguration.getActualClassName()
145 : ForkedBooter.class.getName(), threadNumber );
146 }
147
148 OutputStreamFlushableCommandline createCommandLine( List<String> classPath, boolean useJar, boolean shadefire,
149 String providerThatHasMainMethod, int threadNumber )
150 throws SurefireBooterForkException
151 {
152 OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
153
154 cli.setExecutable( jvmExecutable );
155
156 if ( argLine != null )
157 {
158 cli.createArg().setLine(
159 replaceThreadNumberPlaceholder( stripNewLines( replacePropertyExpressions( argLine ) ),
160 threadNumber ) );
161 }
162
163 for ( Map.Entry<String, String> entry : environmentVariables.entrySet() )
164 {
165 String value = entry.getValue();
166 cli.addEnvironment( entry.getKey(), value == null ? "" : value );
167 }
168
169 if ( getDebugLine() != null && !getDebugLine().isEmpty() )
170 {
171 cli.createArg().setLine( getDebugLine() );
172 }
173
174 if ( useJar )
175 {
176 File jarFile;
177 try
178 {
179 jarFile = createJar( classPath, providerThatHasMainMethod );
180 }
181 catch ( IOException e )
182 {
183 throw new SurefireBooterForkException( "Error creating archive file", e );
184 }
185
186 cli.createArg().setValue( "-jar" );
187
188 cli.createArg().setValue( jarFile.getAbsolutePath() );
189 }
190 else
191 {
192 cli.addEnvironment( "CLASSPATH", StringUtils.join( classPath.iterator(), File.pathSeparator ) );
193
194 final String forkedBooter =
195 providerThatHasMainMethod != null ? providerThatHasMainMethod : ForkedBooter.class.getName();
196
197 cli.createArg().setValue( shadefire ? new Relocator().relocate( forkedBooter ) : forkedBooter );
198 }
199
200 cli.setWorkingDirectory( getWorkingDirectory( threadNumber ).getAbsolutePath() );
201
202 return cli;
203 }
204
205 private File getWorkingDirectory( int threadNumber )
206 throws SurefireBooterForkException
207 {
208 File cwd = new File( replaceThreadNumberPlaceholder( workingDirectory.getAbsolutePath(), threadNumber ) );
209 if ( !cwd.exists() && !cwd.mkdirs() )
210 {
211 throw new SurefireBooterForkException( "Cannot create workingDirectory " + cwd.getAbsolutePath() );
212 }
213 if ( !cwd.isDirectory() )
214 {
215 throw new SurefireBooterForkException(
216 "WorkingDirectory " + cwd.getAbsolutePath() + " exists and is not a directory" );
217 }
218 return cwd;
219 }
220
221 private String replaceThreadNumberPlaceholder( String argLine, int threadNumber )
222 {
223 return argLine.replace( AbstractSurefireMojo.THREAD_NUMBER_PLACEHOLDER,
224 String.valueOf( threadNumber ) ).replace( AbstractSurefireMojo.FORK_NUMBER_PLACEHOLDER,
225 String.valueOf( threadNumber ) );
226 }
227
228
229
230
231
232
233
234
235 private String replacePropertyExpressions( String argLine )
236 {
237 if ( argLine == null )
238 {
239 return null;
240 }
241
242 for ( final String key : modelProperties.stringPropertyNames() )
243 {
244 String field = "@{" + key + "}";
245 if ( argLine.contains( field ) )
246 {
247 argLine = argLine.replace( field, modelProperties.getProperty( key, "" ) );
248 }
249 }
250
251 return argLine;
252 }
253
254
255
256
257
258
259
260
261
262
263 private File createJar( List<String> classPath, String startClassName )
264 throws IOException
265 {
266 File file = File.createTempFile( "surefirebooter", ".jar", tempDirectory );
267 if ( !debug )
268 {
269 file.deleteOnExit();
270 }
271 FileOutputStream fos = new FileOutputStream( file );
272 JarOutputStream jos = new JarOutputStream( fos );
273 try
274 {
275 jos.setLevel( JarOutputStream.STORED );
276 JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
277 jos.putNextEntry( je );
278
279 Manifest man = new Manifest();
280
281
282
283 StringBuilder cp = new StringBuilder();
284 for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
285 {
286 File file1 = new File( it.next() );
287 String uri = file1.toURI().toASCIIString();
288 cp.append( uri );
289 if ( file1.isDirectory() && !uri.endsWith( "/" ) )
290 {
291 cp.append( '/' );
292 }
293
294 if ( it.hasNext() )
295 {
296 cp.append( ' ' );
297 }
298 }
299
300 man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
301 man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
302 man.getMainAttributes().putValue( "Main-Class", startClassName );
303
304 man.write( jos );
305
306 jos.closeEntry();
307 jos.flush();
308
309 return file;
310 }
311 finally
312 {
313 jos.close();
314 }
315 }
316
317 public boolean isDebug()
318 {
319 return debug;
320 }
321
322 public String getDebugLine()
323 {
324 return debugLine;
325 }
326
327 public File getTempDirectory()
328 {
329 return tempDirectory;
330 }
331
332 public int getForkCount()
333 {
334 return forkCount;
335 }
336
337 public boolean isReuseForks()
338 {
339 return reuseForks;
340 }
341
342 private static String stripNewLines( String argLine )
343 {
344 return argLine.replace( "\n", " " ).replace( "\r", " " );
345 }
346
347
348
349
350
351
352
353
354
355 private static <K, V> Map<K, V> toImmutable( Map<K, V> map )
356 {
357 return map == null ? Collections.<K, V>emptyMap() : new ImmutableMap<K, V>( map );
358 }
359 }