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