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