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