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