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