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.JdkAttributes;
23 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
24 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
25 import org.apache.maven.surefire.booter.AbstractPathConfiguration;
26 import org.apache.maven.surefire.booter.Classpath;
27 import org.apache.maven.surefire.booter.StartupConfiguration;
28 import org.apache.maven.surefire.booter.SurefireBooterForkException;
29 import org.apache.maven.surefire.extensions.ForkNodeFactory;
30 import org.apache.maven.surefire.api.util.internal.ImmutableMap;
31 import org.apache.maven.surefire.shared.utils.cli.CommandLineException;
32
33 import javax.annotation.Nonnull;
34 import javax.annotation.Nullable;
35 import java.io.File;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Map.Entry;
40 import java.util.Properties;
41
42 import static org.apache.maven.plugin.surefire.SurefireHelper.replaceForkThreadsInPath;
43 import static org.apache.maven.plugin.surefire.util.Relocator.relocate;
44 import static org.apache.maven.plugin.surefire.SurefireHelper.replaceThreadNumberPlaceholders;
45 import static org.apache.maven.surefire.booter.Classpath.join;
46
47
48
49
50
51
52
53 public abstract class DefaultForkConfiguration
54 extends ForkConfiguration
55 {
56 @Nonnull private final Classpath booterClasspath;
57 @Nonnull private final File tempDirectory;
58 @Nullable
59 private final String debugLine;
60 @Nonnull private final File workingDirectory;
61 @Nonnull private final Properties modelProperties;
62 @Nullable private final String argLine;
63 @Nonnull private final Map<String, String> environmentVariables;
64 @Nonnull private final String[] excludedEnvironmentVariables;
65 private final boolean debug;
66 private final int forkCount;
67 private final boolean reuseForks;
68 @Nonnull private final Platform pluginPlatform;
69 @Nonnull private final ConsoleLogger log;
70 @Nonnull private final ForkNodeFactory forkNodeFactory;
71
72 @SuppressWarnings( "checkstyle:parameternumber" )
73 protected DefaultForkConfiguration( @Nonnull Classpath booterClasspath,
74 @Nonnull File tempDirectory,
75 @Nullable String debugLine,
76 @Nonnull File workingDirectory,
77 @Nonnull Properties modelProperties,
78 @Nullable String argLine,
79 @Nonnull Map<String, String> environmentVariables,
80 @Nonnull String[] excludedEnvironmentVariables,
81 boolean debug,
82 int forkCount,
83 boolean reuseForks,
84 @Nonnull Platform pluginPlatform,
85 @Nonnull ConsoleLogger log,
86 @Nonnull ForkNodeFactory forkNodeFactory )
87 {
88 this.booterClasspath = booterClasspath;
89 this.tempDirectory = tempDirectory;
90 this.debugLine = debugLine;
91 this.workingDirectory = workingDirectory;
92 this.modelProperties = modelProperties;
93 this.argLine = argLine;
94 this.environmentVariables = toImmutable( environmentVariables );
95 this.excludedEnvironmentVariables = excludedEnvironmentVariables;
96 this.debug = debug;
97 this.forkCount = forkCount;
98 this.reuseForks = reuseForks;
99 this.pluginPlatform = pluginPlatform;
100 this.log = log;
101 this.forkNodeFactory = forkNodeFactory;
102 }
103
104 protected abstract void resolveClasspath( @Nonnull Commandline cli,
105 @Nonnull String booterThatHasMainMethod,
106 @Nonnull StartupConfiguration config,
107 @Nonnull File dumpLogDirectory )
108 throws SurefireBooterForkException;
109
110 @Nonnull
111 protected String extendJvmArgLine( @Nonnull String jvmArgLine )
112 {
113 return jvmArgLine;
114 }
115
116 @Nonnull
117 @Override
118 public final ForkNodeFactory getForkNodeFactory()
119 {
120 return forkNodeFactory;
121 }
122
123
124
125
126
127
128
129
130 @Nonnull
131 @Override
132 public Commandline createCommandLine( @Nonnull StartupConfiguration config,
133 int forkNumber,
134 @Nonnull File dumpLogDirectory )
135 throws SurefireBooterForkException
136 {
137 try
138 {
139 Commandline cli =
140 new Commandline( getExcludedEnvironmentVariables() );
141
142 cli.setWorkingDirectory( getWorkingDirectory( forkNumber ).getAbsolutePath() );
143
144 for ( Entry<String, String> entry : getEnvironmentVariables().entrySet() )
145 {
146 String value = entry.getValue();
147 cli.addEnvironment( entry.getKey(), value == null ? "" : value );
148 }
149
150 cli.setExecutable( getJdkForTests().getJvmExecutable().getAbsolutePath() );
151
152 String jvmArgLine = newJvmArgLine( forkNumber );
153 if ( !jvmArgLine.isEmpty() )
154 {
155 cli.createArg()
156 .setLine( jvmArgLine );
157 }
158
159 if ( getDebugLine() != null && !getDebugLine().isEmpty() )
160 {
161 cli.createArg()
162 .setLine( getDebugLine() );
163 }
164
165 resolveClasspath( cli, findStartClass( config ), config, dumpLogDirectory );
166
167 return cli;
168 }
169 catch ( CommandLineException e )
170 {
171 throw new SurefireBooterForkException( e.getLocalizedMessage(), e );
172 }
173 }
174
175 protected ConsoleLogger getLogger()
176 {
177 return log;
178 }
179
180 @Nonnull
181 protected List<String> toCompleteClasspath( @Nonnull StartupConfiguration conf ) throws SurefireBooterForkException
182 {
183 AbstractPathConfiguration pathConfig = conf.getClasspathConfiguration();
184 if ( pathConfig.isClassPathConfig() == pathConfig.isModularPathConfig() )
185 {
186 throw new SurefireBooterForkException( "Could not find class-path config nor modular class-path either." );
187 }
188
189 Classpath bootClasspath = getBooterClasspath();
190 Classpath testClasspath = pathConfig.getTestClasspath();
191 Classpath providerClasspath = pathConfig.getProviderClasspath();
192 Classpath completeClasspath = join( join( bootClasspath, testClasspath ), providerClasspath );
193
194 getLogger().debug( completeClasspath.getLogMessage( "boot classpath:" ) );
195 getLogger().debug( completeClasspath.getCompactLogMessage( "boot(compact) classpath:" ) );
196
197 return completeClasspath.getClassPath();
198 }
199
200 @Nonnull
201 private File getWorkingDirectory( int forkNumber )
202 throws SurefireBooterForkException
203 {
204 File cwd = replaceForkThreadsInPath( getWorkingDirectory(), forkNumber );
205
206 if ( !cwd.exists() && !cwd.mkdirs() )
207 {
208 throw new SurefireBooterForkException( "Cannot create workingDirectory " + cwd.getAbsolutePath() );
209 }
210
211 if ( !cwd.isDirectory() )
212 {
213 throw new SurefireBooterForkException(
214 "WorkingDirectory " + cwd.getAbsolutePath() + " exists and is not a directory" );
215 }
216 return cwd;
217 }
218
219
220
221
222
223
224
225
226 @Nonnull
227 private String interpolateArgLineWithPropertyExpressions()
228 {
229 if ( getArgLine() == null )
230 {
231 return "";
232 }
233
234 String resolvedArgLine = getArgLine().trim();
235
236 if ( resolvedArgLine.isEmpty() )
237 {
238 return "";
239 }
240
241 for ( final String key : getModelProperties().stringPropertyNames() )
242 {
243 String field = "@{" + key + "}";
244 if ( getArgLine().contains( field ) )
245 {
246 resolvedArgLine = resolvedArgLine.replace( field, getModelProperties().getProperty( key, "" ) );
247 }
248 }
249
250 return resolvedArgLine;
251 }
252
253 @Nonnull
254 private static String stripWhitespace( @Nonnull String argLine )
255 {
256 return argLine.replaceAll( "\\s", " " );
257 }
258
259
260
261
262
263
264
265
266
267 @Nonnull
268 private static <K, V> Map<K, V> toImmutable( @Nullable Map<K, V> map )
269 {
270 return map == null ? Collections.<K, V>emptyMap() : new ImmutableMap<>( map );
271 }
272
273 @Override
274 @Nonnull
275 public File getTempDirectory()
276 {
277 return tempDirectory;
278 }
279
280 @Override
281 @Nullable
282 protected String getDebugLine()
283 {
284 return debugLine;
285 }
286
287 @Override
288 @Nonnull
289 protected File getWorkingDirectory()
290 {
291 return workingDirectory;
292 }
293
294 @Override
295 @Nonnull
296 protected Properties getModelProperties()
297 {
298 return modelProperties;
299 }
300
301 @Override
302 @Nullable
303 protected String getArgLine()
304 {
305 return argLine;
306 }
307
308 @Override
309 @Nonnull
310 protected Map<String, String> getEnvironmentVariables()
311 {
312 return environmentVariables;
313 }
314
315 @Nonnull
316 @Override
317 protected String[] getExcludedEnvironmentVariables()
318 {
319 return excludedEnvironmentVariables;
320 }
321
322 @Override
323 protected boolean isDebug()
324 {
325 return debug;
326 }
327
328 @Override
329 protected int getForkCount()
330 {
331 return forkCount;
332 }
333
334 @Override
335 protected boolean isReuseForks()
336 {
337 return reuseForks;
338 }
339
340 @Override
341 @Nonnull
342 protected Platform getPluginPlatform()
343 {
344 return pluginPlatform;
345 }
346
347 @Override
348 @Nonnull
349 protected JdkAttributes getJdkForTests()
350 {
351 return getPluginPlatform().getJdkExecAttributesForTests();
352 }
353
354 @Override
355 @Nonnull
356 protected Classpath getBooterClasspath()
357 {
358 return booterClasspath;
359 }
360
361 @Nonnull
362 private String newJvmArgLine( int forks )
363 {
364 String interpolatedArgs = stripWhitespace( interpolateArgLineWithPropertyExpressions() );
365 String argsWithReplacedForkNumbers = replaceThreadNumberPlaceholders( interpolatedArgs, forks );
366 return extendJvmArgLine( argsWithReplacedForkNumbers );
367 }
368
369 @Nonnull
370 private static String findStartClass( StartupConfiguration config )
371 {
372 return config.isShadefire() ? relocate( DEFAULT_PROVIDER_CLASS ) : DEFAULT_PROVIDER_CLASS;
373 }
374 }