View Javadoc
1   // CHECKSTYLE_OFF: FileLength|RegexpHeader
2   package org.apache.maven.plugin.surefire;
3   
4   /*
5    * Licensed to the Apache Software Foundation (ASF) under one
6    * or more contributor license agreements.  See the NOTICE file
7    * distributed with this work for additional information
8    * regarding copyright ownership.  The ASF licenses this file
9    * to you under the Apache License, Version 2.0 (the
10   * "License"); you may not use this file except in compliance
11   * with the License.  You may obtain a copy of the License at
12   *
13   *     http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing,
16   * software distributed under the License is distributed on an
17   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   * KIND, either express or implied.  See the License for the
19   * specific language governing permissions and limitations
20   * under the License.
21   */
22  
23  import org.apache.maven.artifact.Artifact;
24  import org.apache.maven.artifact.DefaultArtifact;
25  import org.apache.maven.artifact.handler.ArtifactHandler;
26  import org.apache.maven.artifact.repository.ArtifactRepository;
27  import org.apache.maven.model.Dependency;
28  import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
29  import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
30  import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
31  import org.apache.maven.plugins.annotations.Component;
32  import org.apache.maven.project.ProjectBuildingRequest;
33  import org.apache.maven.repository.RepositorySystem;
34  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
35  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
36  import org.apache.maven.artifact.versioning.ArtifactVersion;
37  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
38  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
39  import org.apache.maven.artifact.versioning.VersionRange;
40  import org.apache.maven.execution.MavenSession;
41  import org.apache.maven.plugin.AbstractMojo;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.plugin.descriptor.PluginDescriptor;
45  import org.apache.maven.plugin.surefire.booterclient.ChecksumCalculator;
46  import org.apache.maven.plugin.surefire.booterclient.ForkConfiguration;
47  import org.apache.maven.plugin.surefire.booterclient.ForkStarter;
48  import org.apache.maven.plugin.surefire.booterclient.ClasspathForkConfiguration;
49  import org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration;
50  import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfiguration;
51  import org.apache.maven.plugin.surefire.booterclient.Platform;
52  import org.apache.maven.plugin.surefire.booterclient.ProviderDetector;
53  import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
54  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
55  import org.apache.maven.plugin.surefire.util.DependencyScanner;
56  import org.apache.maven.plugin.surefire.util.DirectoryScanner;
57  import org.apache.maven.plugins.annotations.Parameter;
58  import org.apache.maven.project.MavenProject;
59  import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
60  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
61  import org.apache.maven.shared.utils.io.FileUtils;
62  import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
63  import org.apache.maven.surefire.booter.Classpath;
64  import org.apache.maven.surefire.booter.ClasspathConfiguration;
65  import org.apache.maven.surefire.booter.KeyValueSource;
66  import org.apache.maven.surefire.booter.ModularClasspath;
67  import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
68  import org.apache.maven.surefire.booter.ProcessCheckerType;
69  import org.apache.maven.surefire.booter.ProviderConfiguration;
70  import org.apache.maven.surefire.booter.ProviderParameterNames;
71  import org.apache.maven.surefire.booter.Shutdown;
72  import org.apache.maven.surefire.booter.StartupConfiguration;
73  import org.apache.maven.surefire.booter.SurefireBooterForkException;
74  import org.apache.maven.surefire.booter.SurefireExecutionException;
75  import org.apache.maven.surefire.cli.CommandLineOption;
76  import org.apache.maven.surefire.providerapi.SurefireProvider;
77  import org.apache.maven.surefire.report.ReporterConfiguration;
78  import org.apache.maven.surefire.suite.RunResult;
79  import org.apache.maven.surefire.testset.DirectoryScannerParameters;
80  import org.apache.maven.surefire.testset.RunOrderParameters;
81  import org.apache.maven.surefire.testset.TestArtifactInfo;
82  import org.apache.maven.surefire.testset.TestListResolver;
83  import org.apache.maven.surefire.testset.TestRequest;
84  import org.apache.maven.surefire.testset.TestSetFailedException;
85  import org.apache.maven.surefire.util.DefaultScanResult;
86  import org.apache.maven.surefire.util.RunOrder;
87  import org.apache.maven.toolchain.DefaultToolchain;
88  import org.apache.maven.toolchain.Toolchain;
89  import org.apache.maven.toolchain.ToolchainManager;
90  import org.codehaus.plexus.logging.Logger;
91  import org.codehaus.plexus.languages.java.jpms.LocationManager;
92  import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
93  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
94  
95  import javax.annotation.Nonnull;
96  import java.io.File;
97  import java.io.IOException;
98  import java.math.BigDecimal;
99  import java.nio.file.Files;
100 import java.nio.file.Paths;
101 import java.util.ArrayList;
102 import java.util.Collection;
103 import java.util.Collections;
104 import java.util.Enumeration;
105 import java.util.HashMap;
106 import java.util.HashSet;
107 import java.util.LinkedHashSet;
108 import java.util.List;
109 import java.util.Map;
110 import java.util.Map.Entry;
111 import java.util.Properties;
112 import java.util.Set;
113 import java.util.SortedSet;
114 import java.util.TreeSet;
115 import java.util.concurrent.ConcurrentHashMap;
116 
117 import static java.lang.Boolean.TRUE;
118 import static java.lang.Thread.currentThread;
119 import static java.util.Arrays.asList;
120 import static java.util.Collections.addAll;
121 import static java.util.Collections.singletonList;
122 import static java.util.Collections.singletonMap;
123 import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
124 import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
125 import static org.apache.maven.artifact.ArtifactUtils.artifactMapByVersionlessId;
126 import static org.apache.maven.plugin.surefire.SurefireDependencyResolver.isWithinVersionSpec;
127 import static org.apache.maven.plugin.surefire.util.DependencyScanner.filter;
128 import static org.apache.maven.plugin.surefire.SurefireHelper.replaceThreadNumberPlaceholders;
129 import static org.apache.maven.shared.utils.StringUtils.capitalizeFirstLetter;
130 import static org.apache.maven.shared.utils.StringUtils.isEmpty;
131 import static org.apache.maven.shared.utils.StringUtils.isNotBlank;
132 import static org.apache.maven.shared.utils.StringUtils.isNotEmpty;
133 import static org.apache.maven.shared.utils.StringUtils.split;
134 import static org.apache.maven.surefire.booter.SystemUtils.JAVA_SPECIFICATION_VERSION;
135 import static org.apache.maven.surefire.booter.SystemUtils.endsWithJavaPath;
136 import static org.apache.maven.surefire.booter.SystemUtils.isBuiltInJava9AtLeast;
137 import static org.apache.maven.surefire.booter.SystemUtils.isJava9AtLeast;
138 import static org.apache.maven.surefire.booter.SystemUtils.toJdkHomeFromJvmExec;
139 import static org.apache.maven.surefire.booter.SystemUtils.toJdkVersionFromReleaseFile;
140 import static org.apache.maven.surefire.suite.RunResult.failure;
141 import static org.apache.maven.surefire.suite.RunResult.noTestsRun;
142 
143 /**
144  * Abstract base class for running tests using Surefire.
145  *
146  * @author Stephen Connolly
147  * @version $Id: SurefirePlugin.java 945065 2010-05-17 10:26:22Z stephenc $
148  */
149 public abstract class AbstractSurefireMojo
150     extends AbstractMojo
151     implements SurefireExecutionParameters
152 {
153     private static final String FORK_ONCE = "once";
154     private static final String FORK_ALWAYS = "always";
155     private static final String FORK_NEVER = "never";
156     private static final String FORK_PERTHREAD = "perthread";
157     private static final Map<String, String> JAVA_9_MATCHER_OLD_NOTATION = singletonMap( "version", "[1.9,)" );
158     private static final Map<String, String> JAVA_9_MATCHER = singletonMap( "version", "[9,)" );
159     private static final Platform PLATFORM = new Platform();
160 
161     private final ProviderDetector providerDetector = new ProviderDetector();
162 
163     private final ClasspathCache classpathCache = new ClasspathCache();
164 
165     /**
166      * Note: use the legacy system property <em>disableXmlReport</em> set to {@code true} to disable the report.
167      */
168     @Parameter
169     private SurefireStatelessReporter statelessTestsetReporter;
170 
171     @Parameter
172     private SurefireConsoleOutputReporter consoleOutputReporter;
173 
174     @Parameter
175     private SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter;
176 
177     /**
178      * Information about this plugin, mainly used to lookup this plugin's configuration from the currently executing
179      * project.
180      *
181      * @since 2.12
182      */
183     @Parameter( defaultValue = "${plugin}", readonly = true, required = true )
184     private PluginDescriptor pluginDescriptor;
185 
186     /**
187      * Set this to "true" to skip running tests, but still compile them. Its use is NOT RECOMMENDED, but quite
188      * convenient on occasion.<br>
189      * Failsafe plugin deprecated the parameter {@code skipTests} and the parameter will be removed in
190      * <i>Failsafe 3.0.0</i> as it is a source of conflicts between Failsafe and Surefire plugin.
191      *
192      * @since 2.4
193      */
194     @Parameter( property = "skipTests", defaultValue = "false" )
195     protected boolean skipTests;
196 
197     /**
198      * This old parameter is just like {@code skipTests}, but bound to the old property "maven.test.skip.exec".
199      *
200      * @since 2.3
201      * @deprecated Use skipTests instead.
202      */
203     @Deprecated
204     @Parameter( property = "maven.test.skip.exec" )
205     protected boolean skipExec;
206 
207     /**
208      * Set this to "true" to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable it using
209      * the "maven.test.skip" property, because maven.test.skip disables both running the tests and compiling the tests.
210      * Consider using the {@code skipTests} parameter instead.
211      */
212     @Parameter( property = "maven.test.skip", defaultValue = "false" )
213     protected boolean skip;
214 
215     /**
216      * The Maven Project Object.
217      */
218     @Parameter( defaultValue = "${project}", required = true, readonly = true )
219     private MavenProject project;
220 
221     /**
222      * The base directory of the project being tested. This can be obtained in your integration test via
223      * System.getProperty("basedir").
224      */
225     @Parameter( defaultValue = "${basedir}", readonly = true, required = true )
226     protected File basedir;
227 
228     /**
229      * The directory containing generated test classes of the project being tested. This will be included at the
230      * beginning of the test classpath. *
231      */
232     @Parameter( defaultValue = "${project.build.testOutputDirectory}" )
233     protected File testClassesDirectory;
234 
235     /**
236      * List of dependencies to exclude from the test classpath. Each dependency string must follow the format
237      * <i>groupId:artifactId</i>. For example: <i>org.acme:project-a</i>
238      *
239      * @since 2.6
240      */
241     @Parameter( property = "maven.test.dependency.excludes" )
242     private String[] classpathDependencyExcludes;
243 
244     /**
245      * A dependency scope to exclude from the test classpath. The scope should be one of the scopes defined by
246      * org.apache.maven.artifact.Artifact. This includes the following:
247      * <br>
248      * <ul>
249      * <li><i>compile</i> - system, provided, compile
250      * <li><i>runtime</i> - compile, runtime
251      * <li><i>compile+runtime</i> - system, provided, compile, runtime
252      * <li><i>runtime+system</i> - system, compile, runtime
253      * <li><i>test</i> - system, provided, compile, runtime, test
254      * </ul>
255      *
256      * @since 2.6
257      */
258     @Parameter( defaultValue = "" )
259     private String classpathDependencyScopeExclude;
260 
261     /**
262      * Additional elements to be appended to the classpath.
263      *
264      * @since 2.4
265      */
266     @Parameter( property = "maven.test.additionalClasspath" )
267     private String[] additionalClasspathElements;
268 
269     /**
270      * The test source directory containing test class sources.
271      *
272      * @since 2.2
273      */
274     @Parameter( defaultValue = "${project.build.testSourceDirectory}", required = true )
275     private File testSourceDirectory;
276 
277     /**
278      * A list of &lt;exclude&gt; elements specifying the tests (by pattern) that should be excluded in testing. When not
279      * specified and when the {@code test} parameter is not specified, the default excludes will be <br>
280      * <pre><code>
281      * {@literal <excludes>}
282      *     {@literal <exclude>}**{@literal /}*$*{@literal </exclude>}
283      * {@literal </excludes>}
284      * </code></pre>
285      * (which excludes all inner classes).
286      * <br>
287      * This parameter is ignored if the TestNG {@code suiteXmlFiles} parameter is specified.
288      * <br>
289      * Each exclude item may also contain a comma-separated sub-list of items, which will be treated as multiple
290      * &nbsp;&lt;exclude&gt; entries.<br>
291      * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
292      * <pre><code>
293      * {@literal <exclude>}%regex[pkg.*Slow.*.class], Unstable*{@literal </exclude>}
294      * </code></pre>
295      * <br>
296      * <b>Notice that</b> these values are relative to the directory containing generated test classes of the project
297      * being tested. This directory is declared by the parameter {@code testClassesDirectory} which defaults
298      * to the POM property <code>${project.build.testOutputDirectory}</code>, typically
299      * <code>{@literal src/test/java}</code> unless overridden.
300      */
301     @Parameter
302     // TODO use regex for fully qualified class names in 3.0 and change the filtering abilities
303     private List<String> excludes;
304 
305     /**
306      * ArtifactRepository of the localRepository. To obtain the directory of localRepository in unit tests use
307      * System.getProperty("localRepository").
308      */
309     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
310     private ArtifactRepository localRepository;
311 
312     /**
313      * List of System properties to pass to the JUnit tests.
314      *
315      * @deprecated Use systemPropertyVariables instead.
316      */
317     @Deprecated
318     @Parameter
319     private Properties systemProperties;
320 
321     /**
322      * List of System properties to pass to the JUnit tests.
323      *
324      * @since 2.5
325      */
326     @Parameter
327     private Map<String, String> systemPropertyVariables;
328 
329     /**
330      * List of System properties, loaded from a file, to pass to the JUnit tests.
331      *
332      * @since 2.8.2
333      */
334     @Parameter
335     private File systemPropertiesFile;
336 
337     /**
338      * List of properties for configuring all TestNG related configurations. This is the new preferred method of
339      * configuring TestNG.
340      *
341      * @since 2.4
342      */
343     @Parameter
344     private Properties properties;
345 
346     /**
347      * Map of plugin artifacts.
348      */
349     @Parameter( property = "plugin.artifactMap", required = true, readonly = true )
350     private Map<String, Artifact> pluginArtifactMap;
351 
352     /**
353      * Map of project artifacts.
354      */
355     @Parameter( property = "project.artifactMap", readonly = true, required = true )
356     private Map<String, Artifact> projectArtifactMap;
357 
358     /**
359      * Add custom text into report filename: TEST-testClassName-reportNameSuffix.xml,
360      * testClassName-reportNameSuffix.txt and testClassName-reportNameSuffix-output.txt.
361      * File TEST-testClassName-reportNameSuffix.xml has changed attributes 'testsuite'--'name'
362      * and 'testcase'--'classname' - reportNameSuffix is added to the attribute value.
363      */
364     @Parameter( property = "surefire.reportNameSuffix", defaultValue = "" )
365     private String reportNameSuffix;
366 
367     /**
368      * Set this to "true" to redirect the unit test standard output to a file (found in
369      * reportsDirectory/testName-output.txt).
370      *
371      * @since 2.3
372      */
373     @Parameter( property = "maven.test.redirectTestOutputToFile", defaultValue = "false" )
374     private boolean redirectTestOutputToFile;
375 
376     /**
377      * Set this to "true" to cause a failure if there are no tests to run. Defaults to "false".
378      *
379      * @since 2.4
380      */
381     @Parameter( property = "failIfNoTests" )
382     private Boolean failIfNoTests;
383 
384     /**
385      * <strong>DEPRECATED</strong> since version 2.14. Use {@code forkCount} and {@code reuseForks} instead.
386      * <br>
387      * <br>
388      * Option to specify the forking mode. Can be {@code never}, {@code once}, {@code always}, {@code perthread}.<br>
389      * The {@code none} and {@code pertest} are also accepted for backwards compatibility.<br>
390      * The {@code always} forks for each test-class.<br>
391      * The {@code perthread} creates the number of parallel forks specified by {@code threadCount}, where each forked
392      * JVM is executing one test-class. See also the parameter {@code reuseForks} for the lifetime of JVM.
393      *
394      * @since 2.1
395      */
396     @Parameter( property = "forkMode", defaultValue = "once" )
397     private String forkMode;
398 
399     /**
400      * Relative path to <i>temporary-surefire-boot</i> directory containing internal Surefire temporary files.
401      * <br>
402      * The <i>temporary-surefire-boot</i> directory is <i>project.build.directory</i> on most platforms or
403      * <i>system default temporary-directory</i> specified by the system property {@code java.io.tmpdir}
404      * on Windows (see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1400">SUREFIRE-1400</a>).
405      * <br>
406      * It is deleted after the test set has completed.
407      *
408      * @since 2.20
409      */
410     @Parameter( property = "tempDir", defaultValue = "surefire" )
411     private String tempDir;
412 
413     /**
414      * Option to specify the jvm (or path to the java executable) to use with the forking options. For the default, the
415      * jvm will be a new instance of the same VM as the one used to run Maven. JVM settings are not inherited from
416      * MAVEN_OPTS.
417      *
418      * @since 2.1
419      */
420     @Parameter( property = "jvm" )
421     private String jvm;
422 
423     /**
424      * Arbitrary JVM options to set on the command line.
425      * <br>
426      * <br>
427      * Since the Version 2.17 using an alternate syntax for {@code argLine}, <b>@{...}</b> allows late replacement
428      * of properties when the plugin is executed, so properties that have been modified by other plugins will be picked
429      * up correctly.
430      * See the Frequently Asked Questions page with more details:<br>
431      * <a href="http://maven.apache.org/surefire/maven-surefire-plugin/faq.html">
432      *     http://maven.apache.org/surefire/maven-surefire-plugin/faq.html</a>;
433      * <br>
434      * <a href="http://maven.apache.org/surefire/maven-failsafe-plugin/faq.html">
435      *     http://maven.apache.org/surefire/maven-failsafe-plugin/faq.html</a>;
436      *
437      * @since 2.1
438      */
439     @Parameter( property = "argLine" )
440     private String argLine;
441 
442     /**
443      * Additional environment variables to set on the command line.
444      *
445      * @since 2.1.3
446      */
447     @Parameter
448     private Map<String, String> environmentVariables = new HashMap<>();
449 
450     /**
451      * Command line working directory.
452      *
453      * @since 2.1.3
454      */
455     @Parameter( property = "basedir" )
456     private File workingDirectory;
457 
458     /**
459      * When false it makes tests run using the standard classloader delegation instead of the default Maven isolated
460      * classloader. Only used when forking ({@code forkMode} is not {@code none}).<br>
461      * Setting it to false helps with some problems caused by conflicts between xml parsers in the classpath and the
462      * Java 5 provider parser.
463      *
464      * @since 2.1
465      */
466     @Parameter( property = "childDelegation", defaultValue = "false" )
467     private boolean childDelegation;
468 
469     /**
470      * (TestNG/JUnit47 provider with JUnit4.8+ only) Groups for this test. Only classes/methods/etc decorated with one
471      * of the groups specified here will be included in test run, if specified.<br>
472      * For JUnit, this parameter forces the use of the 4.7 provider<br>
473      * This parameter is ignored if the {@code suiteXmlFiles} parameter is specified.<br>
474      * Since version 2.18.1 and JUnit 4.12, the {@code @Category} annotation type is automatically inherited from
475      * superclasses, see {@code @java.lang.annotation.Inherited}. Make sure that test class inheritance still makes
476      * sense together with {@code @Category} annotation of the JUnit 4.12 or higher appeared in superclass.
477      *
478      * @since 2.2
479      */
480     @Parameter( property = "groups" )
481     private String groups;
482 
483     /**
484      * (TestNG/JUnit47 provider with JUnit4.8+ only) Excluded groups. Any methods/classes/etc with one of the groups
485      * specified in this list will specifically not be run.<br>
486      * For JUnit, this parameter forces the use of the 4.7 provider.<br>
487      * This parameter is ignored if the {@code suiteXmlFiles} parameter is specified.<br>
488      * Since version 2.18.1 and JUnit 4.12, the {@code @Category} annotation type is automatically inherited from
489      * superclasses, see {@code @java.lang.annotation.Inherited}. Make sure that test class inheritance still makes
490      * sense together with {@code @Category} annotation of the JUnit 4.12 or higher appeared in superclass.
491      *
492      * @since 2.2
493      */
494     @Parameter( property = "excludedGroups" )
495     private String excludedGroups;
496 
497     /**
498      * Allows you to specify the name of the JUnit artifact. If not set, {@code junit:junit} will be used.
499      *
500      * @since 2.3.1
501      */
502     @Parameter( property = "junitArtifactName", defaultValue = "junit:junit" )
503     private String junitArtifactName;
504 
505     /**
506      * Allows you to specify the name of the TestNG artifact. If not set, {@code org.testng:testng} will be used.
507      *
508      * @since 2.3.1
509      */
510     @Parameter( property = "testNGArtifactName", defaultValue = "org.testng:testng" )
511     private String testNGArtifactName;
512 
513     /**
514      * (TestNG/JUnit 4.7 provider) The attribute thread-count allows you to specify how many threads should be
515      * allocated for this execution. Only makes sense to use in conjunction with the {@code parallel} parameter.
516      *
517      * @since 2.2
518      */
519     @Parameter( property = "threadCount" )
520     private int threadCount;
521 
522     /**
523      * Option to specify the number of VMs to fork in parallel in order to execute the tests. When terminated with "C",
524      * the number part is multiplied with the number of CPU cores. Floating point value are only accepted together with
525      * "C". If set to "0", no VM is forked and all tests are executed within the main process.<br>
526      * <br>
527      * Example values: "1.5C", "4"<br>
528      * <br>
529      * The system properties and the {@code argLine} of the forked processes may contain the place holder string
530      * <code>${surefire.forkNumber}</code>, which is replaced with a fixed number for each of the parallel forks,
531      * ranging from <b>1</b> to the effective value of {@code forkCount} times the maximum number of parallel
532      * Surefire executions in maven parallel builds, i.e. the effective value of the <b>-T</b> command line
533      * argument of maven core.
534      *
535      * @since 2.14
536      */
537     @Parameter( property = "forkCount", defaultValue = "1" )
538     private String forkCount;
539 
540     /**
541      * Indicates if forked VMs can be reused. If set to "false", a new VM is forked for each test class to be executed.
542      * If set to "true", up to {@code forkCount} VMs will be forked and then reused to execute all tests.
543      *
544      * @since 2.13
545      */
546 
547     @Parameter( property = "reuseForks", defaultValue = "true" )
548     private boolean reuseForks;
549 
550     /**
551      * (JUnit 4.7 provider) Indicates that threadCount, threadCountSuites, threadCountClasses, threadCountMethods
552      * are per cpu core.
553      *
554      * @since 2.5
555      */
556     @Parameter( property = "perCoreThreadCount", defaultValue = "true" )
557     private boolean perCoreThreadCount;
558 
559     /**
560      * (JUnit 4.7 provider) Indicates that the thread pool will be unlimited. The {@code parallel} parameter and
561      * the actual number of classes/methods will decide. Setting this to "true" effectively disables
562      * {@code perCoreThreadCount} and {@code threadCount}. Defaults to "false".
563      *
564      * @since 2.5
565      */
566     @Parameter( property = "useUnlimitedThreads", defaultValue = "false" )
567     private boolean useUnlimitedThreads;
568 
569     /**
570      * (TestNG provider) When you use the parameter {@code parallel}, TestNG will try to run all your test methods
571      * in separate threads, except for methods that depend on each other, which will be run in the same thread in order
572      * to respect their order of execution.
573      * <br>
574      * (JUnit 4.7 provider) Supports values {@code classes}, {@code methods}, {@code both} to run
575      * in separate threads been controlled by {@code threadCount}.
576      * <br>
577      * <br>
578      * Since version 2.16 (JUnit 4.7 provider), the value {@code both} is <strong>DEPRECATED</strong>.
579      * Use {@code classesAndMethods} instead.
580      * <br>
581      * <br>
582      * Since version 2.16 (JUnit 4.7 provider), additional vales are available:
583      * <br>
584      * {@code suites}, {@code suitesAndClasses}, {@code suitesAndMethods}, {@code classesAndMethods}, {@code all}.
585      * <br>
586      * By default, Surefire does not execute tests in parallel. You can set the parameter {@code parallel} to
587      * {@code none} to explicitly disable parallel execution (e.g. when disabling parallel execution in special Maven
588      * profiles when executing coverage analysis).
589      *
590      * @since 2.2
591      */
592     @Parameter( property = "parallel" )
593     private String parallel;
594 
595     /**
596      * (JUnit 4.7 / provider only) The thread counts do not exceed the number of parallel suite, class runners and
597      * average number of methods per class if set to <strong>true</strong>.
598      * <br>
599      * True by default.
600      *
601      * @since 2.17
602      */
603     @Parameter( property = "parallelOptimized", defaultValue = "true" )
604     private boolean parallelOptimized;
605 
606     /**
607      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test suites, i.e.:
608      * <ul>
609      *  <li>number of concurrent suites if {@code threadCount} is 0 or unspecified</li>
610      *  <li>limited suites concurrency if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
611      *  <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
612      *  concurrency is computed from ratio. For instance {@code parallel=all} and the ratio between
613      *      {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is
614      *      <b>2</b>:3:5, there is 20% of {@code threadCount} which appeared in concurrent suites.</li>
615      * </ul>
616      *
617      * Only makes sense to use in conjunction with the {@code parallel} parameter.
618      * The default value <b>0</b> behaves same as unspecified one.
619      *
620      * @since 2.16
621      */
622     @Parameter( property = "threadCountSuites", defaultValue = "0" )
623     private int threadCountSuites;
624 
625     /**
626      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test classes, i.e.:
627      * <ul>
628      *  <li>number of concurrent classes if {@code threadCount} is 0 or unspecified</li>
629      *  <li>limited classes concurrency if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
630      *  <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
631      *  concurrency is computed from ratio. For instance {@code parallel=all} and the ratio between
632      *      {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is
633      *      2:<b>3</b>:5, there is 30% of {@code threadCount} in concurrent classes.</li>
634      *  <li>as in the previous case but without this leaf thread-count. Example: {@code parallel=suitesAndClasses},
635      *  {@code threadCount=16}, {@code threadCountSuites=5}, {@code threadCountClasses} is unspecified leaf, the number
636      *  of concurrent classes is varying from &gt;= 11 to 14 or 15. The {@code threadCountSuites} become
637      *  given number of threads.</li>
638      * </ul>
639      *
640      * Only makes sense to use in conjunction with the {@code parallel} parameter.
641      * The default value <b>0</b> behaves same as unspecified one.
642      *
643      * @since 2.16
644      */
645     @Parameter( property = "threadCountClasses", defaultValue = "0" )
646     private int threadCountClasses;
647 
648     /**
649      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test methods, i.e.:
650      * <ul>
651      * <li>number of concurrent methods if {@code threadCount} is 0 or unspecified</li>
652      * <li>limited concurrency of methods if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
653      * <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
654      * concurrency is computed from ratio. For instance parallel=all and the ratio between
655      * {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is 2:3:<b>5</b>,
656      * there is 50% of {@code threadCount} which appears in concurrent methods.</li>
657      * <li>as in the previous case but without this leaf thread-count. Example: {@code parallel=all},
658      * {@code threadCount=16}, {@code threadCountSuites=2}, {@code threadCountClasses=3}, but {@code threadCountMethods}
659      * is unspecified leaf, the number of concurrent methods is varying from &gt;= 11 to 14 or 15.
660      * The {@code threadCountSuites} and {@code threadCountClasses} become given number of threads.</li>
661      * </ul>
662      * Only makes sense to use in conjunction with the {@code parallel} parameter. The default value <b>0</b>
663      * behaves same as unspecified one.
664      *
665      * @since 2.16
666      */
667     @Parameter( property = "threadCountMethods", defaultValue = "0" )
668     private int threadCountMethods;
669 
670     /**
671      * Whether to trim the stack trace in the reports to just the lines within the test, or show the full trace.
672      *
673      * @since 2.2
674      */
675     @Parameter( property = "trimStackTrace", defaultValue = "true" )
676     private boolean trimStackTrace;
677 
678     /**
679      * The remote plugin repositories declared in the POM.
680      *
681      * @since 2.2
682      */
683     @Parameter( defaultValue = "${project.pluginArtifactRepositories}", readonly = true, required = true )
684     private List<ArtifactRepository> remoteRepositories;
685 
686     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
687     private List<ArtifactRepository> projectRemoteRepositories;
688 
689     /**
690      * Flag to disable the generation of report files in xml format.
691      * Deprecated since 3.0.0-M4.
692      * Instead use <em>disable</em> within {@code statelessTestsetReporter} since of 3.0.0-M6.
693      * @since 2.2
694      */
695     @Deprecated // todo make readonly to handle system property
696     @Parameter( property = "disableXmlReport", defaultValue = "false" )
697     private boolean disableXmlReport;
698 
699     /**
700      * By default, Surefire enables JVM assertions for the execution of your test cases. To disable the assertions, set
701      * this flag to "false".
702      *
703      * @since 2.3.1
704      */
705     @Parameter( property = "enableAssertions", defaultValue = "true" )
706     private boolean enableAssertions;
707 
708     /**
709      * The current build session instance.
710      */
711     @Parameter( defaultValue = "${session}", required = true, readonly = true )
712     private MavenSession session;
713 
714     @Component
715     private Logger logger;
716 
717     /**
718      * (TestNG only) Define the factory class used to create all test instances.
719      *
720      * @since 2.5
721      */
722     @Parameter( property = "objectFactory" )
723     private String objectFactory;
724 
725     /**
726      * Parallel Maven Execution.
727      */
728     @Parameter( defaultValue = "${session.parallel}", readonly = true )
729     private Boolean parallelMavenExecution;
730 
731     /**
732      * Read-only parameter with value of Maven property <i>project.build.directory</i>.
733      * @since 2.20
734      */
735     @Parameter( defaultValue = "${project.build.directory}", readonly = true, required = true )
736     private File projectBuildDirectory;
737 
738     /**
739      * List of dependencies to scan for test classes to include in the test run.
740      * The child elements of this element must be &lt;dependency&gt; elements, and the
741      * contents of each of these elements must be a string which follows the general form:
742      *
743      * <p>{@code groupId[:artifactId[:type[:classifier][:version]]]}</p>
744      *
745      * <p>The wildcard character <code>*</code> can be used within the sub parts of those composite identifiers to
746      * do glob-like pattern matching. The classifier may be omitted when matching dependencies without a classifier.</p>
747      *
748      * <p>Examples:</p>
749      *
750      * <ul>
751      *     <li>{@code group} or, equivalently, {@code group:*}</li>
752      *     <li>{@code g*p:*rtifac*}</li>
753      *     <li>{@code group:*:jar}</li>
754      *     <li>{@code group:artifact:*:1.0.0} (no classifier)</li>
755      *     <li>{@code group:*:test-jar:tests}</li>
756      *     <li>{@code *:artifact:*:*:1.0.0}</li>
757      * </ul>
758      *
759      * <p>Since version 2.22.0 you can scan for test classes from a project
760      * dependency of your multi-module project.</p>
761      *
762      * <p>In versions before 3.0.0-M4, only <code>groupId:artifactId</code> is supported.</p>
763      *
764      * @since 2.15
765      */
766     @Parameter( property = "dependenciesToScan" )
767     private String[] dependenciesToScan;
768 
769     /**
770      *
771      */
772     @Component
773     private ToolchainManager toolchainManager;
774 
775     @Component
776     private LocationManager locationManager;
777 
778     @Component
779     private RepositorySystem repositorySystem;
780 
781     @Component
782     private DependencyResolver dependencyResolver;
783 
784     private Artifact surefireBooterArtifact;
785 
786     private Toolchain toolchain;
787 
788     private int effectiveForkCount = -1;
789 
790     protected abstract String getPluginName();
791 
792     protected abstract int getRerunFailingTestsCount();
793 
794     @Override
795     public abstract List<String> getIncludes();
796 
797     public abstract File getIncludesFile();
798 
799     @Override
800     public abstract void setIncludes( List<String> includes );
801 
802     public abstract File getExcludesFile();
803 
804     /**
805      * Calls {@link #getSuiteXmlFiles()} as {@link List list}.
806      * Never returns <tt>null</tt>.
807      *
808      * @return list of TestNG suite XML files provided by MOJO
809      */
810     protected abstract List<File> suiteXmlFiles();
811 
812     /**
813      * @return {@code true} if {@link #getSuiteXmlFiles() suite-xml files array} is not empty.
814      */
815     protected abstract boolean hasSuiteXmlFiles();
816 
817     protected abstract String[] getExcludedEnvironmentVariables();
818 
819     public abstract File[] getSuiteXmlFiles();
820 
821     public abstract void setSuiteXmlFiles( File[] suiteXmlFiles );
822 
823     public abstract String getRunOrder();
824 
825     public abstract void setRunOrder( String runOrder );
826 
827     protected abstract void handleSummary( RunResult summary, Exception firstForkException )
828         throws MojoExecutionException, MojoFailureException;
829 
830     protected abstract boolean isSkipExecution();
831 
832     protected abstract String[] getDefaultIncludes();
833 
834     protected abstract String getReportSchemaLocation();
835 
836     protected abstract boolean useModulePath();
837 
838     protected abstract void setUseModulePath( boolean useModulePath );
839 
840     protected abstract String getEnableProcessChecker();
841 
842     /**
843      * This plugin MOJO artifact.
844      *
845      * @return non-null plugin artifact
846      */
847     protected Artifact getMojoArtifact()
848     {
849         return getPluginDescriptor().getPluginArtifact();
850     }
851 
852     private String getDefaultExcludes()
853     {
854         return "**/*$*";
855     }
856 
857     private SurefireDependencyResolver surefireDependencyResolver;
858 
859     private TestListResolver specificTests;
860 
861     private TestListResolver includedExcludedTests;
862 
863     private List<CommandLineOption> cli;
864 
865     private volatile PluginConsoleLogger consoleLogger;
866 
867     @Override
868     public void execute()
869         throws MojoExecutionException, MojoFailureException
870     {
871         cli = commandLineOptions();
872         // Stuff that should have been final
873         setupStuff();
874 
875         if ( verifyParameters() && !hasExecutedBefore() )
876         {
877             DefaultScanResult scan = scanForTestClasses();
878             if ( !hasSuiteXmlFiles() && scan.isEmpty() )
879             {
880                 if ( getEffectiveFailIfNoTests() )
881                 {
882                     throw new MojoFailureException(
883                         "No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)" );
884                 }
885                 handleSummary( noTestsRun(), null );
886                 return;
887             }
888             logReportsDirectory();
889             executeAfterPreconditionsChecked( scan );
890         }
891     }
892 
893     void setLogger( Logger logger )
894     {
895         this.logger = logger;
896     }
897 
898     @Nonnull
899     protected final PluginConsoleLogger getConsoleLogger()
900     {
901         if ( consoleLogger == null )
902         {
903             synchronized ( this )
904             {
905                 if ( consoleLogger == null )
906                 {
907                     consoleLogger = new PluginConsoleLogger( logger );
908                 }
909             }
910         }
911         return consoleLogger;
912     }
913 
914     private void setupStuff()
915     {
916         surefireDependencyResolver = new SurefireDependencyResolver( getRepositorySystem(),
917                 getConsoleLogger(), getLocalRepository(),
918                 getRemoteRepositories(),
919                 getProjectRemoteRepositories(),
920                 getPluginName(), getDependencyResolver() );
921 
922         surefireBooterArtifact = getBooterArtifact();
923         if ( surefireBooterArtifact == null )
924         {
925             throw new RuntimeException( "Unable to locate surefire-booter in the list of plugin artifacts" );
926         }
927 
928         if ( getToolchainManager() != null )
929         {
930             toolchain = getToolchainManager().getToolchainFromBuildContext( "jdk", getSession() );
931         }
932     }
933 
934     @Nonnull
935     private DefaultScanResult scanForTestClasses()
936         throws MojoFailureException
937     {
938         DefaultScanResult scan = scanDirectories();
939         DefaultScanResult scanDeps = scanDependencies();
940         return scan.append( scanDeps );
941     }
942 
943     private DefaultScanResult scanDirectories()
944         throws MojoFailureException
945     {
946         DirectoryScanner scanner = new DirectoryScanner( getTestClassesDirectory(), getIncludedAndExcludedTests() );
947         return scanner.scan();
948     }
949 
950     @SuppressWarnings( "unchecked" )
951     List<Artifact> getProjectTestArtifacts()
952     {
953         return project.getTestArtifacts();
954     }
955 
956     DefaultScanResult scanDependencies() throws MojoFailureException
957     {
958         if ( getDependenciesToScan() == null )
959         {
960             return null;
961         }
962         else
963         {
964             try
965             {
966                 DefaultScanResult result = null;
967 
968                 List<Artifact> dependenciesToScan =
969                         filter( getProjectTestArtifacts(), asList( getDependenciesToScan() ) );
970 
971                 for ( Artifact artifact : dependenciesToScan )
972                 {
973                     String type = artifact.getType();
974                     File out = artifact.getFile();
975                     if ( out == null || !out.exists()
976                             || !( "jar".equals( type ) || out.isDirectory() || out.getName().endsWith( ".jar" ) ) )
977                     {
978                         continue;
979                     }
980 
981                     if ( out.isFile() )
982                     {
983                         DependencyScanner scanner =
984                                 new DependencyScanner( singletonList( out ), getIncludedAndExcludedTests() );
985                         result = result == null ? scanner.scan() : result.append( scanner.scan() );
986                     }
987                     else if ( out.isDirectory() )
988                     {
989                         DirectoryScanner scanner =
990                                 new DirectoryScanner( out, getIncludedAndExcludedTests() );
991                         result = result == null ? scanner.scan() : result.append( scanner.scan() );
992                     }
993                 }
994 
995                 return result;
996             }
997             catch ( Exception e )
998             {
999                 throw new MojoFailureException( e.getLocalizedMessage(), e );
1000             }
1001         }
1002     }
1003 
1004     boolean verifyParameters()
1005         throws MojoFailureException, MojoExecutionException
1006     {
1007         setProperties( new SurefireProperties( getProperties() ) );
1008         if ( isSkipExecution() )
1009         {
1010             getConsoleLogger().info( "Tests are skipped." );
1011             return false;
1012         }
1013 
1014         String jvmToUse = getJvm();
1015         if ( toolchain != null )
1016         {
1017             getConsoleLogger().info( "Toolchain in maven-" + getPluginName() + "-plugin: " + toolchain );
1018             if ( jvmToUse != null )
1019             {
1020                 getConsoleLogger().warning( "Toolchains are ignored, 'jvm' parameter is set to " + jvmToUse );
1021             }
1022         }
1023 
1024         if ( !getTestClassesDirectory().exists()
1025             && ( getDependenciesToScan() == null || getDependenciesToScan().length == 0 ) )
1026         {
1027             if ( TRUE.equals( getFailIfNoTests() ) )
1028             {
1029                 throw new MojoFailureException( "No tests to run!" );
1030             }
1031             getConsoleLogger().info( "No tests to run." );
1032         }
1033         else
1034         {
1035             ensureEnableProcessChecker();
1036             convertDeprecatedForkMode();
1037             ensureWorkingDirectoryExists();
1038             ensureParallelRunningCompatibility();
1039             ensureThreadCountWithPerThread();
1040             warnIfUselessUseSystemClassLoaderParameter();
1041             warnIfDefunctGroupsCombinations();
1042             warnIfRerunClashes();
1043             warnIfWrongShutdownValue();
1044             warnIfNotApplicableSkipAfterFailureCount();
1045             warnIfIllegalTempDir();
1046         }
1047         return true;
1048     }
1049 
1050     private void executeAfterPreconditionsChecked( @Nonnull DefaultScanResult scanResult )
1051         throws MojoExecutionException, MojoFailureException
1052     {
1053         TestClassPath testClasspath = generateTestClasspath();
1054         List<ProviderInfo> providers = createProviders( testClasspath );
1055 
1056         RunResult current = noTestsRun();
1057 
1058         Exception firstForkException = null;
1059         for ( ProviderInfo provider : providers )
1060         {
1061             try
1062             {
1063                 current = current.aggregate( executeProvider( provider, scanResult, testClasspath ) );
1064             }
1065             catch ( SurefireBooterForkException | SurefireExecutionException | TestSetFailedException e )
1066             {
1067                 if ( firstForkException == null )
1068                 {
1069                     firstForkException = e;
1070                 }
1071             }
1072         }
1073 
1074         if ( firstForkException != null )
1075         {
1076             current = failure( current, firstForkException );
1077         }
1078 
1079         handleSummary( current, firstForkException );
1080     }
1081 
1082     protected List<ProviderInfo> createProviders( TestClassPath testClasspath )
1083         throws MojoExecutionException
1084     {
1085         Artifact junitDepArtifact = getJunitDepArtifact();
1086         return new ProviderList( new DynamicProviderInfo( null ),
1087                               new TestNgProviderInfo( getTestNgArtifact() ),
1088                               new JUnitPlatformProviderInfo( getJunitPlatformArtifact(), testClasspath ),
1089                               new JUnitCoreProviderInfo( getJunitArtifact(), junitDepArtifact ),
1090                               new JUnit4ProviderInfo( getJunitArtifact(), junitDepArtifact ),
1091                               new JUnit3ProviderInfo() )
1092             .resolve();
1093     }
1094 
1095     private SurefireProperties setupProperties()
1096     {
1097         SurefireProperties sysProps = null;
1098         try
1099         {
1100             sysProps = SurefireProperties.loadProperties( systemPropertiesFile );
1101         }
1102         catch ( IOException e )
1103         {
1104             String msg = "The system property file '" + systemPropertiesFile.getAbsolutePath() + "' can't be read.";
1105             if ( getConsoleLogger().isDebugEnabled() )
1106             {
1107                 getConsoleLogger().debug( msg, e );
1108             }
1109             else
1110             {
1111                 getConsoleLogger().warning( msg );
1112             }
1113         }
1114 
1115         SurefireProperties result =
1116             SurefireProperties.calculateEffectiveProperties( getSystemProperties(), getSystemPropertyVariables(),
1117                                                              getUserProperties(), sysProps );
1118 
1119         result.setProperty( "basedir", getBasedir().getAbsolutePath() );
1120         result.setProperty( "user.dir", getWorkingDirectory().getAbsolutePath() );
1121         result.setProperty( "localRepository", getLocalRepository().getBasedir() );
1122         if ( isForking() )
1123         {
1124             for ( Object o : result.propertiesThatCannotBeSetASystemProperties() )
1125             {
1126                 if ( getArgLine() == null || !getArgLine().contains( "-D" + o + "=" ) )
1127                 {
1128                     getConsoleLogger().warning( o + " cannot be set as system property, use <argLine>-D"
1129                                                         + o + "=...</argLine> instead"
1130                     );
1131                 }
1132             }
1133             for ( Object systemPropertyMatchingArgLine : systemPropertiesMatchingArgLine( result ) )
1134             {
1135                 getConsoleLogger()
1136                         .warning( "The system property "
1137                                           + systemPropertyMatchingArgLine
1138                                           + " is configured twice! "
1139                                           + "The property appears in <argLine/> and any of <systemPropertyVariables/>, "
1140                                           + "<systemProperties/> or user property."
1141                         );
1142             }
1143         }
1144         if ( getConsoleLogger().isDebugEnabled() )
1145         {
1146             showToLog( result, getConsoleLogger() );
1147         }
1148         return result;
1149     }
1150 
1151     private Set<Object> systemPropertiesMatchingArgLine( SurefireProperties result )
1152     {
1153         Set<Object> intersection = new HashSet<>();
1154         if ( isNotBlank( getArgLine() ) )
1155         {
1156             for ( Object systemProperty : result.getStringKeySet() )
1157             {
1158                 if ( getArgLine().contains( "-D" + systemProperty + "=" ) )
1159                 {
1160                     intersection.add( systemProperty );
1161                 }
1162             }
1163 
1164             Set<Object> ignored = result.propertiesThatCannotBeSetASystemProperties();
1165             intersection.removeAll( ignored );
1166         }
1167         return intersection;
1168     }
1169 
1170     private void showToLog( SurefireProperties props, ConsoleLogger log )
1171     {
1172         for ( Object key : props.getStringKeySet() )
1173         {
1174             String value = props.getProperty( (String) key );
1175             log.debug( "Setting system property [" + key + "]=[" + value + "]" );
1176         }
1177     }
1178 
1179     @Nonnull
1180     private RunResult executeProvider( @Nonnull ProviderInfo provider, @Nonnull DefaultScanResult scanResult,
1181                                        @Nonnull TestClassPath testClasspathWrapper )
1182         throws MojoExecutionException, MojoFailureException, SurefireExecutionException, SurefireBooterForkException,
1183         TestSetFailedException
1184     {
1185         SurefireProperties effectiveProperties = setupProperties();
1186         ClassLoaderConfiguration classLoaderConfiguration = getClassLoaderConfiguration();
1187         provider.addProviderProperties();
1188         RunOrderParameters runOrderParameters =
1189             new RunOrderParameters( getRunOrder(), getStatisticsFile( getConfigChecksum() ) );
1190 
1191         Platform platform = PLATFORM.withJdkExecAttributesForTests( getEffectiveJvm() );
1192         if ( isNotForking() )
1193         {
1194             createCopyAndReplaceForkNumPlaceholder( effectiveProperties, 1 ).copyToSystemProperties();
1195 
1196             InPluginVMSurefireStarter surefireStarter = createInprocessStarter( provider, classLoaderConfiguration,
1197                     runOrderParameters, scanResult, platform, testClasspathWrapper );
1198             return surefireStarter.runSuitesInProcess( scanResult );
1199         }
1200         else
1201         {
1202             ForkConfiguration forkConfiguration = createForkConfiguration( platform );
1203             if ( getConsoleLogger().isDebugEnabled() )
1204             {
1205                 showMap( getEnvironmentVariables(), "environment variable" );
1206                 showArray( getExcludedEnvironmentVariables(), "excluded environment variable" );
1207             }
1208 
1209             Properties originalSystemProperties = (Properties) System.getProperties().clone();
1210             ForkStarter forkStarter = null;
1211             try
1212             {
1213                 forkStarter = createForkStarter( provider, forkConfiguration, classLoaderConfiguration,
1214                                                        runOrderParameters, getConsoleLogger(), scanResult, platform,
1215                                                        testClasspathWrapper );
1216 
1217                 return forkStarter.run( effectiveProperties, scanResult );
1218             }
1219             catch ( SurefireBooterForkException e )
1220             {
1221                 forkStarter.killOrphanForks();
1222                 throw e;
1223             }
1224             finally
1225             {
1226                 System.setProperties( originalSystemProperties );
1227                 cleanupForkConfiguration( forkConfiguration );
1228             }
1229         }
1230     }
1231 
1232     public static SurefireProperties createCopyAndReplaceForkNumPlaceholder(
1233         SurefireProperties effectiveSystemProperties, int threadNumber )
1234     {
1235         SurefireProperties filteredProperties = new SurefireProperties( ( KeyValueSource) effectiveSystemProperties );
1236         for ( Entry<Object, Object> entry : effectiveSystemProperties.entrySet() )
1237         {
1238             if ( entry.getValue() instanceof String )
1239             {
1240                 String value = (String) entry.getValue();
1241                 filteredProperties.put( entry.getKey(), replaceThreadNumberPlaceholders( value, threadNumber ) );
1242             }
1243         }
1244         return filteredProperties;
1245     }
1246 
1247     protected void cleanupForkConfiguration( ForkConfiguration forkConfiguration )
1248     {
1249         if ( !getConsoleLogger().isDebugEnabled() && forkConfiguration != null )
1250         {
1251             File tempDirectory = forkConfiguration.getTempDirectory();
1252             try
1253             {
1254                 FileUtils.deleteDirectory( tempDirectory );
1255             }
1256             catch ( IOException e )
1257             {
1258                 getConsoleLogger()
1259                         .warning( "Could not delete temp directory " + tempDirectory + " because " + e.getMessage() );
1260             }
1261         }
1262     }
1263 
1264     protected void logReportsDirectory()
1265     {
1266         logDebugOrCliShowErrors(
1267             capitalizeFirstLetter( getPluginName() ) + " report directory: " + getReportsDirectory() );
1268     }
1269 
1270     public RepositorySystem getRepositorySystem()
1271     {
1272         return repositorySystem;
1273     }
1274 
1275     public void setRepositorySystem( RepositorySystem repositorySystem )
1276     {
1277         this.repositorySystem = repositorySystem;
1278     }
1279 
1280     public DependencyResolver getDependencyResolver()
1281     {
1282         return dependencyResolver;
1283     }
1284 
1285     public void setDependencyResolver( DependencyResolver dependencyResolver )
1286     {
1287         this.dependencyResolver = dependencyResolver;
1288     }
1289 
1290     private boolean existsModuleDescriptor()
1291     {
1292         return getModuleDescriptor().isFile();
1293     }
1294 
1295     private File getModuleDescriptor()
1296     {
1297         return new File( getClassesDirectory(), "module-info.class" );
1298     }
1299 
1300     private boolean canExecuteProviderWithModularPath( Platform platform )
1301     {
1302         return useModulePath()
1303                 && platform.getJdkExecAttributesForTests().isJava9AtLeast()
1304                 && existsModuleDescriptor();
1305     }
1306 
1307     /**
1308      * Converts old TestNG configuration parameters over to new properties based configuration
1309      * method. (if any are defined the old way)
1310      */
1311     private void convertTestNGParameters() throws MojoExecutionException
1312     {
1313         if ( this.getParallel() != null )
1314         {
1315             getProperties().setProperty( ProviderParameterNames.PARALLEL_PROP, this.getParallel() );
1316         }
1317         convertGroupParameters();
1318 
1319         if ( this.getThreadCount() > 0 )
1320         {
1321             getProperties().setProperty( ProviderParameterNames.THREADCOUNT_PROP,
1322                                          Integer.toString( this.getThreadCount() ) );
1323         }
1324         if ( this.getObjectFactory() != null )
1325         {
1326             getProperties().setProperty( "objectfactory", this.getObjectFactory() );
1327         }
1328         if ( this.getTestClassesDirectory() != null )
1329         {
1330             getProperties().setProperty( "testng.test.classpath", getTestClassesDirectory().getAbsolutePath() );
1331         }
1332 
1333         Artifact testNgArtifact = getTestNgArtifact();
1334         if ( testNgArtifact != null )
1335         {
1336             DefaultArtifactVersion defaultArtifactVersion = new DefaultArtifactVersion( testNgArtifact.getVersion() );
1337             getProperties().setProperty( "testng.configurator", getConfiguratorName( defaultArtifactVersion,
1338                                                                                            getConsoleLogger()
1339                     )
1340             );
1341         }
1342     }
1343 
1344     private static String getConfiguratorName( ArtifactVersion version, PluginConsoleLogger log )
1345         throws MojoExecutionException
1346     {
1347         try
1348         {
1349             VersionRange range = VersionRange.createFromVersionSpec( "[4.7,5.2)" );
1350             if ( range.containsVersion( version ) )
1351             {
1352                 return "org.apache.maven.surefire.testng.conf.TestNG4751Configurator";
1353             }
1354             range = VersionRange.createFromVersionSpec( "[5.2,5.3)" );
1355             if ( range.containsVersion( version ) )
1356             {
1357                 return "org.apache.maven.surefire.testng.conf.TestNG52Configurator";
1358             }
1359             range = VersionRange.createFromVersionSpec( "[5.3,5.10)" );
1360             if ( range.containsVersion( version ) )
1361             {
1362                 return "org.apache.maven.surefire.testng.conf.TestNGMapConfigurator";
1363             }
1364             range = VersionRange.createFromVersionSpec( "[5.10,5.13)" );
1365             if ( range.containsVersion( version ) )
1366             {
1367                 return "org.apache.maven.surefire.testng.conf.TestNG510Configurator";
1368             }
1369             range = VersionRange.createFromVersionSpec( "[5.13,5.14.1)" );
1370             if ( range.containsVersion( version ) )
1371             {
1372                 return "org.apache.maven.surefire.testng.conf.TestNG513Configurator";
1373             }
1374             range = VersionRange.createFromVersionSpec( "[5.14.1,5.14.3)" );
1375             if ( range.containsVersion( version ) )
1376             {
1377                 log.warning( "The 'reporter' or 'listener' may not work properly in TestNG 5.14.1 and 5.14.2." );
1378                 return "org.apache.maven.surefire.testng.conf.TestNG5141Configurator";
1379             }
1380             range = VersionRange.createFromVersionSpec( "[5.14.3,6.0)" );
1381             if ( range.containsVersion( version ) )
1382             {
1383                 if ( version.equals( new DefaultArtifactVersion( "[5.14.3,5.14.5]" ) ) )
1384                 {
1385                     throw new MojoExecutionException( "TestNG 5.14.3-5.14.5 is not supported. "
1386                             + "System dependency org.testng:guice missed path." );
1387                 }
1388                 return "org.apache.maven.surefire.testng.conf.TestNG5143Configurator";
1389             }
1390             range = VersionRange.createFromVersionSpec( "[6.0,)" );
1391             if ( range.containsVersion( version ) )
1392             {
1393                 return "org.apache.maven.surefire.testng.conf.TestNG60Configurator";
1394             }
1395 
1396             throw new MojoExecutionException( "Unknown TestNG version " + version );
1397         }
1398         catch ( InvalidVersionSpecificationException invsex )
1399         {
1400             throw new MojoExecutionException( "Bug in plugin. Please report it with the attached stacktrace", invsex );
1401         }
1402     }
1403 
1404 
1405     private void convertGroupParameters()
1406     {
1407         if ( this.getExcludedGroups() != null )
1408         {
1409             getProperties().setProperty( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP, this.getExcludedGroups() );
1410         }
1411         if ( this.getGroups() != null )
1412         {
1413             getProperties().setProperty( ProviderParameterNames.TESTNG_GROUPS_PROP, this.getGroups() );
1414         }
1415     }
1416 
1417     protected boolean isAnyConcurrencySelected()
1418     {
1419         return getParallel() != null && !getParallel().trim().isEmpty();
1420     }
1421 
1422     protected boolean isAnyGroupsSelected()
1423     {
1424         return this.getGroups() != null || this.getExcludedGroups() != null;
1425     }
1426 
1427     /**
1428      * Converts old JUnit configuration parameters over to new properties based configuration
1429      * method. (if any are defined the old way)
1430      */
1431     private void convertJunitCoreParameters() throws MojoExecutionException
1432     {
1433         checkThreadCountEntity( getThreadCountSuites(), "suites" );
1434         checkThreadCountEntity( getThreadCountClasses(), "classes" );
1435         checkThreadCountEntity( getThreadCountMethods(), "methods" );
1436 
1437         String usedParallel = ( getParallel() != null ) ? getParallel() : "none";
1438 
1439         if ( !"none".equals( usedParallel ) )
1440         {
1441             checkNonForkedThreads( parallel );
1442         }
1443 
1444         getProperties().setProperty( ProviderParameterNames.PARALLEL_PROP, usedParallel );
1445         getProperties().setProperty( ProviderParameterNames.THREADCOUNT_PROP, Integer.toString( getThreadCount() ) );
1446         getProperties().setProperty( "perCoreThreadCount", Boolean.toString( getPerCoreThreadCount() ) );
1447         getProperties().setProperty( "useUnlimitedThreads", Boolean.toString( getUseUnlimitedThreads() ) );
1448         getProperties().setProperty( ProviderParameterNames.THREADCOUNTSUITES_PROP,
1449                                      Integer.toString( getThreadCountSuites() ) );
1450         getProperties().setProperty( ProviderParameterNames.THREADCOUNTCLASSES_PROP,
1451                                      Integer.toString( getThreadCountClasses() ) );
1452         getProperties().setProperty( ProviderParameterNames.THREADCOUNTMETHODS_PROP,
1453                                      Integer.toString( getThreadCountMethods() ) );
1454         getProperties().setProperty( ProviderParameterNames.PARALLEL_TIMEOUT_PROP,
1455                                      Double.toString( getParallelTestsTimeoutInSeconds() ) );
1456         getProperties().setProperty( ProviderParameterNames.PARALLEL_TIMEOUTFORCED_PROP,
1457                                      Double.toString( getParallelTestsTimeoutForcedInSeconds() ) );
1458         getProperties().setProperty( ProviderParameterNames.PARALLEL_OPTIMIZE_PROP,
1459                                      Boolean.toString( isParallelOptimized() ) );
1460 
1461         String message = "parallel='" + usedParallel + '\''
1462             + ", perCoreThreadCount=" + getPerCoreThreadCount()
1463             + ", threadCount=" + getThreadCount()
1464             + ", useUnlimitedThreads=" + getUseUnlimitedThreads()
1465             + ", threadCountSuites=" + getThreadCountSuites()
1466             + ", threadCountClasses=" + getThreadCountClasses()
1467             + ", threadCountMethods=" + getThreadCountMethods()
1468             + ", parallelOptimized=" + isParallelOptimized();
1469 
1470         logDebugOrCliShowErrors( message );
1471     }
1472 
1473     private void checkNonForkedThreads( String parallel ) throws MojoExecutionException
1474     {
1475         if ( "suites".equals( parallel ) )
1476         {
1477             if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountSuites() > 0 ) )
1478             {
1479                 throw new MojoExecutionException(
1480                         "Use threadCount or threadCountSuites > 0 or useUnlimitedThreads=true for parallel='suites'" );
1481             }
1482             setThreadCountClasses( 0 );
1483             setThreadCountMethods( 0 );
1484         }
1485         else if ( "classes".equals( parallel ) )
1486         {
1487             if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountClasses() > 0 ) )
1488             {
1489                 throw new MojoExecutionException(
1490                         "Use threadCount or threadCountClasses > 0 or useUnlimitedThreads=true for parallel='classes'"
1491                       );
1492             }
1493             setThreadCountSuites( 0 );
1494             setThreadCountMethods( 0 );
1495         }
1496         else if ( "methods".equals( parallel ) )
1497         {
1498             if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountMethods() > 0 ) )
1499             {
1500                 throw new MojoExecutionException(
1501                         "Use threadCount or threadCountMethods > 0 or useUnlimitedThreads=true for parallel='methods'"
1502                       );
1503             }
1504             setThreadCountSuites( 0 );
1505             setThreadCountClasses( 0 );
1506         }
1507         else if ( "suitesAndClasses".equals( parallel ) )
1508         {
1509             if ( !( getUseUnlimitedThreads()
1510                     || onlyThreadCount()
1511                     || getThreadCountSuites() > 0 && getThreadCountClasses() > 0
1512                         && getThreadCount() == 0 && getThreadCountMethods() == 0
1513                     || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountClasses() > 0
1514                         && getThreadCountMethods() == 0
1515                     || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCount() > getThreadCountSuites()
1516                         && getThreadCountClasses() == 0 && getThreadCountMethods() == 0 ) )
1517             {
1518                 throw new MojoExecutionException( "Use useUnlimitedThreads=true, "
1519                         + "or only threadCount > 0, "
1520                         + "or (threadCountSuites > 0 and threadCountClasses > 0), "
1521                         + "or (threadCount > 0 and threadCountSuites > 0 and threadCountClasses > 0) "
1522                         + "or (threadCount > 0 and threadCountSuites > 0 and threadCount > threadCountSuites) "
1523                         + "for parallel='suitesAndClasses' or 'both'" );
1524             }
1525             setThreadCountMethods( 0 );
1526         }
1527         else if ( "suitesAndMethods".equals( parallel ) )
1528         {
1529             if ( !( getUseUnlimitedThreads()
1530                     || onlyThreadCount()
1531                     || getThreadCountSuites() > 0 && getThreadCountMethods() > 0
1532                         && getThreadCount() == 0 && getThreadCountClasses() == 0
1533                     || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountMethods() > 0
1534                         && getThreadCountClasses() == 0
1535                     || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCount() > getThreadCountSuites()
1536                         && getThreadCountClasses() == 0 && getThreadCountMethods() == 0 ) )
1537             {
1538                 throw new MojoExecutionException( "Use useUnlimitedThreads=true, "
1539                         + "or only threadCount > 0, "
1540                         + "or (threadCountSuites > 0 and threadCountMethods > 0), "
1541                         + "or (threadCount > 0 and threadCountSuites > 0 and threadCountMethods > 0), "
1542                         + "or (threadCount > 0 and threadCountSuites > 0 and threadCount > threadCountSuites) "
1543                         + "for parallel='suitesAndMethods'" );
1544             }
1545             setThreadCountClasses( 0 );
1546         }
1547         else if ( "both".equals( parallel ) || "classesAndMethods".equals( parallel ) )
1548         {
1549             if ( !( getUseUnlimitedThreads()
1550                     || onlyThreadCount()
1551                     || getThreadCountClasses() > 0 && getThreadCountMethods() > 0
1552                         && getThreadCount() == 0 && getThreadCountSuites() == 0
1553                     || getThreadCount() > 0 && getThreadCountClasses() > 0 && getThreadCountMethods() > 0
1554                         && getThreadCountSuites() == 0
1555                     || getThreadCount() > 0 && getThreadCountClasses() > 0 && getThreadCount() > getThreadCountClasses()
1556                         && getThreadCountSuites() == 0 && getThreadCountMethods() == 0 ) )
1557             {
1558                 throw new MojoExecutionException( "Use useUnlimitedThreads=true, "
1559                         + "or only threadCount > 0, "
1560                         + "or (threadCountClasses > 0 and threadCountMethods > 0), "
1561                         + "or (threadCount > 0 and threadCountClasses > 0 and threadCountMethods > 0), "
1562                         + "or (threadCount > 0 and threadCountClasses > 0 and threadCount > threadCountClasses) "
1563                         + "for parallel='both' or parallel='classesAndMethods'" );
1564             }
1565             setThreadCountSuites( 0 );
1566         }
1567         else if ( "all".equals( parallel ) )
1568         {
1569             if ( !( getUseUnlimitedThreads()
1570                     || onlyThreadCount()
1571                     || getThreadCountSuites() > 0 && getThreadCountClasses() > 0 && getThreadCountMethods() > 0
1572                     || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountClasses() > 0
1573                         && getThreadCountMethods() == 0
1574                         && getThreadCount() > ( getThreadCountSuites() + getThreadCountClasses() ) ) )
1575             {
1576                 throw new MojoExecutionException( "Use useUnlimitedThreads=true, "
1577                         + "or only threadCount > 0, "
1578                         + "or (threadCountSuites > 0 and threadCountClasses > 0 and threadCountMethods > 0), "
1579                         + "or every thread-count is specified, "
1580                         + "or (threadCount > 0 and threadCountSuites > 0 and threadCountClasses > 0 "
1581                         + "and threadCount > threadCountSuites + threadCountClasses) "
1582                         + "for parallel='all'" );
1583             }
1584         }
1585         else
1586         {
1587             throw new MojoExecutionException( "Illegal parallel='" + parallel + "'" );
1588         }
1589     }
1590 
1591     private boolean onlyThreadCount()
1592     {
1593         return getThreadCount() > 0 && getThreadCountSuites() == 0 && getThreadCountClasses() == 0
1594                 && getThreadCountMethods() == 0;
1595     }
1596 
1597     private static void checkThreadCountEntity( int count, String entity )
1598         throws MojoExecutionException
1599     {
1600         if ( count < 0 )
1601         {
1602             throw new MojoExecutionException(
1603                     "parallel maven execution does not allow negative thread-count" + entity );
1604         }
1605     }
1606 
1607     private boolean isJunit47Compatible( Artifact artifact )
1608     {
1609         return isWithinVersionSpec( artifact, "[4.7,)" );
1610     }
1611 
1612     private boolean isAnyJunit4( Artifact artifact )
1613     {
1614         return isWithinVersionSpec( artifact, "[4.0,)" );
1615     }
1616 
1617     private static boolean isForkModeNever( String forkMode )
1618     {
1619         return FORK_NEVER.equals( forkMode );
1620     }
1621 
1622     protected boolean isForking()
1623     {
1624         return 0 < getEffectiveForkCount();
1625     }
1626 
1627     String getEffectiveForkMode()
1628     {
1629         String forkMode1 = getForkMode();
1630 
1631         if ( toolchain != null && isForkModeNever( forkMode1 ) )
1632         {
1633             return FORK_ONCE;
1634         }
1635 
1636         return getEffectiveForkMode( forkMode1 );
1637     }
1638 
1639     private List<RunOrder> getRunOrders()
1640     {
1641         String runOrderString = getRunOrder();
1642         RunOrder[] runOrder = runOrderString == null ? RunOrder.DEFAULT : RunOrder.valueOfMulti( runOrderString );
1643         return asList( runOrder );
1644     }
1645 
1646     private boolean requiresRunHistory()
1647     {
1648         final List<RunOrder> runOrders = getRunOrders();
1649         return runOrders.contains( RunOrder.BALANCED ) || runOrders.contains( RunOrder.FAILEDFIRST );
1650     }
1651 
1652     private boolean getEffectiveFailIfNoTests()
1653     {
1654         if ( isSpecificTestSpecified() )
1655         {
1656             if ( getFailIfNoSpecifiedTests() != null )
1657             {
1658                 return getFailIfNoSpecifiedTests();
1659             }
1660             else if ( getFailIfNoTests() != null )
1661             {
1662                 return getFailIfNoTests();
1663             }
1664             else
1665             {
1666                 return true;
1667             }
1668         }
1669         else
1670         {
1671             return getFailIfNoTests() != null && getFailIfNoTests();
1672         }
1673     }
1674 
1675     private ProviderConfiguration createProviderConfiguration( RunOrderParameters runOrderParameters )
1676         throws MojoExecutionException, MojoFailureException
1677     {
1678         final ReporterConfiguration reporterConfiguration =
1679             new ReporterConfiguration( getReportsDirectory(), isTrimStackTrace() );
1680 
1681         final Artifact testNgArtifact = getTestNgArtifact();
1682         final boolean isTestNg = testNgArtifact != null;
1683         final TestArtifactInfo testNg =
1684             isTestNg ? new TestArtifactInfo( testNgArtifact.getVersion(), testNgArtifact.getClassifier() ) : null;
1685         final TestRequest testSuiteDefinition = new TestRequest( suiteXmlFiles(),
1686                                                                  getTestSourceDirectory(),
1687                                                                  getSpecificTests(),
1688                                                                  getRerunFailingTestsCount() );
1689 
1690         final boolean actualFailIfNoTests;
1691         DirectoryScannerParameters directoryScannerParameters = null;
1692         if ( hasSuiteXmlFiles() && !isSpecificTestSpecified() )
1693         {
1694             actualFailIfNoTests = getFailIfNoTests() != null && getFailIfNoTests();
1695             if ( !isTestNg )
1696             {
1697                 throw new MojoExecutionException( "suiteXmlFiles is configured, but there is no TestNG dependency" );
1698             }
1699         }
1700         else
1701         {
1702             if ( isSpecificTestSpecified() )
1703             {
1704                 actualFailIfNoTests = getEffectiveFailIfNoTests();
1705                 setFailIfNoTests( actualFailIfNoTests );
1706             }
1707             else
1708             {
1709                 actualFailIfNoTests = getFailIfNoTests() != null && getFailIfNoTests();
1710             }
1711 
1712             // @todo remove these three params and use DirectoryScannerParameters to pass into DirectoryScanner only
1713             // @todo or remove it in next major version :: 3.0
1714             // @todo remove deprecated methods in ProviderParameters => included|excluded|specificTests not needed here
1715 
1716             List<String> actualIncludes = getIncludeList(); // Collections.emptyList(); behaves same
1717             List<String> actualExcludes = getExcludeList(); // Collections.emptyList(); behaves same
1718             // Collections.emptyList(); behaves same
1719             List<String> specificTests = Collections.emptyList();
1720 
1721             directoryScannerParameters =
1722                 new DirectoryScannerParameters( getTestClassesDirectory(), actualIncludes, actualExcludes,
1723                                                 specificTests, actualFailIfNoTests, getRunOrder() );
1724         }
1725 
1726         Map<String, String> providerProperties = toStringProperties( getProperties() );
1727 
1728         return new ProviderConfiguration( directoryScannerParameters, runOrderParameters, actualFailIfNoTests,
1729                                           reporterConfiguration,
1730                                           testNg, // Not really used in provider. Limited to de/serializer.
1731                                           testSuiteDefinition, providerProperties, null,
1732                                           false, cli, getSkipAfterFailureCount(),
1733                                           Shutdown.parameterOf( getShutdown() ),
1734                                           getForkedProcessExitTimeoutInSeconds() );
1735     }
1736 
1737     private static Map<String, String> toStringProperties( Properties properties )
1738     {
1739         Map<String, String> h = new ConcurrentHashMap<>( properties.size() );
1740         for ( Enumeration e = properties.keys() ; e.hasMoreElements() ; )
1741         {
1742             Object k = e.nextElement();
1743             Object v = properties.get( k );
1744             if ( k.getClass() == String.class && v.getClass() == String.class )
1745             {
1746                 h.put( (String) k, (String) v );
1747             }
1748         }
1749         return h;
1750     }
1751 
1752     private File getStatisticsFile( String configurationHash )
1753     {
1754         return new File( getBasedir(), ".surefire-" + configurationHash );
1755     }
1756 
1757     private StartupConfiguration createStartupConfiguration( @Nonnull ProviderInfo provider, boolean isInprocess,
1758                                                              @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
1759                                                              @Nonnull DefaultScanResult scanResult,
1760                                                              @Nonnull Platform platform,
1761                                                              @Nonnull TestClassPath testClasspathWrapper )
1762         throws MojoExecutionException
1763     {
1764         try
1765         {
1766             Set<Artifact> providerArtifacts = provider.getProviderClasspath();
1767             String providerName = provider.getProviderName();
1768             if ( canExecuteProviderWithModularPath( platform ) && !isInprocess )
1769             {
1770                 String jvmExecutable = platform.getJdkExecAttributesForTests().getJvmExecutable();
1771                 String javaHome = Paths.get( jvmExecutable )
1772                         .toAbsolutePath()
1773                         .normalize()
1774                         .getParent()
1775                         .getParent()
1776                         .toString();
1777 
1778                 return newStartupConfigWithModularPath( classLoaderConfiguration, providerArtifacts, providerName,
1779                         getModuleDescriptor(), scanResult, javaHome, testClasspathWrapper );
1780             }
1781             else
1782             {
1783                 return newStartupConfigWithClasspath( classLoaderConfiguration, providerArtifacts, providerName,
1784                         testClasspathWrapper );
1785             }
1786         }
1787         catch ( IOException e )
1788         {
1789             throw new MojoExecutionException( e.getMessage(), e );
1790         }
1791     }
1792 
1793     private StartupConfiguration newStartupConfigWithClasspath(
1794             @Nonnull ClassLoaderConfiguration classLoaderConfiguration, @Nonnull Set<Artifact> providerArtifacts,
1795             @Nonnull String providerName, @Nonnull TestClassPath testClasspathWrapper )
1796     {
1797         Classpath testClasspath = testClasspathWrapper.toClasspath();
1798 
1799         Classpath providerClasspath = classpathCache.getCachedClassPath( providerName );
1800         if ( providerClasspath == null )
1801         {
1802             providerClasspath = classpathCache.setCachedClasspath( providerName, providerArtifacts );
1803         }
1804 
1805         getConsoleLogger().debug( testClasspath.getLogMessage( "test classpath:" ) );
1806         getConsoleLogger().debug( providerClasspath.getLogMessage( "provider classpath:" ) );
1807         getConsoleLogger().debug( testClasspath.getCompactLogMessage( "test(compact) classpath:" ) );
1808         getConsoleLogger().debug( providerClasspath.getCompactLogMessage( "provider(compact) classpath:" ) );
1809 
1810         Artifact[] additionalInProcArtifacts =
1811                 { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
1812         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
1813         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
1814         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
1815         getConsoleLogger().debug( inProcClasspath.getCompactLogMessage( "in-process(compact) classpath:" ) );
1816 
1817         ClasspathConfiguration classpathConfiguration = new ClasspathConfiguration( testClasspath, providerClasspath,
1818                 inProcClasspath, effectiveIsEnableAssertions(), isChildDelegation() );
1819 
1820         return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
1821                 false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
1822     }
1823 
1824     private static Set<Artifact> retainInProcArtifactsUnique( Set<Artifact> providerArtifacts,
1825                                                          Artifact... inPluginArtifacts )
1826     {
1827         Set<Artifact> result = new LinkedHashSet<>();
1828         for ( Artifact inPluginArtifact : inPluginArtifacts )
1829         {
1830             boolean contains = false;
1831             for ( Artifact providerArtifact : providerArtifacts )
1832             {
1833                 if ( hasGroupArtifactId( providerArtifact.getGroupId(), providerArtifact.getArtifactId(),
1834                         inPluginArtifact ) )
1835                 {
1836                     contains = true;
1837                     break;
1838                 }
1839             }
1840             if ( !contains )
1841             {
1842                 result.add( inPluginArtifact );
1843             }
1844         }
1845         return result;
1846     }
1847 
1848     private static boolean hasGroupArtifactId( String groupId, String artifactId, Artifact artifact )
1849     {
1850         return groupId.equals( artifact.getGroupId() ) && artifactId.equals( artifact.getArtifactId() );
1851     }
1852 
1853     private static Classpath createInProcClasspath( Classpath providerClasspath, Set<Artifact> newArtifacts )
1854     {
1855         Classpath inprocClasspath = providerClasspath.clone();
1856         for ( Artifact newArtifact : newArtifacts )
1857         {
1858             inprocClasspath = inprocClasspath.addClassPathElementUrl( newArtifact.getFile().getAbsolutePath() );
1859         }
1860         return inprocClasspath;
1861     }
1862 
1863     /**
1864      * For testing purposes - Mockito.
1865      * @return plexus component
1866      */
1867     private LocationManager getLocationManager()
1868     {
1869         return locationManager;
1870     }
1871 
1872     private StartupConfiguration newStartupConfigWithModularPath(
1873             @Nonnull ClassLoaderConfiguration classLoaderConfiguration, @Nonnull Set<Artifact> providerArtifacts,
1874             @Nonnull String providerName, @Nonnull File moduleDescriptor, @Nonnull DefaultScanResult scanResult,
1875             @Nonnull String javaHome, @Nonnull TestClassPath testClasspathWrapper )
1876             throws IOException
1877     {
1878         Classpath testClasspath = testClasspathWrapper.toClasspath();
1879 
1880         Classpath providerClasspath = classpathCache.getCachedClassPath( providerName );
1881         if ( providerClasspath == null )
1882         {
1883             providerClasspath = classpathCache.setCachedClasspath( providerName, providerArtifacts );
1884         }
1885 
1886         ResolvePathsRequest<String> req = ResolvePathsRequest.ofStrings( testClasspath.getClassPath() )
1887                 .setJdkHome( javaHome )
1888                 .setMainModuleDescriptor( moduleDescriptor.getAbsolutePath() );
1889 
1890         ResolvePathsResult<String> result = getLocationManager().resolvePaths( req );
1891         for ( Entry<String, Exception> entry : result.getPathExceptions().entrySet() )
1892         {
1893             // Probably JDK version < 9. Other known causes: passing a non-jar or a corrupted jar.
1894             getConsoleLogger()
1895                     .warning( "Exception for '" + entry.getKey() + "'.", entry.getValue() );
1896         }
1897 
1898         testClasspath = new Classpath( result.getClasspathElements() );
1899         Classpath testModulepath = new Classpath( result.getModulepathElements().keySet() );
1900 
1901         SortedSet<String> packages = new TreeSet<>();
1902 
1903         for ( String className : scanResult.getClasses() )
1904         {
1905             packages.add( substringBeforeLast( className, "." ) );
1906         }
1907 
1908         getConsoleLogger().debug( "main module descriptor name: " + result.getMainModuleDescriptor().name() );
1909 
1910         ModularClasspath modularClasspath = new ModularClasspath( result.getMainModuleDescriptor().name(),
1911                 testModulepath.getClassPath(), packages, getTestClassesDirectory() );
1912 
1913         Artifact[] additionalInProcArtifacts =
1914                 { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
1915         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
1916         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
1917 
1918         ModularClasspathConfiguration classpathConfiguration = new ModularClasspathConfiguration( modularClasspath,
1919                 testClasspath, providerClasspath, inProcClasspath, effectiveIsEnableAssertions(), isChildDelegation() );
1920 
1921         getConsoleLogger().debug( testClasspath.getLogMessage( "test classpath:" ) );
1922         getConsoleLogger().debug( testModulepath.getLogMessage( "test modulepath:" ) );
1923         getConsoleLogger().debug( providerClasspath.getLogMessage( "provider classpath:" ) );
1924         getConsoleLogger().debug( testClasspath.getCompactLogMessage( "test(compact) classpath:" ) );
1925         getConsoleLogger().debug( testModulepath.getCompactLogMessage( "test(compact) modulepath:" ) );
1926         getConsoleLogger().debug( providerClasspath.getCompactLogMessage( "provider(compact) classpath:" ) );
1927         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
1928         getConsoleLogger().debug( inProcClasspath.getCompactLogMessage( "in-process(compact) classpath:" ) );
1929 
1930         return new StartupConfiguration( providerName, classpathConfiguration, classLoaderConfiguration, isForking(),
1931                 false, ProcessCheckerType.toEnum( getEnableProcessChecker() ) );
1932     }
1933 
1934     private Artifact getCommonArtifact()
1935     {
1936         return getPluginArtifactMap().get( "org.apache.maven.surefire:maven-surefire-common" );
1937     }
1938 
1939     private Artifact getExtensionsArtifact()
1940     {
1941         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-api" );
1942     }
1943 
1944     private Artifact getApiArtifact()
1945     {
1946         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-api" );
1947     }
1948 
1949     private Artifact getLoggerApiArtifact()
1950     {
1951         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-logger-api" );
1952     }
1953 
1954     private Artifact getBooterArtifact()
1955     {
1956         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-booter" );
1957     }
1958 
1959     private Artifact getShadefireArtifact()
1960     {
1961         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-shadefire" );
1962     }
1963 
1964     private StartupReportConfiguration getStartupReportConfiguration( String configChecksum, boolean isForkMode )
1965     {
1966         SurefireStatelessReporter xmlReporter =
1967                 statelessTestsetReporter == null
1968                         ? new SurefireStatelessReporter( /*todo call def. constr.*/ isDisableXmlReport(), "3.0" )
1969                         : statelessTestsetReporter;
1970 
1971         xmlReporter.setDisable( isDisableXmlReport() ); // todo change to Boolean in the version 3.0.0-M6
1972 
1973         SurefireConsoleOutputReporter outReporter =
1974                 consoleOutputReporter == null ? new SurefireConsoleOutputReporter() : consoleOutputReporter;
1975 
1976         SurefireStatelessTestsetInfoReporter testsetReporter =
1977                 statelessTestsetInfoReporter == null
1978                         ? new SurefireStatelessTestsetInfoReporter() : statelessTestsetInfoReporter;
1979 
1980         return new StartupReportConfiguration( isUseFile(), isPrintSummary(), getReportFormat(),
1981                                                isRedirectTestOutputToFile(),
1982                                                getReportsDirectory(), isTrimStackTrace(), getReportNameSuffix(),
1983                                                getStatisticsFile( configChecksum ), requiresRunHistory(),
1984                                                getRerunFailingTestsCount(), getReportSchemaLocation(), getEncoding(),
1985                                                isForkMode, xmlReporter, outReporter, testsetReporter );
1986     }
1987 
1988     private boolean isSpecificTestSpecified()
1989     {
1990         return isNotBlank( getTest() );
1991     }
1992 
1993     @Nonnull private List<String> readListFromFile( @Nonnull final File file )
1994     {
1995         getConsoleLogger().debug( "Reading list from: " + file );
1996 
1997         if ( !file.exists() )
1998         {
1999             throw new RuntimeException( "Failed to load list from file: " + file );
2000         }
2001 
2002         try
2003         {
2004             List<String> list = FileUtils.loadFile( file );
2005 
2006             if ( getConsoleLogger().isDebugEnabled() )
2007             {
2008                 getConsoleLogger().debug( "List contents:" );
2009                 for ( String entry : list )
2010                 {
2011                     getConsoleLogger().debug( "  " + entry );
2012                 }
2013             }
2014             return list;
2015         }
2016         catch ( IOException e )
2017         {
2018             throw new RuntimeException( "Failed to load list from file: " + file, e );
2019         }
2020     }
2021 
2022     private void maybeAppendList( List<String> base, List<String> list )
2023     {
2024         if ( list != null )
2025         {
2026             base.addAll( list );
2027         }
2028     }
2029 
2030     @Nonnull private List<String> getExcludeList()
2031         throws MojoFailureException
2032     {
2033         List<String> actualExcludes = null;
2034         if ( isSpecificTestSpecified() )
2035         {
2036             actualExcludes = Collections.emptyList();
2037         }
2038         else
2039         {
2040             if ( getExcludesFile() != null )
2041             {
2042                 actualExcludes = readListFromFile( getExcludesFile() );
2043             }
2044 
2045             if ( actualExcludes == null )
2046             {
2047                 actualExcludes = getExcludes();
2048             }
2049             else
2050             {
2051                 maybeAppendList( actualExcludes, getExcludes() );
2052             }
2053 
2054             checkMethodFilterInIncludesExcludes( actualExcludes );
2055 
2056             if ( actualExcludes == null || actualExcludes.isEmpty() )
2057             {
2058                 actualExcludes = Collections.singletonList( getDefaultExcludes() );
2059             }
2060         }
2061         return filterNulls( actualExcludes );
2062     }
2063 
2064     private List<String> getIncludeList()
2065         throws MojoFailureException
2066     {
2067         List<String> includes = null;
2068         if ( isSpecificTestSpecified() )
2069         {
2070             includes = new ArrayList<>();
2071             addAll( includes, split( getTest(), "," ) );
2072         }
2073         else
2074         {
2075             if ( getIncludesFile() != null )
2076             {
2077                 includes = readListFromFile( getIncludesFile() );
2078             }
2079 
2080             if ( includes == null )
2081             {
2082                 includes = getIncludes();
2083             }
2084             else
2085             {
2086                 maybeAppendList( includes, getIncludes() );
2087             }
2088 
2089             checkMethodFilterInIncludesExcludes( includes );
2090 
2091             if ( includes == null || includes.isEmpty() )
2092             {
2093                 includes = asList( getDefaultIncludes() );
2094             }
2095         }
2096 
2097         return filterNulls( includes );
2098     }
2099 
2100     private void checkMethodFilterInIncludesExcludes( Iterable<String> patterns )
2101         throws MojoFailureException
2102     {
2103         if ( patterns != null )
2104         {
2105             for ( String pattern : patterns )
2106             {
2107                 if ( pattern != null && pattern.contains( "#" ) )
2108                 {
2109                     throw new MojoFailureException( "Method filter prohibited in "
2110                                                         + "includes|excludes|includesFile|excludesFile parameter: "
2111                                                         + pattern );
2112                 }
2113             }
2114         }
2115     }
2116 
2117     private TestListResolver getIncludedAndExcludedTests()
2118         throws MojoFailureException
2119     {
2120         if ( includedExcludedTests == null )
2121         {
2122             includedExcludedTests = new TestListResolver( getIncludeList(), getExcludeList() );
2123         }
2124         return includedExcludedTests;
2125     }
2126 
2127     public TestListResolver getSpecificTests()
2128     {
2129         if ( specificTests == null )
2130         {
2131             specificTests = new TestListResolver( getTest() );
2132         }
2133         return specificTests;
2134     }
2135 
2136     @Nonnull private List<String> filterNulls( @Nonnull List<String> toFilter )
2137     {
2138         List<String> result = new ArrayList<>( toFilter.size() );
2139         for ( String item : toFilter )
2140         {
2141             if ( item != null )
2142             {
2143                 item = item.trim();
2144                 if ( !item.isEmpty() )
2145                 {
2146                     result.add( item );
2147                 }
2148             }
2149         }
2150 
2151         return result;
2152     }
2153 
2154     private Artifact getTestNgArtifact()
2155         throws MojoExecutionException
2156     {
2157         Artifact artifact = getProjectArtifactMap().get( getTestNGArtifactName() );
2158         Artifact projectArtifact = project.getArtifact();
2159         String projectArtifactName = projectArtifact.getGroupId() + ":" + projectArtifact.getArtifactId();
2160 
2161         if ( artifact != null )
2162         {
2163             VersionRange range = createVersionRange();
2164             if ( !range.containsVersion( new DefaultArtifactVersion( artifact.getVersion() ) ) )
2165             {
2166                 throw new MojoExecutionException(
2167                     "TestNG support requires version 4.7 or above. You have declared version "
2168                         + artifact.getVersion() );
2169             }
2170         }
2171         else if ( projectArtifactName.equals( getTestNGArtifactName() ) )
2172         {
2173             artifact = projectArtifact;
2174         }
2175 
2176         return artifact;
2177 
2178     }
2179 
2180     private VersionRange createVersionRange()
2181     {
2182         try
2183         {
2184             return VersionRange.createFromVersionSpec( "[4.7,)" );
2185         }
2186         catch ( InvalidVersionSpecificationException e )
2187         {
2188             throw new RuntimeException( e );
2189         }
2190     }
2191 
2192     private Artifact getJunitArtifact()
2193     {
2194         Artifact artifact = getProjectArtifactMap().get( getJunitArtifactName() );
2195         Artifact projectArtifact = project.getArtifact();
2196         String projectArtifactName = projectArtifact.getGroupId() + ":" + projectArtifact.getArtifactId();
2197 
2198         if ( artifact == null && projectArtifactName.equals( getJunitArtifactName() ) )
2199         {
2200             artifact = projectArtifact;
2201         }
2202 
2203         return artifact;
2204     }
2205 
2206     private Artifact getJunitDepArtifact()
2207     {
2208         return getProjectArtifactMap().get( "junit:junit-dep" );
2209     }
2210 
2211     private Artifact getJunitPlatformArtifact()
2212     {
2213         Artifact artifact = getProjectArtifactMap().get( "org.junit.platform:junit-platform-commons" );
2214         if ( artifact == null )
2215         {
2216             artifact = getPluginArtifactMap().get( "org.junit.platform:junit-platform-engine" );
2217         }
2218 
2219         Artifact projectArtifact = project.getArtifact();
2220         String projectGroupId = projectArtifact.getGroupId();
2221         if ( artifact == null && ( "org.junit.platform".equals( projectGroupId )
2222                 || "org.junit.jupiter".equals( projectGroupId )
2223                 || "org.junit.vintage".equals( projectGroupId ) ) )
2224         {
2225             artifact = projectArtifact;
2226         }
2227 
2228         return artifact;
2229     }
2230 
2231     private ForkStarter createForkStarter( @Nonnull ProviderInfo provider, @Nonnull ForkConfiguration forkConfiguration,
2232                                            @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
2233                                            @Nonnull RunOrderParameters runOrderParameters, @Nonnull ConsoleLogger log,
2234                                            @Nonnull DefaultScanResult scanResult, @Nonnull Platform platform,
2235                                            @Nonnull TestClassPath testClasspathWrapper )
2236         throws MojoExecutionException, MojoFailureException
2237     {
2238         StartupConfiguration startupConfiguration = createStartupConfiguration( provider, false,
2239                 classLoaderConfiguration, scanResult, platform, testClasspathWrapper );
2240         String configChecksum = getConfigChecksum();
2241         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, true );
2242         ProviderConfiguration providerConfiguration = createProviderConfiguration( runOrderParameters );
2243         return new ForkStarter( providerConfiguration, startupConfiguration, forkConfiguration,
2244                                 getForkedProcessTimeoutInSeconds(), startupReportConfiguration, log );
2245     }
2246 
2247     private InPluginVMSurefireStarter createInprocessStarter( @Nonnull ProviderInfo provider,
2248                                                               @Nonnull ClassLoaderConfiguration classLoaderConfig,
2249                                                               @Nonnull RunOrderParameters runOrderParameters,
2250                                                               @Nonnull DefaultScanResult scanResult,
2251                                                               @Nonnull Platform platform,
2252                                                               @Nonnull TestClassPath testClasspathWrapper )
2253         throws MojoExecutionException, MojoFailureException
2254     {
2255         StartupConfiguration startupConfiguration = createStartupConfiguration( provider, true, classLoaderConfig,
2256                 scanResult, platform, testClasspathWrapper );
2257         String configChecksum = getConfigChecksum();
2258         StartupReportConfiguration startupReportConfiguration = getStartupReportConfiguration( configChecksum, false );
2259         ProviderConfiguration providerConfiguration = createProviderConfiguration( runOrderParameters );
2260         return new InPluginVMSurefireStarter( startupConfiguration, providerConfiguration, startupReportConfiguration,
2261                                               getConsoleLogger() );
2262     }
2263 
2264     @Nonnull
2265     private ForkConfiguration createForkConfiguration( Platform platform )
2266     {
2267         File tmpDir = getSurefireTempDir();
2268 
2269         Artifact shadeFire = getShadefireArtifact();
2270 
2271         Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
2272 
2273         if ( canExecuteProviderWithModularPath( platform ) )
2274         {
2275             return new ModularClasspathForkConfiguration( bootClasspath,
2276                     tmpDir,
2277                     getEffectiveDebugForkedProcess(),
2278                     getWorkingDirectory() != null ? getWorkingDirectory() : getBasedir(),
2279                     getProject().getModel().getProperties(),
2280                     getArgLine(),
2281                     getEnvironmentVariables(),
2282                     getExcludedEnvironmentVariables(),
2283                     getConsoleLogger().isDebugEnabled(),
2284                     getEffectiveForkCount(),
2285                     reuseForks,
2286                     platform,
2287                     getConsoleLogger() );
2288         }
2289         else if ( getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable() )
2290         {
2291             return new JarManifestForkConfiguration( bootClasspath,
2292                     tmpDir,
2293                     getEffectiveDebugForkedProcess(),
2294                     getWorkingDirectory() != null ? getWorkingDirectory() : getBasedir(),
2295                     getProject().getModel().getProperties(),
2296                     getArgLine(),
2297                     getEnvironmentVariables(),
2298                     getExcludedEnvironmentVariables(),
2299                     getConsoleLogger().isDebugEnabled(),
2300                     getEffectiveForkCount(),
2301                     reuseForks,
2302                     platform,
2303                     getConsoleLogger() );
2304         }
2305         else
2306         {
2307             return new ClasspathForkConfiguration( bootClasspath,
2308                     tmpDir,
2309                     getEffectiveDebugForkedProcess(),
2310                     getWorkingDirectory() != null ? getWorkingDirectory() : getBasedir(),
2311                     getProject().getModel().getProperties(),
2312                     getArgLine(),
2313                     getEnvironmentVariables(),
2314                     getExcludedEnvironmentVariables(),
2315                     getConsoleLogger().isDebugEnabled(),
2316                     getEffectiveForkCount(),
2317                     reuseForks,
2318                     platform,
2319                     getConsoleLogger() );
2320         }
2321     }
2322 
2323     private void ensureEnableProcessChecker() throws MojoFailureException
2324     {
2325         if ( !ProcessCheckerType.isValid( getEnableProcessChecker() ) )
2326         {
2327             throw new MojoFailureException( "Unexpected value '"
2328                     + getEnableProcessChecker()
2329                     + "' in the configuration parameter 'enableProcessChecker'." );
2330         }
2331     }
2332 
2333     private void convertDeprecatedForkMode()
2334     {
2335         String effectiveForkMode = getEffectiveForkMode();
2336         // FORK_ONCE (default) is represented by the default values of forkCount and reuseForks
2337         if ( FORK_PERTHREAD.equals( effectiveForkMode ) )
2338         {
2339             forkCount = String.valueOf( threadCount );
2340         }
2341         else if ( FORK_NEVER.equals( effectiveForkMode ) )
2342         {
2343             forkCount = "0";
2344         }
2345         else if ( FORK_ALWAYS.equals( effectiveForkMode ) )
2346         {
2347             forkCount = "1";
2348             reuseForks = false;
2349         }
2350 
2351         if ( !FORK_ONCE.equals( getForkMode() ) )
2352         {
2353             getConsoleLogger().warning( "The parameter forkMode is deprecated since version 2.14. "
2354                                                 + "Use forkCount and reuseForks instead." );
2355         }
2356     }
2357 
2358     @SuppressWarnings( "checkstyle:emptyblock" )
2359     protected int getEffectiveForkCount()
2360     {
2361         if ( effectiveForkCount < 0 )
2362         {
2363             try
2364             {
2365                 effectiveForkCount = convertWithCoreCount( forkCount );
2366             }
2367             catch ( NumberFormatException ignored )
2368             {
2369             }
2370 
2371             if ( effectiveForkCount < 0 )
2372             {
2373                 throw new IllegalArgumentException( "Fork count " + forkCount.trim() + " is not a legal value." );
2374             }
2375         }
2376 
2377         return effectiveForkCount;
2378     }
2379 
2380     protected int convertWithCoreCount( String count )
2381     {
2382         String trimmed = count.trim();
2383         if ( trimmed.endsWith( "C" ) )
2384         {
2385             double multiplier = Double.parseDouble( trimmed.substring( 0, trimmed.length() - 1 ) );
2386             double calculated = multiplier * ( (double) Runtime.getRuntime().availableProcessors() );
2387             return calculated > 0d ? Math.max( (int) calculated, 1 ) : 0;
2388         }
2389         else
2390         {
2391             return Integer.parseInt( trimmed );
2392         }
2393     }
2394 
2395     private String getEffectiveDebugForkedProcess()
2396     {
2397         String debugForkedProcess = getDebugForkedProcess();
2398         if ( "true".equals( debugForkedProcess ) )
2399         {
2400             return "-Xdebug -Xnoagent -Djava.compiler=NONE"
2401                 + " -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005";
2402         }
2403         return debugForkedProcess;
2404     }
2405 
2406     private JdkAttributes getEffectiveJvm() throws MojoFailureException
2407     {
2408         if ( isNotEmpty( jvm ) )
2409         {
2410             File pathToJava = new File( jvm ).getAbsoluteFile();
2411             if ( !endsWithJavaPath( pathToJava.getPath() ) )
2412             {
2413                 throw new MojoFailureException( "Given path does not end with java executor \""
2414                                                         + pathToJava.getPath() + "\"." );
2415             }
2416 
2417             if ( !( pathToJava.isFile()
2418                             || "java".equals( pathToJava.getName() ) && pathToJava.getParentFile().isDirectory() ) )
2419             {
2420                 throw new MojoFailureException( "Given path to java executor does not exist \""
2421                                                         + pathToJava.getPath() + "\"." );
2422             }
2423 
2424             File jdkHome = toJdkHomeFromJvmExec( pathToJava.getPath() );
2425             BigDecimal version = jdkHome == null ? null : toJdkVersionFromReleaseFile( jdkHome );
2426             boolean javaVersion9 = version == null ? isJava9AtLeast( pathToJava.getPath() ) : isJava9AtLeast( version );
2427             return new JdkAttributes( pathToJava.getPath(), javaVersion9 );
2428         }
2429 
2430         if ( toolchain != null )
2431         {
2432             String jvmToUse = toolchain.findTool( "java" );
2433             if ( isNotEmpty( jvmToUse ) )
2434             {
2435                 boolean javaVersion9 = false;
2436 
2437                 if ( toolchain instanceof DefaultToolchain )
2438                 {
2439                     DefaultToolchain defaultToolchain = (DefaultToolchain) toolchain;
2440                     javaVersion9 = defaultToolchain.matchesRequirements( JAVA_9_MATCHER )
2441                                              || defaultToolchain.matchesRequirements( JAVA_9_MATCHER_OLD_NOTATION );
2442                 }
2443 
2444                 if ( !javaVersion9 )
2445                 {
2446                     javaVersion9 = isJava9AtLeast( jvmToUse );
2447                 }
2448 
2449                 return new JdkAttributes( jvmToUse, javaVersion9 );
2450             }
2451         }
2452 
2453         // use the same JVM as the one used to run Maven (the "java.home" one)
2454         String jvmToUse = System.getProperty( "java.home" ) + File.separator + "bin" + File.separator + "java";
2455         getConsoleLogger().debug( "Using JVM: " + jvmToUse + " with Java version "
2456                 + JAVA_SPECIFICATION_VERSION.toPlainString() );
2457 
2458         return new JdkAttributes( jvmToUse, isBuiltInJava9AtLeast() );
2459     }
2460 
2461     /**
2462      * Where surefire stores its own temp files
2463      *
2464      * @return A file pointing to the location of surefire's own temp files
2465      */
2466     File getSurefireTempDir()
2467     {
2468         File result = IS_OS_WINDOWS ? createSurefireBootDirectoryInTemp() : createSurefireBootDirectoryInBuild();
2469         try
2470         {
2471             File canonical = result.getCanonicalFile();
2472             if ( !result.equals( canonical ) )
2473             {
2474                 getConsoleLogger()
2475                         .debug( "Canonicalized tempDir path '" + result + "' to '" + canonical + "'" );
2476             }
2477             return canonical;
2478         }
2479         catch ( IOException e )
2480         {
2481             getConsoleLogger()
2482                     .error( "Could not canonicalize tempDir path '" + result + "'", e );
2483         }
2484         return result;
2485     }
2486 
2487     /**
2488      * Operates on raw plugin parameters, not the "effective" values.
2489      *
2490      * @return The checksum
2491      */
2492     private String getConfigChecksum()
2493     {
2494         ChecksumCalculator checksum = new ChecksumCalculator();
2495         checksum.add( getPluginName() );
2496         checksum.add( isSkipTests() );
2497         checksum.add( isSkipExec() );
2498         checksum.add( isSkip() );
2499         checksum.add( getTestClassesDirectory() );
2500         checksum.add( getClassesDirectory() );
2501         checksum.add( getClasspathDependencyExcludes() );
2502         checksum.add( getClasspathDependencyScopeExclude() );
2503         checksum.add( getAdditionalClasspathElements() );
2504         checksum.add( getReportsDirectory() );
2505         checksum.add( getProjectBuildDirectory() );
2506         checksum.add( getTestSourceDirectory() );
2507         checksum.add( getTest() );
2508         checksum.add( getIncludes() );
2509         checksum.add( getSkipAfterFailureCount() );
2510         checksum.add( getShutdown() );
2511         checksum.add( getExcludes() );
2512         checksum.add( getLocalRepository() );
2513         checksum.add( getSystemProperties() );
2514         checksum.add( getSystemPropertyVariables() );
2515         checksum.add( getSystemPropertiesFile() );
2516         checksum.add( getProperties() );
2517         checksum.add( isPrintSummary() );
2518         checksum.add( getReportFormat() );
2519         checksum.add( getReportNameSuffix() );
2520         checksum.add( isUseFile() );
2521         checksum.add( isRedirectTestOutputToFile() );
2522         checksum.add( getForkMode() );
2523         checksum.add( getForkCount() );
2524         checksum.add( isReuseForks() );
2525         checksum.add( getJvm() );
2526         checksum.add( getArgLine() );
2527         checksum.add( getDebugForkedProcess() );
2528         checksum.add( getForkedProcessTimeoutInSeconds() );
2529         checksum.add( getParallelTestsTimeoutInSeconds() );
2530         checksum.add( getParallelTestsTimeoutForcedInSeconds() );
2531         checksum.add( getEnvironmentVariables() );
2532         checksum.add( getExcludedEnvironmentVariables() );
2533         checksum.add( getWorkingDirectory() );
2534         checksum.add( isChildDelegation() );
2535         checksum.add( getGroups() );
2536         checksum.add( getExcludedGroups() );
2537         checksum.add( getSuiteXmlFiles() );
2538         checksum.add( getJunitArtifact() );
2539         checksum.add( getTestNGArtifactName() );
2540         checksum.add( getThreadCount() );
2541         checksum.add( getThreadCountSuites() );
2542         checksum.add( getThreadCountClasses() );
2543         checksum.add( getThreadCountMethods() );
2544         checksum.add( getPerCoreThreadCount() );
2545         checksum.add( getUseUnlimitedThreads() );
2546         checksum.add( getParallel() );
2547         checksum.add( isParallelOptimized() );
2548         checksum.add( isTrimStackTrace() );
2549         checksum.add( getRemoteRepositories() );
2550         checksum.add( getProjectRemoteRepositories() );
2551         checksum.add( isDisableXmlReport() );
2552         checksum.add( isUseSystemClassLoader() );
2553         checksum.add( isUseManifestOnlyJar() );
2554         checksum.add( getEncoding() );
2555         checksum.add( isEnableAssertions() );
2556         checksum.add( getObjectFactory() );
2557         checksum.add( getFailIfNoTests() );
2558         checksum.add( getRunOrder() );
2559         checksum.add( getDependenciesToScan() );
2560         checksum.add( getForkedProcessExitTimeoutInSeconds() );
2561         checksum.add( getRerunFailingTestsCount() );
2562         checksum.add( getTempDir() );
2563         checksum.add( useModulePath() );
2564         checksum.add( getEnableProcessChecker() );
2565         addPluginSpecificChecksumItems( checksum );
2566         return checksum.getSha1();
2567     }
2568 
2569     protected void addPluginSpecificChecksumItems( ChecksumCalculator checksum )
2570     {
2571 
2572     }
2573 
2574     protected boolean hasExecutedBefore()
2575     {
2576         // A tribute to Linus Torvalds
2577         String configChecksum = getConfigChecksum();
2578         @SuppressWarnings( "unchecked" ) Map<String, String> pluginContext = getPluginContext();
2579         if ( pluginContext.containsKey( configChecksum ) )
2580         {
2581             getConsoleLogger()
2582                     .info( "Skipping execution of surefire because it has already been run for this configuration" );
2583             return true;
2584         }
2585         pluginContext.put( configChecksum, configChecksum );
2586 
2587         return false;
2588     }
2589 
2590     @Nonnull
2591     protected ClassLoaderConfiguration getClassLoaderConfiguration()
2592     {
2593         return isForking()
2594             ? new ClassLoaderConfiguration( isUseSystemClassLoader(), isUseManifestOnlyJar() )
2595             : new ClassLoaderConfiguration( false, false );
2596     }
2597 
2598     /**
2599      * Generates the test classpath.
2600      *
2601      * @return the classpath elements
2602      */
2603     private TestClassPath generateTestClasspath()
2604     {
2605         Set<Artifact> classpathArtifacts = getProject().getArtifacts();
2606 
2607         if ( getClasspathDependencyScopeExclude() != null && !getClasspathDependencyScopeExclude().isEmpty() )
2608         {
2609             ArtifactFilter dependencyFilter = new ScopeArtifactFilter( getClasspathDependencyScopeExclude() );
2610             classpathArtifacts = filterArtifacts( classpathArtifacts, dependencyFilter );
2611         }
2612 
2613         if ( getClasspathDependencyExcludes() != null )
2614         {
2615             List<String> excludedDependencies = asList( getClasspathDependencyExcludes() );
2616             ArtifactFilter dependencyFilter = new PatternIncludesArtifactFilter( excludedDependencies );
2617             classpathArtifacts = filterArtifacts( classpathArtifacts, dependencyFilter );
2618         }
2619 
2620         return new TestClassPath( classpathArtifacts, getClassesDirectory(),
2621                 getTestClassesDirectory(), getAdditionalClasspathElements() );
2622     }
2623 
2624     /**
2625      * Return a new set containing only the artifacts accepted by the given filter.
2626      *
2627      * @param artifacts The unfiltered artifacts
2628      * @param filter    The filter to apply
2629      * @return The filtered result
2630      */
2631     private static Set<Artifact> filterArtifacts( Set<Artifact> artifacts, ArtifactFilter filter )
2632     {
2633         Set<Artifact> filteredArtifacts = new LinkedHashSet<>();
2634 
2635         for ( Artifact artifact : artifacts )
2636         {
2637             if ( !filter.include( artifact ) )
2638             {
2639                 filteredArtifacts.add( artifact );
2640             }
2641         }
2642 
2643         return filteredArtifacts;
2644     }
2645 
2646     private void showMap( Map<?, ?> map, String setting )
2647     {
2648         for ( Object o : map.keySet() )
2649         {
2650             String key = (String) o;
2651             String value = (String) map.get( key );
2652             getConsoleLogger().debug( "Setting " + setting + " [" + key + "]=[" + value + "]" );
2653         }
2654     }
2655 
2656     private <T> void showArray( T[] array, String setting )
2657     {
2658         for ( T e : array )
2659         {
2660             getConsoleLogger().debug( "Setting " + setting + " [" + e + "]" );
2661         }
2662     }
2663 
2664     private Classpath getArtifactClasspath( Artifact surefireArtifact )
2665     {
2666         Classpath existing = classpathCache.getCachedClassPath( surefireArtifact.getArtifactId() );
2667         if ( existing == null )
2668         {
2669             List<String> items = new ArrayList<>();
2670             Set<Artifact> booterArtifacts =
2671                     surefireDependencyResolver.resolvePluginArtifact( surefireArtifact ).getArtifacts();
2672             for ( Artifact artifact : booterArtifacts )
2673             {
2674                 getConsoleLogger().debug(
2675                     "Adding to " + getPluginName() + " booter test classpath: " + artifact.getFile().getAbsolutePath()
2676                     + " Scope: " + artifact.getScope() );
2677                 items.add( artifact.getFile().getAbsolutePath() );
2678             }
2679             existing = new Classpath( items );
2680             classpathCache.setCachedClasspath( surefireArtifact.getArtifactId(), existing );
2681         }
2682         return existing;
2683     }
2684 
2685     private Properties getUserProperties()
2686     {
2687         return getSession().getUserProperties();
2688     }
2689 
2690     private void ensureWorkingDirectoryExists()
2691         throws MojoFailureException
2692     {
2693         if ( getWorkingDirectory() == null )
2694         {
2695             throw new MojoFailureException( "workingDirectory cannot be null" );
2696         }
2697 
2698         if ( isForking() )
2699         {
2700             // Postpone directory creation till forked JVM creation
2701             // see ForkConfiguration.createCommandLine
2702             return;
2703         }
2704 
2705         if ( !getWorkingDirectory().exists() )
2706         {
2707             if ( !getWorkingDirectory().mkdirs() )
2708             {
2709                 throw new MojoFailureException( "Cannot create workingDirectory " + getWorkingDirectory() );
2710             }
2711         }
2712 
2713         if ( !getWorkingDirectory().isDirectory() )
2714         {
2715             throw new MojoFailureException(
2716                 "workingDirectory " + getWorkingDirectory() + " exists and is not a directory" );
2717         }
2718     }
2719 
2720     private void ensureParallelRunningCompatibility()
2721         throws MojoFailureException
2722     {
2723         if ( isMavenParallel() && isNotForking() )
2724         {
2725             throw new MojoFailureException( "parallel maven execution is not compatible with surefire forkCount 0" );
2726         }
2727     }
2728 
2729     private void ensureThreadCountWithPerThread()
2730         throws MojoFailureException
2731     {
2732         if ( FORK_PERTHREAD.equals( getEffectiveForkMode() ) && getThreadCount() < 1 )
2733         {
2734             throw new MojoFailureException( "Fork mode perthread requires a thread count" );
2735         }
2736     }
2737 
2738     private void warnIfUselessUseSystemClassLoaderParameter()
2739     {
2740         if ( isUseSystemClassLoader() && isNotForking() )
2741         {
2742             getConsoleLogger().warning( "useSystemClassloader setting has no effect when not forking" );
2743         }
2744     }
2745 
2746     private boolean isNotForking()
2747     {
2748         return !isForking();
2749     }
2750 
2751     private List<CommandLineOption> commandLineOptions()
2752     {
2753         return SurefireHelper.commandLineOptions( getSession(), getConsoleLogger() );
2754     }
2755 
2756     private void warnIfDefunctGroupsCombinations()
2757         throws MojoFailureException, MojoExecutionException
2758     {
2759         if ( isAnyGroupsSelected() )
2760         {
2761             if ( getTestNgArtifact() == null )
2762             {
2763                 Artifact junitArtifact = getJunitArtifact();
2764                 boolean junit47Compatible = isJunit47Compatible( junitArtifact );
2765                 boolean junit5PlatformCompatible = getJunitPlatformArtifact() != null;
2766                 if ( !junit47Compatible && !junit5PlatformCompatible )
2767                 {
2768                     if ( junitArtifact != null )
2769                     {
2770                         throw new MojoFailureException( "groups/excludedGroups are specified but JUnit version on "
2771                                                             + "classpath is too old to support groups. "
2772                                                             + "Check your dependency:tree to see if your project "
2773                                                             + "is picking up an old junit version" );
2774                     }
2775                     throw new MojoFailureException( "groups/excludedGroups require TestNG, JUnit48+ or JUnit 5 "
2776                             + "(a specific engine required on classpath) on project test classpath" );
2777                 }
2778             }
2779 
2780         }
2781     }
2782 
2783     private void warnIfRerunClashes()
2784         throws MojoFailureException
2785     {
2786         if ( getRerunFailingTestsCount() < 0 )
2787         {
2788             throw new MojoFailureException( "Parameter \"rerunFailingTestsCount\" should not be negative." );
2789         }
2790 
2791         if ( getSkipAfterFailureCount() < 0 )
2792         {
2793             throw new MojoFailureException( "Parameter \"skipAfterFailureCount\" should not be negative." );
2794         }
2795     }
2796 
2797     private void warnIfWrongShutdownValue()
2798         throws MojoFailureException
2799     {
2800         if ( !Shutdown.isKnown( getShutdown() ) )
2801         {
2802             throw new MojoFailureException( "Parameter \"shutdown\" should have values " + Shutdown.listParameters() );
2803         }
2804     }
2805 
2806     private void warnIfNotApplicableSkipAfterFailureCount()
2807         throws MojoFailureException
2808     {
2809         int skipAfterFailureCount = getSkipAfterFailureCount();
2810 
2811         if ( skipAfterFailureCount < 0 )
2812         {
2813             throw new MojoFailureException( "Parameter \"skipAfterFailureCount\" should not be negative." );
2814         }
2815         else if ( skipAfterFailureCount > 0 )
2816         {
2817             try
2818             {
2819                 Artifact testng = getTestNgArtifact();
2820                 if ( testng != null )
2821                 {
2822                     VersionRange range = VersionRange.createFromVersionSpec( "[5.10,)" );
2823                     if ( !range.containsVersion( new DefaultArtifactVersion( testng.getVersion() ) ) )
2824                     {
2825                         throw new MojoFailureException(
2826                             "Parameter \"skipAfterFailureCount\" expects TestNG Version 5.10 or higher. "
2827                                 + "java.lang.NoClassDefFoundError: org/testng/IInvokedMethodListener" );
2828                     }
2829                 }
2830                 else
2831                 {
2832                     // TestNG is dependent on JUnit
2833                     Artifact junit = getJunitArtifact();
2834                     if ( junit != null )
2835                     {
2836                         VersionRange range = VersionRange.createFromVersionSpec( "[4.0,)" );
2837                         if ( !range.containsVersion( new DefaultArtifactVersion( junit.getVersion() ) ) )
2838                         {
2839                             throw new MojoFailureException(
2840                                 "Parameter \"skipAfterFailureCount\" expects JUnit Version 4.0 or higher. "
2841                                     + "java.lang.NoSuchMethodError: "
2842                                     + "org.junit.runner.notification.RunNotifier.pleaseStop()V" );
2843                         }
2844                     }
2845                 }
2846             }
2847             catch ( MojoExecutionException e )
2848             {
2849                 throw new MojoFailureException( e.getLocalizedMessage() );
2850             }
2851             catch ( InvalidVersionSpecificationException e )
2852             {
2853                 throw new RuntimeException( e );
2854             }
2855         }
2856     }
2857 
2858     private void warnIfIllegalTempDir() throws MojoFailureException
2859     {
2860         if ( isEmpty( getTempDir() ) )
2861         {
2862             throw new MojoFailureException( "Parameter 'tempDir' should not be blank string." );
2863         }
2864     }
2865 
2866     final class TestNgProviderInfo
2867         implements ProviderInfo
2868     {
2869         private final Artifact testNgArtifact;
2870 
2871         TestNgProviderInfo( Artifact testNgArtifact )
2872         {
2873             this.testNgArtifact = testNgArtifact;
2874         }
2875 
2876         @Override
2877         @Nonnull public String getProviderName()
2878         {
2879             return "org.apache.maven.surefire.testng.TestNGProvider";
2880         }
2881 
2882         @Override
2883         public boolean isApplicable()
2884         {
2885             return testNgArtifact != null;
2886         }
2887 
2888         @Override
2889         public void addProviderProperties() throws MojoExecutionException
2890         {
2891             convertTestNGParameters();
2892         }
2893 
2894         @Override
2895         @Nonnull
2896         public Set<Artifact> getProviderClasspath()
2897         {
2898             Artifact surefireArtifact = getBooterArtifact();
2899             String version = surefireArtifact.getBaseVersion();
2900             return surefireDependencyResolver.getProviderClasspath( "surefire-testng", version );
2901         }
2902     }
2903 
2904     final class JUnit3ProviderInfo
2905         implements ProviderInfo
2906     {
2907         @Override
2908         @Nonnull public String getProviderName()
2909         {
2910             return "org.apache.maven.surefire.junit.JUnit3Provider";
2911         }
2912 
2913         @Override
2914         public boolean isApplicable()
2915         {
2916             return true;
2917         }
2918 
2919         @Override
2920         public void addProviderProperties()
2921         {
2922         }
2923 
2924         @Override
2925         @Nonnull
2926         public Set<Artifact> getProviderClasspath()
2927         {
2928             // add the JUnit provider as default - it doesn't require JUnit to be present,
2929             // since it supports POJO tests.
2930             String version = surefireBooterArtifact.getBaseVersion();
2931             return surefireDependencyResolver.getProviderClasspath( "surefire-junit3", version );
2932         }
2933     }
2934 
2935     final class JUnit4ProviderInfo
2936         implements ProviderInfo
2937     {
2938         private final Artifact junitArtifact;
2939 
2940         private final Artifact junitDepArtifact;
2941 
2942         JUnit4ProviderInfo( Artifact junitArtifact, Artifact junitDepArtifact )
2943         {
2944             this.junitArtifact = junitArtifact;
2945             this.junitDepArtifact = junitDepArtifact;
2946         }
2947 
2948         @Override
2949         @Nonnull public String getProviderName()
2950         {
2951             return "org.apache.maven.surefire.junit4.JUnit4Provider";
2952         }
2953 
2954         @Override
2955         public boolean isApplicable()
2956         {
2957             return junitDepArtifact != null || isAnyJunit4( junitArtifact );
2958         }
2959 
2960         @Override
2961         public void addProviderProperties()
2962         {
2963         }
2964 
2965         @Override
2966         @Nonnull
2967         public Set<Artifact> getProviderClasspath()
2968         {
2969             String version = surefireBooterArtifact.getBaseVersion();
2970             return surefireDependencyResolver.getProviderClasspath( "surefire-junit4", version );
2971         }
2972     }
2973 
2974     final class JUnitPlatformProviderInfo
2975         implements ProviderInfo
2976     {
2977         private static final String PROVIDER_DEP_GID = "org.junit.platform";
2978         private static final String PROVIDER_DEP_AID = "junit-platform-launcher";
2979 
2980         private final Artifact junitPlatformArtifact;
2981         private final TestClassPath testClasspath;
2982 
2983         JUnitPlatformProviderInfo( Artifact junitPlatformArtifact, TestClassPath testClasspath )
2984         {
2985             this.junitPlatformArtifact = junitPlatformArtifact;
2986             this.testClasspath = testClasspath;
2987         }
2988 
2989         @Override
2990         @Nonnull
2991         public String getProviderName()
2992         {
2993             return "org.apache.maven.surefire.junitplatform.JUnitPlatformProvider";
2994         }
2995 
2996         @Override
2997         public boolean isApplicable()
2998         {
2999             return junitPlatformArtifact != null;
3000         }
3001 
3002         @Override
3003         public void addProviderProperties()
3004         {
3005             convertGroupParameters();
3006         }
3007 
3008         @Override
3009         @Nonnull
3010         public Set<Artifact> getProviderClasspath() throws MojoExecutionException
3011         {
3012             String surefireVersion = surefireBooterArtifact.getBaseVersion();
3013             Map<String, Artifact> providerArtifacts =
3014                     surefireDependencyResolver.getProviderClasspathAsMap( "surefire-junit-platform", surefireVersion );
3015             Map<String, Artifact> testDependencies = testClasspath.getTestDependencies();
3016 
3017             if ( hasDependencyPlatformEngine( testDependencies ) )
3018             {
3019                 String filterTestDependency = "org.junit.platform:junit-platform-engine";
3020                 getConsoleLogger().debug( "Test dependencies contain " + filterTestDependency );
3021                 narrowProviderDependencies( filterTestDependency, providerArtifacts, testDependencies );
3022             }
3023             else
3024             {
3025                 ProjectBuildingRequest request = getSession().getProjectBuildingRequest();
3026                 Collection<Dependency> pluginDependencies = getPluginDescriptor().getPlugin().getDependencies();
3027                 Set<Artifact> engines =
3028                         surefireDependencyResolver.resolvePluginDependencies( request, pluginDependencies );
3029                 if ( hasDependencyPlatformEngine( engines ) )
3030                 {
3031                     Map<String, Artifact> engineArtifacts = artifactMapByVersionlessId( engines );
3032                     providerArtifacts.putAll( engineArtifacts );
3033                     alignVersions( providerArtifacts, engineArtifacts );
3034                 }
3035                 else if ( hasDependencyJupiterAPI( testDependencies ) )
3036                 {
3037                     String engineGroupId = "org.junit.jupiter";
3038                     String engineArtifactId = "junit-jupiter-engine";
3039                     String engineCoordinates = engineGroupId + ":" + engineArtifactId;
3040                     String api = "org.junit.jupiter:junit-jupiter-api";
3041                     getConsoleLogger().debug( "Test dependencies contain " + api + ". Resolving " + engineCoordinates );
3042                     String engineVersion = testDependencies.get( api ).getBaseVersion();
3043                     addEngineByApi( engineGroupId, engineArtifactId, engineVersion,
3044                             providerArtifacts, testDependencies );
3045                 }
3046             }
3047             providerArtifacts.keySet().removeAll( testDependencies.keySet() );
3048             return new LinkedHashSet<>( providerArtifacts.values() );
3049         }
3050 
3051         private void addEngineByApi( String engineGroupId, String engineArtifactId, String engineVersion,
3052                                      Map<String, Artifact> providerArtifacts, Map<String, Artifact> testDependencies )
3053         {
3054             providerArtifacts.keySet().removeAll( testDependencies.keySet() );
3055             for ( Artifact dep : resolve( engineGroupId, engineArtifactId, engineVersion, null, "jar" ) )
3056             {
3057                 String key = dep.getGroupId() + ":" + dep.getArtifactId();
3058                 if ( !testDependencies.containsKey( key ) )
3059                 {
3060                     providerArtifacts.put( key, dep );
3061                 }
3062             }
3063             alignVersions( providerArtifacts, testDependencies );
3064         }
3065 
3066         private void narrowProviderDependencies( String filterTestDependency,
3067                                                  Map<String, Artifact> providerArtifacts,
3068                                                  Map<String, Artifact> testDependencies )
3069         {
3070             Artifact engine = testDependencies.get( filterTestDependency );
3071             String groupId = engine.getGroupId();
3072             String artifactId = engine.getArtifactId();
3073             String version = engine.getBaseVersion();
3074             String classifier = engine.getClassifier();
3075             String type = engine.getType();
3076             for ( Artifact engineDep : resolve( groupId, artifactId, version, classifier, type ) )
3077             {
3078                 providerArtifacts.remove( engineDep.getGroupId() + ":" + engineDep.getArtifactId() );
3079                 getConsoleLogger().debug( "Removed artifact " + engineDep
3080                         + " from provider. Already appears in test classpath." );
3081             }
3082             alignVersions( providerArtifacts, testDependencies );
3083         }
3084 
3085         private void alignVersions( Map<String, Artifact> providerArtifacts,
3086                                     Map<String, Artifact> referencedDependencies )
3087         {
3088             String version = referencedDependencies.get( "org.junit.platform:junit-platform-commons" ).getBaseVersion();
3089             for ( Artifact launcherArtifact : resolve( PROVIDER_DEP_GID, PROVIDER_DEP_AID, version, null, "jar" ) )
3090             {
3091                 String key = launcherArtifact.getGroupId() + ":" + launcherArtifact.getArtifactId();
3092                 if ( providerArtifacts.containsKey( key ) )
3093                 {
3094                     providerArtifacts.put( key, launcherArtifact );
3095                 }
3096             }
3097         }
3098 
3099         private Set<Artifact> resolve( String g, String a, String v, String c, String t )
3100         {
3101             ArtifactHandler handler = junitPlatformArtifact.getArtifactHandler();
3102             Artifact artifact = new DefaultArtifact( g, a, v, null, t, c, handler );
3103             getConsoleLogger().debug( "Resolving artifact " + g + ":" + a + ":" + v );
3104             Set<Artifact> r = surefireDependencyResolver.resolveProjectArtifact( artifact ).getArtifacts();
3105             getConsoleLogger().debug( "Resolved artifact " + g + ":" + a + ":" + v + " to " + r );
3106             return r;
3107         }
3108 
3109         private boolean hasDependencyJupiterAPI( Map<String, Artifact> dependencies )
3110         {
3111             return dependencies.containsKey( "org.junit.jupiter:junit-jupiter-api" )
3112                     || hasGroupArtifactId( "org.junit.jupiter", "junit-jupiter-api", getProject().getArtifact() );
3113         }
3114 
3115         private boolean hasDependencyPlatformEngine( Map<String, Artifact> dependencies )
3116         {
3117             return dependencies.containsKey( "org.junit.platform:junit-platform-engine" )
3118                     || hasGroupArtifactId( "org.junit.platform", "junit-platform-engine", getProject().getArtifact() );
3119         }
3120 
3121         private boolean hasDependencyPlatformEngine( Collection<Artifact> dependencies )
3122         {
3123             if ( hasGroupArtifactId( "org.junit.platform", "junit-platform-engine", getProject().getArtifact() ) )
3124             {
3125                 return true;
3126             }
3127 
3128             for ( Artifact dependency : dependencies )
3129             {
3130                 if ( dependency.getGroupId().equals( "org.junit.platform" )
3131                         && dependency.getArtifactId().equals( "junit-platform-engine" ) )
3132                 {
3133                     return true;
3134                 }
3135             }
3136 
3137             return false;
3138         }
3139     }
3140 
3141     final class JUnitCoreProviderInfo
3142         implements ProviderInfo
3143     {
3144         private final Artifact junitArtifact;
3145 
3146         private final Artifact junitDepArtifact;
3147 
3148         JUnitCoreProviderInfo( Artifact junitArtifact, Artifact junitDepArtifact )
3149         {
3150             this.junitArtifact = junitArtifact;
3151             this.junitDepArtifact = junitDepArtifact;
3152         }
3153 
3154         @Override
3155         @Nonnull public String getProviderName()
3156         {
3157             return "org.apache.maven.surefire.junitcore.JUnitCoreProvider";
3158         }
3159 
3160         private boolean is47CompatibleJunitDep()
3161         {
3162             return junitDepArtifact != null && isJunit47Compatible( junitDepArtifact );
3163         }
3164 
3165         @Override
3166         public boolean isApplicable()
3167         {
3168             final boolean isJunitArtifact47 = isAnyJunit4( junitArtifact ) && isJunit47Compatible( junitArtifact );
3169             final boolean isAny47ProvidersForces = isAnyConcurrencySelected() || isAnyGroupsSelected();
3170             return isAny47ProvidersForces && ( isJunitArtifact47 || is47CompatibleJunitDep() );
3171         }
3172 
3173         @Override
3174         public void addProviderProperties() throws MojoExecutionException
3175         {
3176             convertJunitCoreParameters();
3177             convertGroupParameters();
3178         }
3179 
3180         @Override
3181         @Nonnull
3182         public Set<Artifact> getProviderClasspath()
3183         {
3184             String version = surefireBooterArtifact.getBaseVersion();
3185             return surefireDependencyResolver.getProviderClasspath( "surefire-junit47", version );
3186         }
3187     }
3188 
3189     /**
3190      * Provides the Provider information for manually configured providers.
3191      */
3192     final class DynamicProviderInfo
3193         implements ConfigurableProviderInfo
3194     {
3195         final String providerName;
3196 
3197         DynamicProviderInfo( String providerName )
3198         {
3199             this.providerName = providerName;
3200         }
3201 
3202         @Override
3203         public ProviderInfo instantiate( String providerName )
3204         {
3205             return new DynamicProviderInfo( providerName );
3206         }
3207 
3208         @Override
3209         @Nonnull
3210         public String getProviderName()
3211         {
3212             return providerName;
3213         }
3214 
3215         @Override
3216         public boolean isApplicable()
3217         {
3218             return true;
3219         }
3220 
3221         @Override
3222         public void addProviderProperties() throws MojoExecutionException
3223         {
3224             // Ok this is a bit lazy.
3225             convertJunitCoreParameters();
3226             convertTestNGParameters();
3227         }
3228 
3229         @Override
3230         @Nonnull
3231         public Set<Artifact> getProviderClasspath()
3232         {
3233             return surefireDependencyResolver.addProviderToClasspath( getPluginArtifactMap(), getMojoArtifact(),
3234                     getApiArtifact(), getLoggerApiArtifact() );
3235         }
3236     }
3237 
3238     /**
3239      * @author Kristian Rosenvold
3240      */
3241     final class ProviderList
3242     {
3243         private final ProviderInfo[] wellKnownProviders;
3244 
3245         private final ConfigurableProviderInfo dynamicProvider;
3246 
3247         ProviderList( ConfigurableProviderInfo dynamicProviderInfo, ProviderInfo... wellKnownProviders )
3248         {
3249             this.wellKnownProviders = wellKnownProviders;
3250             this.dynamicProvider = dynamicProviderInfo;
3251         }
3252 
3253         @Nonnull List<ProviderInfo> resolve()
3254         {
3255             List<ProviderInfo> providersToRun = new ArrayList<>();
3256             Set<String> manuallyConfiguredProviders = getManuallyConfiguredProviders();
3257             for ( String name : manuallyConfiguredProviders )
3258             {
3259                 ProviderInfo wellKnown = findByName( name );
3260                 ProviderInfo providerToAdd = wellKnown != null ? wellKnown : dynamicProvider.instantiate( name );
3261                 logDebugOrCliShowErrors( "Using configured provider " + providerToAdd.getProviderName() );
3262                 providersToRun.add( providerToAdd );
3263             }
3264             return manuallyConfiguredProviders.isEmpty() ? autoDetectOneProvider() : providersToRun;
3265         }
3266 
3267         @Nonnull private List<ProviderInfo> autoDetectOneProvider()
3268         {
3269             List<ProviderInfo> providersToRun = new ArrayList<>();
3270             for ( ProviderInfo wellKnownProvider : wellKnownProviders )
3271             {
3272                 if ( wellKnownProvider.isApplicable() )
3273                 {
3274                     providersToRun.add( wellKnownProvider );
3275                     return providersToRun;
3276                 }
3277             }
3278             return providersToRun;
3279         }
3280 
3281         private Set<String> getManuallyConfiguredProviders()
3282         {
3283             try
3284             {
3285                 ClassLoader cl = currentThread().getContextClassLoader();
3286                 return providerDetector.lookupServiceNames( SurefireProvider.class, cl );
3287             }
3288             catch ( IOException e )
3289             {
3290                 throw new RuntimeException( e );
3291             }
3292         }
3293 
3294         private ProviderInfo findByName( String providerClassName )
3295         {
3296             for ( ProviderInfo wellKnownProvider : wellKnownProviders )
3297             {
3298                 if ( wellKnownProvider.getProviderName().equals( providerClassName ) )
3299                 {
3300                     return wellKnownProvider;
3301                 }
3302             }
3303             return null;
3304         }
3305     }
3306 
3307     File createSurefireBootDirectoryInBuild()
3308     {
3309         File tmp = new File( getProjectBuildDirectory(), getTempDir() );
3310         //noinspection ResultOfMethodCallIgnored
3311         tmp.mkdirs();
3312         return tmp;
3313     }
3314 
3315     File createSurefireBootDirectoryInTemp()
3316     {
3317         try
3318         {
3319             return Files.createTempDirectory( getTempDir() ).toFile();
3320         }
3321         catch ( IOException e )
3322         {
3323             return createSurefireBootDirectoryInBuild();
3324         }
3325     }
3326 
3327     @Override
3328     public List<String> getExcludes()
3329     {
3330         return excludes;
3331     }
3332 
3333     @Override
3334     public void setExcludes( List<String> excludes )
3335     {
3336         this.excludes = excludes;
3337     }
3338 
3339     @Override
3340     public ArtifactRepository getLocalRepository()
3341     {
3342         return localRepository;
3343     }
3344 
3345     @Override
3346     public void setLocalRepository( ArtifactRepository localRepository )
3347     {
3348         this.localRepository = localRepository;
3349     }
3350 
3351     public Properties getSystemProperties()
3352     {
3353         return systemProperties;
3354     }
3355 
3356     @SuppressWarnings( { "UnusedDeclaration", "deprecation" } )
3357     public void setSystemProperties( Properties systemProperties )
3358     {
3359         this.systemProperties = systemProperties;
3360     }
3361 
3362     public Map<String, String> getSystemPropertyVariables()
3363     {
3364         return systemPropertyVariables;
3365     }
3366 
3367     @SuppressWarnings( "UnusedDeclaration" )
3368     public void setSystemPropertyVariables( Map<String, String> systemPropertyVariables )
3369     {
3370         this.systemPropertyVariables = systemPropertyVariables;
3371     }
3372 
3373     public File getSystemPropertiesFile()
3374     {
3375         return systemPropertiesFile;
3376     }
3377 
3378     @SuppressWarnings( "UnusedDeclaration" )
3379     public void setSystemPropertiesFile( File systemPropertiesFile )
3380     {
3381         this.systemPropertiesFile = systemPropertiesFile;
3382     }
3383 
3384     private Properties getProperties()
3385     {
3386         return properties;
3387     }
3388 
3389     public void setProperties( Properties properties )
3390     {
3391         this.properties = properties;
3392     }
3393 
3394     public Map<String, Artifact> getPluginArtifactMap()
3395     {
3396         return pluginArtifactMap;
3397     }
3398 
3399     @SuppressWarnings( "UnusedDeclaration" )
3400     public void setPluginArtifactMap( Map<String, Artifact> pluginArtifactMap )
3401     {
3402         this.pluginArtifactMap = pluginArtifactMap;
3403     }
3404 
3405     public Map<String, Artifact> getProjectArtifactMap()
3406     {
3407         return projectArtifactMap;
3408     }
3409 
3410     @SuppressWarnings( "UnusedDeclaration" )
3411     public void setProjectArtifactMap( Map<String, Artifact> projectArtifactMap )
3412     {
3413         this.projectArtifactMap = projectArtifactMap;
3414     }
3415 
3416     public String getReportNameSuffix()
3417     {
3418         return reportNameSuffix;
3419     }
3420 
3421     @SuppressWarnings( "UnusedDeclaration" )
3422     public void setReportNameSuffix( String reportNameSuffix )
3423     {
3424         this.reportNameSuffix = reportNameSuffix;
3425     }
3426 
3427 
3428     public boolean isRedirectTestOutputToFile()
3429     {
3430         return redirectTestOutputToFile;
3431     }
3432 
3433     @SuppressWarnings( "UnusedDeclaration" )
3434     public void setRedirectTestOutputToFile( boolean redirectTestOutputToFile )
3435     {
3436         this.redirectTestOutputToFile = redirectTestOutputToFile;
3437     }
3438 
3439 
3440     public Boolean getFailIfNoTests()
3441     {
3442         return failIfNoTests;
3443     }
3444 
3445     public void setFailIfNoTests( boolean failIfNoTests )
3446     {
3447         this.failIfNoTests = failIfNoTests;
3448     }
3449 
3450     public String getForkMode()
3451     {
3452         return forkMode;
3453     }
3454 
3455     @SuppressWarnings( "UnusedDeclaration" )
3456     public void setForkMode( String forkMode )
3457     {
3458         this.forkMode = forkMode;
3459     }
3460 
3461     public String getJvm()
3462     {
3463         return jvm;
3464     }
3465 
3466     public String getArgLine()
3467     {
3468         return argLine;
3469     }
3470 
3471     @SuppressWarnings( "UnusedDeclaration" )
3472     public void setArgLine( String argLine )
3473     {
3474         this.argLine = argLine;
3475     }
3476 
3477 
3478     public Map<String, String> getEnvironmentVariables()
3479     {
3480         return environmentVariables;
3481     }
3482 
3483     @SuppressWarnings( "UnusedDeclaration" )
3484     public void setEnvironmentVariables( Map<String, String> environmentVariables )
3485     {
3486         this.environmentVariables = environmentVariables;
3487     }
3488 
3489     public File getWorkingDirectory()
3490     {
3491         return workingDirectory;
3492     }
3493 
3494     @SuppressWarnings( "UnusedDeclaration" )
3495     public void setWorkingDirectory( File workingDirectory )
3496     {
3497         this.workingDirectory = workingDirectory;
3498     }
3499 
3500     public boolean isChildDelegation()
3501     {
3502         return childDelegation;
3503     }
3504 
3505     @SuppressWarnings( "UnusedDeclaration" )
3506     public void setChildDelegation( boolean childDelegation )
3507     {
3508         this.childDelegation = childDelegation;
3509     }
3510 
3511     public String getGroups()
3512     {
3513         return groups;
3514     }
3515 
3516     @SuppressWarnings( "UnusedDeclaration" )
3517     public void setGroups( String groups )
3518     {
3519         this.groups = groups;
3520     }
3521 
3522     public String getExcludedGroups()
3523     {
3524         return excludedGroups;
3525     }
3526 
3527     @SuppressWarnings( "UnusedDeclaration" )
3528     public void setExcludedGroups( String excludedGroups )
3529     {
3530         this.excludedGroups = excludedGroups;
3531     }
3532 
3533     public String getJunitArtifactName()
3534     {
3535         return junitArtifactName;
3536     }
3537 
3538     @SuppressWarnings( "UnusedDeclaration" )
3539     public void setJunitArtifactName( String junitArtifactName )
3540     {
3541         this.junitArtifactName = junitArtifactName;
3542     }
3543 
3544     public String getTestNGArtifactName()
3545     {
3546         return testNGArtifactName;
3547     }
3548 
3549     @SuppressWarnings( "UnusedDeclaration" )
3550     public void setTestNGArtifactName( String testNGArtifactName )
3551     {
3552         this.testNGArtifactName = testNGArtifactName;
3553     }
3554 
3555     public int getThreadCount()
3556     {
3557         return threadCount;
3558     }
3559 
3560     @SuppressWarnings( "UnusedDeclaration" )
3561     public void setThreadCount( int threadCount )
3562     {
3563         this.threadCount = threadCount;
3564     }
3565 
3566     public boolean getPerCoreThreadCount()
3567     {
3568         return perCoreThreadCount;
3569     }
3570 
3571     @SuppressWarnings( "UnusedDeclaration" )
3572     public void setPerCoreThreadCount( boolean perCoreThreadCount )
3573     {
3574         this.perCoreThreadCount = perCoreThreadCount;
3575     }
3576 
3577     public boolean getUseUnlimitedThreads()
3578     {
3579         return useUnlimitedThreads;
3580     }
3581 
3582     @SuppressWarnings( "UnusedDeclaration" )
3583     public void setUseUnlimitedThreads( boolean useUnlimitedThreads )
3584     {
3585         this.useUnlimitedThreads = useUnlimitedThreads;
3586     }
3587 
3588     public String getParallel()
3589     {
3590         return parallel;
3591     }
3592 
3593     @SuppressWarnings( "UnusedDeclaration" )
3594     public void setParallel( String parallel )
3595     {
3596         this.parallel = parallel;
3597     }
3598 
3599     public boolean isParallelOptimized()
3600     {
3601         return parallelOptimized;
3602     }
3603 
3604     @SuppressWarnings( "UnusedDeclaration" )
3605     public void setParallelOptimized( boolean parallelOptimized )
3606     {
3607         this.parallelOptimized = parallelOptimized;
3608     }
3609 
3610     public int getThreadCountSuites()
3611     {
3612         return threadCountSuites;
3613     }
3614 
3615     public void setThreadCountSuites( int threadCountSuites )
3616     {
3617         this.threadCountSuites = threadCountSuites;
3618     }
3619 
3620     public int getThreadCountClasses()
3621     {
3622         return threadCountClasses;
3623     }
3624 
3625     public void setThreadCountClasses( int threadCountClasses )
3626     {
3627         this.threadCountClasses = threadCountClasses;
3628     }
3629 
3630     public int getThreadCountMethods()
3631     {
3632         return threadCountMethods;
3633     }
3634 
3635     public void setThreadCountMethods( int threadCountMethods )
3636     {
3637         this.threadCountMethods = threadCountMethods;
3638     }
3639 
3640     public boolean isTrimStackTrace()
3641     {
3642         return trimStackTrace;
3643     }
3644 
3645     @SuppressWarnings( "UnusedDeclaration" )
3646     public void setTrimStackTrace( boolean trimStackTrace )
3647     {
3648         this.trimStackTrace = trimStackTrace;
3649     }
3650 
3651     public List<ArtifactRepository> getProjectRemoteRepositories()
3652     {
3653         return projectRemoteRepositories;
3654     }
3655 
3656     @SuppressWarnings( "UnusedDeclaration" )
3657     public void setProjectRemoteRepositories( List<ArtifactRepository> projectRemoteRepositories )
3658     {
3659         this.projectRemoteRepositories = projectRemoteRepositories;
3660     }
3661 
3662     public List<ArtifactRepository> getRemoteRepositories()
3663     {
3664         return remoteRepositories;
3665     }
3666 
3667     @SuppressWarnings( "UnusedDeclaration" )
3668     public void setRemoteRepositories( List<ArtifactRepository> remoteRepositories )
3669     {
3670         this.remoteRepositories = remoteRepositories;
3671     }
3672 
3673     public boolean isDisableXmlReport()
3674     {
3675         return disableXmlReport;
3676     }
3677 
3678     @SuppressWarnings( "UnusedDeclaration" )
3679     public void setDisableXmlReport( boolean disableXmlReport )
3680     {
3681         this.disableXmlReport = disableXmlReport;
3682     }
3683 
3684 
3685     public boolean isEnableAssertions()
3686     {
3687         return enableAssertions;
3688     }
3689 
3690     public boolean effectiveIsEnableAssertions()
3691     {
3692         if ( getArgLine() != null )
3693         {
3694             List<String> args = asList( getArgLine().split( " " ) );
3695             if ( args.contains( "-da" ) || args.contains( "-disableassertions" ) )
3696             {
3697                 return false;
3698             }
3699         }
3700         return isEnableAssertions();
3701     }
3702 
3703     @SuppressWarnings( "UnusedDeclaration" )
3704     public void setEnableAssertions( boolean enableAssertions )
3705     {
3706         this.enableAssertions = enableAssertions;
3707     }
3708 
3709     public MavenSession getSession()
3710     {
3711         return session;
3712     }
3713 
3714     @SuppressWarnings( "UnusedDeclaration" )
3715     public void setSession( MavenSession session )
3716     {
3717         this.session = session;
3718     }
3719 
3720     public String getObjectFactory()
3721     {
3722         return objectFactory;
3723     }
3724 
3725     @SuppressWarnings( "UnusedDeclaration" )
3726     public void setObjectFactory( String objectFactory )
3727     {
3728         this.objectFactory = objectFactory;
3729     }
3730 
3731     public ToolchainManager getToolchainManager()
3732     {
3733         return toolchainManager;
3734     }
3735 
3736     @SuppressWarnings( "UnusedDeclaration" )
3737     public void setToolchainManager( ToolchainManager toolchainManager )
3738     {
3739         this.toolchainManager = toolchainManager;
3740     }
3741 
3742     public boolean isMavenParallel()
3743     {
3744         return parallelMavenExecution != null && parallelMavenExecution;
3745     }
3746 
3747     public String[] getDependenciesToScan()
3748     {
3749         return dependenciesToScan;
3750     }
3751 
3752     public void setDependenciesToScan( String[] dependenciesToScan )
3753     {
3754         this.dependenciesToScan = dependenciesToScan;
3755     }
3756 
3757     @SuppressWarnings( "UnusedDeclaration" )
3758     void setPluginDescriptor( PluginDescriptor pluginDescriptor )
3759     {
3760         this.pluginDescriptor = pluginDescriptor;
3761     }
3762 
3763     public PluginDescriptor getPluginDescriptor()
3764     {
3765         return pluginDescriptor;
3766     }
3767 
3768     public MavenProject getProject()
3769     {
3770         return project;
3771     }
3772 
3773     @SuppressWarnings( "UnusedDeclaration" )
3774     public void setProject( MavenProject project )
3775     {
3776         this.project = project;
3777     }
3778 
3779     @Override
3780     public File getTestSourceDirectory()
3781     {
3782         return testSourceDirectory;
3783     }
3784 
3785     @Override
3786     public void setTestSourceDirectory( File testSourceDirectory )
3787     {
3788         this.testSourceDirectory = testSourceDirectory;
3789     }
3790 
3791     public String getForkCount()
3792     {
3793         return forkCount;
3794     }
3795 
3796     public boolean isReuseForks()
3797     {
3798         return reuseForks;
3799     }
3800 
3801     public String[] getAdditionalClasspathElements()
3802     {
3803         return additionalClasspathElements;
3804     }
3805 
3806     public void setAdditionalClasspathElements( String[] additionalClasspathElements )
3807     {
3808         this.additionalClasspathElements = additionalClasspathElements;
3809     }
3810 
3811     public String[] getClasspathDependencyExcludes()
3812     {
3813         return classpathDependencyExcludes;
3814     }
3815 
3816     public void setClasspathDependencyExcludes( String[] classpathDependencyExcludes )
3817     {
3818         this.classpathDependencyExcludes = classpathDependencyExcludes;
3819     }
3820 
3821     public String getClasspathDependencyScopeExclude()
3822     {
3823         return classpathDependencyScopeExclude;
3824     }
3825 
3826     public void setClasspathDependencyScopeExclude( String classpathDependencyScopeExclude )
3827     {
3828         this.classpathDependencyScopeExclude = classpathDependencyScopeExclude;
3829     }
3830 
3831     public File getProjectBuildDirectory()
3832     {
3833         return projectBuildDirectory;
3834     }
3835 
3836     public void setProjectBuildDirectory( File projectBuildDirectory )
3837     {
3838         this.projectBuildDirectory = projectBuildDirectory;
3839     }
3840 
3841     protected void logDebugOrCliShowErrors( String s )
3842     {
3843         SurefireHelper.logDebugOrCliShowErrors( s, getConsoleLogger(), cli );
3844     }
3845 
3846     public String getTempDir()
3847     {
3848         return tempDir;
3849     }
3850 
3851     public void setTempDir( String tempDir )
3852     {
3853         this.tempDir = tempDir;
3854     }
3855 
3856     private static String getEffectiveForkMode( String forkMode )
3857     {
3858         if ( "pertest".equalsIgnoreCase( forkMode ) )
3859         {
3860             return FORK_ALWAYS;
3861         }
3862         else if ( "none".equalsIgnoreCase( forkMode ) )
3863         {
3864             return FORK_NEVER;
3865         }
3866         else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
3867                 || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( FORK_PERTHREAD ) )
3868         {
3869             return forkMode;
3870         }
3871         else
3872         {
3873             throw new IllegalArgumentException( "Fork mode " + forkMode + " is not a legal value" );
3874         }
3875     }
3876 
3877     private static final class ClasspathCache
3878     {
3879         private final Map<String, Classpath> classpaths = new HashMap<>( 4 );
3880 
3881         private Classpath getCachedClassPath( @Nonnull String artifactId )
3882         {
3883             return classpaths.get( artifactId );
3884         }
3885 
3886         private void setCachedClasspath( @Nonnull String key, @Nonnull Classpath classpath )
3887         {
3888             classpaths.put( key, classpath );
3889         }
3890 
3891         private Classpath setCachedClasspath( @Nonnull String key, @Nonnull Set<Artifact> artifacts )
3892         {
3893             Collection<String> files = new ArrayList<>();
3894             for ( Artifact artifact : artifacts )
3895             {
3896                 files.add( artifact.getFile().getAbsolutePath() );
3897             }
3898             Classpath classpath = new Classpath( files );
3899             setCachedClasspath( key, classpath );
3900             return classpath;
3901         }
3902     }
3903 }