You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@maven.apache.org by GitBox <gi...@apache.org> on 2018/07/21 08:30:30 UTC

[GitHub] asfgit closed pull request #165: Merge master to junit5

asfgit closed pull request #165: Merge master to junit5
URL: https://github.com/apache/maven-surefire/pull/165
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 000000000..b3ace123d
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,31 @@
+pipeline {
+    agent none
+    stages {
+        stage('Parallel Unix and Windows Build') {
+            steps {
+                parallel unix: {
+                    node("${env.NIX_LABEL}") {
+                        checkout scm
+                        withEnv(["JAVA_HOME=${tool('JDK 1.8.0_144')}", "PATH+MAVEN=${tool('Maven 3.5.0')}/bin:${env.JAVA_HOME}/bin"]) {
+                            sh "mvn clean install jacoco:report -B -U -e -fae -V -P run-its,jenkins -Dsurefire.useFile=false -Dfailsafe.useFile=false -Dintegration-test-port=8084   \\\"-Djdk.home=${tool('JDK 9 b181')}\\\""
+                        }
+                        jacoco changeBuildStatus: false, execPattern: '**/*.exec', sourcePattern: '**/src/main/java', exclusionPattern: 'pkg/*.class,plexusConflict/*.class,**/surefire570/**/*.class,siblingAggregator/*.class,surefire257/*.class,surefire979/*.class,org/apache/maven/surefire/crb/*.class,org/apache/maven/plugins/surefire/selfdestruct/*.class,org/apache/maven/plugins/surefire/dumppid/*.class,org/apache/maven/plugin/surefire/*.class,org/apache/maven/plugin/failsafe/*.class,jiras/**/*.class,org/apache/maven/surefire/testng/*.class,org/apache/maven/surefire/testprovider/*.class,**/test/*.class,**/org/apache/maven/surefire/group/parse/*.class'
+                        junit healthScaleFactor: 0.0, allowEmptyResults: true, keepLongStdio: true, testResults: '**/surefire-integration-tests/target/failsafe-reports/**/*.xml,**/surefire-integration-tests/target/surefire-reports/**/*.xml,**/maven-*/target/surefire-reports/**/*.xml,**/surefire-*/target/surefire-reports/**/*.xml,**/common-*/target/surefire-reports/**/*.xml'
+                    }
+                },
+                windows: {
+                    node("${env.WIN_LABEL}") {
+                        checkout scm
+                        withEnv(["JAVA_HOME=${tool('JDK 1.8_121 (Windows Only)')}", "PATH+MAVEN=${tool('Maven 3.5.0 (Windows)')}\\bin;${env.JAVA_HOME}\\bin"]) {
+                            bat "mvn clean install jacoco:report -B -U -e -fae -V -P run-its,jenkins -Dsurefire.useFile=false -Dfailsafe.useFile=false -Dintegration-test-port=8084"
+                        }
+                    }
+                }
+            }
+        }
+    }
+    options {
+        buildDiscarder(logRotator(numToKeepStr:'3'))
+        timeout(time: 10, unit: 'HOURS')
+    }
+}
diff --git a/README.md b/README.md
index e7b4b0753..2361f5d01 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
 [![Built with Maven](http://maven.apache.org/images/logos/maven-feather.png)](https://maven.apache.org/surefire/) [![CI](https://img.shields.io/badge/CI-Jenkins-red.svg?style=flat-square)](https://jenkins-ci.org/)
 
+[![forks](https://img.shields.io/github/forks/apache/maven-surefire.svg?style=social&label=Fork)](https://github.com/apache/maven-surefire/)
+
+# The Maven Community
+
 [![chat](https://www.irccloud.com/invite-svg?channel=maven&amp;hostname=irc.freenode.net&amp;port=6697&amp;ssl=1)](https://maven.apache.org/community.html) [Join us @ irc://freenode/maven] or [Webchat with us @channel maven]
 
 # Release Notes
@@ -8,8 +12,6 @@
 
 [JIRA Change Log]
 
-[![tag](http://img.shields.io/github/tag/apache/maven-surefire.svg)](https://github.com/apache/maven-surefire/releases)
-
 Usage of [maven-surefire-plugin], [maven-failsafe-plugin], [maven-surefire-report-plugin].
 
 # Project Documentation
@@ -20,20 +22,23 @@ Usage of [maven-surefire-plugin], [maven-failsafe-plugin], [maven-surefire-repor
 
 [![dependencies](https://www.versioneye.com/java/org.apache.maven.plugins:maven-surefire-plugin/badge.svg?style=plastic)](https://builds.apache.org/job/maven-surefire/depgraph-view/) Maven 2.2.1 Plugin API
 
-[![license](http://img.shields.io/:license-apache-red.svg?style=plastic)](http://www.apache.org/licenses/LICENSE-2.0.html) [![coverage](https://img.shields.io/jenkins/c/https/builds.apache.org/maven-surefire.svg?style=plastic)](https://builds.apache.org/job/maven-surefire/jacoco/) [![tests](https://img.shields.io/jenkins/t/https/builds.apache.org/maven-surefire.svg?style=plastic)](https://builds.apache.org/job/maven-surefire/lastBuild/testReport/) [![Build Status](https://builds.apache.org/job/maven-surefire/badge/icon?style=plastic)](https://builds.apache.org/job/maven-surefire) [![Build Status](https://builds.apache.org/job/maven-surefire-windows/badge/icon?style=plastic)](https://builds.apache.org/job/maven-surefire-windows) [![Build Status](https://builds.apache.org/job/maven-surefire-mvn-2.2.1/badge/icon?style=plastic)](https://builds.apache.org/job/maven-surefire-mvn-2.2.1)
+[![license](http://img.shields.io/:license-apache-red.svg?style=plastic)](http://www.apache.org/licenses/LICENSE-2.0.html) [![tests](https://img.shields.io/jenkins/t/https/builds.apache.org/view/M-R/view/Maven/job/maven-surefire-pipeline/job/master.svg?style=plastic)](https://builds.apache.org/view/M-R/view/Maven/job/maven-surefire-pipeline/job/master/lastBuild/testReport/) [![Build Status](https://builds.apache.org/view/M-R/view/Maven/job/maven-surefire-pipeline/job/master/badge/icon?style=plastic)](https://builds.apache.org/view/M-R/view/Maven/job/maven-surefire-pipeline/job/master/)
 
 # Development Information
 
-Surefire needs to use Maven 3.1.0+ and JDK 1.6+ to be built.
-But in order to run IT tests, you can do:
-* In order to run tests for a release check during the vote the following memory requirements are needed:
-  $ export MAVEN_OPTS="-Xmx768m -XX:MaxPermSize=1g -XX:SoftRefLRUPolicyMSPerMB=50 -Djava.awt.headless=true"
-* $ mvn install site site:stage -P reporting,run-its
+In order to build Surefire project use **Maven 3.1.0+** and **JDK 1.8**.   
+
+But in order to run IT tests, you can do:   
 
-Deploying web site
-------------------
+* In order to run tests for a release check during the vote the following memory requirements are needed:   
+  **(on Linux/Unix)** *export MAVEN_OPTS="-Xmx768m -XX:MaxMetaspaceSize=864m -XX:SoftRefLRUPolicyMSPerMB=50 -Djava.awt.headless=true"*  
+  **(on Windows)** *set MAVEN_OPTS="-Xmx768m -XX:MaxMetaspaceSize=864m -XX:SoftRefLRUPolicyMSPerMB=50 -Djava.awt.headless=true"*    
+* In order to run the build with **JDK 9** **on Windows** (**on Linux/Unix modify system property jdk.home**):  
+  *mvn install site site:stage -P reporting,run-its "-Djdk.home=e:\Program Files\Java\jdk9\"* 
+  
+### Deploying web site
 
-see http://maven.apache.org/developers/website/deploy-component-reference-documentation.html
+See http://maven.apache.org/developers/website/deploy-component-reference-documentation.html
 
 [Join us @ irc://freenode/maven]: https://www.irccloud.com/invite?channel=maven&amp;hostname=irc.freenode.net&amp;port=6697&amp;ssl=1
 [Webchat with us @channel maven]: http://webchat.freenode.net/?channels=%23maven
diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml
index 91c767798..ace323190 100644
--- a/maven-failsafe-plugin/pom.xml
+++ b/maven-failsafe-plugin/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.maven.plugins</groupId>
@@ -44,27 +44,10 @@
   </properties>
 
   <dependencies>
-    <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-plugin-api</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>maven-surefire-common</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.shared</groupId>
-      <artifactId>maven-shared-utils</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.plugin-tools</groupId>
-      <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
-    </dependency>
     <dependency>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-surefire-plugin</artifactId>
@@ -73,6 +56,14 @@
       <type>zip</type>
       <classifier>site-source</classifier>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
@@ -168,8 +159,7 @@
             </goals>
             <configuration>
               <target name="generate-failsafe-test-report">
-                <move file="${project.build.directory}/source-site/resources/xsd/surefire-test-report.xsd"
-                      tofile="${project.build.directory}/source-site/resources/xsd/failsafe-test-report.xsd"/>
+                <move file="${project.build.directory}/source-site/resources/xsd/surefire-test-report.xsd" tofile="${project.build.directory}/source-site/resources/xsd/failsafe-test-report.xsd" />
               </target>
             </configuration>
           </execution>
@@ -196,6 +186,8 @@
               <artifactSet>
                 <includes>
                   <include>org.apache.maven.shared:maven-shared-utils</include>
+                  <include>commons-io:commons-io</include>
+                  <include>org.apache.commons:commons-lang3</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -203,6 +195,14 @@
                   <pattern>org.apache.maven.shared</pattern>
                   <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern>
                 </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.io</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.lang3</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern>
+                </relocation>
               </relocations>
             </configuration>
           </execution>
@@ -282,6 +282,7 @@
                   <projectsDirectory>src/it</projectsDirectory>
                   <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
                   <goals>
+                    <goal>clean</goal>
                     <goal>verify</goal>
                   </goals>
                   <setupIncludes>
@@ -290,7 +291,7 @@
                   <pomIncludes>
                     <pomInclude>*/pom.xml</pomInclude>
                   </pomIncludes>
-                  <postBuildHookScript>verify.bsh</postBuildHookScript>
+                  <postBuildHookScript>verify</postBuildHookScript>
                   <settingsFile>src/it/settings.xml</settingsFile>
                   <skipInvocation>${skipTests}</skipInvocation>
                   <streamLogs>false</streamLogs>
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index c0048d535..3474b164c 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -19,12 +19,7 @@
  * under the License.
  */
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
+import org.apache.maven.artifact.Artifact;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
@@ -33,22 +28,29 @@
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
-import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.surefire.suite.RunResult;
 
-import static org.apache.maven.shared.utils.ReaderFactory.FILE_ENCODING;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils.writeSummary;
 
 /**
  * Run integration tests using Surefire.
  *
  * @author Jason van Zyl
  * @author Stephen Connolly
- * @noinspection JavaDoc,
  */
 @Mojo( name = "integration-test", requiresProject = true, requiresDependencyResolution = ResolutionScope.TEST,
-       defaultPhase = LifecyclePhase.INTEGRATION_TEST, threadSafe = true )
+             defaultPhase = LifecyclePhase.INTEGRATION_TEST, threadSafe = true )
 public class IntegrationTestMojo
-    extends AbstractSurefireMojo
+        extends AbstractSurefireMojo
 {
 
     private static final String FAILSAFE_IN_PROGRESS_CONTEXT_KEY = "failsafe-in-progress";
@@ -56,7 +58,7 @@
     /**
      * The path representing project <em>JAR</em> file, if exists; Otherwise the directory containing generated
      * classes of the project being tested. This will be included after the test classes in the test classpath.
-     * Defaults to built artifact <em>JAR</em> file or ${project.build.outputDirectory}.
+     * Defaults to built artifact <em>JAR</em> file or <code>${project.build.outputDirectory}</code>.
      */
     @Parameter
     private File classesDirectory;
@@ -79,25 +81,28 @@
     @Parameter( defaultValue = "${project.build.directory}/failsafe-reports" )
     private File reportsDirectory;
 
+    @SuppressWarnings( "checkstyle:linelength" )
     /**
-     * Specify this parameter to run individual tests by file name, overriding the <code>includes/excludes</code>
-     * parameters. Each pattern you specify here will be used to create an include pattern formatted like
-     * <code>**&#47;${test}.java</code>, so you can just type "-Dit.test=MyTest" to run a single test called
-     * "foo/MyTest.java".<br/>
-     * This parameter overrides the <code>includes/excludes</code> parameters, and the TestNG <code>suiteXmlFiles</code>
-     * parameter.
-     * <p/>
-     * Since 2.7.3 You can execute a limited number of methods in the test with adding #myMethod or #my*ethod. E.g. type
-     * "-Dit.test=MyTest#myMethod" <b>supported for junit 4.x and testNg</b>
-     * <br/>
-     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):<br/>
-     * "-Dit.test=???IT, !Unstable*, pkg&#47;**&#47;Ci*leIT.java, *IT#test*One+testTwo?????, #fast*+slowTest"<br/>
-     * "-Dit.test=Basic*, !%regex[.*.Unstable.*], !%regex[.*.MyIT.class#one.*|two.*], %regex[#fast.*|slow.*]"<br/>
-     * <br/>
-     * The Parameterized JUnit runner <em>describes</em> test methods using an index in brackets, so the non-regex
-     * method pattern would become: <em>#testMethod[*]</em>. If using <em>@Parameters(name="{index}: fib({0})={1}")</em>
-     * and selecting the index e.g. 5 in pattern, the non-regex method pattern would become <em>#testMethod[5:*]</em>.
-     * <br/>
+     * Specify this parameter to run individual tests by file name, overriding parameter {@code includes} and
+     * {@code excludes}. Each pattern you specify here will be used to create an include pattern formatted like
+     * <code>**{@literal /}${it.test}.java</code>, so you can just type {@code -Dit.test=MyIT} to run
+     * a single test file called "foo/MyIT.java". The test patterns prefixed with a <em>!</em> will be excluded.
+     * <br>
+     * This parameter overrides the parameter {@code includes} and {@code excludes}, and the TestNG parameter
+     * {@code suiteXmlFiles}.
+     * <br>
+     * Since 2.7.3 You can execute a limited number of methods in the test with adding <i>#myMethod</i> or
+     * <i>#my*ethod</i>. E.g. type {@code -Dit.test=MyIT#myMethod} <b>supported for junit 4.x and TestNg.</b>
+     * <br>
+     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
+     * <pre><code>"-Dit.test=???IT, !Unstable*, pkg{@literal /}**{@literal /}Ci*leIT.java, *IT#test*One+testTwo?????, #fast*+slowTest"</code></pre>
+     * or e.g.
+     * <br>
+     * <pre><code>"-Dit.test=Basic*, !%regex[.*.Unstable.*], !%regex[.*.MyIT.class#one.*|two.*], %regex[#fast.*|slow.*]"</code></pre>
+     * <br>
+     * The Parameterized JUnit runner {@code describes} test methods using an index in brackets, so the non-regex
+     * method pattern would become: {@code #testMethod[*]}. If using <code>@Parameters(name="{index}: fib({0})={1}")</code>
+     * and selecting the index e.g. 5 in pattern, the non-regex method pattern would become {@code #testMethod[5:*]}.
      */
     @Parameter( property = "it.test" )
     private String test;
@@ -128,7 +133,7 @@
     private boolean useFile;
 
     /**
-     * Set this to "true" to cause a failure if the none of the tests specified in -Dtest=... are run. Defaults to
+     * Set this to "true" to cause a failure if none of the tests specified in -Dtest=... are run. Defaults to
      * "true".
      *
      * @since 2.12
@@ -139,7 +144,7 @@
     /**
      * Attach a debugger to the forked JVM. If set to "true", the process will suspend and wait for a debugger to attach
      * on port 5005. If set to some other string, that string will be appended to the argLine, allowing you to configure
-     * arbitrary debuggability options (without overwriting the other options specified through the <code>argLine</code>
+     * arbitrary debugging ability options (without overwriting the other options specified through the {@code argLine}
      * parameter).
      *
      * @since 2.4
@@ -159,22 +164,22 @@
     /**
      * Forked process is normally terminated without any significant delay after given tests have completed.
      * If the particular tests started non-daemon Thread(s), the process hangs instead of been properly terminated
-     * by <em>System.exit()</em>. Use this parameter in order to determine the timeout of terminating the process.
+     * by {@code System.exit()}. Use this parameter in order to determine the timeout of terminating the process.
      * <a href="http://maven.apache.org/surefire/maven-failsafe-plugin/examples/shutdown.html">see the documentation:
      * http://maven.apache.org/surefire/maven-failsafe-plugin/examples/shutdown.html</a>
      *
-     * @since 2.19.2
+     * @since 2.20
      */
     @Parameter( property = "failsafe.exitTimeout", defaultValue = "30" )
     private int forkedProcessExitTimeoutInSeconds;
 
     /**
      * Stop executing queued parallel JUnit tests after a certain number of seconds.
-     * <br/>
-     * Example values: "3.5", "4"<br/>
-     * <br/>
+     * <br>
+     * Example values: "3.5", "4"<br>
+     * <br>
      * If set to 0, wait forever, never timing out.
-     * Makes sense with specified <code>parallel</code> different from "none".
+     * Makes sense with specified {@code parallel} different from "none".
      *
      * @since 2.16
      */
@@ -183,40 +188,45 @@
 
     /**
      * Stop executing queued parallel JUnit tests
-     * and <em>interrupt</em> currently running tests after a certain number of seconds.
-     * <br/>
-     * Example values: "3.5", "4"<br/>
-     * <br/>
+     * and <i>interrupt</i> currently running tests after a certain number of seconds.
+     * <br>
+     * Example values: "3.5", "4"<br>
+     * <br>
      * If set to 0, wait forever, never timing out.
-     * Makes sense with specified <code>parallel</code> different from "none".
+     * Makes sense with specified {@code parallel} different from "none".
      *
      * @since 2.16
      */
     @Parameter( property = "failsafe.parallel.forcedTimeout" )
     private double parallelTestsTimeoutForcedInSeconds;
 
+    @SuppressWarnings( "checkstyle:linelength" )
     /**
-     * A list of &lt;include> elements specifying the tests (by pattern) that should be included in testing. When not
-     * specified and when the <code>test</code> parameter is not specified, the default includes will be <code><br/>
-     * &lt;includes><br/>
-     * &nbsp;&lt;include>**&#47;IT*.java&lt;/include><br/>
-     * &nbsp;&lt;include>**&#47;*IT.java&lt;/include><br/>
-     * &nbsp;&lt;include>**&#47;*ITCase.java&lt;/include><br/>
-     * &lt;/includes><br/>
-     * </code>
-     * <p/>
+     * A list of {@literal <include>} elements specifying the test filter (by pattern) of tests which should be
+     * included in testing. If it is not specified and the {@code test} parameter is unspecified as well, the default
+     * includes is
+     * <pre><code>
+     * {@literal <includes>}
+     *     {@literal <include>}**{@literal /}IT*.java{@literal </include>}
+     *     {@literal <include>}**{@literal /}*IT.java{@literal </include>}
+     *     {@literal <include>}**{@literal /}*ITCase.java{@literal </include>}
+     * {@literal </includes>}
+     * </code></pre>
+     * <br>
      * Each include item may also contain a comma-separated sublist of items, which will be treated as multiple
-     * &nbsp;&lt;include> entries.<br/>
-     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):<br/>
-     * &nbsp;&lt;include>%regex[.*[Cat|Dog].*], Basic????, !Unstable*&lt;/include><br/>
-     * &nbsp;&lt;include>%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], pkg&#47;**&#47;*Fast*.java&lt;/include><br/>
-     * <p/>
-     * This parameter is ignored if the TestNG <code>suiteXmlFiles</code> parameter is specified.<br/>
-     * <br/>
-     * <em>Notice that</em> these values are relative to the directory containing generated test classes of the project
-     * being tested. This directory is declared by the parameter <code>testClassesDirectory</code> which defaults
-     * to the POM property <code>${project.build.testOutputDirectory}</code>, typically <em>src/test/java</em>
-     * unless overridden.
+     * {@literal <include>} entries.<br>
+     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
+     * <pre><code>
+     * {@literal <include>}%regex[.*[Cat|Dog].*], Basic????, !Unstable*{@literal </include>}
+     * {@literal <include>}%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], pkg{@literal /}**{@literal /}*Fast*.java{@literal </include>}
+     * </code></pre>
+     * <br>
+     * This parameter is ignored if the TestNG {@code suiteXmlFiles} parameter is specified.<br>
+     * <br>
+     * <b>Notice that</b> these values are relative to the directory containing generated test classes of the project
+     * being tested. This directory is declared by the parameter {@code testClassesDirectory} which defaults
+     * to the POM property <code>${project.build.testOutputDirectory}</code>, typically
+     * <code>{@literal src/test/java}</code> unless overridden.
      */
     @Parameter
     private List<String> includes;
@@ -235,9 +245,9 @@
      * By default, Surefire forks your tests using a manifest-only JAR; set this parameter to "false" to force it to
      * launch your tests with a plain old Java classpath. (See the
      * <a href="http://maven.apache.org/plugins/maven-failsafe-plugin/examples/class-loading.html">
-     *     http://maven.apache.org/plugins/maven-failsafe-plugin/examples/class-loading.html</a>
+     * http://maven.apache.org/plugins/maven-failsafe-plugin/examples/class-loading.html</a>
      * for a more detailed explanation of manifest-only JARs and their benefits.)
-     * <br/>
+     * <br>
      * Beware, setting this to "false" may cause your tests to fail on Windows if your classpath is too long.
      *
      * @since 2.4.3
@@ -247,6 +257,9 @@
 
     /**
      * The character encoding scheme to be applied.
+     * Deprecated since 2.20.1 and used encoding UTF-8 in <tt>failsafe-summary.xml</tt>.
+     *
+     * @deprecated since of 2.20.1
      */
     @Parameter( property = "encoding", defaultValue = "${project.reporting.outputEncoding}" )
     private String encoding;
@@ -261,10 +274,10 @@
     private int rerunFailingTestsCount;
 
     /**
-     * (TestNG) List of &lt;suiteXmlFile> elements specifying TestNG suite xml file locations. Note that
-     * <code>suiteXmlFiles</code> is incompatible with several other parameters of this plugin, like
-     * <code>includes/excludes</code>.<br/>
-     * This parameter is ignored if the <code>test</code> parameter is specified (allowing you to run a single test
+     * (TestNG) List of &lt;suiteXmlFile&gt; elements specifying TestNG suite xml file locations. Note that
+     * {@code suiteXmlFiles} is incompatible with several other parameters of this plugin, like
+     * {@code includes} and {@code excludes}.<br>
+     * This parameter is ignored if the {@code test} parameter is specified (allowing you to run a single test
      * instead of an entire suite).
      *
      * @since 2.2
@@ -273,26 +286,26 @@
     private File[] suiteXmlFiles;
 
     /**
-     * Defines the order the tests will be run in. Supported values are "alphabetical", "reversealphabetical", "random",
-     * "hourly" (alphabetical on even hours, reverse alphabetical on odd hours), "failedfirst", "balanced" and
-     * "filesystem".
-     * <br/>
-     * <br/>
+     * Defines the order the tests will be run in. Supported values are {@code alphabetical},
+     * {@code reversealphabetical}, {@code random}, {@code hourly} (alphabetical on even hours, reverse alphabetical
+     * on odd hours), {@code failedfirst}, {@code balanced} and {@code filesystem}.
+     * <br>
+     * <br>
      * Odd/Even for hourly is determined at the time the of scanning the classpath, meaning it could change during a
      * multi-module build.
-     * <br/>
-     * <br/>
+     * <br>
+     * <br>
      * Failed first will run tests that failed on previous run first, as well as new tests for this run.
-     * <br/>
-     * <br/>
+     * <br>
+     * <br>
      * Balanced is only relevant with parallel=classes, and will try to optimize the run-order of the tests reducing the
      * overall execution time. Initially a statistics file is created and every next test run will reorder classes.
-     * <br/>
-     * <br/>
-     * Note that the statistics are stored in a file named .surefire-XXXXXXXXX beside pom.xml, and should not be checked
-     * into version control. The "XXXXX" is the SHA1 checksum of the entire surefire configuration, so different
-     * configurations will have different statistics files, meaning if you change any config settings you will re-run
-     * once before new statistics data can be established.
+     * <br>
+     * <br>
+     * Note that the statistics are stored in a file named <b>.surefire-XXXXXXXXX</b> beside <i>pom.xml</i> and
+     * should not be checked into version control. The "XXXXX" is the SHA1 checksum of the entire surefire
+     * configuration, so different configurations will have different statistics files, meaning if you change any
+     * configuration settings you will re-run once before new statistics data can be established.
      *
      * @since 2.7
      */
@@ -300,33 +313,39 @@
     private String runOrder;
 
     /**
-     * A file containing include patterns. Blank lines, or lines starting with # are ignored. If {@code includes} are
-     * also specified, these patterns are appended. Example with path, simple and regex includes:<br/>
-     * &#042;&#047;test/*<br/>
-     * &#042;&#042;&#047;NotIncludedByDefault.java<br/>
-     * %regex[.*Test.*|.*Not.*]<br/>
+     * A file containing include patterns, each in a next line. Blank lines, or lines starting with # are ignored.
+     * If {@code includes} are also specified, these patterns are appended. Example with path, simple and regex
+     * includes:
+     * <pre><code>
+     * *{@literal /}it{@literal /}*
+     * **{@literal /}NotIncludedByDefault.java
+     * %regex[.*IT.*|.*Not.*]
+     * </code></pre>
      */
     @Parameter( property = "failsafe.includesFile" )
     private File includesFile;
 
     /**
-     * A file containing exclude patterns. Blank lines, or lines starting with # are ignored. If {@code excludes} are
-     * also specified, these patterns are appended. Example with path, simple and regex excludes:<br/>
-     * &#042;&#047;test/*<br/>
-     * &#042;&#042;&#047;DontRunTest.*<br/>
-     * %regex[.*Test.*|.*Not.*]<br/>
+     * A file containing exclude patterns, each in a next line. Blank lines, or lines starting with # are ignored.
+     * If {@code excludes} are also specified, these patterns are appended.
+     * Example with path, simple and regex excludes:
+     * <pre><code>
+     * *{@literal /}it{@literal /}*
+     * **{@literal /}DontRunIT.*
+     * %regex[.*IT.*|.*Not.*]
+     * </code></pre>
      */
     @Parameter( property = "failsafe.excludesFile" )
     private File excludesFile;
 
     /**
      * Set to error/failure count in order to skip remaining tests.
-     * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br/>
-     * Enable with system property -Dfailsafe.skipAfterFailureCount=1 or any number greater than zero.
-     * Defaults to "0".<br/>
-     * See the prerequisites and limitations in documentation:<br/>
+     * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br>
+     * Enable with system property {@code -Dfailsafe.skipAfterFailureCount=1} or any number greater than zero.
+     * Defaults to "0".<br>
+     * See the prerequisites and limitations in documentation:<br>
      * <a href="http://maven.apache.org/plugins/maven-failsafe-plugin/examples/skip-after-failure.html">
-     *     http://maven.apache.org/plugins/maven-failsafe-plugin/examples/skip-after-failure.html</a>
+     * http://maven.apache.org/plugins/maven-failsafe-plugin/examples/skip-after-failure.html</a>
      *
      * @since 2.19
      */
@@ -334,26 +353,34 @@
     private int skipAfterFailureCount;
 
     /**
-     * After the plugin process is shutdown by sending SIGTERM signal (CTRL+C), SHUTDOWN command is received by every
-     * forked JVM. By default (shutdown=testset) forked JVM would not continue with new test which means that
-     * the current test may still continue to run.<br/>
-     * The parameter can be configured with other two values "exit" and "kill".<br/>
-     * Using "exit" forked JVM executes System.exit(1) after the plugin process has received SIGTERM signal.<br/>
-     * Using "kill" the JVM executes Runtime.halt(1) and kills itself.
+     * After the plugin process is shutdown by sending <i>SIGTERM signal (CTRL+C)</i>, <i>SHUTDOWN command</i> is
+     * received by every forked JVM.
+     * <br>
+     * By default ({@code shutdown=testset}) forked JVM would not continue with new test which means that
+     * the current test may still continue to run.
+     * <br>
+     * The parameter can be configured with other two values {@code exit} and {@code kill}.
+     * <br>
+     * Using {@code exit} forked JVM executes {@code System.exit(1)} after the plugin process has received
+     * <i>SIGTERM signal</i>.
+     * <br>
+     * Using {@code kill} the JVM executes {@code Runtime.halt(1)} and kills itself.
      *
      * @since 2.19
      */
     @Parameter( property = "failsafe.shutdown", defaultValue = "testset" )
     private String shutdown;
 
+    @Override
     protected int getRerunFailingTestsCount()
     {
         return rerunFailingTestsCount;
     }
 
+    @Override
     @SuppressWarnings( "unchecked" )
     protected void handleSummary( RunResult summary, Exception firstForkException )
-        throws MojoExecutionException, MojoFailureException
+            throws MojoExecutionException, MojoFailureException
     {
         File summaryFile = getSummaryFile();
         if ( !summaryFile.getParentFile().isDirectory() )
@@ -365,9 +392,9 @@ protected void handleSummary( RunResult summary, Exception firstForkException )
         try
         {
             Object token = getPluginContext().get( FAILSAFE_IN_PROGRESS_CONTEXT_KEY );
-            summary.writeSummary( summaryFile, token != null, getEncodingOrDefault() );
+            writeSummary( summary, summaryFile, token != null );
         }
-        catch ( IOException e )
+        catch ( Exception e )
         {
             throw new MojoExecutionException( e.getMessage(), e );
         }
@@ -375,22 +402,6 @@ protected void handleSummary( RunResult summary, Exception firstForkException )
         getPluginContext().put( FAILSAFE_IN_PROGRESS_CONTEXT_KEY, FAILSAFE_IN_PROGRESS_CONTEXT_KEY );
     }
 
-    private String getEncodingOrDefault()
-    {
-        if ( StringUtils.isEmpty( encoding ) )
-        {
-            getConsoleLogger().warning( "File encoding has not been set, using platform encoding "
-                + FILE_ENCODING
-                + ", i.e. build is platform dependent! The file encoding for reports output files "
-                + "should be provided by the POM property ${project.reporting.outputEncoding}." );
-            return FILE_ENCODING;
-        }
-        else
-        {
-            return encoding;
-        }
-    }
-
     private boolean isJarArtifact( File artifactFile )
     {
         return artifactFile != null && artifactFile.isFile() && artifactFile.getName().toLowerCase().endsWith( ".jar" );
@@ -408,17 +419,20 @@ private static File toAbsoluteCanonical( File f )
         }
     }
 
+    @Override
     @SuppressWarnings( "deprecation" )
     protected boolean isSkipExecution()
     {
         return isSkip() || isSkipTests() || isSkipITs() || isSkipExec();
     }
 
+    @Override
     protected String getPluginName()
     {
         return "failsafe";
     }
 
+    @Override
     protected String[] getDefaultIncludes()
     {
         return new String[]{ "**/IT*.java", "**/*IT.java", "**/*ITCase.java" };
@@ -430,11 +444,20 @@ protected String getReportSchemaLocation()
         return "https://maven.apache.org/surefire/maven-failsafe-plugin/xsd/failsafe-test-report.xsd";
     }
 
+    @Override
+    protected Artifact getMojoArtifact()
+    {
+        final Map<String, Artifact> pluginArtifactMap = getPluginArtifactMap();
+        return pluginArtifactMap.get( "org.apache.maven.plugins:maven-failsafe-plugin" );
+    }
+
+    @Override
     public boolean isSkipTests()
     {
         return skipTests;
     }
 
+    @Override
     public void setSkipTests( boolean skipTests )
     {
         this.skipTests = skipTests;
@@ -450,6 +473,7 @@ public void setSkipITs( boolean skipITs )
         this.skipITs = skipITs;
     }
 
+    @Override
     @SuppressWarnings( "deprecation" )
     @Deprecated
     public boolean isSkipExec()
@@ -457,6 +481,7 @@ public boolean isSkipExec()
         return skipExec;
     }
 
+    @Override
     @SuppressWarnings( "deprecation" )
     @Deprecated
     public void setSkipExec( boolean skipExec )
@@ -464,31 +489,37 @@ public void setSkipExec( boolean skipExec )
         this.skipExec = skipExec;
     }
 
+    @Override
     public boolean isSkip()
     {
         return skip;
     }
 
+    @Override
     public void setSkip( boolean skip )
     {
         this.skip = skip;
     }
 
+    @Override
     public File getBasedir()
     {
         return basedir;
     }
 
+    @Override
     public void setBasedir( File basedir )
     {
         this.basedir = basedir;
     }
 
+    @Override
     public File getTestClassesDirectory()
     {
         return testClassesDirectory;
     }
 
+    @Override
     public void setTestClassesDirectory( File testClassesDirectory )
     {
         this.testClassesDirectory = testClassesDirectory;
@@ -499,6 +530,7 @@ public void setTestClassesDirectory( File testClassesDirectory )
      * {@link #useSystemClassLoader} is ignored and the {@link org.apache.maven.surefire.booter.IsolatedClassLoader} is
      * used instead. See the resolution of {@link #getClassLoaderConfiguration() ClassLoaderConfiguration}.
      */
+    @Override
     public File getClassesDirectory()
     {
         File artifact = getProject().getArtifact().getFile();
@@ -506,6 +538,7 @@ public File getClassesDirectory()
         return isDefaultClsDir ? ( isJarArtifact( artifact ) ? artifact : defaultClassesDirectory ) : classesDirectory;
     }
 
+    @Override
     public void setClassesDirectory( File classesDirectory )
     {
         this.classesDirectory = toAbsoluteCanonical( classesDirectory );
@@ -516,21 +549,25 @@ public void setDefaultClassesDirectory( File defaultClassesDirectory )
         this.defaultClassesDirectory = toAbsoluteCanonical( defaultClassesDirectory );
     }
 
+    @Override
     public File getReportsDirectory()
     {
         return reportsDirectory;
     }
 
+    @Override
     public void setReportsDirectory( File reportsDirectory )
     {
         this.reportsDirectory = reportsDirectory;
     }
 
+    @Override
     public String getTest()
     {
         return test;
     }
 
+    @Override
     public void setTest( String test )
     {
         this.test = test;
@@ -546,101 +583,121 @@ public void setSummaryFile( File summaryFile )
         this.summaryFile = summaryFile;
     }
 
+    @Override
     public boolean isPrintSummary()
     {
         return printSummary;
     }
 
+    @Override
     public void setPrintSummary( boolean printSummary )
     {
         this.printSummary = printSummary;
     }
 
+    @Override
     public String getReportFormat()
     {
         return reportFormat;
     }
 
+    @Override
     public void setReportFormat( String reportFormat )
     {
         this.reportFormat = reportFormat;
     }
 
+    @Override
     public boolean isUseFile()
     {
         return useFile;
     }
 
+    @Override
     public void setUseFile( boolean useFile )
     {
         this.useFile = useFile;
     }
 
+    @Override
     public String getDebugForkedProcess()
     {
         return debugForkedProcess;
     }
 
+    @Override
     public void setDebugForkedProcess( String debugForkedProcess )
     {
         this.debugForkedProcess = debugForkedProcess;
     }
 
+    @Override
     public int getForkedProcessTimeoutInSeconds()
     {
         return forkedProcessTimeoutInSeconds;
     }
 
+    @Override
     public void setForkedProcessTimeoutInSeconds( int forkedProcessTimeoutInSeconds )
     {
         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
     }
 
+    @Override
     public int getForkedProcessExitTimeoutInSeconds()
     {
         return forkedProcessExitTimeoutInSeconds;
     }
 
+    @Override
     public void setForkedProcessExitTimeoutInSeconds( int forkedProcessExitTimeoutInSeconds )
     {
         this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds;
     }
 
+    @Override
     public double getParallelTestsTimeoutInSeconds()
     {
         return parallelTestsTimeoutInSeconds;
     }
 
+    @Override
     public void setParallelTestsTimeoutInSeconds( double parallelTestsTimeoutInSeconds )
     {
         this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds;
     }
 
+    @Override
     public double getParallelTestsTimeoutForcedInSeconds()
     {
         return parallelTestsTimeoutForcedInSeconds;
     }
 
+    @Override
     public void setParallelTestsTimeoutForcedInSeconds( double parallelTestsTimeoutForcedInSeconds )
     {
         this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds;
     }
 
+    @Override
     public boolean isUseSystemClassLoader()
     {
         return useSystemClassLoader;
     }
 
+    @Override
     public void setUseSystemClassLoader( boolean useSystemClassLoader )
     {
         this.useSystemClassLoader = useSystemClassLoader;
     }
 
+    @Override
     public boolean isUseManifestOnlyJar()
     {
         return useManifestOnlyJar;
     }
 
+    @Override
     public void setUseManifestOnlyJar( boolean useManifestOnlyJar )
     {
         this.useManifestOnlyJar = useManifestOnlyJar;
@@ -658,27 +715,32 @@ public void setTestFailureIgnore( boolean testFailureIgnore )
         // ignore
     }
 
+    @Override
     protected void addPluginSpecificChecksumItems( ChecksumCalculator checksum )
     {
         checksum.add( skipITs );
         checksum.add( summaryFile );
     }
 
+    @Override
     public Boolean getFailIfNoSpecifiedTests()
     {
         return failIfNoSpecifiedTests;
     }
 
+    @Override
     public void setFailIfNoSpecifiedTests( boolean failIfNoSpecifiedTests )
     {
         this.failIfNoSpecifiedTests = failIfNoSpecifiedTests;
     }
 
+    @Override
     public int getSkipAfterFailureCount()
     {
         return skipAfterFailureCount;
     }
 
+    @Override
     public String getShutdown()
     {
         return shutdown;
@@ -696,22 +758,26 @@ public void setIncludes( List<String> includes )
         this.includes = includes;
     }
 
+    @Override
     public File[] getSuiteXmlFiles()
     {
         return suiteXmlFiles.clone();
     }
 
+    @Override
     @SuppressWarnings( "UnusedDeclaration" )
     public void setSuiteXmlFiles( File[] suiteXmlFiles )
     {
         this.suiteXmlFiles = suiteXmlFiles.clone();
     }
 
+    @Override
     public String getRunOrder()
     {
         return runOrder;
     }
 
+    @Override
     @SuppressWarnings( "UnusedDeclaration" )
     public void setRunOrder( String runOrder )
     {
@@ -741,4 +807,9 @@ protected final boolean hasSuiteXmlFiles()
     {
         return suiteXmlFiles != null && suiteXmlFiles.length != 0;
     }
+
+    static Charset toCharset( String encoding )
+    {
+        return Charset.forName( Charset.isSupported( encoding ) ? encoding : encoding.toUpperCase( Locale.ROOT ) );
+    }
 }
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/VerifyMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/VerifyMojo.java
index 37199a3b3..b96acb145 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/VerifyMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/VerifyMojo.java
@@ -19,18 +19,11 @@
  * under the License.
  */
 
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Collection;
-
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils;
 import org.apache.maven.plugin.surefire.SurefireHelper;
 import org.apache.maven.plugin.surefire.SurefireReportParameters;
 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
@@ -41,11 +34,11 @@
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.suite.RunResult;
 
+import java.io.File;
+import java.util.Collection;
+
 import static org.apache.maven.plugin.surefire.SurefireHelper.reportExecution;
-import static org.apache.maven.shared.utils.ReaderFactory.FILE_ENCODING;
 import static org.apache.maven.shared.utils.StringUtils.capitalizeFirstLetter;
-import static org.apache.maven.shared.utils.StringUtils.isEmpty;
-import static org.apache.maven.shared.utils.io.IOUtil.close;
 import static org.apache.maven.surefire.suite.RunResult.noTestsRun;
 
 /**
@@ -54,10 +47,11 @@
  * @author Stephen Connolly
  * @author Jason van Zyl
  */
+@SuppressWarnings( "unused" )
 @Mojo( name = "verify", defaultPhase = LifecyclePhase.VERIFY, requiresProject = true, threadSafe = true )
 public class VerifyMojo
-    extends AbstractMojo
-    implements SurefireReportParameters
+        extends AbstractMojo
+        implements SurefireReportParameters
 {
 
     /**
@@ -125,16 +119,12 @@
 
     /**
      * The summary file to read integration test results from.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "${project.build.directory}/failsafe-reports/failsafe-summary.xml", required = true )
     private File summaryFile;
 
     /**
      * Additional summary files to read integration test results from.
-     *
-     * @noinspection UnusedDeclaration, MismatchedReadAndWriteOfArray
      * @since 2.6
      */
     @Parameter
@@ -150,8 +140,9 @@
 
     /**
      * The character encoding scheme to be applied.
+     * Deprecated since 2.20.1 and used encoding UTF-8 in <tt>failsafe-summary.xml</tt>.
      *
-     * @noinspection UnusedDeclaration
+     * @deprecated since of 2.20.1
      */
     @Parameter( property = "encoding", defaultValue = "${project.reporting.outputEncoding}" )
     private String encoding;
@@ -166,50 +157,35 @@
 
     private volatile PluginConsoleLogger consoleLogger;
 
+    @Override
     public void execute()
-        throws MojoExecutionException, MojoFailureException
+            throws MojoExecutionException, MojoFailureException
     {
         cli = commandLineOptions();
         if ( verifyParameters() )
         {
-            logDebugOrCliShowErrors(
-                capitalizeFirstLetter( getPluginName() ) + " report directory: " + getReportsDirectory() );
+            logDebugOrCliShowErrors( capitalizeFirstLetter( getPluginName() )
+                                             + " report directory: " + getReportsDirectory() );
 
             RunResult summary;
             try
             {
-                final String encoding;
-                if ( isEmpty( this.encoding ) )
-                {
-                    getConsoleLogger()
-                            .warning( "File encoding has not been set, using platform encoding "
-                                              + FILE_ENCODING
-                                              + ", i.e. build is platform dependent! The file encoding for "
-                                              + "reports output files should be provided by the POM property "
-                                              + "${project.reporting.outputEncoding}." );
-                    encoding = FILE_ENCODING;
-                }
-                else
-                {
-                    encoding = this.encoding;
-                }
-
-                summary = existsSummaryFile() ? readSummary( encoding, summaryFile ) : noTestsRun();
+                summary = existsSummaryFile() ? readSummary( summaryFile ) : noTestsRun();
 
                 if ( existsSummaryFiles() )
                 {
                     for ( final File summaryFile : summaryFiles )
                     {
-                        summary = summary.aggregate( readSummary( encoding, summaryFile ) );
+                        summary = summary.aggregate( readSummary( summaryFile ) );
                     }
                 }
             }
-            catch ( IOException e )
+            catch ( Exception e )
             {
                 throw new MojoExecutionException( e.getMessage(), e );
             }
 
-            reportExecution( this, summary, getConsoleLogger() );
+            reportExecution( this, summary, getConsoleLogger(), null );
         }
     }
 
@@ -228,29 +204,13 @@ private PluginConsoleLogger getConsoleLogger()
         return consoleLogger;
     }
 
-    private RunResult readSummary( String encoding, File summaryFile )
-        throws IOException
+    private RunResult readSummary( File summaryFile ) throws Exception
     {
-        FileInputStream fileInputStream = null;
-        BufferedInputStream bufferedInputStream = null;
-        Reader reader = null;
-        try
-        {
-            fileInputStream = new FileInputStream( summaryFile );
-            bufferedInputStream = new BufferedInputStream( fileInputStream );
-            reader = new InputStreamReader( bufferedInputStream, encoding );
-            return RunResult.fromInputStream( bufferedInputStream, encoding );
-        }
-        finally
-        {
-            close( reader );
-            close( bufferedInputStream );
-            close( fileInputStream );
-        }
+        return FailsafeSummaryXmlUtils.toRunResult( summaryFile );
     }
 
     protected boolean verifyParameters()
-        throws MojoFailureException
+            throws MojoFailureException
     {
         if ( isSkip() || isSkipTests() || isSkipITs() || isSkipExec() )
         {
@@ -285,11 +245,13 @@ protected String getPluginName()
         return null;
     }
 
+    @Override
     public boolean isSkipTests()
     {
         return skipTests;
     }
 
+    @Override
     public void setSkipTests( boolean skipTests )
     {
         this.skipTests = skipTests;
@@ -305,73 +267,87 @@ public void setSkipITs( boolean skipITs )
         this.skipITs = skipITs;
     }
 
+    @Override
     @Deprecated
     public boolean isSkipExec()
     {
         return skipExec;
     }
 
+    @Override
     @Deprecated
     public void setSkipExec( boolean skipExec )
     {
         this.skipExec = skipExec;
     }
 
+    @Override
     public boolean isSkip()
     {
         return skip;
     }
 
+    @Override
     public void setSkip( boolean skip )
     {
         this.skip = skip;
     }
 
+    @Override
     public boolean isTestFailureIgnore()
     {
         return testFailureIgnore;
     }
 
+    @Override
     public void setTestFailureIgnore( boolean testFailureIgnore )
     {
         this.testFailureIgnore = testFailureIgnore;
     }
 
+    @Override
     public File getBasedir()
     {
         return basedir;
     }
 
+    @Override
     public void setBasedir( File basedir )
     {
         this.basedir = basedir;
     }
 
+    @Override
     public File getTestClassesDirectory()
     {
         return testClassesDirectory;
     }
 
+    @Override
     public void setTestClassesDirectory( File testClassesDirectory )
     {
         this.testClassesDirectory = testClassesDirectory;
     }
 
+    @Override
     public File getReportsDirectory()
     {
         return reportsDirectory;
     }
 
+    @Override
     public void setReportsDirectory( File reportsDirectory )
     {
         this.reportsDirectory = reportsDirectory;
     }
 
+    @Override
     public Boolean getFailIfNoTests()
     {
         return failIfNoTests;
     }
 
+    @Override
     public void setFailIfNoTests( boolean failIfNoTests )
     {
         this.failIfNoTests = failIfNoTests;
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/util/FailsafeSummaryXmlUtils.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/util/FailsafeSummaryXmlUtils.java
new file mode 100644
index 000000000..d06da13b3
--- /dev/null
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/util/FailsafeSummaryXmlUtils.java
@@ -0,0 +1,144 @@
+package org.apache.maven.plugin.failsafe.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.surefire.suite.RunResult;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+import static java.lang.Boolean.parseBoolean;
+import static java.lang.Integer.parseInt;
+import static org.apache.commons.lang3.StringEscapeUtils.escapeXml10;
+import static org.apache.commons.lang3.StringEscapeUtils.unescapeXml;
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public final class FailsafeSummaryXmlUtils
+{
+    private static final String FAILSAFE_SUMMARY_XML_SCHEMA_LOCATION =
+            "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/failsafe-summary.xsd";
+
+    private static final String FAILSAFE_SUMMARY_XML_NIL_ATTR =
+            " xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
+
+    private static final String FAILSAFE_SUMMARY_XML_TEMPLATE =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+                    + "<failsafe-summary xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
+                    + " xsi:noNamespaceSchemaLocation=\"" + FAILSAFE_SUMMARY_XML_SCHEMA_LOCATION + "\""
+                    + " result=\"%s\" timeout=\"%s\">\n"
+                    + "    <completed>%d</completed>\n"
+                    + "    <errors>%d</errors>\n"
+                    + "    <failures>%d</failures>\n"
+                    + "    <skipped>%d</skipped>\n"
+                    + "    <failureMessage%s>%s</failureMessage>\n"
+                    + "</failsafe-summary>";
+
+    private FailsafeSummaryXmlUtils()
+    {
+        throw new IllegalStateException( "No instantiable constructor." );
+    }
+
+    public static RunResult toRunResult( File failsafeSummaryXml ) throws Exception
+    {
+        XPathFactory xpathFactory = XPathFactory.newInstance();
+        XPath xpath = xpathFactory.newXPath();
+
+        FileInputStream is = new FileInputStream( failsafeSummaryXml );
+
+        try
+        {
+            Node root = (Node) xpath.evaluate( "/", new InputSource( is ), XPathConstants.NODE );
+
+            String completed = xpath.evaluate( "/failsafe-summary/completed", root );
+            String errors = xpath.evaluate( "/failsafe-summary/errors", root );
+            String failures = xpath.evaluate( "/failsafe-summary/failures", root );
+            String skipped = xpath.evaluate( "/failsafe-summary/skipped", root );
+            String failureMessage = xpath.evaluate( "/failsafe-summary/failureMessage", root );
+            String timeout = xpath.evaluate( "/failsafe-summary/@timeout", root );
+
+            return new RunResult( parseInt( completed ), parseInt( errors ), parseInt( failures ), parseInt( skipped ),
+                                        isBlank( failureMessage ) ? null : unescapeXml( failureMessage ),
+                                        parseBoolean( timeout )
+            );
+        }
+        finally
+        {
+            is.close();
+        }
+    }
+
+    public static void fromRunResultToFile( RunResult fromRunResult, File toFailsafeSummaryXml )
+            throws IOException
+    {
+        String failure = fromRunResult.getFailure();
+        String xml = String.format( Locale.ROOT, FAILSAFE_SUMMARY_XML_TEMPLATE,
+                                          fromRunResult.getFailsafeCode(),
+                                          String.valueOf( fromRunResult.isTimeout() ),
+                                          fromRunResult.getCompletedCount(),
+                                          fromRunResult.getErrors(),
+                                          fromRunResult.getFailures(),
+                                          fromRunResult.getSkipped(),
+                                          isBlank( failure ) ? FAILSAFE_SUMMARY_XML_NIL_ATTR : "",
+                                          isBlank( failure ) ? "" : escapeXml10( failure )
+        );
+
+        FileOutputStream os = new FileOutputStream( toFailsafeSummaryXml );
+        try
+        {
+            IOUtils.write( xml, os, UTF_8 );
+        }
+        finally
+        {
+            os.close();
+        }
+    }
+
+    public static void writeSummary( RunResult mergedSummary, File mergedSummaryFile, boolean inProgress )
+            throws Exception
+    {
+        if ( !mergedSummaryFile.getParentFile().isDirectory() )
+        {
+            //noinspection ResultOfMethodCallIgnored
+            mergedSummaryFile.getParentFile().mkdirs();
+        }
+
+        if ( mergedSummaryFile.exists() && inProgress )
+        {
+            RunResult runResult = toRunResult( mergedSummaryFile );
+            mergedSummary = mergedSummary.aggregate( runResult );
+        }
+
+        fromRunResultToFile( mergedSummary, mergedSummaryFile );
+    }
+}
diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
index cd69d68d0..8414d6b00 100644
--- a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
+++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
@@ -1 +1 @@
-package org.apache.maven.plugin.failsafe;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.project.MavenProject;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.fest.assertions.Assertions.assertThat;

/**
 * @since 2.19.2
 */
public class IntegrationTestMojoTest
{
    private IntegrationTestMojo mojo;

    @Before
    public void init() throws InvalidVersionSpecificationException, IOException
    {
        Artifact artifact = new DefaultArtifact( "g", "a", createFromVersionSpec( "1.0" ), "compile", "jar", "", null );
        artifact.setFile( new File( "./target/tmp/a-1.0.jar" ) );
        new File( "./target/tmp" ).mkdir();
        artifact.getFile().createNewFile();
        mojo = spy( IntegrationTestMojo.class );
        MavenProject project = mock( MavenProject.class );
        when( project.getArtifact() ).thenReturn( artifact );
        when( mojo.getProject() ).thenReturn( project );
    }

    @Test
    public void shouldBeJar()
    {
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "a-1.0.jar" );
    }

    @Test
    public void shouldBeAnotherJar()
    {
        mojo.setClassesDirectory( new File( "./target/another-1.0.jar" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "another-1.0.jar" );
    }

    @Test
    public void shouldBeClasses()
    {
        mojo.setClassesDirectory( new File( "./target/classes" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "classes" );
    }
}
\ No newline at end of file
+package org.apache.maven.plugin.failsafe;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.project.MavenProject;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.fest.assertions.Assertions.assertThat;

/**
 * @since 2.20
 */
public class IntegrationTestMojoTest
{
    private IntegrationTestMojo mojo;

    @Before
    public void init() throws InvalidVersionSpecificationException, IOException
    {
        Artifact artifact = new DefaultArtifact( "g", "a", createFromVersionSpec( "1.0" ), "compile", "jar", "", null );
        artifact.setFile( new File( "./target/tmp/a-1.0.jar" ) );
        new File( "./target/tmp" ).mkdir();
        artifact.getFile().createNewFile();
        mojo = spy( IntegrationTestMojo.class );
        MavenProject project = mock( MavenProject.class );
        when( project.getArtifact() ).thenReturn( artifact );
        when( mojo.getProject() ).thenReturn( project );
    }

    @Test
    public void shouldBeJar()
    {
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "a-1.0.jar" );
    }

    @Test
    public void shouldBeAnotherJar()
    {
        mojo.setClassesDirectory( new File( "./target/another-1.0.jar" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "another-1.0.jar" );
    }

    @Test
    public void shouldBeClasses()
    {
        mojo.setClassesDirectory( new File( "./target/classes" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "classes" );
    }
}
\ No newline at end of file
diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java
new file mode 100644
index 000000000..96708a8a5
--- /dev/null
+++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/MarshallerUnmarshallerTest.java
@@ -0,0 +1,99 @@
+package org.apache.maven.plugin.failsafe;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils;
+import org.apache.maven.surefire.suite.RunResult;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class MarshallerUnmarshallerTest
+{
+    @Test
+    public void shouldUnmarshallExistingXmlFile() throws Exception
+    {
+        File xml = new File( "target/test-classes/org/apache/maven/plugin/failsafe/failsafe-summary.xml" );
+        RunResult summary = FailsafeSummaryXmlUtils.toRunResult( xml );
+
+        assertThat( summary.getCompletedCount() )
+                .isEqualTo( 7 );
+
+        assertThat( summary.getErrors() )
+                .isEqualTo( 1 );
+
+        assertThat( summary.getFailures() )
+                .isEqualTo( 2 );
+
+        assertThat( summary.getSkipped() )
+                .isEqualTo( 3 );
+
+        assertThat( summary.getFailure() )
+                .contains( "There was an error in the forked processtest "
+                                   + "subsystem#no method RuntimeException Hi There!"
+                );
+
+        assertThat( summary.getFailure() )
+                .contains( "There was an error in the forked processtest "
+                                   + "subsystem#no method RuntimeException Hi There! $&>>"
+                                   + "\n\tat org.apache.maven.plugin.surefire.booterclient.ForkStarter"
+                                   + ".awaitResultsDone(ForkStarter.java:489)"
+                );
+    }
+
+    @Test
+    public void shouldMarshallAndUnmarshallSameXml() throws Exception
+    {
+        RunResult expected =
+                new RunResult( 7, 1, 2, 3, 2,
+                                     "There was an error in the forked processtest "
+                                             + "subsystem#no method RuntimeException Hi There! $&>>"
+                                             + "\n\tat org.apache.maven.plugin.surefire.booterclient.ForkStarter"
+                                             + ".awaitResultsDone(ForkStarter.java:489)", true );
+
+        File xml = File.createTempFile( "failsafe-summary", ".xml" );
+        FailsafeSummaryXmlUtils.writeSummary( expected, xml, false );
+
+        RunResult actual = FailsafeSummaryXmlUtils.toRunResult( xml );
+
+        assertThat( actual.getFailures() )
+                .isEqualTo( expected.getFailures() );
+
+        assertThat( actual.isTimeout() )
+                .isEqualTo( expected.isTimeout() );
+
+        assertThat( actual.getCompletedCount() )
+                .isEqualTo( expected.getCompletedCount() );
+
+        assertThat( actual.getErrors() )
+                .isEqualTo( expected.getErrors() );
+
+        assertThat( actual.getFailures() )
+                .isEqualTo( expected.getFailures() );
+
+        assertThat( actual.getSkipped() )
+                .isEqualTo( expected.getSkipped() );
+
+        assertThat( actual.getFailure() )
+                .isEqualTo( expected.getFailure() );
+    }
+}
diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java
new file mode 100644
index 000000000..5e75ebfa3
--- /dev/null
+++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java
@@ -0,0 +1,151 @@
+package org.apache.maven.plugin.failsafe;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils;
+import org.apache.maven.surefire.suite.RunResult;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.charset.Charset;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public class RunResultTest
+{
+
+    @Test
+    public void testAggregatedValues()
+    {
+        RunResult simple = getSimpleAggregate();
+
+        assertThat( simple.getCompletedCount() )
+                .isEqualTo( 20 );
+
+        assertThat( simple.getErrors() )
+                .isEqualTo( 3 );
+
+        assertThat( simple.getFailures() )
+                .isEqualTo( 7 );
+
+        assertThat( simple.getSkipped() )
+                .isEqualTo( 4 );
+
+        assertThat( simple.getFlakes() )
+                .isEqualTo( 2 );
+    }
+
+    @Test
+    public void testSerialization()
+            throws Exception
+    {
+        writeReadCheck( getSimpleAggregate() );
+    }
+
+    @Test
+    public void testFailures()
+            throws Exception
+    {
+        writeReadCheck( new RunResult( 0, 1, 2, 3, "stacktraceHere", false ) );
+    }
+
+    @Test
+    public void testSkipped()
+            throws Exception
+    {
+        writeReadCheck( new RunResult( 3, 2, 1, 0, null, true ) );
+    }
+
+    @Test
+    public void testAppendSerialization()
+            throws Exception
+    {
+        RunResult simpleAggregate = getSimpleAggregate();
+        RunResult additional = new RunResult( 2, 1, 2, 2, "msg " + ( (char) 0x0E01 ), true );
+
+        File summary = File.createTempFile( "failsafe", "test" );
+        FailsafeSummaryXmlUtils.writeSummary( simpleAggregate, summary, false );
+        FailsafeSummaryXmlUtils.writeSummary( additional, summary, true );
+        RunResult actual = FailsafeSummaryXmlUtils.toRunResult( summary );
+        //noinspection ResultOfMethodCallIgnored
+        summary.delete();
+
+        RunResult expected = simpleAggregate.aggregate( additional );
+
+        assertThat( expected.getCompletedCount() )
+                .isEqualTo( 22 );
+
+        assertThat( expected.getErrors() )
+                .isEqualTo( 4 );
+
+        assertThat( expected.getFailures() )
+                .isEqualTo( 9 );
+
+        assertThat( expected.getSkipped() )
+                .isEqualTo( 6 );
+
+        assertThat( expected.getFlakes() )
+                .isEqualTo( 2 );
+
+        assertThat( expected.getFailure() )
+                .isEqualTo( "msg " + ( (char) 0x0E01 ) );
+
+        assertThat( expected.isTimeout() )
+                .isTrue();
+
+        assertThat( actual )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void shouldAcceptAliasCharset()
+    {
+        Charset charset1 = IntegrationTestMojo.toCharset( "UTF8" );
+        assertThat( charset1.name() ).isEqualTo( "UTF-8" );
+
+        Charset charset2 = IntegrationTestMojo.toCharset( "utf8" );
+        assertThat( charset2.name() ).isEqualTo( "UTF-8" );
+    }
+
+    private void writeReadCheck( RunResult expected )
+            throws Exception
+    {
+        File tmp = File.createTempFile( "test", "xml" );
+        FailsafeSummaryXmlUtils.fromRunResultToFile( expected, tmp );
+
+        RunResult actual = FailsafeSummaryXmlUtils.toRunResult( tmp );
+        //noinspection ResultOfMethodCallIgnored
+        tmp.delete();
+
+        assertThat( actual )
+                .isEqualTo( expected );
+    }
+
+    private RunResult getSimpleAggregate()
+    {
+        RunResult resultOne = new RunResult( 10, 1, 3, 2, 1 );
+        RunResult resultTwo = new RunResult( 10, 2, 4, 2, 1 );
+        return resultOne.aggregate( resultTwo );
+    }
+}
diff --git a/maven-failsafe-plugin/src/test/resources/org/apache/maven/plugin/failsafe/failsafe-summary.xml b/maven-failsafe-plugin/src/test/resources/org/apache/maven/plugin/failsafe/failsafe-summary.xml
new file mode 100644
index 000000000..2b15bca78
--- /dev/null
+++ b/maven-failsafe-plugin/src/test/resources/org/apache/maven/plugin/failsafe/failsafe-summary.xml
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements.  See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership.  The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License.  You may obtain a copy of the License at
~
~     http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied.  See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<failsafe-summary result="254" timeout="false">
  <completed>7</completed>
  <errors>1</errors>
  <failures>2</failures>
  <skipped>3</skipped>
  <failureMessage><![CDATA[org.apache.maven.surefire.booter.SurefireBooterForkException: ExecutionException There was an error in the forked processtest subsystem#no method RuntimeException Hi There! $&amp;&gt;&gt;
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.awaitResultsDone(ForkStarter.java:489)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.runSuitesForkOnceMultiple(ForkStarter.java:364)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:288)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:240)
	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1075)
	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:905)
	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:783)
	at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:133)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:108)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:76)
	at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:116)
	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:361)
	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:155)
	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:584)
	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:213)
	at org.apache.maven.cli.MavenCli.main(MavenCli.java:157)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
Caused by: java.lang.RuntimeException: There was an error in the forked processtest subsystem#no method RuntimeException Hi There!
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:658)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:527)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.access$500(ForkStarter.java:117)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter$1.call(ForkStarter.java:358)
	at org.apache.maven.plugin.surefire.booterclient.ForkStarter$1.call(ForkStarter.java:337)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
]]></failureMessage>
</failsafe-summary>
\ No newline at end of file
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 54825b757..1bcd7c55c 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>maven-surefire-common</artifactId>
@@ -44,7 +44,6 @@
     <dependency>
       <groupId>org.apache.maven.plugin-tools</groupId>
       <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
@@ -93,6 +92,7 @@
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.maven.shared</groupId>
@@ -113,8 +113,7 @@
     </dependency>
     <dependency>
       <groupId>org.mockito</groupId>
-      <artifactId>mockito-all</artifactId>
-      <version>1.8.4</version>
+      <artifactId>mockito-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
@@ -124,7 +123,6 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>build-helper-maven-plugin</artifactId>
-        <version>1.12</version>
         <executions>
           <execution>
             <id>add-source</id>
@@ -141,6 +139,7 @@
         </executions>
       </plugin>
       <plugin>
+        <!-- Remove in 3.0 -->
         <artifactId>maven-dependency-plugin</artifactId>
         <executions>
           <execution>
@@ -161,13 +160,11 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <jvm>${jdk.home}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
-          <classpathDependencyExcludes>
-            <classpathDependencyExclude>org.fusesource.jansi:jansi</classpathDependencyExclude>
-          </classpathDependencyExcludes>
         </configuration>
         <dependencies>
           <dependency>
@@ -193,6 +190,7 @@
                   <include>org.apache.maven.shared:maven-shared-utils</include>
                   <include>org.apache.maven.shared:maven-common-artifact-filters</include>
                   <include>commons-io:commons-io</include>
+                  <include>org.apache.commons:commons-lang3</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -204,6 +202,10 @@
                   <pattern>org.apache.commons.io</pattern>
                   <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern>
                 </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.lang3</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern>
+                </relocation>
               </relocations>
             </configuration>
           </execution>
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index dd05e0564..27d091765 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -20,23 +20,6 @@
  * under the License.
  */
 
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.factory.ArtifactFactory;
 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
@@ -60,15 +43,16 @@
 import org.apache.maven.plugin.surefire.booterclient.ChecksumCalculator;
 import org.apache.maven.plugin.surefire.booterclient.ForkConfiguration;
 import org.apache.maven.plugin.surefire.booterclient.ForkStarter;
+import org.apache.maven.plugin.surefire.booterclient.Platform;
 import org.apache.maven.plugin.surefire.booterclient.ProviderDetector;
 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.util.DependencyScanner;
 import org.apache.maven.plugin.surefire.util.DirectoryScanner;
 import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
-import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.shared.utils.io.FileUtils;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
@@ -82,7 +66,6 @@
 import org.apache.maven.surefire.booter.SurefireExecutionException;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
@@ -93,14 +76,51 @@
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.apache.maven.surefire.util.DefaultScanResult;
 import org.apache.maven.surefire.util.RunOrder;
+import org.apache.maven.surefire.util.SurefireReflectionException;
+import org.apache.maven.toolchain.DefaultToolchain;
 import org.apache.maven.toolchain.Toolchain;
 import org.apache.maven.toolchain.ToolchainManager;
 
 import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static java.lang.Thread.currentThread;
+import static java.util.Collections.singletonMap;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
 import static org.apache.maven.shared.utils.StringUtils.capitalizeFirstLetter;
+import static org.apache.maven.shared.utils.StringUtils.isEmpty;
 import static org.apache.maven.shared.utils.StringUtils.isNotBlank;
+import static org.apache.maven.shared.utils.StringUtils.isNotEmpty;
+import static org.apache.maven.shared.utils.StringUtils.split;
+import static org.apache.maven.surefire.booter.SystemUtils.endsWithJavaPath;
+import static org.apache.maven.surefire.booter.SystemUtils.isJava9AtLeast;
+import static org.apache.maven.surefire.booter.SystemUtils.toJdkHomeFromJvmExec;
+import static org.apache.maven.surefire.booter.SystemUtils.toJdkVersionFromReleaseFile;
+import static org.apache.maven.surefire.suite.RunResult.failure;
+import static org.apache.maven.surefire.suite.RunResult.noTestsRun;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeGetter;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeStaticMethod;
+import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;
 
 /**
  * Abstract base class for running tests using Surefire.
@@ -112,6 +132,14 @@
     extends AbstractMojo
     implements SurefireExecutionParameters
 {
+    private static final Map<String, String> JAVA_9_MATCHER_OLD_NOTATION = singletonMap( "version", "[1.9,)" );
+
+    private static final Map<String, String> JAVA_9_MATCHER = singletonMap( "version", "[9,)" );
+
+    private static final Platform PLATFORM = new Platform();
+
+    private static final File SYSTEM_TMP_DIR = new File( System.getProperty( "java.io.tmpdir" ) );
+
     private final ProviderDetector providerDetector = new ProviderDetector();
 
     /**
@@ -133,7 +161,7 @@
     protected boolean skipTests;
 
     /**
-     * This old parameter is just like <code>skipTests</code>, but bound to the old property "maven.test.skip.exec".
+     * This old parameter is just like {@code skipTests}, but bound to the old property "maven.test.skip.exec".
      *
      * @since 2.3
      * @deprecated Use skipTests instead.
@@ -145,7 +173,7 @@
     /**
      * Set this to "true" to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable it using
      * the "maven.test.skip" property, because maven.test.skip disables both running the tests and compiling the tests.
-     * Consider using the <code>skipTests</code> parameter instead.
+     * Consider using the {@code skipTests} parameter instead.
      */
     @Parameter( property = "maven.test.skip", defaultValue = "false" )
     protected boolean skip;
@@ -182,7 +210,7 @@
     /**
      * A dependency scope to exclude from the test classpath. The scope should be one of the scopes defined by
      * org.apache.maven.artifact.Artifact. This includes the following:
-     * <p/>
+     * <br>
      * <ul>
      * <li><i>compile</i> - system, provided, compile
      * <li><i>runtime</i> - compile, runtime
@@ -213,25 +241,31 @@
     private File testSourceDirectory;
 
     /**
-     * A list of &lt;exclude> elements specifying the tests (by pattern) that should be excluded in testing. When not
-     * specified and when the <code>test</code> parameter is not specified, the default excludes will be <code><br/>
-     * &lt;excludes><br/>
-     * &nbsp;&lt;exclude>**&#47;*$*&lt;/exclude><br/>
-     * &lt;/excludes><br/>
-     * </code> (which excludes all inner classes).<br>
-     * This parameter is ignored if the TestNG <code>suiteXmlFiles</code> parameter is specified.
-     * <p/>
-     * Each exclude item may also contain a comma-separated sublist of items, which will be treated as multiple
-     * &nbsp;&lt;exclude> entries.<br/>
-     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):<br/>
-     * <exclude>%regex[pkg.*Slow.*.class], Unstable*</exclude><br/>
-     * <br/>
-     * <em>Notice that</em> these values are relative to the directory containing generated test classes of the project
-     * being tested. This directory is declared by the parameter <code>testClassesDirectory</code> which defaults
-     * to the POM property <code>${project.build.testOutputDirectory}</code>, typically <em>src/test/java</em>
-     * unless overridden.
+     * A list of &lt;exclude&gt; elements specifying the tests (by pattern) that should be excluded in testing. When not
+     * specified and when the {@code test} parameter is not specified, the default excludes will be <br>
+     * <pre><code>
+     * {@literal <excludes>}
+     *     {@literal <exclude>}**{@literal /}*$*{@literal </exclude>}
+     * {@literal </excludes>}
+     * </code></pre>
+     * (which excludes all inner classes).
+     * <br>
+     * This parameter is ignored if the TestNG {@code suiteXmlFiles} parameter is specified.
+     * <br>
+     * Each exclude item may also contain a comma-separated sub-list of items, which will be treated as multiple
+     * &nbsp;&lt;exclude&gt; entries.<br>
+     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
+     * <pre><code>
+     * {@literal <exclude>}%regex[pkg.*Slow.*.class], Unstable*{@literal </exclude>}
+     * </code></pre>
+     * <br>
+     * <b>Notice that</b> these values are relative to the directory containing generated test classes of the project
+     * being tested. This directory is declared by the parameter {@code testClassesDirectory} which defaults
+     * to the POM property <code>${project.build.testOutputDirectory}</code>, typically
+     * <code>{@literal src/test/java}</code> unless overridden.
      */
     @Parameter
+    // TODO use regex for fully qualified class names in 3.0 and change the filtering abilities
     private List<String> excludes;
 
     /**
@@ -316,19 +350,34 @@
     private Boolean failIfNoTests;
 
     /**
-     * <strong>DEPRECATED</strong> since version 2.14. Use <code>forkCount</code> and <code>reuseForks</code> instead.
-     * <br/>
-     * <br/>
-     * Option to specify the forking mode. Can be "never", "once", "always", "perthread". "none" and "pertest" are also
-     * accepted for backwards compatibility. "always" forks for each test-class. "perthread" will create
-     * <code>threadCount</code> parallel forks, each executing one test-class. See also parameter
-     * <code>reuseForks</code>.<br/>
+     * <strong>DEPRECATED</strong> since version 2.14. Use {@code forkCount} and {@code reuseForks} instead.
+     * <br>
+     * <br>
+     * Option to specify the forking mode. Can be {@code never}, {@code once}, {@code always}, {@code perthread}.<br>
+     * The {@code none} and {@code pertest} are also accepted for backwards compatibility.<br>
+     * The {@code always} forks for each test-class.<br>
+     * The {@code perthread} creates the number of parallel forks specified by {@code threadCount}, where each forked
+     * JVM is executing one test-class. See also the parameter {@code reuseForks} for the lifetime of JVM.
      *
      * @since 2.1
      */
     @Parameter( property = "forkMode", defaultValue = "once" )
     private String forkMode;
 
+    /**
+     * Relative path to <i>temporary-surefire-boot</i> directory containing internal Surefire temporary files.
+     * <br>
+     * The <i>temporary-surefire-boot</i> directory is <i>project.build.directory</i> on most platforms or
+     * <i>system default temporary-directory</i> specified by the system property {@code java.io.tmpdir}
+     * on Windows (see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1400">SUREFIRE-1400</a>).
+     * <br>
+     * It is deleted after the test set has completed.
+     *
+     * @since 2.20
+     */
+    @Parameter( property = "tempDir", defaultValue = "surefire" )
+    private String tempDir;
+
     /**
      * Option to specify the jvm (or path to the java executable) to use with the forking options. For the default, the
      * jvm will be a new instance of the same VM as the one used to run Maven. JVM settings are not inherited from
@@ -341,14 +390,15 @@
 
     /**
      * Arbitrary JVM options to set on the command line.
-     * <br/>
-     * <br/>
-     * Using an alternate syntax for <em>argLine</em>, <pre>@{...}</pre> allows late replacement of properties when the
-     * plugin is executed, so properties that have been modified by other plugins will be picked up correctly.
-     * See the Frequently Asked Questions page with more details:<br/>
+     * <br>
+     * <br>
+     * Since the Version 2.17 using an alternate syntax for {@code argLine}, <b>@{...}</b> allows late replacement
+     * of properties when the plugin is executed, so properties that have been modified by other plugins will be picked
+     * up correctly.
+     * See the Frequently Asked Questions page with more details:<br>
      * <a href="http://maven.apache.org/surefire/maven-surefire-plugin/faq.html">
      *     http://maven.apache.org/surefire/maven-surefire-plugin/faq.html</a>
-     * <br/>
+     * <br>
      * <a href="http://maven.apache.org/surefire/maven-failsafe-plugin/faq.html">
      *     http://maven.apache.org/surefire/maven-failsafe-plugin/faq.html</a>
      *
@@ -375,7 +425,7 @@
 
     /**
      * When false it makes tests run using the standard classloader delegation instead of the default Maven isolated
-     * classloader. Only used when forking (forkMode is not "none").<br/>
+     * classloader. Only used when forking ({@code forkMode} is not {@code none}).<br>
      * Setting it to false helps with some problems caused by conflicts between xml parsers in the classpath and the
      * Java 5 provider parser.
      *
@@ -386,12 +436,12 @@
 
     /**
      * (TestNG/JUnit47 provider with JUnit4.8+ only) Groups for this test. Only classes/methods/etc decorated with one
-     * of the groups specified here will be included in test run, if specified.<br/>
-     * For JUnit, this parameter forces the use of the 4.7 provider<br/>
-     * This parameter is ignored if the <code>suiteXmlFiles</code> parameter is specified.<br/>
-     * Since version 2.18.1 and JUnit 4.12, the <em>@Category<em> annotation type is automatically inherited from
-     * superclasses, see <em>@java.lang.annotation.Inherited</em>. Make sure that test class inheritance still makes
-     * sense together with <em>@Category<em> annotation of the JUnit 4.12 or higher appeared in superclass.
+     * of the groups specified here will be included in test run, if specified.<br>
+     * For JUnit, this parameter forces the use of the 4.7 provider<br>
+     * This parameter is ignored if the {@code suiteXmlFiles} parameter is specified.<br>
+     * Since version 2.18.1 and JUnit 4.12, the {@code @Category} annotation type is automatically inherited from
+     * superclasses, see {@code @java.lang.annotation.Inherited}. Make sure that test class inheritance still makes
+     * sense together with {@code @Category} annotation of the JUnit 4.12 or higher appeared in superclass.
      *
      * @since 2.2
      */
@@ -400,12 +450,12 @@
 
     /**
      * (TestNG/JUnit47 provider with JUnit4.8+ only) Excluded groups. Any methods/classes/etc with one of the groups
-     * specified in this list will specifically not be run.<br/>
-     * For JUnit, this parameter forces the use of the 4.7 provider<br/>
-     * This parameter is ignored if the <code>suiteXmlFiles</code> parameter is specified.<br/>
-     * Since version 2.18.1 and JUnit 4.12, the <em>@Category<em> annotation type is automatically inherited from
-     * superclasses, see <em>@java.lang.annotation.Inherited</em>. Make sure that test class inheritance still makes
-     * sense together with <em>@Category<em> annotation of the JUnit 4.12 or higher appeared in superclass.
+     * specified in this list will specifically not be run.<br>
+     * For JUnit, this parameter forces the use of the 4.7 provider.<br>
+     * This parameter is ignored if the {@code suiteXmlFiles} parameter is specified.<br>
+     * Since version 2.18.1 and JUnit 4.12, the {@code @Category} annotation type is automatically inherited from
+     * superclasses, see {@code @java.lang.annotation.Inherited}. Make sure that test class inheritance still makes
+     * sense together with {@code @Category} annotation of the JUnit 4.12 or higher appeared in superclass.
      *
      * @since 2.2
      */
@@ -413,7 +463,7 @@
     private String excludedGroups;
 
     /**
-     * Allows you to specify the name of the JUnit artifact. If not set, <code>junit:junit</code> will be used.
+     * Allows you to specify the name of the JUnit artifact. If not set, {@code junit:junit} will be used.
      *
      * @since 2.3.1
      */
@@ -421,7 +471,7 @@
     private String junitArtifactName;
 
     /**
-     * Allows you to specify the name of the TestNG artifact. If not set, <code>org.testng:testng</code> will be used.
+     * Allows you to specify the name of the TestNG artifact. If not set, {@code org.testng:testng} will be used.
      *
      * @since 2.3.1
      */
@@ -430,7 +480,7 @@
 
     /**
      * (TestNG/JUnit 4.7 provider) The attribute thread-count allows you to specify how many threads should be
-     * allocated for this execution. Only makes sense to use in conjunction with the <code>parallel</code> parameter.
+     * allocated for this execution. Only makes sense to use in conjunction with the {@code parallel} parameter.
      *
      * @since 2.2
      */
@@ -440,14 +490,14 @@
     /**
      * Option to specify the number of VMs to fork in parallel in order to execute the tests. When terminated with "C",
      * the number part is multiplied with the number of CPU cores. Floating point value are only accepted together with
-     * "C". If set to "0", no VM is forked and all tests are executed within the main process.<br/>
-     * <br/>
-     * Example values: "1.5C", "4"<br/>
-     * <br/>
-     * The system properties and the <code>argLine</code> of the forked processes may contain the place holder string
+     * "C". If set to "0", no VM is forked and all tests are executed within the main process.<br>
+     * <br>
+     * Example values: "1.5C", "4"<br>
+     * <br>
+     * The system properties and the {@code argLine} of the forked processes may contain the place holder string
      * <code>${surefire.forkNumber}</code>, which is replaced with a fixed number for each of the parallel forks,
-     * ranging from <code>1</code> to the effective value of <code>forkCount</code> times the maximum number of parallel
-     * Surefire executions in maven parallel builds, i.e. the effective value of the <code>-T</code> command line
+     * ranging from <b>1</b> to the effective value of {@code forkCount} times the maximum number of parallel
+     * Surefire executions in maven parallel builds, i.e. the effective value of the <b>-T</b> command line
      * argument of maven core.
      *
      * @since 2.14
@@ -457,7 +507,7 @@
 
     /**
      * Indicates if forked VMs can be reused. If set to "false", a new VM is forked for each test class to be executed.
-     * If set to "true", up to <code>forkCount</code> VMs will be forked and then reused to execute all tests.
+     * If set to "true", up to {@code forkCount} VMs will be forked and then reused to execute all tests.
      *
      * @since 2.13
      */
@@ -475,9 +525,9 @@
     private boolean perCoreThreadCount;
 
     /**
-     * (JUnit 4.7 provider) Indicates that the thread pool will be unlimited. The <code>parallel</code> parameter and
+     * (JUnit 4.7 provider) Indicates that the thread pool will be unlimited. The {@code parallel} parameter and
      * the actual number of classes/methods will decide. Setting this to "true" effectively disables
-     * <code>perCoreThreadCount</code> and <code>threadCount</code>. Defaults to "false".
+     * {@code perCoreThreadCount} and {@code threadCount}. Defaults to "false".
      *
      * @since 2.5
      */
@@ -485,18 +535,21 @@
     private boolean useUnlimitedThreads;
 
     /**
-     * (TestNG provider) When you use the <code>parallel</code> attribute, TestNG will try to run all your test methods
+     * (TestNG provider) When you use the parameter {@code parallel}, TestNG will try to run all your test methods
      * in separate threads, except for methods that depend on each other, which will be run in the same thread in order
      * to respect their order of execution.
-     * <p/>
-     * (JUnit 4.7 provider) Supports values "classes"/"methods"/"both" to run in separate threads, as controlled by
-     * <code>threadCount</code>.<br/>
-     * <br/>
-     * Since version 2.16 (JUnit 4.7 provider), the value "both" is <strong>DEPRECATED</strong>.
-     * Use <strong>"classesAndMethods"</strong> instead.<br/>
-     * <br/>
-     * Since version 2.16 (JUnit 4.7 provider), additional vales are available
-     * "suites"/"suitesAndClasses"/"suitesAndMethods"/"classesAndMethods"/"all".
+     * <br>
+     * (JUnit 4.7 provider) Supports values {@code classes}, {@code methods}, {@code both} to run
+     * in separate threads been controlled by {@code threadCount}.
+     * <br>
+     * <br>
+     * Since version 2.16 (JUnit 4.7 provider), the value {@code both} is <strong>DEPRECATED</strong>.
+     * Use {@code classesAndMethods} instead.
+     * <br>
+     * <br>
+     * Since version 2.16 (JUnit 4.7 provider), additional vales are available:
+     * <br>
+     * {@code suites}, {@code suitesAndClasses}, {@code suitesAndMethods}, {@code classesAndMethods}, {@code all}.
      *
      * @since 2.2
      */
@@ -506,7 +559,7 @@
     /**
      * (JUnit 4.7 / provider only) The thread counts do not exceed the number of parallel suite, class runners and
      * average number of methods per class if set to <strong>true</strong>.
-     * <p/>
+     * <br>
      * True by default.
      *
      * @since 2.17
@@ -517,16 +570,16 @@
     /**
      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test suites, i.e.:
      * <ul>
-     *  <li>number of concurrent suites if <code>threadCount</code> is 0 or unspecified</li>
-     *  <li>limited suites concurrency if <code>useUnlimitedThreads</code> is set to <strong>true</strong></li>
-     *  <li>if <code>threadCount</code> and certain thread-count parameters are &gt; 0 for <code>parallel</code>, the
-     *  concurrency is computed from ratio. For instance parallel=all and the ratio between
-     *      <em>threadCountSuites</em>:<code>threadCountClasses</code>:<code>threadCountMethods</code> is
-     *      <em>2</em>:3:5, there is 20% of <code>threadCount</code> in concurrent suites.</li>
+     *  <li>number of concurrent suites if {@code threadCount} is 0 or unspecified</li>
+     *  <li>limited suites concurrency if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
+     *  <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
+     *  concurrency is computed from ratio. For instance {@code parallel=all} and the ratio between
+     *      {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is
+     *      <b>2</b>:3:5, there is 20% of {@code threadCount} which appeared in concurrent suites.</li>
      * </ul>
      *
-     * Only makes sense to use in conjunction with the <code>parallel</code> parameter.
-     * The default value <code>0</code> behaves same as unspecified one.
+     * Only makes sense to use in conjunction with the {@code parallel} parameter.
+     * The default value <b>0</b> behaves same as unspecified one.
      *
      * @since 2.16
      */
@@ -536,20 +589,20 @@
     /**
      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test classes, i.e.:
      * <ul>
-     *  <li>number of concurrent classes if <code>threadCount</code> is 0 or unspecified</li>
-     *  <li>limited classes concurrency if <code>useUnlimitedThreads</code> is set to <strong>true</strong></li>
-     *  <li>if <code>threadCount</code> and certain thread-count parameters are &gt; 0 for <code>parallel</code>, the
-     *  concurrency is computed from ratio. For instance parallel=all and the ratio between
-     *      <code>threadCountSuites</code>:<em>threadCountClasses</em>:<code>threadCountMethods</code> is
-     *      2:<em>3</em>:5, there is 30% of <code>threadCount</code> in concurrent classes.</li>
-     *  <li>as in the previous case but without this leaf thread-count. Example: parallel=suitesAndClasses,
-     *  threadCount=16, threadCountSuites=5, threadCountClasses is unspecified leaf, the number of concurrent classes
-     *  is varying from &gt;= 11 to 14 or 15. The threadCountSuites become number of threads.
-     *  </li>
+     *  <li>number of concurrent classes if {@code threadCount} is 0 or unspecified</li>
+     *  <li>limited classes concurrency if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
+     *  <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
+     *  concurrency is computed from ratio. For instance {@code parallel=all} and the ratio between
+     *      {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is
+     *      2:<b>3</b>:5, there is 30% of {@code threadCount} in concurrent classes.</li>
+     *  <li>as in the previous case but without this leaf thread-count. Example: {@code parallel=suitesAndClasses},
+     *  {@code threadCount=16}, {@code threadCountSuites=5}, {@code threadCountClasses} is unspecified leaf, the number
+     *  of concurrent classes is varying from &gt;= 11 to 14 or 15. The {@code threadCountSuites} become
+     *  given number of threads.</li>
      * </ul>
      *
-     * Only makes sense to use in conjunction with the <code>parallel</code> parameter.
-     * The default value <code>0</code> behaves same as unspecified one.
+     * Only makes sense to use in conjunction with the {@code parallel} parameter.
+     * The default value <b>0</b> behaves same as unspecified one.
      *
      * @since 2.16
      */
@@ -559,18 +612,18 @@
     /**
      * (JUnit 4.7 provider) This attribute allows you to specify the concurrency in test methods, i.e.:
      * <ul>
-     * <li>number of concurrent methods if <code>threadCount</code> is 0 or unspecified</li>
-     * <li>limited concurrency of methods if <code>useUnlimitedThreads</code> is set to <strong>true</strong></li>
-     * <li>if <code>threadCount</code> and certain thread-count parameters are &gt; 0 for <code>parallel</code>, the
+     * <li>number of concurrent methods if {@code threadCount} is 0 or unspecified</li>
+     * <li>limited concurrency of methods if {@code useUnlimitedThreads} is set to <strong>true</strong></li>
+     * <li>if {@code threadCount} and certain thread-count parameters are &gt; 0 for {@code parallel}, the
      * concurrency is computed from ratio. For instance parallel=all and the ratio between
-     * <code>threadCountSuites</code>:<code>threadCountClasses</code>:<em>threadCountMethods</em> is 2:3:<em>5</em>,
-     * there is 50% of <code>threadCount</code> in concurrent methods.</li>
-     * <li>as in the previous case but without this leaf thread-count. Example: parallel=all, threadCount=16,
-     * threadCountSuites=2, threadCountClasses=3, but threadCountMethods is unspecified leaf, the number of concurrent
-     * methods is varying from &gt;= 11 to 14 or 15. The threadCountSuites and threadCountClasses become number of
-     * threads.</li>
+     * {@code threadCountSuites}:{@code threadCountClasses}:{@code threadCountMethods} is 2:3:<b>5</b>,
+     * there is 50% of {@code threadCount} which appears in concurrent methods.</li>
+     * <li>as in the previous case but without this leaf thread-count. Example: {@code parallel=all},
+     * {@code threadCount=16}, {@code threadCountSuites=2}, {@code threadCountClasses=3}, but {@code threadCountMethods}
+     * is unspecified leaf, the number of concurrent methods is varying from &gt;= 11 to 14 or 15.
+     * The {@code threadCountSuites} and {@code threadCountClasses} become given number of threads.</li>
      * </ul>
-     * Only makes sense to use in conjunction with the <code>parallel</code> parameter. The default value <code>0</code>
+     * Only makes sense to use in conjunction with the {@code parallel} parameter. The default value <b>0</b>
      * behaves same as unspecified one.
      *
      * @since 2.16
@@ -649,6 +702,13 @@
     @Parameter( defaultValue = "${session.parallel}", readonly = true )
     private Boolean parallelMavenExecution;
 
+    /**
+     * Read-only parameter with value of Maven property <i>project.build.directory</i>.
+     * @since 2.20
+     */
+    @Parameter( defaultValue = "${project.build.directory}", readonly = true )
+    private File projectBuildDirectory;
+
     /**
      * List of dependencies to scan for test classes to include in the test run.
      * The child elements of this element must be &lt;dependency&gt; elements, and the
@@ -690,10 +750,12 @@
 
     protected abstract int getRerunFailingTestsCount();
 
+    @Override
     public abstract List<String> getIncludes();
 
     public abstract File getIncludesFile();
 
+    @Override
     public abstract void setIncludes( List<String> includes );
 
     public abstract File getExcludesFile();
@@ -701,11 +763,13 @@
     /**
      * Calls {@link #getSuiteXmlFiles()} as {@link List list}.
      * Never returns <tt>null</tt>.
+     *
+     * @return list of TestNG suite XML files provided by MOJO
      */
     protected abstract List<File> suiteXmlFiles();
 
     /**
-     * @return <tt>true</tt> if {@link #getSuiteXmlFiles() suite-xml files array} is not empty.
+     * @return {@code true} if {@link #getSuiteXmlFiles() suite-xml files array} is not empty.
      */
     protected abstract boolean hasSuiteXmlFiles();
 
@@ -726,6 +790,8 @@ protected abstract void handleSummary( RunResult summary, Exception firstForkExc
 
     protected abstract String getReportSchemaLocation();
 
+    protected abstract Artifact getMojoArtifact();
+
     private String getDefaultExcludes()
     {
         return "**/*$*";
@@ -741,6 +807,7 @@ private String getDefaultExcludes()
 
     private volatile PluginConsoleLogger consoleLogger;
 
+    @Override
     public void execute()
         throws MojoExecutionException, MojoFailureException
     {
@@ -758,7 +825,7 @@ public void execute()
                     throw new MojoFailureException(
                         "No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)" );
                 }
-                handleSummary( RunResult.noTestsRun(), null );
+                handleSummary( noTestsRun(), null );
                 return;
             }
             logReportsDirectory();
@@ -843,7 +910,7 @@ boolean verifyParameters()
             getConsoleLogger().info( "Toolchain in maven-" + getPluginName() + "-plugin: " + toolchain );
             if ( jvmToUse != null )
             {
-                getConsoleLogger().warning( "Toolchains are ignored, 'executable' parameter is set to " + jvmToUse );
+                getConsoleLogger().warning( "Toolchains are ignored, 'jvm' parameter is set to " + jvmToUse );
             }
         }
 
@@ -867,6 +934,7 @@ boolean verifyParameters()
             warnIfRerunClashes();
             warnIfWrongShutdownValue();
             warnIfNotApplicableSkipAfterFailureCount();
+            warnIfIllegalTempDir();
         }
         return true;
     }
@@ -874,10 +942,9 @@ boolean verifyParameters()
     private void executeAfterPreconditionsChecked( DefaultScanResult scanResult )
         throws MojoExecutionException, MojoFailureException
     {
-
         List<ProviderInfo> providers = createProviders();
 
-        RunResult current = RunResult.noTestsRun();
+        RunResult current = noTestsRun();
 
         Exception firstForkException = null;
         for ( ProviderInfo provider : providers )
@@ -911,7 +978,7 @@ private void executeAfterPreconditionsChecked( DefaultScanResult scanResult )
 
         if ( firstForkException != null )
         {
-            current = RunResult.failure( current, firstForkException );
+            current = failure( current, firstForkException );
         }
 
         handleSummary( current, firstForkException );
@@ -1241,7 +1308,7 @@ private void convertGroupParameters()
 
     protected boolean isAnyConcurrencySelected()
     {
-        return this.getParallel() != null && this.getParallel().trim().length() > 0;
+        return getParallel() != null && !getParallel().trim().isEmpty();
     }
 
     protected boolean isAnyGroupsSelected()
@@ -1600,13 +1667,14 @@ StartupConfiguration createStartupConfiguration( ProviderInfo provider,
             Classpath providerClasspath = ClasspathCache.getCachedClassPath( providerName );
             if ( providerClasspath == null )
             {
+                // todo: 100 milli seconds, try to fetch List<String> within classpath asynchronously
                 providerClasspath = provider.getProviderClasspath();
                 ClasspathCache.setCachedClasspath( providerName, providerClasspath );
             }
             Artifact surefireArtifact = getCommonArtifact();
-            Classpath inprocClassPath = providerClasspath.
-                    addClassPathElementUrl( surefireArtifact.getFile().getAbsolutePath() )
-                    .addClassPathElementUrl( getApiArtifact().getFile().getAbsolutePath() );
+            Classpath inprocClassPath =
+                    providerClasspath.addClassPathElementUrl( surefireArtifact.getFile().getAbsolutePath() )
+                            .addClassPathElementUrl( getApiArtifact().getFile().getAbsolutePath() );
 
             final Classpath testClasspath = generateTestClasspath();
 
@@ -1739,7 +1807,7 @@ private void maybeAppendList( List<String> base, List<String> list )
         if ( isSpecificTestSpecified() )
         {
             includes = new ArrayList<String>();
-            Collections.addAll( includes, StringUtils.split( getTest(), "," ) );
+            Collections.addAll( includes, split( getTest(), "," ) );
         }
         else
         {
@@ -1812,7 +1880,7 @@ public TestListResolver getSpecificTests()
             if ( item != null )
             {
                 item = item.trim();
-                if ( item.length() != 0 )
+                if ( !item.isEmpty() )
                 {
                     result.add( item );
                 }
@@ -1903,17 +1971,15 @@ private InPluginVMSurefireStarter createInprocessStarter( ProviderInfo provider,
         ProviderConfiguration providerConfiguration = createProviderConfiguration( runOrderParameters );
         return new InPluginVMSurefireStarter( startupConfiguration, providerConfiguration,
                                                     startupReportConfiguration, consoleLogger );
-
     }
 
-    protected ForkConfiguration getForkConfiguration()
+    private ForkConfiguration getForkConfiguration() throws MojoFailureException
     {
         File tmpDir = getSurefireTempDir();
-        //noinspection ResultOfMethodCallIgnored
-        tmpDir.mkdirs();
 
         Artifact shadeFire = getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-shadefire" );
 
+        // todo: 150 milli seconds, try to fetch List<String> within classpath asynchronously
         final Classpath bootClasspathConfiguration =
             getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
 
@@ -1922,7 +1988,7 @@ protected ForkConfiguration getForkConfiguration()
                                       getWorkingDirectory() != null ? getWorkingDirectory() : getBasedir(),
                                       getProject().getModel().getProperties(),
                                       getArgLine(), getEnvironmentVariables(), getConsoleLogger().isDebugEnabled(),
-                                      getEffectiveForkCount(), reuseForks );
+                                      getEffectiveForkCount(), reuseForks, PLATFORM );
     }
 
     private void convertDeprecatedForkMode()
@@ -1998,24 +2064,59 @@ private String getEffectiveDebugForkedProcess()
         return debugForkedProcess;
     }
 
-    private String getEffectiveJvm()
+    private JdkAttributes getEffectiveJvm() throws MojoFailureException
     {
-        String jvmToUse = getJvm();
-        if ( toolchain != null && jvmToUse == null )
+        if ( isNotEmpty( jvm ) )
         {
-            jvmToUse = toolchain.findTool( "java" ); //NOI18N
+            File pathToJava = new File( jvm ).getAbsoluteFile();
+            if ( !endsWithJavaPath( pathToJava.getPath() ) )
+            {
+                throw new MojoFailureException( "Given path does not end with java executor \""
+                                                        + pathToJava.getPath() + "\"." );
+            }
+
+            if ( !( pathToJava.isFile()
+                            || "java".equals( pathToJava.getName() ) && pathToJava.getParentFile().isDirectory() ) )
+            {
+                throw new MojoFailureException( "Given path to java executor does not exist \""
+                                                        + pathToJava.getPath() + "\"." );
+            }
+
+            File jdkHome = toJdkHomeFromJvmExec( pathToJava.getPath() );
+            Double version = jdkHome == null ? null : toJdkVersionFromReleaseFile( jdkHome );
+            boolean javaVersion9 = version == null ? isJava9AtLeast( pathToJava.getPath() ) : isJava9AtLeast( version );
+            return new JdkAttributes( pathToJava.getPath(), javaVersion9 );
         }
 
-        if ( StringUtils.isEmpty( jvmToUse ) )
+        if ( toolchain != null )
         {
-            // use the same JVM as the one used to run Maven (the "java.home" one)
-            jvmToUse = System.getProperty( "java.home" ) + File.separator + "bin" + File.separator + "java";
-            getConsoleLogger().debug( "Using JVM: " + jvmToUse );
+            String jvmToUse = toolchain.findTool( "java" );
+            if ( isNotEmpty( jvmToUse ) )
+            {
+                boolean javaVersion9 = false;
+
+                if ( toolchain instanceof DefaultToolchain )
+                {
+                    DefaultToolchain defaultToolchain = (DefaultToolchain) toolchain;
+                    javaVersion9 = defaultToolchain.matchesRequirements( JAVA_9_MATCHER )
+                                             || defaultToolchain.matchesRequirements( JAVA_9_MATCHER_OLD_NOTATION );
+                }
+
+                if ( !javaVersion9 )
+                {
+                    javaVersion9 = isJava9AtLeast( jvmToUse );
+                }
+
+                return new JdkAttributes( jvmToUse, javaVersion9 );
+            }
         }
 
-        return jvmToUse;
-    }
+        // use the same JVM as the one used to run Maven (the "java.home" one)
+        String jvmToUse = System.getProperty( "java.home" ) + File.separator + "bin" + File.separator + "java";
+        getConsoleLogger().debug( "Using JVM: " + jvmToUse + " with Java version " + JAVA_RECENT.toString() );
 
+        return new JdkAttributes( jvmToUse, isJavaVersionAtLeast( JAVA_9 ) );
+    }
 
     private Artifact getSurefireBooterArtifact()
     {
@@ -2034,13 +2135,13 @@ private Artifact getSurefireBooterArtifact()
      *
      * @return A file pointing to the location of surefire's own temp files
      */
-    private File getSurefireTempDir()
+    File getSurefireTempDir()
     {
-        return new File( getReportsDirectory().getParentFile(), "surefire" );
+        return IS_OS_WINDOWS ? createSurefireBootDirectoryInTemp() : createSurefireBootDirectoryInBuild();
     }
 
     /**
-     * Operates on raw plugin paramenters, not the "effective" values.
+     * Operates on raw plugin parameters, not the "effective" values.
      *
      * @return The checksum
      */
@@ -2057,6 +2158,7 @@ private String getConfigChecksum()
         checksum.add( getClasspathDependencyScopeExclude() );
         checksum.add( getAdditionalClasspathElements() );
         checksum.add( getReportsDirectory() );
+        checksum.add( getProjectBuildDirectory() );
         checksum.add( getTestSourceDirectory() );
         checksum.add( getTest() );
         checksum.add( getIncludes() );
@@ -2110,6 +2212,7 @@ private String getConfigChecksum()
         checksum.add( getDependenciesToScan() );
         checksum.add( getForkedProcessExitTimeoutInSeconds() );
         checksum.add( getRerunFailingTestsCount() );
+        checksum.add( getTempDir() );
         addPluginSpecificChecksumItems( checksum );
         return checksum.getSha1();
     }
@@ -2164,7 +2267,7 @@ private Classpath generateTestClasspath()
 
         @SuppressWarnings( "unchecked" ) Set<Artifact> classpathArtifacts = getProject().getArtifacts();
 
-        if ( getClasspathDependencyScopeExclude() != null && !getClasspathDependencyScopeExclude().equals( "" ) )
+        if ( getClasspathDependencyScopeExclude() != null && !getClasspathDependencyScopeExclude().isEmpty() )
         {
             ArtifactFilter dependencyFilter = new ScopeArtifactFilter( getClasspathDependencyScopeExclude() );
             classpathArtifacts = this.filterArtifacts( classpathArtifacts, dependencyFilter );
@@ -2196,7 +2299,7 @@ private Classpath generateTestClasspath()
             {
                 if ( classpathElement != null )
                 {
-                    Collections.addAll( classpath, StringUtils.split( classpathElement, "," ) );
+                    Collections.addAll( classpath, split( classpathElement, "," ) );
                 }
             }
         }
@@ -2515,6 +2618,14 @@ else if ( skipAfterFailureCount > 0 )
         }
     }
 
+    private void warnIfIllegalTempDir() throws MojoFailureException
+    {
+        if ( isEmpty( getTempDir() ) )
+        {
+            throw new MojoFailureException( "Parameter 'tempDir' should not be blank string." );
+        }
+    }
+
     final class TestNgProviderInfo
         implements ProviderInfo
     {
@@ -2525,21 +2636,25 @@ else if ( skipAfterFailureCount > 0 )
             this.testNgArtifact = testNgArtifact;
         }
 
+        @Override
         @Nonnull public String getProviderName()
         {
             return "org.apache.maven.surefire.testng.TestNGProvider";
         }
 
+        @Override
         public boolean isApplicable()
         {
             return testNgArtifact != null;
         }
 
+        @Override
         public void addProviderProperties() throws MojoExecutionException
         {
             convertTestNGParameters();
         }
 
+        @Override
         public Classpath getProviderClasspath()
             throws ArtifactResolutionException, ArtifactNotFoundException
         {
@@ -2552,20 +2667,24 @@ public Classpath getProviderClasspath()
     final class JUnit3ProviderInfo
         implements ProviderInfo
     {
+        @Override
         @Nonnull public String getProviderName()
         {
             return "org.apache.maven.surefire.junit.JUnit3Provider";
         }
 
+        @Override
         public boolean isApplicable()
         {
             return true;
         }
 
+        @Override
         public void addProviderProperties() throws MojoExecutionException
         {
         }
 
+        @Override
         public Classpath getProviderClasspath()
             throws ArtifactResolutionException, ArtifactNotFoundException
         {
@@ -2590,20 +2709,24 @@ public Classpath getProviderClasspath()
             this.junitDepArtifact = junitDepArtifact;
         }
 
+        @Override
         @Nonnull public String getProviderName()
         {
             return "org.apache.maven.surefire.junit4.JUnit4Provider";
         }
 
+        @Override
         public boolean isApplicable()
         {
             return junitDepArtifact != null || isAnyJunit4( junitArtifact );
         }
 
+        @Override
         public void addProviderProperties() throws MojoExecutionException
         {
         }
 
+        @Override
         public Classpath getProviderClasspath()
             throws ArtifactResolutionException, ArtifactNotFoundException
         {
@@ -2660,6 +2783,7 @@ public Classpath getProviderClasspath()
             this.junitDepArtifact = junitDepArtifact;
         }
 
+        @Override
         @Nonnull public String getProviderName()
         {
             return "org.apache.maven.surefire.junitcore.JUnitCoreProvider";
@@ -2670,6 +2794,7 @@ private boolean is47CompatibleJunitDep()
             return junitDepArtifact != null && isJunit47Compatible( junitDepArtifact );
         }
 
+        @Override
         public boolean isApplicable()
         {
             final boolean isJunitArtifact47 = isAnyJunit4( junitArtifact ) && isJunit47Compatible( junitArtifact );
@@ -2677,12 +2802,14 @@ public boolean isApplicable()
             return isAny47ProvidersForcers && ( isJunitArtifact47 || is47CompatibleJunitDep() );
         }
 
+        @Override
         public void addProviderProperties() throws MojoExecutionException
         {
             convertJunitCoreParameters();
             convertGroupParameters();
         }
 
+        @Override
         public Classpath getProviderClasspath()
             throws ArtifactResolutionException, ArtifactNotFoundException
         {
@@ -2704,22 +2831,26 @@ public Classpath getProviderClasspath()
             this.providerName = providerName;
         }
 
+        @Override
         public ProviderInfo instantiate( String providerName )
         {
             return new DynamicProviderInfo( providerName );
         }
 
+        @Override
         @Nonnull
         public String getProviderName()
         {
             return providerName;
         }
 
+        @Override
         public boolean isApplicable()
         {
             return true;
         }
 
+        @Override
         public void addProviderProperties() throws MojoExecutionException
         {
             // Ok this is a bit lazy.
@@ -2727,12 +2858,11 @@ public void addProviderProperties() throws MojoExecutionException
             convertTestNGParameters();
         }
 
+        @Override
         public Classpath getProviderClasspath()
             throws ArtifactResolutionException, ArtifactNotFoundException
         {
-            final Map<String, Artifact> pluginArtifactMap = getPluginArtifactMap();
-            Artifact plugin = pluginArtifactMap.get( "org.apache.maven.plugins:maven-surefire-plugin" );
-            return dependencyResolver.addProviderToClasspath( pluginArtifactMap, plugin );
+            return dependencyResolver.addProviderToClasspath( pluginArtifactMap, getMojoArtifact() );
         }
     }
 
@@ -2805,29 +2935,100 @@ private ProviderInfo findByName( String providerClassName )
         }
     }
 
+    File createSurefireBootDirectoryInBuild()
+    {
+        File tmp = new File( getProjectBuildDirectory(), getTempDir() );
+        //noinspection ResultOfMethodCallIgnored
+        tmp.mkdirs();
+        return tmp;
+    }
+
+    // todo use Java7 java.nio.file.Files.createTempDirectory()
+    File createSurefireBootDirectoryInTemp()
+    {
+        if ( isJavaVersionAtLeast( JAVA_1_7 ) )
+        {
+            try
+            {
+                return new File( SYSTEM_TMP_DIR, createTmpDirectoryNameWithJava7( getTempDir() ) );
+            }
+            catch ( IOException e )
+            {
+                return createSurefireBootDirectoryInBuild();
+            }
+        }
+        else
+        {
+            try
+            {
+                File tmp = File.createTempFile( getTempDir(), null );
+                //noinspection ResultOfMethodCallIgnored
+                return tmp.mkdirs() ? tmp : createSurefireBootDirectoryInBuild();
+            }
+            catch ( IOException e )
+            {
+                return createSurefireBootDirectoryInBuild();
+            }
+        }
+    }
+
+    /**
+     * Reflection call of java.nio.file.Files.createTempDirectory( "surefire" ).
+     * @return Java 7 NIO Path
+     */
+    static Object createTmpDirectoryWithJava7( String directoryPrefix )
+            throws IOException
+    {
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        Class<?> filesType = tryLoadClass( classLoader, "java.nio.file.Files" );
+        Class<?> fileAttributeType = tryLoadClass( classLoader, "java.nio.file.attribute.FileAttribute" );
+        Object attrs = Array.newInstance( fileAttributeType, 0 );
+        try
+        {
+            return invokeStaticMethod( filesType, "createTempDirectory",
+                                             new Class<?>[]{ String.class, attrs.getClass() },
+                                             new Object[]{ directoryPrefix, attrs } );
+        }
+        catch ( SurefireReflectionException e )
+        {
+            Throwable cause = e.getCause();
+            throw cause instanceof IOException ? (IOException) cause : new IOException( cause );
+        }
+    }
+
+    static String createTmpDirectoryNameWithJava7( String directoryPrefix )
+            throws IOException
+    {
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        Class<?> pathType = tryLoadClass( classLoader, "java.nio.file.Path" );
+        Object path = createTmpDirectoryWithJava7( directoryPrefix );
+        return invokeGetter( pathType, path, "getFileName" ).toString();
+    }
+
+    @Override
     public List<String> getExcludes()
     {
         return excludes;
     }
 
+    @Override
     public void setExcludes( List<String> excludes )
     {
         this.excludes = excludes;
     }
 
+    @Override
     public ArtifactRepository getLocalRepository()
     {
         return localRepository;
     }
 
+    @Override
     public void setLocalRepository( ArtifactRepository localRepository )
     {
         this.localRepository = localRepository;
     }
 
-    /**
-     * @noinspection deprecation
-     */
     public Properties getSystemProperties()
     {
         return systemProperties;
@@ -3274,11 +3475,13 @@ public void setProject( MavenProject project )
         this.project = project;
     }
 
+    @Override
     public File getTestSourceDirectory()
     {
         return testSourceDirectory;
     }
 
+    @Override
     public void setTestSourceDirectory( File testSourceDirectory )
     {
         this.testSourceDirectory = testSourceDirectory;
@@ -3324,8 +3527,28 @@ public void setClasspathDependencyScopeExclude( String classpathDependencyScopeE
         this.classpathDependencyScopeExclude = classpathDependencyScopeExclude;
     }
 
+    public File getProjectBuildDirectory()
+    {
+        return projectBuildDirectory;
+    }
+
+    public void setProjectBuildDirectory( File projectBuildDirectory )
+    {
+        this.projectBuildDirectory = projectBuildDirectory;
+    }
+
     protected void logDebugOrCliShowErrors( String s )
     {
         SurefireHelper.logDebugOrCliShowErrors( s, getConsoleLogger(), cli );
     }
+
+    public String getTempDir()
+    {
+        return tempDir;
+    }
+
+    public void setTempDir( String tempDir )
+    {
+        this.tempDir = tempDir;
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
index f0d299e43..b97c1928b 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
@@ -35,7 +35,7 @@
 
 /**
  * Starts the provider in the same VM as the surefire plugin.
- * <p/>
+ * <br>
  * This part of the booter is always guaranteed to be in the
  * same vm as the tests will be run in.
  *
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/JdkAttributes.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/JdkAttributes.java
new file mode 100644
index 000000000..65b12548a
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/JdkAttributes.java
@@ -0,0 +1,48 @@
+package org.apache.maven.plugin.surefire;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class JdkAttributes
+{
+    private final String jvmExecutable;
+    private final boolean java9AtLeast;
+
+    public JdkAttributes( String jvmExecutable, boolean java9AtLeast )
+    {
+        this.jvmExecutable = requireNonNull( jvmExecutable, "null path to java executable" );
+        this.java9AtLeast = java9AtLeast;
+    }
+
+    public String getJvmExecutable()
+    {
+        return jvmExecutable;
+    }
+
+    public boolean isJava9AtLeast()
+    {
+        return java9AtLeast;
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
index 482ce0058..28e7ff04f 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
@@ -19,13 +19,6 @@
  * under the License.
  */
 
-import java.io.File;
-import java.io.PrintStream;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-
 import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
 import org.apache.maven.plugin.surefire.report.DirectConsoleOutput;
 import org.apache.maven.plugin.surefire.report.FileReporter;
@@ -35,13 +28,18 @@
 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
 
 import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static org.apache.maven.plugin.surefire.report.ConsoleReporter.BRIEF;
 import static org.apache.maven.plugin.surefire.report.ConsoleReporter.PLAIN;
 
 /**
  * All the parameters used to construct reporters
- * <p/>
+ * <br>
  *
  * @author Kristian Rosenvold
  */
@@ -79,8 +77,6 @@
 
     private final String xsdSchemaLocation;
 
-    private final Properties testVmSystemProperties = new Properties();
-
     private final Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistory
         = new ConcurrentHashMap<String, Map<String, List<WrappedReportEntry>>>();
 
@@ -111,6 +107,8 @@ public StartupReportConfiguration( boolean useFile, boolean printSummary, String
 
     /**
      * For testing purposes only.
+     *
+     * @return StartupReportConfiguration fo testing purposes
      */
     public static StartupReportConfiguration defaultValue()
     {
@@ -122,6 +120,8 @@ public static StartupReportConfiguration defaultValue()
 
     /**
      * For testing purposes only.
+     *
+     * @return StartupReportConfiguration fo testing purposes
      */
     public static StartupReportConfiguration defaultNoXml()
     {
@@ -213,11 +213,6 @@ public File getStatisticsFile()
         return statisticsFile;
     }
 
-    public Properties getTestVmSystemProperties()
-    {
-        return testVmSystemProperties;
-    }
-
     public boolean isTrimStackTrace()
     {
         return trimStackTrace;
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
index 96fb09e4d..240427f18 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
@@ -27,7 +27,6 @@
  * This interface contains all the common parameters that have different implementations in Surefire vs IntegrationTest
  *
  * @author Stephen Connolly
- * @noinspection UnusedDeclaration, UnusedDeclaration
  */
 public interface SurefireExecutionParameters
 {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
index 3f87c3cb5..dd29cb4f9 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java
@@ -26,6 +26,8 @@
 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.apache.maven.surefire.util.internal.DumpFileUtils;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -34,10 +36,13 @@
 import java.util.List;
 
 import static java.util.Collections.unmodifiableList;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT;
+import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMP_FILE_EXT;
+import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_ERROR;
-import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_WARN;
 import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_INFO;
-import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
+import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_WARN;
 import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
 
 /**
@@ -45,6 +50,31 @@
  */
 public final class SurefireHelper
 {
+    private static final String DUMP_FILE_DATE = DumpFileUtils.newFormattedDateFileName();
+
+    public static final String DUMP_FILE_PREFIX = DUMP_FILE_DATE + "-jvmRun";
+
+    public static final String DUMPSTREAM_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMPSTREAM_FILE_EXT;
+
+    /**
+     * The maximum path that does not require long path prefix on Windows.<br>
+     * See {@code sun/nio/fs/WindowsPath} in
+     * <a href=
+     * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
+     * OpenJDK</a>
+     * and <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>.
+     * <br>
+     * The maximum path is 260 minus 1 (NUL) but for directories it is 260
+     * minus 12 minus 1 (to allow for the creation of a 8.3 file in the directory).
+     */
+    private static final int MAX_PATH_LENGTH_WINDOWS = 247;
+
+    private static final String[] DUMP_FILES_PRINT =
+            {
+                    "[date]-jvmRun[N]" + DUMP_FILE_EXT,
+                    "[date]" + DUMPSTREAM_FILE_EXT,
+                    "[date]-jvmRun[N]" + DUMPSTREAM_FILE_EXT
+            };
 
     /**
      * Do not instantiate.
@@ -54,49 +84,32 @@ private SurefireHelper()
         throw new IllegalAccessError( "Utility class" );
     }
 
+    public static String[] getDumpFilesToPrint()
+    {
+        return DUMP_FILES_PRINT.clone();
+    }
+
     public static void reportExecution( SurefireReportParameters reportParameters, RunResult result,
-                                        PluginConsoleLogger log )
+                                        PluginConsoleLogger log, Exception firstForkException )
         throws MojoFailureException, MojoExecutionException
     {
-        boolean timeoutOrOtherFailure = result.isFailureOrTimeout();
-
-        if ( !timeoutOrOtherFailure )
+        if ( firstForkException == null && !result.isTimeout() && result.isErrorFree() )
         {
-            if ( result.getCompletedCount() == 0 )
+            if ( result.getCompletedCount() == 0 && failIfNoTests( reportParameters ) )
             {
-                if ( reportParameters.getFailIfNoTests() == null || !reportParameters.getFailIfNoTests() )
-                {
-                    return;
-                }
-                throw new MojoFailureException(
-                    "No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)" );
-            }
-
-            if ( result.isErrorFree() )
-            {
-                return;
+                throw new MojoFailureException( "No tests were executed!  "
+                                                        + "(Set -DfailIfNoTests=false to ignore this error.)" );
             }
+            return;
         }
 
-        String msg = timeoutOrOtherFailure
-            ? "There was a timeout or other error in the fork"
-            : "There are test failures.\n\nPlease refer to " + reportParameters.getReportsDirectory()
-                + " for the individual test results.";
-
         if ( reportParameters.isTestFailureIgnore() )
         {
-            log.error( msg );
+            log.error( createErrorMessage( reportParameters, result, firstForkException ) );
         }
         else
         {
-            if ( result.isFailure() )
-            {
-                throw new MojoExecutionException( msg );
-            }
-            else
-            {
-                throw new MojoFailureException( msg );
-            }
+            throwException( reportParameters, result, firstForkException );
         }
     }
 
@@ -166,6 +179,28 @@ else if ( cli.contains( SHOW_ERRORS ) )
         }
     }
 
+    /**
+     * Escape file path for Windows when the path is too long; otherwise returns {@code path}.
+     * <br>
+     * See <a href=
+     * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
+     * sun/nio/fs/WindowsPath</a> for "long path" value explanation (=247), and
+     * <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>
+     * for detailed escaping strategy explanation: in short, {@code \\?\} prefix for path with drive letter
+     * or {@code \\?\UNC\} for UNC path.
+     *
+     * @param path    source path
+     * @return escaped to platform path
+     */
+    public static String escapeToPlatformPath( String path )
+    {
+        if ( IS_OS_WINDOWS && path.length() > MAX_PATH_LENGTH_WINDOWS )
+        {
+            path = path.startsWith( "\\\\" ) ? "\\\\?\\UNC\\" + path.substring( 2 ) : "\\\\?\\" + path;
+        }
+        return path;
+    }
+
     private static String getFailureBehavior( MavenExecutionRequest request )
         throws NoSuchMethodException, InvocationTargetException, IllegalAccessException
     {
@@ -181,4 +216,69 @@ private static String getFailureBehavior( MavenExecutionRequest request )
         }
     }
 
+    private static boolean failIfNoTests( SurefireReportParameters reportParameters )
+    {
+        return reportParameters.getFailIfNoTests() != null && reportParameters.getFailIfNoTests();
+    }
+
+    private static boolean isFatal( Exception firstForkException )
+    {
+        return firstForkException != null && !( firstForkException instanceof TestSetFailedException );
+    }
+
+    private static void throwException( SurefireReportParameters reportParameters, RunResult result,
+                                           Exception firstForkException )
+            throws MojoFailureException, MojoExecutionException
+    {
+        if ( isFatal( firstForkException ) || result.isInternalError()  )
+        {
+            throw new MojoExecutionException( createErrorMessage( reportParameters, result, firstForkException ),
+                                                    firstForkException );
+        }
+        else
+        {
+            throw new MojoFailureException( createErrorMessage( reportParameters, result, firstForkException ),
+                                                  firstForkException );
+        }
+    }
+
+    private static String createErrorMessage( SurefireReportParameters reportParameters, RunResult result,
+                                              Exception firstForkException )
+    {
+        StringBuilder msg = new StringBuilder( 512 );
+
+        if ( result.isTimeout() )
+        {
+            msg.append( "There was a timeout or other error in the fork" );
+        }
+        else
+        {
+            msg.append( "There are test failures.\n\nPlease refer to " )
+                    .append( reportParameters.getReportsDirectory() )
+                    .append( " for the individual test results." )
+                    .append( '\n' )
+                    .append( "Please refer to dump files (if any exist) " )
+                    .append( DUMP_FILES_PRINT[0] )
+                    .append( ", " )
+                    .append( DUMP_FILES_PRINT[1] )
+                    .append( " and " )
+                    .append( DUMP_FILES_PRINT[2] )
+                    .append( "." );
+        }
+
+        if ( firstForkException != null && firstForkException.getLocalizedMessage() != null )
+        {
+            msg.append( '\n' )
+                    .append( firstForkException.getLocalizedMessage() );
+        }
+
+        if ( result.isFailure() )
+        {
+            msg.append( '\n' )
+                    .append( result.getFailure() );
+        }
+
+        return msg.toString();
+    }
+
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
index 3663f3978..8d080b75e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
@@ -92,6 +92,7 @@ public synchronized void clear()
         super.clear();
     }
 
+    @Override
     public synchronized Enumeration<Object> keys()
     {
         return Collections.enumeration( items );
@@ -146,7 +147,6 @@ static SurefireProperties calculateEffectiveProperties( Properties systemPropert
 
         result.copyPropertiesFrom( props );
 
-        copyProperties( result, systemPropertyVariables );
         copyProperties( result, systemPropertyVariables );
 
         // We used to take all of our system properties and dump them in with the
@@ -170,6 +170,7 @@ public static void copyProperties( Properties target, Map<String, String> source
         }
     }
 
+    @Override
     public void copyTo( Map<Object, Object> target )
     {
         target.putAll( this );
@@ -191,6 +192,14 @@ public void setProperty( String key, Boolean aBoolean )
         }
     }
 
+    public void setProperty( String key, Long value )
+    {
+        if ( value != null )
+        {
+            setProperty( key, value.toString() );
+        }
+    }
+
     public void addList( List<?> items, String propertyPrefix )
     {
         if ( items != null && !items.isEmpty() )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireReportParameters.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireReportParameters.java
index 29997878f..f345b466b 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireReportParameters.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireReportParameters.java
@@ -25,7 +25,6 @@
  * The parameters required to report on a surefire execution.
  *
  * @author Stephen Connolly
- * @noinspection UnusedDeclaration
  */
 public interface SurefireReportParameters
 {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
index 2aac04be8..591e89c81 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
@@ -44,12 +44,12 @@
 
 /**
  * Knows how to serialize and deserialize the booter configuration.
- * <p/>
+ * <br>
  * The internal serialization format is through a properties file. The long-term goal of this
  * class is not to expose this implementation information to its clients. This still leaks somewhat,
  * and there are some cases where properties are being accessed as "Properties" instead of
  * more representative domain objects.
- * <p/>
+ * <br>
  *
  * @author Jason van Zyl
  * @author Emmanuel Venisse
@@ -61,7 +61,7 @@
 {
     private final ForkConfiguration forkConfiguration;
 
-    public BooterSerializer( ForkConfiguration forkConfiguration )
+    BooterSerializer( ForkConfiguration forkConfiguration )
     {
         this.forkConfiguration = forkConfiguration;
     }
@@ -69,13 +69,15 @@ public BooterSerializer( ForkConfiguration forkConfiguration )
     /**
      * Does not modify sourceProperties
      */
-    public File serialize( KeyValueSource sourceProperties, ProviderConfiguration booterConfiguration,
-                           StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream )
+    File serialize( KeyValueSource sourceProperties, ProviderConfiguration booterConfiguration,
+                    StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream,
+                    Long pid )
         throws IOException
     {
-
         SurefireProperties properties = new SurefireProperties( sourceProperties );
 
+        properties.setProperty( PLUGIN_PID, pid );
+
         ClasspathConfiguration cp = providerConfiguration.getClasspathConfiguration();
         properties.setClasspath( ClasspathConfiguration.CLASSPATH, cp.getTestClasspath() );
         properties.setClasspath( ClasspathConfiguration.SUREFIRE_CLASSPATH, cp.getProviderClasspath() );
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ChecksumCalculator.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ChecksumCalculator.java
index cc6a80801..5931f9ee3 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ChecksumCalculator.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ChecksumCalculator.java
@@ -19,15 +19,17 @@
  * under the License.
  */
 
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+
 import java.io.File;
-import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.repository.ArtifactRepository;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
 
 /**
  * @author Kristian Rosenvold
@@ -153,7 +155,7 @@ public String getSha1()
         {
             MessageDigest md = MessageDigest.getInstance( "SHA-1" );
             String configValue = getConfig();
-            md.update( configValue.getBytes( "iso-8859-1" ), 0, configValue.length() );
+            md.update( configValue.getBytes( ISO_8859_1 ), 0, configValue.length() );
             byte[] sha1hash = md.digest();
             return asHexString( sha1hash );
         }
@@ -161,10 +163,6 @@ public String getSha1()
         {
             throw new RuntimeException( e );
         }
-        catch ( UnsupportedEncodingException e )
-        {
-            throw new RuntimeException( e );
-        }
     }
 
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index 036ec561b..c96242475 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -20,19 +20,20 @@
  */
 
 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
+import org.apache.maven.plugin.surefire.JdkAttributes;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 import org.apache.maven.plugin.surefire.util.Relocator;
-import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
-import org.apache.maven.surefire.util.UrlUtils;
+import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.Enumeration;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
@@ -40,6 +41,9 @@
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 
+import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
+import static org.apache.maven.shared.utils.StringUtils.join;
+
 /**
  * Configuration for forking tests.
  *
@@ -63,9 +67,9 @@
 
     private final Classpath bootClasspathConfiguration;
 
-    private final String jvmExecutable;
+    private final JdkAttributes jdk;
 
-    private Properties modelProperties;
+    private final Properties modelProperties;
 
     private final String argLine;
 
@@ -79,23 +83,26 @@
 
     private final String debugLine;
 
+    private final Platform pluginPlatform;
+
     @SuppressWarnings( "checkstyle:parameternumber" )
     public ForkConfiguration( Classpath bootClasspathConfiguration, File tmpDir, String debugLine,
-                              String jvmExecutable, File workingDirectory, Properties modelProperties, String argLine,
+                              JdkAttributes jdk, File workingDirectory, Properties modelProperties, String argLine,
                               Map<String, String> environmentVariables, boolean debugEnabled, int forkCount,
-                              boolean reuseForks )
+                              boolean reuseForks, Platform pluginPlatform )
     {
         this.bootClasspathConfiguration = bootClasspathConfiguration;
         this.tempDirectory = tmpDir;
         this.debugLine = debugLine;
-        this.jvmExecutable = jvmExecutable;
+        this.jdk = jdk;
         this.workingDirectory = workingDirectory;
         this.modelProperties = modelProperties;
         this.argLine = argLine;
-        this.environmentVariables = environmentVariables;
+        this.environmentVariables = toImmutable( environmentVariables );
         this.debug = debugEnabled;
         this.forkCount = forkCount;
         this.reuseForks = reuseForks;
+        this.pluginPlatform = pluginPlatform;
     }
 
     public Classpath getBootClasspath()
@@ -125,23 +132,25 @@ else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
     }
 
     /**
-     * @param classPath            cla the classpath arguments
-     * @param startupConfiguration The startup configuration
+     * @param classPath            cli the classpath arguments
+     * @param config               The startup configuration
      * @param threadNumber         the thread number, to be the replacement in the argLine   @return A commandline
+     * @return CommandLine able to flush entire command going to be sent to forked JVM
      * @throws org.apache.maven.surefire.booter.SurefireBooterForkException
      *          when unable to perform the fork
      */
-    public OutputStreamFlushableCommandline createCommandLine( List<String> classPath,
-                                                               StartupConfiguration startupConfiguration,
+    public OutputStreamFlushableCommandline createCommandLine( List<String> classPath, StartupConfiguration config,
                                                                int threadNumber )
-        throws SurefireBooterForkException
+            throws SurefireBooterForkException
     {
-        return createCommandLine( classPath,
-                                  startupConfiguration.getClassLoaderConfiguration()
-                                      .isManifestOnlyJarRequestedAndUsable(),
-                                  startupConfiguration.isShadefire(), startupConfiguration.isProviderMainClass()
-            ? startupConfiguration.getActualClassName()
-            : ForkedBooter.class.getName(), threadNumber );
+        boolean useJar = config.getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable();
+
+        boolean shadefire = config.isShadefire();
+
+        String providerThatHasMainMethod =
+                config.isProviderMainClass() ? config.getActualClassName() : ForkedBooter.class.getName();
+
+        return createCommandLine( classPath, useJar, shadefire, providerThatHasMainMethod, threadNumber );
     }
 
     OutputStreamFlushableCommandline createCommandLine( List<String> classPath, boolean useJar, boolean shadefire,
@@ -150,48 +159,55 @@ OutputStreamFlushableCommandline createCommandLine( List<String> classPath, bool
     {
         OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
 
-        cli.setExecutable( jvmExecutable );
+        cli.setExecutable( jdk.getJvmExecutable() );
 
-        if ( argLine != null )
-        {
-            cli.createArg().setLine(
-                   replaceThreadNumberPlaceholder( stripNewLines( replacePropertyExpressions( argLine ) ),
-                                                   threadNumber ) );
-        }
+        String jvmArgLine =
+                replaceThreadNumberPlaceholder( stripNewLines( replacePropertyExpressions() ), threadNumber );
 
-        if ( environmentVariables != null )
+        if ( jdk.isJava9AtLeast() && !jvmArgLine.contains( "--add-modules" ) )
         {
-            for ( Map.Entry<String, String> entry : environmentVariables.entrySet() )
+            if ( jvmArgLine.isEmpty() )
             {
-                String value = entry.getValue();
-                cli.addEnvironment( entry.getKey(), value == null ? "" : value );
+                jvmArgLine = "--add-modules java.se.ee";
+            }
+            else
+            {
+                jvmArgLine = "--add-modules java.se.ee " + jvmArgLine;
             }
         }
 
-        if ( getDebugLine() != null && !"".equals( getDebugLine() ) )
+        if ( !jvmArgLine.isEmpty() )
+        {
+            cli.createArg().setLine( jvmArgLine );
+        }
+
+        for ( Map.Entry<String, String> entry : environmentVariables.entrySet() )
+        {
+            String value = entry.getValue();
+            cli.addEnvironment( entry.getKey(), value == null ? "" : value );
+        }
+
+        if ( getDebugLine() != null && !getDebugLine().isEmpty() )
         {
             cli.createArg().setLine( getDebugLine() );
         }
 
         if ( useJar )
         {
-            File jarFile;
             try
             {
-                jarFile = createJar( classPath, providerThatHasMainMethod );
+                File jarFile = createJar( classPath, providerThatHasMainMethod );
+                cli.createArg().setValue( "-jar" );
+                cli.createArg().setValue( escapeToPlatformPath( jarFile.getAbsolutePath() ) );
             }
             catch ( IOException e )
             {
                 throw new SurefireBooterForkException( "Error creating archive file", e );
             }
-
-            cli.createArg().setValue( "-jar" );
-
-            cli.createArg().setValue( jarFile.getAbsolutePath() );
         }
         else
         {
-            cli.addEnvironment( "CLASSPATH", StringUtils.join( classPath.iterator(), File.pathSeparator ) );
+            cli.addEnvironment( "CLASSPATH", join( classPath.iterator(), File.pathSeparator ) );
 
             final String forkedBooter =
                 providerThatHasMainMethod != null ? providerThatHasMainMethod : ForkedBooter.class.getName();
@@ -234,31 +250,37 @@ private String replaceThreadNumberPlaceholder( String argLine, int threadNumber
      *
      * This allows other plugins to modify or set properties with the changes getting picked up by surefire.
      */
-    private String replacePropertyExpressions( String argLine )
+    private String replacePropertyExpressions()
     {
         if ( argLine == null )
         {
-            return null;
+            return "";
         }
 
-        for ( Enumeration<?> e = modelProperties.propertyNames(); e.hasMoreElements(); )
+        String resolvedArgLine = argLine.trim();
+
+        if ( resolvedArgLine.isEmpty() )
+        {
+            return "";
+        }
+
+        for ( final String key : modelProperties.stringPropertyNames() )
         {
-            String key = e.nextElement().toString();
             String field = "@{" + key + "}";
             if ( argLine.contains( field ) )
             {
-                argLine = argLine.replace( field, modelProperties.getProperty( key, "" ) );
+                resolvedArgLine = resolvedArgLine.replace( field, modelProperties.getProperty( key, "" ) );
             }
         }
 
-        return argLine;
+        return resolvedArgLine;
     }
 
     /**
      * Create a jar with just a manifest containing a Main-Class entry for BooterConfiguration and a Class-Path entry
      * for all classpath elements.
      *
-     * @param classPath      List&lt;String> of all classpath elements.
+     * @param classPath      List&lt;String&gt; of all classpath elements.
      * @param startClassName  The classname to start (main-class)
      * @return The file pointint to the jar
      * @throws java.io.IOException When a file operation fails.
@@ -284,11 +306,20 @@ private File createJar( List<String> classPath, String startClassName )
             // we can't use StringUtils.join here since we need to add a '/' to
             // the end of directory entries - otherwise the jvm will ignore them.
             StringBuilder cp = new StringBuilder();
-            for ( String el : classPath )
+            for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
             {
-                // NOTE: if File points to a directory, this entry MUST end in '/'.
-                cp.append( UrlUtils.getURL( new File( el ) ).toExternalForm() )
-                        .append( " " );
+                File file1 = new File( it.next() );
+                String uri = file1.toURI().toASCIIString();
+                cp.append( uri );
+                if ( file1.isDirectory() && !uri.endsWith( "/" ) )
+                {
+                    cp.append( '/' );
+                }
+
+                if ( it.hasNext() )
+                {
+                    cp.append( ' ' );
+                }
             }
 
             man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
@@ -296,13 +327,16 @@ private File createJar( List<String> classPath, String startClassName )
             man.getMainAttributes().putValue( "Main-Class", startClassName );
 
             man.write( jos );
+
+            jos.closeEntry();
+            jos.flush();
+
+            return file;
         }
         finally
         {
             jos.close();
         }
-
-        return file;
     }
 
     public boolean isDebug()
@@ -310,11 +344,6 @@ public boolean isDebug()
         return debug;
     }
 
-    public String stripNewLines( String argline )
-    {
-        return argline.replace( "\n", " " ).replace( "\r", " " );
-    }
-
     public String getDebugLine()
     {
         return debugLine;
@@ -330,9 +359,31 @@ public int getForkCount()
         return forkCount;
     }
 
-
     public boolean isReuseForks()
     {
         return reuseForks;
     }
+
+    public Platform getPluginPlatform()
+    {
+        return pluginPlatform;
+    }
+
+    private static String stripNewLines( String argLine )
+    {
+        return argLine.replace( "\n", " " ).replace( "\r", " " );
+    }
+
+    /**
+     * Immutable map.
+     *
+     * @param map    immutable map copies elements from <code>map</code>
+     * @param <K>    key type
+     * @param <V>    value type
+     * @return never returns null
+     */
+    private static <K, V> Map<K, V> toImmutable( Map<K, V> map )
+    {
+        return map == null ? Collections.<K, V>emptyMap() : new ImmutableMap<K, V>( map );
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index d512f2475..3efbd5731 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -28,8 +28,10 @@
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
+import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
 import org.apache.maven.plugin.surefire.booterclient.output.NativeStdErrStreamConsumer;
 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.shared.utils.cli.CommandLineCallable;
 import org.apache.maven.shared.utils.cli.CommandLineException;
@@ -44,7 +46,6 @@
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.testset.TestRequest;
@@ -53,11 +54,9 @@
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Queue;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.Callable;
@@ -70,6 +69,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static java.lang.StrictMath.min;
@@ -80,29 +80,30 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder;
+import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber;
 import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.returnNumber;
 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream
-    .TestLessInputStreamBuilder;
+                      .TestLessInputStreamBuilder;
 import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable;
 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
 import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
 import static org.apache.maven.surefire.booter.Classpath.join;
 import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
-import static org.apache.maven.surefire.suite.RunResult.timeout;
-import static org.apache.maven.surefire.suite.RunResult.failure;
 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
+import static org.apache.maven.surefire.suite.RunResult.failure;
+import static org.apache.maven.surefire.suite.RunResult.timeout;
 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
-import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
 import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
 
 /**
  * Starts the fork or runs in-process.
- * <p/>
+ * <br>
  * Lives only on the plugin-side (not present in remote vms)
- * <p/>
+ * <br>
  * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
  *
  * @author Jason van Zyl
@@ -114,6 +115,8 @@
  */
 public class ForkStarter
 {
+    private static final String EXECUTION_EXCEPTION = "ExecutionException";
+
     private static final long PING_IN_SECONDS = 10;
 
     private static final int TIMEOUT_CHECK_PERIOD_MILLIS = 100;
@@ -151,15 +154,18 @@
     /**
      * Closes stuff, with a shutdown hook to make sure things really get closed.
      */
-    private static class CloseableCloser
+    private final class CloseableCloser
         implements Runnable, Closeable
     {
+        private final int jvmRun;
+
         private final Queue<Closeable> testProvidingInputStream;
 
         private final Thread inputStreamCloserHook;
 
-        public CloseableCloser( Closeable... testProvidingInputStream )
+        public CloseableCloser( int jvmRun, Closeable... testProvidingInputStream )
         {
+            this.jvmRun = jvmRun;
             this.testProvidingInputStream = new ConcurrentLinkedQueue<Closeable>();
             addAll( this.testProvidingInputStream, testProvidingInputStream );
             if ( this.testProvidingInputStream.isEmpty() )
@@ -173,6 +179,7 @@ public CloseableCloser( Closeable... testProvidingInputStream )
             }
         }
 
+        @Override
         @SuppressWarnings( "checkstyle:innerassignment" )
         public void run()
         {
@@ -184,11 +191,21 @@ public void run()
                 }
                 catch ( IOException e )
                 {
-                    // ignore
+                    // This error does not fail a test and does not necessarily mean that the forked JVM std/out stream
+                    // was not closed, see ThreadedStreamConsumer. This error means that JVM wrote messages to a native
+                    // stream which could not be parsed or report failed. The tests may still correctly run nevertheless
+                    // this exception happened => warning on console. The user would see hint to check dump file only
+                    // if tests failed, but if this does not happen then printing warning to console is the only way to
+                    // inform the users.
+                    String msg = "ForkStarter IOException: " + e.getLocalizedMessage() + ".";
+                    File dump = InPluginProcessDumpSingleton.getSingleton()
+                                        .dumpException( e, msg, defaultReporterFactory, jvmRun );
+                    log.warning( msg + " See the dump file " + dump.getAbsolutePath() );
                 }
             }
         }
 
+        @Override
         public void close()
         {
             run();
@@ -253,8 +270,8 @@ private RunResult run( SurefireProperties effectiveSystemProperties, Map<String,
         TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
         PropertiesWrapper props = new PropertiesWrapper( providerProperties );
         TestLessInputStream stream = builder.build();
-        Properties sysProps = startupReportConfiguration.getTestVmSystemProperties();
-        ForkClient forkClient = new ForkClient( forkedReporterFactory, sysProps, stream, log );
+        ForkClient forkClient = new ForkClient( forkedReporterFactory, stream, log, forkConfiguration.isDebug(),
+                new AtomicBoolean() );
         Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() );
         ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder );
         try
@@ -319,20 +336,20 @@ private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveS
             addShutDownHook( shutdown );
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
-            Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
+            final Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
+            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final TestProvidingInputStream testProvidingInputStream : testStreams )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
                 {
+                    @Override
                     public RunResult call()
                         throws Exception
                     {
                         DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration, log );
                         defaultReporterFactories.add( reporter );
-
-                        Properties vmProps = startupReportConfiguration.getTestVmSystemProperties();
-
-                        ForkClient forkClient = new ForkClient( reporter, vmProps, testProvidingInputStream, log )
+                        ForkClient forkClient = new ForkClient( reporter, testProvidingInputStream, log,
+                                forkConfiguration.isDebug(), printedErrorStream )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -384,19 +401,20 @@ private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSys
             addShutDownHook( shutdown );
             int failFastCount = providerConfiguration.getSkipAfterFailureCount();
             final AtomicInteger notifyStreamsToSkipTestsJustNow = new AtomicInteger( failFastCount );
+            final AtomicBoolean printedErrorStream = new AtomicBoolean();
             for ( final Object testSet : getSuitesIterator() )
             {
                 Callable<RunResult> pf = new Callable<RunResult>()
                 {
+                    @Override
                     public RunResult call()
                         throws Exception
                     {
                         DefaultReporterFactory forkedReporterFactory =
                             new DefaultReporterFactory( startupReportConfiguration, log );
                         defaultReporterFactories.add( forkedReporterFactory );
-                        Properties vmProps = startupReportConfiguration.getTestVmSystemProperties();
-                        ForkClient forkClient = new ForkClient( forkedReporterFactory, vmProps,
-                                                                      builder.getImmediateCommands(), log )
+                        ForkClient forkClient = new ForkClient( forkedReporterFactory, builder.getImmediateCommands(),
+                                log, forkConfiguration.isDebug(), printedErrorStream )
                         {
                             @Override
                             protected void stopOnNextTest()
@@ -436,6 +454,7 @@ private static RunResult awaitResultsDone( Collection<Future<RunResult>> results
         throws SurefireBooterForkException
     {
         RunResult globalResult = new RunResult( 0, 0, 0, 0 );
+        SurefireBooterForkException exception = null;
         for ( Future<RunResult> result : results )
         {
             try
@@ -459,10 +478,31 @@ private static RunResult awaitResultsDone( Collection<Future<RunResult>> results
             catch ( ExecutionException e )
             {
                 Throwable realException = e.getCause();
-                String error = realException == null ? "" : realException.getLocalizedMessage();
-                throw new SurefireBooterForkException( "ExecutionException " + error, realException );
+                if ( realException == null )
+                {
+                    if ( exception == null )
+                    {
+                        exception = new SurefireBooterForkException( EXECUTION_EXCEPTION );
+                    }
+                }
+                else
+                {
+                    String previousError = "";
+                    if ( exception != null && !EXECUTION_EXCEPTION.equals( exception.getLocalizedMessage().trim() ) )
+                    {
+                        previousError = exception.getLocalizedMessage() + "\n";
+                    }
+                    String error = previousError + EXECUTION_EXCEPTION + " " + realException.getLocalizedMessage();
+                    exception = new SurefireBooterForkException( error, realException );
+                }
             }
         }
+
+        if ( exception != null )
+        {
+            throw exception;
+        }
+
         return globalResult;
     }
 
@@ -489,6 +529,7 @@ private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkC
         throws SurefireBooterForkException
     {
         int forkNumber = drawNumber();
+        forkClient.setForkNumber( forkNumber );
         try
         {
             return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
@@ -505,14 +546,20 @@ private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkC
                             AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream )
         throws SurefireBooterForkException
     {
+        final String tempDir;
         final File surefireProperties;
         final File systPropsFile;
         try
         {
+            tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
 
             surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
-                                                             startupConfiguration, testSet, readTestsFromInStream );
+                                                                   startupConfiguration, testSet,
+                                                                   readTestsFromInStream,
+                                                                   forkConfiguration.getPluginPlatform().getPid() );
+
+            log.debug( "Determined Maven Process ID " + forkConfiguration.getPluginPlatform().getPid() );
 
             if ( effectiveSystemProperties != null )
             {
@@ -553,31 +600,35 @@ private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkC
             testProvidingInputStream.setFlushReceiverProvider( cli );
         }
 
-        cli.createArg().setFile( surefireProperties );
-
+        cli.createArg().setValue( tempDir );
+        cli.createArg().setValue( DUMP_FILE_PREFIX + forkNumber );
+        cli.createArg().setValue( surefireProperties.getName() );
         if ( systPropsFile != null )
         {
-            cli.createArg().setFile( systPropsFile );
+            cli.createArg().setValue( systPropsFile.getName() );
         }
 
         final ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
-        final CloseableCloser closer =
-                new CloseableCloser( threadedStreamConsumer, requireNonNull( testProvidingInputStream, "null param" ) );
+        final CloseableCloser closer = new CloseableCloser( forkNumber, threadedStreamConsumer,
+                                                            requireNonNull( testProvidingInputStream, "null param" ) );
 
         log.debug( "Forking command line: " + cli );
 
+        Integer result = null;
         RunResult runResult = null;
         SurefireBooterForkException booterForkException = null;
         try
         {
+            NativeStdErrStreamConsumer stdErrConsumer =
+                    new NativeStdErrStreamConsumer( forkClient.getDefaultReporterFactory() );
+
             CommandLineCallable future =
-                executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
-                                              new NativeStdErrStreamConsumer(), 0, closer,
-                                              Charset.forName( FORK_STREAM_CHARSET_NAME ) );
+                    executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer,
+                                                        stdErrConsumer, 0, closer, ISO_8859_1 );
 
             currentForkClients.add( forkClient );
 
-            int result = future.call();
+            result = future.call();
 
             if ( forkClient.hadTimeout() )
             {
@@ -599,6 +650,7 @@ else if ( result != SUCCESS )
         }
         finally
         {
+            currentForkClients.remove( forkClient );
             closer.close();
             if ( runResult == null )
             {
@@ -615,17 +667,23 @@ else if ( result != SUCCESS )
                 {
                     StackTraceWriter errorInFork = forkClient.getErrorInFork();
                     // noinspection ThrowFromFinallyBlock
-                    throw new RuntimeException( "There was an error in the forked process"
+                    throw new SurefireBooterForkException( "There was an error in the forked process"
                                                         + detail
                                                         + '\n'
-                                                        + errorInFork.writeTraceToString(), cause );
+                                                        + errorInFork.getThrowable().getLocalizedMessage(), cause );
                 }
                 if ( !forkClient.isSaidGoodBye() )
                 {
+                    String errorCode = result == null ? "" : "\nProcess Exit Code: " + result;
+                    String testsInProgress = forkClient.hasTestsInProgress() ? "\nCrashed tests:" : "";
+                    for ( String test : forkClient.testsInProgress() )
+                    {
+                        testsInProgress += "\n" + test;
+                    }
                     // noinspection ThrowFromFinallyBlock
-                    throw new RuntimeException(
+                    throw new SurefireBooterForkException(
                         "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
-                            + "\nCommand was " + cli.toString() + detail, cause );
+                            + "\nCommand was " + cli.toString() + detail + errorCode + testsInProgress, cause );
                 }
             }
 
@@ -666,6 +724,7 @@ private static Thread createImmediateShutdownHookThread( final TestLessInputStre
     {
         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
         {
+            @Override
             public void run()
             {
                 builder.getImmediateCommands().shutdown( shutdownType );
@@ -678,6 +737,7 @@ private static Thread createCachableShutdownHookThread( final TestLessInputStrea
     {
         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
         {
+            @Override
             public void run()
             {
                 builder.getCachableCommands().shutdown( shutdownType );
@@ -690,6 +750,7 @@ private static Thread createShutdownHookThread( final Iterable<TestProvidingInpu
     {
         return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable()
         {
+            @Override
             public void run()
             {
                 for ( TestProvidingInputStream stream : streams )
@@ -716,6 +777,7 @@ private static ScheduledExecutorService createTimeoutCheckScheduler()
     {
         return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
         {
+            @Override
             public void run()
             {
                 builder.getImmediateCommands().noop();
@@ -727,6 +789,7 @@ public void run()
     {
         return pingThreadScheduler.scheduleAtFixedRate( new Runnable()
         {
+            @Override
             public void run()
             {
                 for ( TestProvidingInputStream stream : streams )
@@ -741,6 +804,7 @@ public void run()
     {
         return timeoutCheckScheduler.scheduleAtFixedRate( new Runnable()
         {
+            @Override
             public void run()
             {
                 long systemTime = currentTimeMillis();
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
new file mode 100644
index 000000000..10b16dc63
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
@@ -0,0 +1,70 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.booter.SystemUtils;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RunnableFuture;
+
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
+
+/**
+ * Loads platform specifics.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class Platform
+{
+    private final RunnableFuture<Long> pidJob;
+
+    public Platform()
+    {
+        // the job may take 50 or 80 ms
+        pidJob = new FutureTask<Long>( pidJob() );
+        newDaemonThread( pidJob ).start();
+    }
+
+    public Long getPid()
+    {
+        try
+        {
+            return pidJob.get();
+        }
+        catch ( Exception e )
+        {
+            return null;
+        }
+    }
+
+    private static Callable<Long> pidJob()
+    {
+        return new Callable<Long>()
+        {
+            @Override
+            public Long call() throws Exception
+            {
+                return SystemUtils.pid();
+            }
+        };
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ProviderDetector.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ProviderDetector.java
index 3b93eff58..fce3d4642 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ProviderDetector.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ProviderDetector.java
@@ -19,7 +19,7 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.spi.ServiceLoader;
+import org.apache.maven.surefire.providerapi.ServiceLoader;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
@@ -28,7 +28,6 @@
 /**
  * @author Stephen Conolly
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 public final class ProviderDetector
 {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
index 4d6331c03..31b56c4c1 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java
@@ -36,19 +36,23 @@
 {
     private byte[] currentBuffer;
     private int currentPos;
-    private volatile MasterProcessCommand lastCommand;
 
     protected abstract boolean isClosed();
 
     /**
-     * Unnecessarily opposite to {@link #isClosed()} however may respect
-     * {@link #getLastCommand() last command} and {@link #isClosed()}.
+     * Opposite to {@link #isClosed()}.
+     * @return {@code true} if not closed
      */
-    protected abstract boolean canContinue();
+    protected boolean canContinue()
+    {
+        return !isClosed();
+    }
 
     /**
      * Possibly waiting for next command (see {@link #nextCommand()}) unless the stream is atomically
      * closed (see {@link #isClosed()} returns {@code true}) before this method has returned.
+     *
+     * @throws IOException stream error while waiting for notification regarding next test required by forked jvm
      */
     protected void beforeNextCommand()
         throws IOException
@@ -66,11 +70,6 @@ protected final void invalidateInternalBuffer()
         currentPos = 0;
     }
 
-    protected final MasterProcessCommand getLastCommand()
-    {
-        return lastCommand;
-    }
-
     /**
      * Used by single thread in StreamFeeder class.
      *
@@ -84,11 +83,11 @@ public int read()
     {
         if ( isClosed() )
         {
+            tryFlush();
             return -1;
         }
 
-        byte[] buffer = currentBuffer;
-        if ( buffer == null )
+        if ( currentBuffer == null )
         {
             tryFlush();
 
@@ -106,17 +105,16 @@ public int read()
             }
 
             Command cmd = nextCommand();
-            lastCommand = cmd.getCommandType();
-            buffer = lastCommand.hasDataType() ? lastCommand.encode( cmd.getData() ) : lastCommand.encode();
+            MasterProcessCommand cmdType = cmd.getCommandType();
+            currentBuffer = cmdType.hasDataType() ? cmdType.encode( cmd.getData() ) : cmdType.encode();
         }
 
-        int b =  buffer[currentPos++] & 0xff;
-        if ( currentPos == buffer.length )
+        int b =  currentBuffer[currentPos++] & 0xff;
+        if ( currentPos == currentBuffer.length )
         {
-            buffer = null;
+            currentBuffer = null;
             currentPos = 0;
         }
-        currentBuffer = buffer;
         return b;
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java
index 5c8917332..b181de137 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java
@@ -45,4 +45,6 @@
     void shutdown( Shutdown shutdownType );
 
     void noop();
+
+    void acknowledgeByeEventReceived();
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
index 0abf42fa2..eb1ab5b61 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
@@ -48,6 +48,7 @@ private OutputStreamFlushReceiver( OutputStream outputStream )
             this.outputStream = outputStream;
         }
 
+        @Override
         public void flush()
             throws IOException
         {
@@ -71,6 +72,7 @@ public Process execute()
         return process;
     }
 
+    @Override
     public FlushReceiver getFlushReceiver()
     {
         return flushReceiver;
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
index b6ae42cd5..521130c2e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java
@@ -33,6 +33,7 @@
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import static org.apache.maven.surefire.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
 import static org.apache.maven.surefire.booter.Command.toShutdown;
@@ -61,10 +62,12 @@ private TestLessInputStream( TestLessInputStreamBuilder builder )
         this.builder = builder;
     }
 
+    @Override
     public void provideNewTest()
     {
     }
 
+    @Override
     public void skipSinceNextTest()
     {
         if ( canContinue() )
@@ -74,6 +77,7 @@ public void skipSinceNextTest()
         }
     }
 
+    @Override
     public void shutdown( Shutdown shutdownType )
     {
         if ( canContinue() )
@@ -83,6 +87,7 @@ public void shutdown( Shutdown shutdownType )
         }
     }
 
+    @Override
     public void noop()
     {
         if ( canContinue() )
@@ -93,15 +98,19 @@ public void noop()
     }
 
     @Override
-    protected boolean isClosed()
+    public void acknowledgeByeEventReceived()
     {
-        return closed.get();
+        if ( canContinue() )
+        {
+            immediateCommands.add( BYE_ACK );
+            barrier.release();
+        }
     }
 
     @Override
-    protected boolean canContinue()
+    protected boolean isClosed()
     {
-        return !isClosed();
+        return closed.get();
     }
 
     @Override
@@ -181,6 +190,7 @@ public TestLessInputStreamBuilder()
         {
             iterableCachable = new Iterable<Command>()
             {
+                @Override
                 public Iterator<Command> iterator()
                 {
                     return new CIt();
@@ -265,11 +275,13 @@ private static Node nextCachedNode( Node current )
         {
             private Node node = TestLessInputStreamBuilder.this.head;
 
+            @Override
             public boolean hasNext()
             {
                 return examineNext( false ) != null;
             }
 
+            @Override
             public Command next()
             {
                 Command command = examineNext( true );
@@ -280,6 +292,7 @@ public Command next()
                 return command;
             }
 
+            @Override
             public void remove()
             {
                 throw new UnsupportedOperationException();
@@ -302,10 +315,12 @@ private Command examineNext( boolean store )
         private final class ImmediateCommands
             implements NotifiableTestStream
         {
+            @Override
             public void provideNewTest()
             {
             }
 
+            @Override
             public void skipSinceNextTest()
             {
                 Lock lock = rwLock.readLock();
@@ -323,6 +338,7 @@ public void skipSinceNextTest()
                 }
             }
 
+            @Override
             public void shutdown( Shutdown shutdownType )
             {
                 Lock lock = rwLock.readLock();
@@ -340,6 +356,7 @@ public void shutdown( Shutdown shutdownType )
                 }
             }
 
+            @Override
             public void noop()
             {
                 Lock lock = rwLock.readLock();
@@ -356,6 +373,24 @@ public void noop()
                     lock.unlock();
                 }
             }
+
+            @Override
+            public void acknowledgeByeEventReceived()
+            {
+                Lock lock = rwLock.readLock();
+                lock.lock();
+                try
+                {
+                    for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams )
+                    {
+                        aliveStream.acknowledgeByeEventReceived();
+                    }
+                }
+                finally
+                {
+                    lock.unlock();
+                }
+            }
         }
 
         /**
@@ -364,10 +399,12 @@ public void noop()
         private final class CachableCommands
             implements NotifiableTestStream
         {
+            @Override
             public void provideNewTest()
             {
             }
 
+            @Override
             public void skipSinceNextTest()
             {
                 Lock lock = rwLock.readLock();
@@ -385,6 +422,7 @@ public void skipSinceNextTest()
                 }
             }
 
+            @Override
             public void shutdown( Shutdown shutdownType )
             {
                 Lock lock = rwLock.readLock();
@@ -402,6 +440,7 @@ public void shutdown( Shutdown shutdownType )
                 }
             }
 
+            @Override
             public void noop()
             {
                 Lock lock = rwLock.readLock();
@@ -419,6 +458,24 @@ public void noop()
                 }
             }
 
+            @Override
+            public void acknowledgeByeEventReceived()
+            {
+                Lock lock = rwLock.readLock();
+                lock.lock();
+                try
+                {
+                    if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( BYE_ACK ) )
+                    {
+                        release();
+                    }
+                }
+                finally
+                {
+                    lock.unlock();
+                }
+            }
+
             private void release()
             {
                 for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
index 766843d76..bc26ab254 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
@@ -28,7 +28,7 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.booter.Command.BYE_ACK;
 import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
 import static org.apache.maven.surefire.booter.Command.toRunClass;
@@ -36,13 +36,13 @@
 
 /**
  * An {@link java.io.InputStream} that, when read, provides test class names out of a queue.
- * <p/>
+ * <br>
  * The Stream provides only one test at a time, but only after {@link #provideNewTest()} has been invoked.
- * <p/>
+ * <br>
  * After providing each test class name, followed by a newline character, a flush is performed on the
  * {@link FlushReceiver} provided by the {@link FlushReceiverProvider} that can be set using
  * {@link #setFlushReceiverProvider(FlushReceiverProvider)}.
- * <p/>
+ * <br>
  * The instance is used only in reusable forks in {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}
  * by one Thread.
  *
@@ -82,6 +82,7 @@ void testSetFinished()
         }
     }
 
+    @Override
     public void skipSinceNextTest()
     {
         if ( canContinue() )
@@ -91,6 +92,7 @@ public void skipSinceNextTest()
         }
     }
 
+    @Override
     public void shutdown( Shutdown shutdownType )
     {
         if ( canContinue() )
@@ -100,6 +102,7 @@ public void shutdown( Shutdown shutdownType )
         }
     }
 
+    @Override
     public void noop()
     {
         if ( canContinue() )
@@ -109,6 +112,16 @@ public void noop()
         }
     }
 
+    @Override
+    public void acknowledgeByeEventReceived()
+    {
+        if ( canContinue() )
+        {
+            commands.add( BYE_ACK );
+            barrier.release();
+        }
+    }
+
     @Override
     protected Command nextCommand()
     {
@@ -137,15 +150,10 @@ protected boolean isClosed()
         return closed.get();
     }
 
-    @Override
-    protected boolean canContinue()
-    {
-        return getLastCommand() != TEST_SET_FINISHED && !isClosed();
-    }
-
     /**
      * Signal that a new test is to be provided.
      */
+    @Override
     public void provideNewTest()
     {
         if ( canContinue() )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/DeserializedStacktraceWriter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/DeserializedStacktraceWriter.java
index 8832a3642..d7a01b6f4 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/DeserializedStacktraceWriter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/DeserializedStacktraceWriter.java
@@ -25,7 +25,7 @@
 /**
  * Represents a deserialize stacktracewriter that has been
  * marshalled across to the plugin from the fork.
- * <p/>
+ * <br>
  * Might be better to represent this whole thing differently
  *
  * @author Kristian Rosenvold
@@ -46,24 +46,28 @@ public DeserializedStacktraceWriter( String message, String smartTrimmed, String
         this.stackTrace = stackTrace;
     }
 
+    @Override
     public String smartTrimmedStackTrace()
     {
         return smartTrimmed;
     }
 
     // Trimming or not is decided on the forking side
+    @Override
     public String writeTraceToString()
     {
         return stackTrace;
     }
 
+    @Override
     public String writeTrimmedTraceToString()
     {
         return stackTrace;
     }
 
+    @Override
     public SafeThrowable getThrowable()
     {
-        return new SafeThrowable( new Throwable( message ) );
+        return new SafeThrowable( message );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
index e6e5a6eeb..bebc94916 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
@@ -19,30 +19,35 @@
  * under the License.
  */
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.ByteBuffer;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.StringTokenizer;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicLong;
-
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.shared.utils.cli.StreamConsumer;
-import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
-import org.apache.maven.surefire.report.ReporterException;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 
 import static java.lang.Integer.decode;
-import static java.lang.Integer.parseInt;
 import static java.lang.System.currentTimeMillis;
+import static java.util.Collections.unmodifiableMap;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_CONSOLE;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_DEBUG;
@@ -52,14 +57,14 @@
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_STDOUT;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_STOP_ON_NEXT_TEST;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED;
+import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_STARTING;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_ERROR;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_FAILED;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_SKIPPED;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_STARTING;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TEST_SUCCEEDED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED;
-import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_TESTSET_STARTING;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_WARNING;
 import static org.apache.maven.surefire.booter.Shutdown.KILL;
 import static org.apache.maven.surefire.report.CategorizedReportEntry.reportEntry;
@@ -67,45 +72,65 @@
 import static org.apache.maven.surefire.util.internal.StringUtils.unescapeBytes;
 import static org.apache.maven.surefire.util.internal.StringUtils.unescapeString;
 
+// todo move to the same package with ForkStarter
+
 /**
  * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
  *
  * @author Kristian Rosenvold
  */
 public class ForkClient
-    implements StreamConsumer
+     implements StreamConsumer
 {
+    private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
     private static final long START_TIME_ZERO = 0L;
     private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
 
-    private final ConcurrentMap<Integer, RunListener> testSetReporters;
-
     private final DefaultReporterFactory defaultReporterFactory;
 
-    private final Properties testVmSystemProperties;
+    private final Map<String, String> testVmSystemProperties = new ConcurrentHashMap<String, String>();
 
     private final NotifiableTestStream notifiableTestStream;
 
+    private final Queue<String> testsInProgress = new ConcurrentLinkedQueue<String>();
+
     /**
-     * <t>testSetStartedAt</t> is set to non-zero after received
+     * {@code testSetStartedAt} is set to non-zero after received
      * {@link org.apache.maven.surefire.booter.ForkingRunListener#BOOTERCODE_TESTSET_STARTING test-set}.
      */
     private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
 
     private final ConsoleLogger log;
 
+    private final boolean debug;
+
+    /**
+     * prevents from printing same warning
+     */
+    private final AtomicBoolean printedErrorStream;
+
+    /**
+     * Used by single Thread started by {@link ThreadedStreamConsumer} and therefore does not need to be volatile.
+     */
+    private RunListener testSetReporter;
+
+    /**
+     * Written by one Thread and read by another: Main Thread and ForkStarter's Thread.
+     */
     private volatile boolean saidGoodBye;
 
     private volatile StackTraceWriter errorInFork;
 
-    public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties,
-                       NotifiableTestStream notifiableTestStream, ConsoleLogger log )
+    private volatile int forkNumber;
+
+    public ForkClient( DefaultReporterFactory defaultReporterFactory, NotifiableTestStream notifiableTestStream,
+                       ConsoleLogger log, boolean debug, AtomicBoolean printedErrorStream )
     {
-        testSetReporters = new ConcurrentHashMap<Integer, RunListener>();
         this.defaultReporterFactory = defaultReporterFactory;
-        this.testVmSystemProperties = testVmSystemProperties;
         this.notifiableTestStream = notifiableTestStream;
         this.log = log;
+        this.debug = debug;
+        this.printedErrorStream = printedErrorStream;
     }
 
     protected void stopOnNextTest()
@@ -122,6 +147,10 @@ public void kill()
 
     /**
      * Called in concurrent Thread.
+     * Will shutdown if timeout was reached.
+     *
+     * @param currentTimeMillis    current time in millis seconds
+     * @param forkedProcessTimeoutInSeconds timeout in seconds given by MOJO
      */
     public final void tryToTimeout( long currentTimeMillis, int forkedProcessTimeoutInSeconds )
     {
@@ -142,6 +171,7 @@ public final DefaultReporterFactory getDefaultReporterFactory()
         return defaultReporterFactory;
     }
 
+    @Override
     public final void consumeLine( String s )
     {
         if ( isNotBlank( s ) )
@@ -165,118 +195,157 @@ public final boolean hadTimeout()
         return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT;
     }
 
-    private void processLine( String s )
+    private RunListener getTestSetReporter()
+    {
+        if ( testSetReporter == null )
+        {
+            testSetReporter = defaultReporterFactory.createReporter();
+        }
+        return testSetReporter;
+    }
+
+    private void processLine( String event )
     {
+        final OperationalData op;
         try
         {
-            final byte operationId = (byte) s.charAt( 0 );
-            int comma = s.indexOf( ",", 3 );
-            if ( comma < 0 )
+            op = new OperationalData( event );
+        }
+        catch ( RuntimeException e )
+        {
+            logStreamWarning( e, event );
+            return;
+        }
+        final String remaining = op.getData();
+        switch ( op.getOperationId() )
+        {
+            case BOOTERCODE_TESTSET_STARTING:
+                getTestSetReporter().testSetStarting( createReportEntry( remaining ) );
+                setCurrentStartTime();
+                break;
+            case BOOTERCODE_TESTSET_COMPLETED:
+                testsInProgress.clear();
+
+                getTestSetReporter().testSetCompleted( createReportEntry( remaining, testVmSystemProperties ) );
+                break;
+            case BOOTERCODE_TEST_STARTING:
+                ReportEntry reportEntry = createReportEntry( remaining );
+                testsInProgress.offer( reportEntry.getSourceName() );
+
+                getTestSetReporter().testStarting( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_TEST_SUCCEEDED:
+                reportEntry = createReportEntry( remaining );
+                testsInProgress.remove( reportEntry.getSourceName() );
+
+                getTestSetReporter().testSucceeded( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_TEST_FAILED:
+                reportEntry = createReportEntry( remaining );
+                testsInProgress.remove( reportEntry.getSourceName() );
+
+                getTestSetReporter().testFailed( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_TEST_SKIPPED:
+                reportEntry = createReportEntry( remaining );
+                testsInProgress.remove( reportEntry.getSourceName() );
+
+                getTestSetReporter().testSkipped( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_TEST_ERROR:
+                reportEntry = createReportEntry( remaining );
+                testsInProgress.remove( reportEntry.getSourceName() );
+
+                getTestSetReporter().testError( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                reportEntry = createReportEntry( remaining );
+                testsInProgress.remove( reportEntry.getSourceName() );
+
+                getTestSetReporter().testAssumptionFailure( createReportEntry( remaining ) );
+                break;
+            case BOOTERCODE_SYSPROPS:
+                int keyEnd = remaining.indexOf( "," );
+                StringBuilder key = new StringBuilder();
+                StringBuilder value = new StringBuilder();
+                unescapeString( key, remaining.substring( 0, keyEnd ) );
+                unescapeString( value, remaining.substring( keyEnd + 1 ) );
+                testVmSystemProperties.put( key.toString(), value.toString() );
+                break;
+            case BOOTERCODE_STDOUT:
+                writeTestOutput( remaining, true );
+                break;
+            case BOOTERCODE_STDERR:
+                writeTestOutput( remaining, false );
+                break;
+            case BOOTERCODE_CONSOLE:
+                getOrCreateConsoleLogger()
+                        .info( createConsoleMessage( remaining ) );
+                break;
+            case BOOTERCODE_NEXT_TEST:
+                notifiableTestStream.provideNewTest();
+                break;
+            case BOOTERCODE_ERROR:
+                errorInFork = deserializeStackTraceWriter( new StringTokenizer( remaining, "," ) );
+                break;
+            case BOOTERCODE_BYE:
+                saidGoodBye = true;
+                notifiableTestStream.acknowledgeByeEventReceived();
+                break;
+            case BOOTERCODE_STOP_ON_NEXT_TEST:
+                stopOnNextTest();
+                break;
+            case BOOTERCODE_DEBUG:
+                getOrCreateConsoleLogger()
+                        .debug( createConsoleMessage( remaining ) );
+                break;
+            case BOOTERCODE_WARNING:
+                getOrCreateConsoleLogger()
+                        .warning( createConsoleMessage( remaining ) );
+                break;
+            default:
+                logStreamWarning( event );
+        }
+    }
+
+    private void logStreamWarning( String event )
+    {
+        logStreamWarning( null, event );
+    }
+
+    private void logStreamWarning( Throwable e, String event )
+    {
+        if ( event == null || !event.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+        {
+            String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM " + forkNumber + ".";
+
+            InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
+            File dump =
+                    e == null
+                    ? util.dumpText( msg + " Stream '" + event + "'.", defaultReporterFactory, forkNumber )
+                    : util.dumpException( e, msg + " Stream '" + event + "'.", defaultReporterFactory, forkNumber );
+
+            if ( printedErrorStream.compareAndSet( false, true ) )
             {
-                log.warning( s );
-                return;
+                log.warning( msg + " See FAQ web page and the dump file " + dump.getAbsolutePath() );
             }
-            final int channelNumber = parseInt( s.substring( 2, comma ), 16 );
-            int rest = s.indexOf( ",", comma );
-            final String remaining = s.substring( rest + 1 );
 
-            switch ( operationId )
+            if ( debug && event != null )
             {
-                case BOOTERCODE_TESTSET_STARTING:
-                    getOrCreateReporter( channelNumber )
-                            .testSetStarting( createReportEntry( remaining ) );
-                    setCurrentStartTime();
-                    break;
-                case BOOTERCODE_TESTSET_COMPLETED:
-                    getOrCreateReporter( channelNumber )
-                            .testSetCompleted( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_STARTING:
-                    getOrCreateReporter( channelNumber )
-                            .testStarting( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_SUCCEEDED:
-                    getOrCreateReporter( channelNumber )
-                            .testSucceeded( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_FAILED:
-                    getOrCreateReporter( channelNumber )
-                            .testFailed( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_SKIPPED:
-                    getOrCreateReporter( channelNumber )
-                            .testSkipped( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_ERROR:
-                    getOrCreateReporter( channelNumber )
-                            .testError( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                    getOrCreateReporter( channelNumber )
-                            .testAssumptionFailure( createReportEntry( remaining ) );
-                    break;
-                case BOOTERCODE_SYSPROPS:
-                    int keyEnd = remaining.indexOf( "," );
-                    StringBuilder key = new StringBuilder();
-                    StringBuilder value = new StringBuilder();
-                    unescapeString( key, remaining.substring( 0, keyEnd ) );
-                    unescapeString( value, remaining.substring( keyEnd + 1 ) );
-                    synchronized ( testVmSystemProperties )
-                    {
-                        testVmSystemProperties.put( key.toString(), value.toString() );
-                    }
-                    break;
-                case BOOTERCODE_STDOUT:
-                    writeTestOutput( channelNumber, remaining, true );
-                    break;
-                case BOOTERCODE_STDERR:
-                    writeTestOutput( channelNumber, remaining, false );
-                    break;
-                case BOOTERCODE_CONSOLE:
-                    getOrCreateConsoleLogger( channelNumber )
-                            .info( createConsoleMessage( remaining ) );
-                    break;
-                case BOOTERCODE_NEXT_TEST:
-                    notifiableTestStream.provideNewTest();
-                    break;
-                case BOOTERCODE_ERROR:
-                    errorInFork = deserializeStackTraceWriter( new StringTokenizer( remaining, "," ) );
-                    break;
-                case BOOTERCODE_BYE:
-                    saidGoodBye = true;
-                    break;
-                case BOOTERCODE_STOP_ON_NEXT_TEST:
-                    stopOnNextTest();
-                    break;
-                case BOOTERCODE_DEBUG:
-                    getOrCreateConsoleLogger( channelNumber )
-                            .debug( createConsoleMessage( remaining ) );
-                    break;
-                case BOOTERCODE_WARNING:
-                    getOrCreateConsoleLogger( channelNumber )
-                            .warning( createConsoleMessage( remaining ) );
-                    break;
-                default:
-                    log.warning( s );
+                log.debug( event );
             }
         }
-        catch ( NumberFormatException e )
-        {
-            // SUREFIRE-859
-            log.warning( s );
-        }
-        catch ( NoSuchElementException e )
+        else if ( debug )
         {
-            // SUREFIRE-859
-            log.warning( s );
+            log.debug( event );
         }
-        catch ( ReporterException e )
+        else
         {
-            throw new RuntimeException( e );
+            log.info( event );
         }
     }
 
-    private void writeTestOutput( final int channelNumber, final String remaining, final boolean isStdout )
+    private void writeTestOutput( String remaining, boolean isStdout )
     {
         int csNameEnd = remaining.indexOf( ',' );
         String charsetName = remaining.substring( 0, csNameEnd );
@@ -286,20 +355,20 @@ private void writeTestOutput( final int channelNumber, final String remaining, f
         if ( unescaped.hasArray() )
         {
             byte[] convertedBytes = unescaped.array();
-            getOrCreateConsoleOutputReceiver( channelNumber )
+            getOrCreateConsoleOutputReceiver()
                 .writeTestOutput( convertedBytes, unescaped.position(), unescaped.remaining(), isStdout );
         }
         else
         {
             byte[] convertedBytes = new byte[unescaped.remaining()];
             unescaped.get( convertedBytes, 0, unescaped.remaining() );
-            getOrCreateConsoleOutputReceiver( channelNumber )
+            getOrCreateConsoleOutputReceiver()
                 .writeTestOutput( convertedBytes, 0, convertedBytes.length, isStdout );
         }
     }
 
     public final void consumeMultiLineContent( String s )
-        throws IOException
+            throws IOException
     {
         BufferedReader stringReader = new BufferedReader( new StringReader( s ) );
         for ( String s1 = stringReader.readLine(); s1 != null; s1 = stringReader.readLine() )
@@ -313,7 +382,12 @@ private String createConsoleMessage( String remaining )
         return unescape( remaining );
     }
 
-    private ReportEntry createReportEntry( String untokenized )
+    private TestSetReportEntry createReportEntry( String untokenized )
+    {
+        return createReportEntry( untokenized, Collections.<String, String>emptyMap() );
+    }
+
+    private TestSetReportEntry createReportEntry( String untokenized, Map<String, String> systemProperties )
     {
         StringTokenizer tokens = new StringTokenizer( untokenized, "," );
         try
@@ -325,9 +399,9 @@ private ReportEntry createReportEntry( String untokenized )
             String elapsedStr = tokens.nextToken();
             Integer elapsed = "null".equals( elapsedStr ) ? null : decode( elapsedStr );
             final StackTraceWriter stackTraceWriter =
-                tokens.hasMoreTokens() ? deserializeStackTraceWriter( tokens ) : null;
+                    tokens.hasMoreTokens() ? deserializeStackTraceWriter( tokens ) : null;
 
-            return reportEntry( source, name, group, stackTraceWriter, elapsed, message );
+            return reportEntry( source, name, group, stackTraceWriter, elapsed, message, systemProperties );
         }
         catch ( RuntimeException e )
         {
@@ -356,40 +430,30 @@ private String unescape( String source )
         return stringBuffer.toString();
     }
 
+    public final Map<String, String> getTestVmSystemProperties()
+    {
+        return unmodifiableMap( testVmSystemProperties );
+    }
+
     /**
      * Used when getting reporters on the plugin side of a fork.
+     * Used by testing purposes only. May not be volatile variable.
      *
-     * @param channelNumber The logical channel number
      * @return A mock provider reporter
      */
-    public final RunListener getReporter( int channelNumber )
-    {
-        return testSetReporters.get( channelNumber );
-    }
-
-    private RunListener getOrCreateReporter( int channelNumber )
+    public final RunListener getReporter()
     {
-        RunListener reporter = testSetReporters.get( channelNumber );
-        if ( reporter == null )
-        {
-            reporter = defaultReporterFactory.createReporter();
-            RunListener old = testSetReporters.putIfAbsent( channelNumber, reporter );
-            if ( old != null )
-            {
-                reporter = old;
-            }
-        }
-        return reporter;
+        return getTestSetReporter();
     }
 
-    private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver( int channelNumber )
+    private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver()
     {
-        return (ConsoleOutputReceiver) getOrCreateReporter( channelNumber );
+        return (ConsoleOutputReceiver) getTestSetReporter();
     }
 
-    private ConsoleLogger getOrCreateConsoleLogger( int channelNumber )
+    private ConsoleLogger getOrCreateConsoleLogger()
     {
-        return (ConsoleLogger) getOrCreateReporter( channelNumber );
+        return (ConsoleLogger) getTestSetReporter();
     }
 
     public void close( boolean hadTimeout )
@@ -411,4 +475,49 @@ public final boolean isErrorInFork()
     {
         return errorInFork != null;
     }
+
+    public Set<String> testsInProgress()
+    {
+        return new TreeSet<String>( testsInProgress );
+    }
+
+    public boolean hasTestsInProgress()
+    {
+        return !testsInProgress.isEmpty();
+    }
+
+    public void setForkNumber( int forkNumber )
+    {
+        assert this.forkNumber == 0;
+        this.forkNumber = forkNumber;
+    }
+
+    private static final class OperationalData
+    {
+        private final byte operationId;
+        private final String data;
+
+        OperationalData( String event )
+        {
+            operationId = (byte) event.charAt( 0 );
+            int comma = event.indexOf( ",", 3 );
+            if ( comma < 0 )
+            {
+                throw new IllegalArgumentException( "Stream stdin corrupted. Expected comma after third character "
+                                                            + "in command '" + event + "'." );
+            }
+            int rest = event.indexOf( ",", comma );
+            data = event.substring( rest + 1 );
+        }
+
+        byte getOperationId()
+        {
+            return operationId;
+        }
+
+        String getData()
+        {
+            return data;
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
new file mode 100644
index 000000000..0676b5290
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/InPluginProcessDumpSingleton.java
@@ -0,0 +1,91 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.apache.maven.surefire.util.internal.DumpFileUtils;
+
+import java.io.File;
+
+import static java.lang.String.format;
+import static org.apache.maven.plugin.surefire.SurefireHelper.DUMPSTREAM_FILENAME_FORMATTER;
+import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT;
+
+/**
+ * Reports errors to dump file.
+ * Used only within java process of the plugin itself and not the forked JVM.
+ */
+public final class InPluginProcessDumpSingleton
+{
+    private static final InPluginProcessDumpSingleton SINGLETON = new InPluginProcessDumpSingleton();
+
+    private final String creationDate = DumpFileUtils.newFormattedDateFileName();
+
+    private InPluginProcessDumpSingleton()
+    {
+    }
+
+    public static InPluginProcessDumpSingleton getSingleton()
+    {
+        return SINGLETON;
+    }
+
+    public synchronized File dumpException( Throwable t, String msg, DefaultReporterFactory defaultReporterFactory,
+                                            int jvmRun )
+    {
+        File dump = newDumpFile( defaultReporterFactory, jvmRun );
+        DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dump );
+        return dump;
+    }
+
+    public synchronized void dumpException( Throwable t, String msg, DefaultReporterFactory defaultReporterFactory )
+    {
+        DumpFileUtils.dumpException( t, msg == null ? "null" : msg, newDumpFile( defaultReporterFactory ) );
+    }
+
+    public synchronized void dumpException( Throwable t, DefaultReporterFactory defaultReporterFactory )
+    {
+        DumpFileUtils.dumpException( t, newDumpFile( defaultReporterFactory ) );
+    }
+
+    public synchronized File dumpText( String msg, DefaultReporterFactory defaultReporterFactory, int jvmRun )
+    {
+        File dump = newDumpFile( defaultReporterFactory, jvmRun );
+        DumpFileUtils.dumpText( msg == null ? "null" : msg, dump );
+        return dump;
+    }
+
+    public synchronized void dumpText( String msg, DefaultReporterFactory defaultReporterFactory )
+    {
+        DumpFileUtils.dumpText( msg == null ? "null" : msg, newDumpFile( defaultReporterFactory ) );
+    }
+
+    private File newDumpFile( DefaultReporterFactory defaultReporterFactory )
+    {
+        File reportsDirectory = defaultReporterFactory.getReportsDirectory();
+        return new File( reportsDirectory, creationDate + DUMPSTREAM_FILE_EXT );
+    }
+
+    private static File newDumpFile( DefaultReporterFactory defaultReporterFactory, int jvmRun )
+    {
+        File reportsDirectory = defaultReporterFactory.getReportsDirectory();
+        return new File( reportsDirectory, format( DUMPSTREAM_FILENAME_FORMATTER, jvmRun ) );
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
new file mode 100644
index 000000000..060994b27
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/MultipleFailureException.java
@@ -0,0 +1,72 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+final class MultipleFailureException
+        extends IOException
+{
+    private final Queue<Throwable> exceptions = new ConcurrentLinkedQueue<Throwable>();
+
+    void addException( Throwable exception )
+    {
+        exceptions.add( exception );
+    }
+
+    boolean hasNestedExceptions()
+    {
+        return !exceptions.isEmpty();
+    }
+
+    @Override
+    public String getLocalizedMessage()
+    {
+        StringBuilder messages = new StringBuilder();
+        for ( Throwable exception : exceptions )
+        {
+            if ( messages.length() != 0 )
+            {
+                messages.append( '\n' );
+            }
+            String message = exception.getLocalizedMessage();
+            messages.append( message == null ? exception.toString() : message );
+        }
+        return messages.toString();
+    }
+
+    @Override
+    public String getMessage()
+    {
+        StringBuilder messages = new StringBuilder();
+        for ( Throwable exception : exceptions )
+        {
+            if ( messages.length() != 0 )
+            {
+                messages.append( '\n' );
+            }
+            String message = exception.getMessage();
+            messages.append( message == null ? exception.toString() : message );
+        }
+        return messages.toString();
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
index 84ca474e5..d4605838c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/NativeStdErrStreamConsumer.java
@@ -19,20 +19,29 @@
  * under the License.
  */
 
+import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.shared.utils.cli.StreamConsumer;
 
 /**
  * Used by forked JMV, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  * @see org.apache.maven.plugin.surefire.booterclient.ForkStarter
  */
 public final class NativeStdErrStreamConsumer
     implements StreamConsumer
 {
+    private final DefaultReporterFactory defaultReporterFactory;
+
+    public NativeStdErrStreamConsumer( DefaultReporterFactory defaultReporterFactory )
+    {
+        this.defaultReporterFactory = defaultReporterFactory;
+    }
+
+    @Override
     public void consumeLine( String line )
     {
-        System.err.println( line );
+        InPluginProcessDumpSingleton.getSingleton().dumpText( line, defaultReporterFactory );
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
index 3f1abd8f0..093c191a5 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
@@ -22,9 +22,13 @@
 import org.apache.maven.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
 
-import java.util.concurrent.BlockingQueue;
 import java.io.Closeable;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.lang.Thread.currentThread;
 
 /**
  * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
@@ -32,100 +36,133 @@
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-    implements StreamConsumer, Closeable
+        implements StreamConsumer, Closeable
 {
-    private static final String POISON = "Pioson";
+    private static final String END_ITEM = "";
 
-    private static final int ITEM_LIMIT_BEFORE_SLEEP = 10000;
+    private static final int ITEM_LIMIT_BEFORE_SLEEP = 10 * 1000;
 
-    private final BlockingQueue<String> items = new LinkedBlockingQueue<String>();
+    private final BlockingQueue<String> items = new ArrayBlockingQueue<String>( ITEM_LIMIT_BEFORE_SLEEP );
+
+    private final AtomicBoolean stop = new AtomicBoolean();
 
     private final Thread thread;
 
     private final Pumper pumper;
 
-    static class Pumper
-        implements Runnable
+    final class Pumper
+            implements Runnable
     {
-        private final BlockingQueue<String> queue;
-
         private final StreamConsumer target;
 
-        private volatile Throwable throwable;
+        private final MultipleFailureException errors = new MultipleFailureException();
 
-
-        Pumper( BlockingQueue<String> queue, StreamConsumer target )
+        Pumper( StreamConsumer target )
         {
-            this.queue = queue;
             this.target = target;
         }
 
+        /**
+         * Calls {@link ForkClient#consumeLine(String)} which may throw any {@link RuntimeException}.<br>
+         * Even if {@link ForkClient} is not fault-tolerant, this method MUST be fault-tolerant and thus the
+         * try-catch block must be inside of the loop which prevents from loosing events from {@link StreamConsumer}.
+         * <br>
+         * If {@link org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter#writeTestOutput} throws
+         * {@link java.io.IOException} and then {@code target.consumeLine()} throws any RuntimeException, this method
+         * MUST NOT skip reading the events from the forked JVM; otherwise we could simply lost events
+         * e.g. acquire-next-test which means that {@link ForkClient} could hang on waiting for old test to complete
+         * and therefore the plugin could be permanently in progress.
+         */
+        @Override
         public void run()
         {
-            try
+            while ( !ThreadedStreamConsumer.this.stop.get() )
             {
-                String item = queue.take();
-                //noinspection StringEquality
-                while ( item != POISON )
+                try
                 {
+                    String item = ThreadedStreamConsumer.this.items.take();
+                    if ( shouldStopQueueing( item ) )
+                    {
+                        return;
+                    }
                     target.consumeLine( item );
-                    item = queue.take();
                 }
-            }
-            catch ( Throwable t )
-            {
-                // Think about what happens if the producer overruns us and creates an OOME. Not nice.
-                // Maybe limit length of blocking queue
-                this.throwable = t;
+                catch ( Throwable t )
+                {
+                    errors.addException( t );
+                }
             }
         }
 
-        public Throwable getThrowable()
+        boolean hasErrors()
         {
-            return throwable;
+            return errors.hasNestedExceptions();
+        }
+
+        void throwErrors() throws IOException
+        {
+            throw errors;
         }
     }
 
     public ThreadedStreamConsumer( StreamConsumer target )
     {
-        pumper = new Pumper( items, target );
+        pumper = new Pumper( target );
         thread = DaemonThreadFactory.newDaemonThread( pumper, "ThreadedStreamConsumer" );
         thread.start();
     }
 
-    @SuppressWarnings( "checkstyle:emptyblock" )
+    @Override
     public void consumeLine( String s )
     {
-        items.add( s );
-        if ( items.size() > ITEM_LIMIT_BEFORE_SLEEP )
+        if ( stop.get() && !thread.isAlive() )
         {
-            try
-            {
-                Thread.sleep( 100 );
-            }
-            catch ( InterruptedException ignore )
-            {
-            }
+            items.clear();
+            return;
         }
-    }
-
 
-    public void close()
-    {
         try
         {
-            items.add( POISON );
-            thread.join();
+            items.put( s );
         }
         catch ( InterruptedException e )
         {
-            throw new RuntimeException( e );
+            currentThread().interrupt();
+            throw new IllegalStateException( e );
+        }
+    }
+
+    @Override
+    public void close()
+            throws IOException
+    {
+        if ( stop.compareAndSet( false, true ) )
+        {
+            items.clear();
+            try
+            {
+                items.put( END_ITEM );
+            }
+            catch ( InterruptedException e )
+            {
+                currentThread().interrupt();
+            }
         }
 
-        //noinspection ThrowableResultOfMethodCallIgnored
-        if ( pumper.getThrowable() != null )
+        if ( pumper.hasErrors() )
         {
-            throw new RuntimeException( pumper.getThrowable() );
+            pumper.throwErrors();
         }
     }
+
+    /**
+     * Compared item with {@link #END_ITEM} by identity.
+     *
+     * @param item    element from <code>items</code>
+     * @return {@code true} if tail of the queue
+     */
+    private boolean shouldStopQueueing( String item )
+    {
+        return item == END_ITEM;
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
index 7884270ed..0f1217cf2 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/log/PluginConsoleLogger.java
@@ -27,7 +27,7 @@
  * Calling {@link Log#isInfoEnabled()} before {@link Log#info(CharSequence)} due to Maven 2.2.1.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  * @see ConsoleLogger
  */
 public final class PluginConsoleLogger
@@ -45,6 +45,7 @@ public boolean isDebugEnabled()
         return mojoLogger.isDebugEnabled();
     }
 
+    @Override
     public void debug( String message )
     {
         if ( mojoLogger.isDebugEnabled() )
@@ -66,6 +67,7 @@ public boolean isInfoEnabled()
         return mojoLogger.isInfoEnabled();
     }
 
+    @Override
     public void info( String message )
     {
         if ( mojoLogger.isInfoEnabled() )
@@ -79,6 +81,7 @@ public boolean isWarnEnabled()
         return mojoLogger.isWarnEnabled();
     }
 
+    @Override
     public void warning( String message )
     {
         if ( mojoLogger.isWarnEnabled() )
@@ -100,6 +103,7 @@ public boolean isErrorEnabled()
         return mojoLogger.isErrorEnabled();
     }
 
+    @Override
     public void error( String message )
     {
         if ( mojoLogger.isErrorEnabled() )
@@ -108,6 +112,7 @@ public void error( String message )
         }
     }
 
+    @Override
     public void error( String message, Throwable t )
     {
         if ( mojoLogger.isErrorEnabled() )
@@ -116,6 +121,7 @@ public void error( String message, Throwable t )
         }
     }
 
+    @Override
     public void error( Throwable t )
     {
         if ( mojoLogger.isErrorEnabled() )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
index f9e59fe87..84cbabe05 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
@@ -19,9 +19,11 @@
  * under the License.
  */
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 
 import org.apache.maven.surefire.report.ReportEntry;
 
@@ -29,7 +31,7 @@
 
 /**
  * Surefire output consumer proxy that writes test output to a {@link java.io.File} for each test suite.
- * <p/>
+ * <br>
  * This class is not threadsafe, but can be serially handed off from thread to thread.
  *
  * @author Kristian Rosenvold
@@ -44,7 +46,7 @@
 
     private String reportEntryName;
 
-    private FileOutputStream fileOutputStream;
+    private OutputStream fileOutputStream;
 
     public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix )
     {
@@ -52,33 +54,47 @@ public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix
         this.reportNameSuffix = reportNameSuffix;
     }
 
+    @Override
     public void testSetStarting( ReportEntry reportEntry )
     {
         close();
         reportEntryName = reportEntry.getName();
     }
 
+    @Override
     public void testSetCompleted( ReportEntry report )
     {
     }
 
+    @Override
     @SuppressWarnings( "checkstyle:emptyblock" )
     public void close()
     {
         if ( fileOutputStream != null )
         {
+            //noinspection EmptyCatchBlock
             try
             {
                 fileOutputStream.flush();
-                fileOutputStream.close();
             }
             catch ( IOException e )
             {
             }
+            finally
+            {
+                try
+                {
+                    fileOutputStream.close();
+                }
+                catch ( IOException ignored )
+                {
+                }
+            }
             fileOutputStream = null;
         }
     }
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
         try
@@ -91,7 +107,7 @@ public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
                     reportsDirectory.mkdirs();
                 }
                 File file = getReportFile( reportsDirectory, reportEntryName, reportNameSuffix, "-output.txt" );
-                fileOutputStream = new FileOutputStream( file );
+                fileOutputStream = new BufferedOutputStream( new FileOutputStream( file ), 16 * 1024 );
             }
             fileOutputStream.write( buf, off, len );
         }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
index 2332856aa..bbc9591cb 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactory.java
@@ -31,6 +31,7 @@
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -55,7 +56,7 @@
 
 /**
  * Provides reporting modules on the plugin side.
- * <p/>
+ * <br>
  * Keeps a centralized count of test run results.
  *
  * @author Kristian Rosenvold
@@ -85,6 +86,7 @@ public DefaultReporterFactory( StartupReportConfiguration reportConfiguration, C
         listeners = new ConcurrentLinkedQueue<TestSetRunListener>();
     }
 
+    @Override
     public RunListener createReporter()
     {
         TestSetRunListener testSetRunListener =
@@ -100,6 +102,11 @@ public RunListener createReporter()
         return testSetRunListener;
     }
 
+    public File getReportsDirectory()
+    {
+        return reportConfiguration.getReportsDirectory();
+    }
+
     private ConsoleReporter createConsoleReporter()
     {
         return shouldReportToConsole() ? new ConsoleReporter( consoleLogger ) : NullConsoleReporter.INSTANCE;
@@ -153,6 +160,7 @@ final void addListener( TestSetRunListener listener )
         listeners.add( listener );
     }
 
+    @Override
     public RunResult close()
     {
         mergeTestHistoryResult();
@@ -201,6 +209,8 @@ public RunStatistics getGlobalRunStatistics()
 
     /**
      * For testing purposes only.
+     *
+     * @return DefaultReporterFactory for testing purposes
      */
     public static DefaultReporterFactory defaultNoXml()
     {
@@ -421,12 +431,12 @@ boolean printTestFailures( TestResultType type )
     enum TestResultType
     {
 
-        error(   "Tests in error: " ),
-        failure( "Failed tests: "   ),
-        flake(   "Flaked tests: "   ),
-        success( "Success: "        ),
-        skipped( "Skipped: "        ),
-        unknown( "Unknown: "        );
+        error(   "Errors: "   ),
+        failure( "Failures: " ),
+        flake(   "Flakes: "   ),
+        success( "Success: "  ),
+        skipped( "Skipped: "  ),
+        unknown( "Unknown: "  );
 
         private final String logPrefix;
 
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DirectConsoleOutput.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DirectConsoleOutput.java
index 25312aaeb..acc19bac9 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DirectConsoleOutput.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/DirectConsoleOutput.java
@@ -30,7 +30,7 @@
 
 /**
  * Outputs test system out/system err directly to the console
- * <p/>
+ * <br>
  * Just a step on the road to getting the separation of reporting concerns
  * operating properly.
  *
@@ -50,6 +50,7 @@ public DirectConsoleOutput( PrintStream sout, PrintStream serr )
     }
 
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
         final PrintStream stream = stdout ? sout : serr;
@@ -64,14 +65,17 @@ public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
         }
     }
 
+    @Override
     public void testSetStarting( ReportEntry reportEntry )
     {
     }
 
+    @Override
     public void testSetCompleted( ReportEntry report )
     {
     }
 
+    @Override
     public void close()
     {
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporter.java
index a4d8c8e06..15b902de9 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporter.java
@@ -22,6 +22,7 @@
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.ReporterException;
 
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -60,7 +61,7 @@ private PrintWriter testSetStarting( ReportEntry report )
 
         try
         {
-            PrintWriter writer = new PrintWriter( new FileWriter( reportFile ) );
+            PrintWriter writer = new PrintWriter( new BufferedWriter( new FileWriter( reportFile ), 16 * 1024 ) );
 
             writer.println( "-------------------------------------------------------------------------------" );
 
@@ -86,9 +87,10 @@ public static File getReportFile( File reportsDirectory, String reportEntryName,
 
     public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetStats, List<String> testResults )
     {
-        PrintWriter writer = testSetStarting( report );
+        PrintWriter writer = null;
         try
         {
+            writer = testSetStarting( report );
             writer.println( testSetStats.getTestSetSummary( report ) );
             for ( String testResult : testResults )
             {
@@ -98,7 +100,10 @@ public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetSta
         }
         finally
         {
-            writer.close();
+            if ( writer != null )
+            {
+                writer.close();
+            }
         }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
index 36bc26951..fd33d8ed0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
@@ -19,6 +19,8 @@
  * under the License.
  */
 
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+
 /**
  * Utils class for file-based reporters
  *
@@ -45,13 +47,6 @@ public static String stripIllegalFilenameChars( String original )
 
     private static String getOSSpecificIllegalChars()
     {
-        if ( System.getProperty( "os.name" ).toLowerCase().startsWith( "win" ) )
-        {
-            return "\\/:*?\"<>|\0";
-        }
-        else
-        {
-            return "/\0";
-        }
+        return IS_OS_WINDOWS ? "\\/:*?\"<>|\0" : "/\0";
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleOutputReceiver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleOutputReceiver.java
index 55e4ee23f..327c77d46 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleOutputReceiver.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleOutputReceiver.java
@@ -25,7 +25,7 @@
  * ConsoleReporter doing nothing rather than using null.
  *
  * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.19.2
+ * @since 2.20
  */
 class NullConsoleOutputReceiver
     implements TestcycleConsoleOutputReceiver
@@ -37,18 +37,22 @@ private NullConsoleOutputReceiver()
     {
     }
 
+    @Override
     public void testSetStarting( ReportEntry reportEntry )
     {
     }
 
+    @Override
     public void testSetCompleted( ReportEntry report )
     {
     }
 
+    @Override
     public void close()
     {
     }
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleReporter.java
index 1bd4e9a03..af68d1e01 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleReporter.java
@@ -28,7 +28,7 @@
  * ConsoleReporter doing nothing rather than using null.
  *
  * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.19.2
+ * @since 2.20
  */
 class NullConsoleReporter
     extends ConsoleReporter
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullFileReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullFileReporter.java
index bf7e3ef3a..bca3c4cb7 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullFileReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullFileReporter.java
@@ -25,7 +25,7 @@
  * FileReporter doing nothing rather than using null.
  *
  * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.19.2
+ * @since 2.20
  */
 class NullFileReporter
     extends FileReporter
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
index 5895c8ad3..7f5d20214 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
@@ -23,7 +23,7 @@
  * FileReporter doing nothing rather than using null.
  *
  * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.19.2
+ * @since 2.20
  */
 class NullStatelessXmlReporter
     extends StatelessXmlReporter
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatisticsReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatisticsReporter.java
index 5e355cac6..3d58096aa 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatisticsReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatisticsReporter.java
@@ -26,7 +26,7 @@
  * StatisticsReporter doing nothing rather than using null.
  *
  * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.19.2
+ * @since 2.20
  */
 class NullStatisticsReporter
     extends StatisticsReporter
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
index 8ebeb96c2..7a7681f88 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
@@ -19,41 +19,42 @@
  * under the License.
  */
 
-import org.apache.maven.shared.utils.io.IOUtil;
 import org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter;
 import org.apache.maven.shared.utils.xml.XMLWriter;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.ReporterException;
 import org.apache.maven.surefire.report.SafeThrowable;
+import org.apache.maven.surefire.util.internal.StringUtils;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
+import java.util.Map.Entry;
 import java.util.StringTokenizer;
 
+import static org.apache.commons.io.IOUtils.closeQuietly;
 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType;
 import static org.apache.maven.plugin.surefire.report.FileReporterUtils.stripIllegalFilenameChars;
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
 import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
 
+@SuppressWarnings( { "javadoc", "checkstyle:javadoctype" } )
 // CHECKSTYLE_OFF: LineLength
 /**
  * XML format reporter writing to <code>TEST-<i>reportName</i>[-<i>suffix</i>].xml</code> file like written and read
  * by Ant's <a href="http://ant.apache.org/manual/Tasks/junit.html"><code>&lt;junit&gt;</code></a> and
  * <a href="http://ant.apache.org/manual/Tasks/junitreport.html"><code>&lt;junitreport&gt;</code></a> tasks,
  * then supported by many tools like CI servers.
- * <p/>
+ * <br>
  * <pre>&lt;?xml version="1.0" encoding="UTF-8"?>
  * &lt;testsuite name="<i>suite name</i>" [group="<i>group</i>"] tests="<i>0</i>" failures="<i>0</i>" errors="<i>0</i>" skipped="<i>0</i>" time="<i>0,###.###</i>">
  *  &lt;properties>
@@ -82,10 +83,6 @@
  */
 public class StatelessXmlReporter
 {
-    private static final String ENCODING = "UTF-8";
-
-    private static final Charset ENCODING_CS = Charset.forName( ENCODING );
-
     private final File reportsDirectory;
 
     private final String reportNameSuffix;
@@ -125,19 +122,19 @@ public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStat
             getAddMethodEntryList( methodRunHistoryMap, methodEntry );
         }
 
-        FileOutputStream outputStream = getOutputStream( testSetReportEntry );
+        OutputStream outputStream = getOutputStream( testSetReportEntry );
         OutputStreamWriter fw = getWriter( outputStream );
         try
         {
             XMLWriter ppw = new PrettyPrintXMLWriter( fw );
-            ppw.setEncoding( ENCODING );
+            ppw.setEncoding( StringUtils.UTF_8.name() );
 
             createTestSuiteElement( ppw, testSetReportEntry, testSetStats, testSetReportEntry.elapsedTimeAsString() );
 
-            showProperties( ppw );
+            showProperties( ppw, testSetReportEntry.getSystemProperties() );
 
             // Iterate through all the test methods in the test class
-            for ( Map.Entry<String, List<WrappedReportEntry>> entry : methodRunHistoryMap.entrySet() )
+            for ( Entry<String, List<WrappedReportEntry>> entry : methodRunHistoryMap.entrySet() )
             {
                 List<WrappedReportEntry> methodEntryList = entry.getValue();
                 if ( methodEntryList == null )
@@ -242,7 +239,7 @@ public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStat
         }
         finally
         {
-            IOUtil.close( fw );
+            closeQuietly( fw );
         }
     }
 
@@ -282,7 +279,7 @@ private TestResultType getTestResultType( List<WrappedReportEntry> methodEntryLi
         return methodRunHistoryMap;
     }
 
-    private FileOutputStream getOutputStream( WrappedReportEntry testSetReportEntry )
+    private OutputStream getOutputStream( WrappedReportEntry testSetReportEntry )
     {
         File reportFile = getReportFile( testSetReportEntry, reportsDirectory, reportNameSuffix );
 
@@ -293,7 +290,7 @@ private FileOutputStream getOutputStream( WrappedReportEntry testSetReportEntry
 
         try
         {
-            return new FileOutputStream( reportFile );
+            return new BufferedOutputStream( new FileOutputStream( reportFile ), 16 * 1024 );
         }
         catch ( Exception e )
         {
@@ -301,9 +298,9 @@ private FileOutputStream getOutputStream( WrappedReportEntry testSetReportEntry
         }
     }
 
-    private static OutputStreamWriter getWriter( FileOutputStream fos )
+    private static OutputStreamWriter getWriter( OutputStream fos )
     {
-        return new OutputStreamWriter( fos, ENCODING_CS );
+        return new OutputStreamWriter( fos, UTF_8 );
     }
 
     private static void getAddMethodEntryList( Map<String, List<WrappedReportEntry>> methodRunHistoryMap,
@@ -336,7 +333,7 @@ private static void startTestElement( XMLWriter ppw, WrappedReportEntry report,
         }
         if ( report.getSourceName() != null )
         {
-            if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
+            if ( reportNameSuffix != null && !reportNameSuffix.isEmpty() )
             {
                 ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
             }
@@ -375,14 +372,14 @@ private void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, T
     }
 
     private static void getTestProblems( OutputStreamWriter outputStreamWriter, XMLWriter ppw,
-                                         WrappedReportEntry report, boolean trimStackTrace, FileOutputStream fw,
+                                         WrappedReportEntry report, boolean trimStackTrace, OutputStream fw,
                                          String testErrorType, boolean createOutErrElementsInside )
     {
         ppw.startElement( testErrorType );
 
         String stackTrace = report.getStackTrace( trimStackTrace );
 
-        if ( report.getMessage() != null && report.getMessage().length() > 0 )
+        if ( report.getMessage() != null && !report.getMessage().isEmpty() )
         {
             ppw.addAttribute( "message", extraEscape( report.getMessage(), true ) );
         }
@@ -421,7 +418,7 @@ private static void getTestProblems( OutputStreamWriter outputStreamWriter, XMLW
 
     // Create system-out and system-err elements
     private static void createOutErrElements( OutputStreamWriter outputStreamWriter, XMLWriter ppw,
-                                              WrappedReportEntry report, FileOutputStream fw )
+                                              WrappedReportEntry report, OutputStream fw )
     {
         EncodingOutputStream eos = new EncodingOutputStream( fw );
         addOutputStreamElement( outputStreamWriter, eos, ppw, report.getStdout(), "system-out" );
@@ -457,39 +454,30 @@ private static void addOutputStreamElement( OutputStreamWriter outputStreamWrite
 
     /**
      * Adds system properties to the XML report.
-     * <p/>
+     * <br>
      *
      * @param xmlWriter The test suite to report to
      */
-    private static void showProperties( XMLWriter xmlWriter )
+    private static void showProperties( XMLWriter xmlWriter, Map<String, String> systemProperties )
     {
         xmlWriter.startElement( "properties" );
-
-        Properties systemProperties = System.getProperties();
-
-        if ( systemProperties != null )
+        for ( final Entry<String, String> entry : systemProperties.entrySet() )
         {
-            Enumeration<?> propertyKeys = systemProperties.propertyNames();
+            final String key = entry.getKey();
+            String value = entry.getValue();
 
-            while ( propertyKeys.hasMoreElements() )
+            if ( value == null )
             {
-                String key = (String) propertyKeys.nextElement();
-
-                String value = systemProperties.getProperty( key );
-
-                if ( value == null )
-                {
-                    value = "null";
-                }
+                value = "null";
+            }
 
-                xmlWriter.startElement( "property" );
+            xmlWriter.startElement( "property" );
 
-                xmlWriter.addAttribute( "name", key );
+            xmlWriter.addAttribute( "name", key );
 
-                xmlWriter.addAttribute( "value", extraEscape( value, true ) );
+            xmlWriter.addAttribute( "value", extraEscape( value, true ) );
 
-                xmlWriter.endElement();
-            }
+            xmlWriter.endElement();
         }
         xmlWriter.endElement();
     }
@@ -545,7 +533,7 @@ else if ( isIllegalEscape( b ) )
                 // there's nothing better we can do! :-(
                 // SUREFIRE-456
                 out.write( ByteConstantsHolder.AMP_BYTES );
-                out.write( String.valueOf( b ).getBytes( ENCODING ) );
+                out.write( String.valueOf( b ).getBytes( UTF_8 ) );
                 out.write( ';' ); // & Will be encoded to amp inside xml encodingSHO
             }
             else
@@ -617,17 +605,10 @@ private static String escapeXml( String text, boolean attribute )
 
         static
         {
-            try
-            {
-                CDATA_START_BYTES = "<![CDATA[".getBytes( ENCODING );
-                CDATA_END_BYTES = "]]>".getBytes( ENCODING );
-                CDATA_ESCAPE_STRING_BYTES = "]]><![CDATA[>".getBytes( ENCODING );
-                AMP_BYTES = "&amp#".getBytes( ENCODING );
-            }
-            catch ( UnsupportedEncodingException e )
-            {
-                throw new RuntimeException( e );
-            }
+            CDATA_START_BYTES = "<![CDATA[".getBytes( UTF_8 );
+            CDATA_END_BYTES = "]]>".getBytes( UTF_8 );
+            CDATA_ESCAPE_STRING_BYTES = "]]><![CDATA[>".getBytes( UTF_8 );
+            AMP_BYTES = "&amp#".getBytes( UTF_8 );
         }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
index dbc802c08..e10b8e829 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
@@ -29,6 +29,7 @@
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.ERROR;
 import static org.apache.maven.plugin.surefire.report.ReportEntryType.FAILURE;
@@ -37,7 +38,7 @@
 
 /**
  * Reports data for a single test set.
- * <p/>
+ * <br>
  *
  * @author Kristian Rosenvold
  */
@@ -86,36 +87,43 @@ public TestSetRunListener( ConsoleReporter consoleReporter, FileReporter fileRep
         testMethodStats = new ArrayList<TestMethodStats>();
     }
 
+    @Override
     public void debug( String message )
     {
         consoleReporter.getConsoleLogger().debug( trimTrailingNewLine( message ) );
     }
 
+    @Override
     public void info( String message )
     {
         consoleReporter.getConsoleLogger().info( trimTrailingNewLine( message ) );
     }
 
+    @Override
     public void warning( String message )
     {
         consoleReporter.getConsoleLogger().warning( trimTrailingNewLine( message ) );
     }
 
+    @Override
     public void error( String message )
     {
         consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ) );
     }
 
+    @Override
     public void error( String message, Throwable t )
     {
         consoleReporter.getConsoleLogger().error( message, t );
     }
 
+    @Override
     public void error( Throwable t )
     {
         consoleReporter.getConsoleLogger().error( t );
     }
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
         try
@@ -136,7 +144,8 @@ public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
         consoleOutputReceiver.writeTestOutput( buf, off, len, stdout );
     }
 
-    public void testSetStarting( ReportEntry report )
+    @Override
+    public void testSetStarting( TestSetReportEntry report )
     {
         detailsForThis.testSetStart();
         consoleReporter.testSetStarting( report );
@@ -149,7 +158,8 @@ public void clearCapture()
         testStdErr = initDeferred( "stderr" );
     }
 
-    public void testSetCompleted( ReportEntry report )
+    @Override
+    public void testSetCompleted( TestSetReportEntry report )
     {
         final WrappedReportEntry wrap = wrapTestSet( report );
         final List<String> testResults =
@@ -166,18 +176,20 @@ public void testSetCompleted( ReportEntry report )
 
         addTestMethodStats();
         detailsForThis.reset();
-
+        clearCapture();
     }
 
     // ----------------------------------------------------------------------
     // Test
     // ----------------------------------------------------------------------
 
+    @Override
     public void testStarting( ReportEntry report )
     {
         detailsForThis.testStart();
     }
 
+    @Override
     public void testSucceeded( ReportEntry reportEntry )
     {
         WrappedReportEntry wrapped = wrap( reportEntry, SUCCESS );
@@ -186,6 +198,7 @@ public void testSucceeded( ReportEntry reportEntry )
         clearCapture();
     }
 
+    @Override
     public void testError( ReportEntry reportEntry )
     {
         WrappedReportEntry wrapped = wrap( reportEntry, ERROR );
@@ -194,6 +207,7 @@ public void testError( ReportEntry reportEntry )
         clearCapture();
     }
 
+    @Override
     public void testFailed( ReportEntry reportEntry )
     {
         WrappedReportEntry wrapped = wrap( reportEntry, FAILURE );
@@ -206,6 +220,7 @@ public void testFailed( ReportEntry reportEntry )
     // Counters
     // ----------------------------------------------------------------------
 
+    @Override
     public void testSkipped( ReportEntry reportEntry )
     {
         WrappedReportEntry wrapped = wrap( reportEntry, SKIPPED );
@@ -215,10 +230,12 @@ public void testSkipped( ReportEntry reportEntry )
         clearCapture();
     }
 
+    @Override
     public void testExecutionSkippedByUser()
     {
     }
 
+    @Override
     public void testAssumptionFailure( ReportEntry report )
     {
         testSkipped( report );
@@ -246,11 +263,11 @@ private WrappedReportEntry wrap( ReportEntry other, ReportEntryType reportEntryT
         return new WrappedReportEntry( other, reportEntryType, estimatedElapsed, testStdOut, testStdErr );
     }
 
-    private WrappedReportEntry wrapTestSet( ReportEntry other )
+    private WrappedReportEntry wrapTestSet( TestSetReportEntry other )
     {
         return new WrappedReportEntry( other, null, other.getElapsed() != null
             ? other.getElapsed()
-            : detailsForThis.getElapsedSinceTestSetStart(), testStdOut, testStdErr );
+            : detailsForThis.getElapsedSinceTestSetStart(), testStdOut, testStdErr, other.getSystemProperties() );
     }
 
     public void close()
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
index 954a2bb32..0aa00c7bb 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/Utf8RecodingDeferredFileOutputStream.java
@@ -19,13 +19,15 @@
  * under the License.
  */
 
+import org.apache.commons.io.output.DeferredFileOutputStream;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 
-import org.apache.commons.io.output.DeferredFileOutputStream;
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
 
 /**
  * A deferred file output stream decorator that recodes the bytes written into the stream from the VM default encoding
@@ -39,8 +41,6 @@
 
     private boolean closed = false;
 
-    private static final Charset UTF8 = Charset.forName( "UTF-8" );
-
     @SuppressWarnings( "checkstyle:magicnumber" )
     public Utf8RecodingDeferredFileOutputStream( String channel )
     {
@@ -55,10 +55,10 @@ public synchronized void write( byte[] buf, int off, int len )
             return;
         }
 
-        if ( !Charset.defaultCharset().equals( UTF8 ) )
+        if ( !Charset.defaultCharset().equals( UTF_8 ) )
         {
             CharBuffer decodedFromDefaultCharset = Charset.defaultCharset().decode( ByteBuffer.wrap( buf, off, len ) );
-            ByteBuffer utf8Encoded = UTF8.encode( decodedFromDefaultCharset );
+            ByteBuffer utf8Encoded = UTF_8.encode( decodedFromDefaultCharset );
 
             if ( utf8Encoded.hasArray() )
             {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
index 2394ff0b3..4565099df 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
@@ -21,14 +21,19 @@
 
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
+import java.util.Collections;
+import java.util.Map;
+
+import static java.util.Collections.unmodifiableMap;
 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
 
 /**
  * @author Kristian Rosenvold
  */
 public class WrappedReportEntry
-    implements ReportEntry
+    implements TestSetReportEntry
 {
     private final ReportEntry original;
 
@@ -40,17 +45,29 @@
 
     private final Utf8RecodingDeferredFileOutputStream stdErr;
 
+    private final Map<String, String> systemProperties;
+
     public WrappedReportEntry( ReportEntry original, ReportEntryType reportEntryType, Integer estimatedElapsed,
                                Utf8RecodingDeferredFileOutputStream stdout,
-                               Utf8RecodingDeferredFileOutputStream stdErr )
+                               Utf8RecodingDeferredFileOutputStream stdErr,
+                               Map<String, String> systemProperties )
     {
         this.original = original;
         this.reportEntryType = reportEntryType;
         this.elapsed = estimatedElapsed;
         this.stdout = stdout;
         this.stdErr = stdErr;
+        this.systemProperties = unmodifiableMap( systemProperties );
+    }
+
+    public WrappedReportEntry( ReportEntry original, ReportEntryType reportEntryType, Integer estimatedElapsed,
+                               Utf8RecodingDeferredFileOutputStream stdout,
+                               Utf8RecodingDeferredFileOutputStream stdErr )
+    {
+        this( original, reportEntryType, estimatedElapsed, stdout, stdErr, Collections.<String, String>emptyMap() );
     }
 
+    @Override
     public Integer getElapsed()
     {
         return elapsed;
@@ -71,11 +88,13 @@ public Utf8RecodingDeferredFileOutputStream getStdErr()
         return stdErr;
     }
 
+    @Override
     public String getSourceName()
     {
         return original.getSourceName();
     }
 
+    @Override
     public String getName()
     {
         return original.getName();
@@ -86,16 +105,19 @@ public String getClassMethodName()
         return getSourceName() + "." + getName();
     }
 
+    @Override
     public String getGroup()
     {
         return original.getGroup();
     }
 
+    @Override
     public StackTraceWriter getStackTraceWriter()
     {
         return original.getStackTraceWriter();
     }
 
+    @Override
     public String getMessage()
     {
         return original.getMessage();
@@ -125,7 +147,7 @@ public String getReportName()
 
     public String getReportName( String suffix )
     {
-        return suffix != null && suffix.length() > 0 ? getReportName() + "(" + suffix + ")" : getReportName();
+        return suffix != null && !suffix.isEmpty() ? getReportName() + "(" + suffix + ")" : getReportName();
     }
 
     public String getOutput( boolean trimStackTrace )
@@ -160,8 +182,15 @@ public boolean isSucceeded()
         return ReportEntryType.SUCCESS == getReportEntryType();
     }
 
+    @Override
     public String getNameWithGroup()
     {
         return original.getNameWithGroup();
     }
+
+    @Override
+    public Map<String, String> getSystemProperties()
+    {
+        return systemProperties;
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
index 6636f2979..8f4f0fdd2 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/FileScanner.java
@@ -62,7 +62,7 @@ private void scan( Collection<String> scannedJavaClassNames,
             for ( File fileOrDir : filesAndDirs )
             {
                 String name = fileOrDir.getName();
-                if ( name.length() != 0 )
+                if ( !name.isEmpty() )
                 {
                     if ( fileOrDir.isFile() )
                     {
@@ -74,7 +74,7 @@ private void scan( Collection<String> scannedJavaClassNames,
                             if ( filter.shouldRun( toFile( path, simpleClassName ), null ) )
                             {
                                 String fullyQualifiedClassName =
-                                    pAckage.length() == 0 ? simpleClassName : pAckage + '.' + simpleClassName;
+                                    pAckage.isEmpty() ? simpleClassName : pAckage + '.' + simpleClassName;
                                 scannedJavaClassNames.add( fullyQualifiedClassName );
                             }
                         }
@@ -122,7 +122,7 @@ private static String toPath( String... subDirectories )
     private String toFile( String path, String fileNameWithoutExtension )
     {
         String pathWithoutExtension =
-            path.length() == 0 ? fileNameWithoutExtension : path + '/' + fileNameWithoutExtension;
+            path.isEmpty() ? fileNameWithoutExtension : path + '/' + fileNameWithoutExtension;
         return pathWithoutExtension + ext;
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/Relocator.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/Relocator.java
index 89117af29..223769866 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/Relocator.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/Relocator.java
@@ -30,11 +30,10 @@
  */
 public class Relocator
 {
-    @Nullable
-    private final String relocation;
-
     private static final String RELOCATION_BASE = "org.apache.maven.surefire.";
 
+    @Nullable
+    private final String relocation;
 
     public Relocator( @Nullable String relocation )
     {
@@ -61,7 +60,7 @@ public Relocator()
         {
             return className;
         }
-        String rest = className.substring( "org.apache.maven.surefire.".length() );
+        String rest = className.substring( RELOCATION_BASE.length() );
         final String s = RELOCATION_BASE + getRelocation() + ".";
         return s + rest;
     }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/surefire/spi/ServiceLoader.java b/maven-surefire-common/src/main/java/org/apache/maven/surefire/providerapi/ServiceLoader.java
similarity index 94%
rename from maven-surefire-common/src/main/java/org/apache/maven/surefire/spi/ServiceLoader.java
rename to maven-surefire-common/src/main/java/org/apache/maven/surefire/providerapi/ServiceLoader.java
index c23e5d627..8753ff16c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/surefire/spi/ServiceLoader.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/surefire/providerapi/ServiceLoader.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.spi;
+package org.apache.maven.surefire.providerapi;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -36,9 +36,12 @@
 import static org.apache.maven.surefire.util.ReflectionUtils.getConstructor;
 
 /**
- * SPI loader for Java 1.5.
+ * SPI loader for Surefire/Failsafe should use {@link Thread#getContextClassLoader() current ClassLoader}.
+ * <br>
+ * The {@link java.util.ServiceLoader} embedded in JVM uses
+ * {@link ClassLoader#getSystemClassLoader() System ClassLoader} and cannot be used in Surefire/Failsafe.
  *
- * @since 2.19.2
+ * @since 2.20
  */
 public class ServiceLoader
 {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
new file mode 100644
index 000000000..a44bea40b
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
@@ -0,0 +1,125 @@
+package org.apache.maven.plugin.surefire;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for {@link AbstractSurefireMojo}.
+ */
+@RunWith( MockitoJUnitRunner.class )
+public class AbstractSurefireMojoTest
+{
+    @Mock
+    private AbstractSurefireMojo mojo;
+
+    @Test
+    public void shouldHaveTmpDirectory() throws IOException
+    {
+        assumeTrue( isJavaVersionAtLeast( JAVA_1_7 ) );
+
+        Path path = (Path) AbstractSurefireMojo.createTmpDirectoryWithJava7( "surefire" );
+
+        assertThat( path )
+                .isNotNull();
+
+        assertThat( path.startsWith( System.getProperty( "java.io.tmpdir" ) ) )
+                .isTrue();
+
+        String dir = path.getName( path.getNameCount() - 1 ).toString();
+
+        assertThat( dir )
+                .startsWith( "surefire" );
+
+        assertThat( dir )
+                .matches( "^surefire[\\d]+$" );
+    }
+
+    @Test
+    public void shouldHaveTmpDirectoryName() throws IOException
+    {
+        assumeTrue( isJavaVersionAtLeast( JAVA_1_7 ) );
+
+        String dir = AbstractSurefireMojo.createTmpDirectoryNameWithJava7( "surefire" );
+
+        assertThat( dir )
+                .isNotNull();
+
+        assertThat( dir )
+                .startsWith( "surefire" );
+
+        assertThat( dir )
+                .matches( "^surefire[\\d]+$" );
+    }
+
+    @Test
+    public void shouldExistTmpDirectory()
+    {
+        when( mojo.getTempDir() ).thenReturn( "surefireX" );
+        when( mojo.getProjectBuildDirectory() ).thenReturn( new File( System.getProperty( "user.dir" ), "target" ) );
+        when( mojo.createSurefireBootDirectoryInTemp() ).thenCallRealMethod();
+        when( mojo.createSurefireBootDirectoryInBuild() ).thenCallRealMethod();
+        when( mojo.getSurefireTempDir() ).thenCallRealMethod();
+
+        File tmp = mojo.createSurefireBootDirectoryInTemp();
+        assertThat( tmp ).isNotNull();
+        assertThat( tmp ).exists();
+        assertThat( tmp.getAbsolutePath() )
+                .startsWith( System.getProperty( "java.io.tmpdir" ) );
+        assertThat( tmp.getName() )
+                .startsWith( "surefireX" );
+
+        tmp = mojo.createSurefireBootDirectoryInBuild();
+        assertThat( tmp ).isNotNull();
+        assertThat( tmp ).exists();
+        assertThat( tmp.getAbsolutePath() ).startsWith( System.getProperty( "user.dir" ) );
+        assertThat( tmp.getName() ).isEqualTo( "surefireX" );
+
+        tmp = mojo.getSurefireTempDir();
+        assertThat( tmp ).isNotNull();
+        assertThat( tmp ).exists();
+        assertThat( tmp.getAbsolutePath() )
+              .startsWith( IS_OS_WINDOWS ? System.getProperty( "java.io.tmpdir" ) : System.getProperty( "user.dir" ) );
+        if ( IS_OS_WINDOWS )
+        {
+            assertThat( tmp.getName() )
+                    .startsWith( "surefireX" );
+        }
+        else
+        {
+            assertThat( tmp.getName() )
+                    .isEqualTo( "surefireX" );
+        }
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java
new file mode 100644
index 000000000..c00f7f9ec
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java
@@ -0,0 +1,89 @@
+package org.apache.maven.plugin.surefire;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Collections.addAll;
+import static java.util.Collections.singleton;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Test of {@link SurefireHelper}.
+ */
+public class SurefireHelperTest
+{
+    @Test
+    public void shouldBeThreeDumpFiles()
+    {
+        String[] dumps = SurefireHelper.getDumpFilesToPrint();
+        assertThat( dumps ).hasSize( 3 );
+        assertThat( dumps ).doesNotHaveDuplicates();
+        List<String> onlyStrings = new ArrayList<String>();
+        addAll( onlyStrings, dumps );
+        onlyStrings.removeAll( singleton( (String) null ) );
+        assertThat( onlyStrings ).hasSize( 3 );
+    }
+
+    @Test
+    public void shouldCloneDumpFiles()
+    {
+        String[] dumps1 = SurefireHelper.getDumpFilesToPrint();
+        String[] dumps2 = SurefireHelper.getDumpFilesToPrint();
+        assertThat( dumps1 ).isNotSameAs( dumps2 );
+    }
+
+    @Test
+    public void testConstants()
+    {
+        assertThat( SurefireHelper.DUMPSTREAM_FILENAME_FORMATTER )
+                .isEqualTo( SurefireHelper.DUMP_FILE_PREFIX + "%d.dumpstream" );
+
+        assertThat( String.format( SurefireHelper.DUMPSTREAM_FILENAME_FORMATTER, 5) )
+                .endsWith( "-jvmRun5.dumpstream" );
+    }
+
+    @Test
+    public void shouldEscapeWindowsPath()
+    {
+        assumeTrue( IS_OS_WINDOWS );
+        String root = "X:\\path\\to\\project\\";
+        String pathToJar = "target\\surefire\\surefirebooter4942721306300108667.jar";
+        int projectNameLength = 247 - root.length() - pathToJar.length();
+        StringBuilder projectFolder = new StringBuilder();
+        for ( int i = 0; i < projectNameLength; i++ )
+        {
+            projectFolder.append( 'x' );
+        }
+        String path = root + projectFolder + "\\" + pathToJar;
+        String escaped = escapeToPlatformPath( path );
+        assertThat( escaped ).isEqualTo( "\\\\?\\" + path );
+
+        path = root + "\\" + pathToJar;
+        escaped = escapeToPlatformPath( path );
+        assertThat( escaped ).isEqualTo( root + "\\" + pathToJar );
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
index cd31d3442..a7c8b9da4 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireReflectorTest.java
@@ -1 +1 @@
-package org.apache.maven.plugin.surefire;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
import org.apache.maven.surefire.booter.IsolatedClassLoader;
import org.apache.maven.surefire.booter.SurefireReflector;
import org.junit.Before;
import org.junit.Test;

import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @see ConsoleLogger
 * @see SurefireReflector
 * @since 2.19.2
 */
public class SurefireReflectorTest
{
    private ConsoleLogger logger;
    private SurefireReflector reflector;

    @Before
    public void prepareData()
    {
        logger = spy( new PrintStreamLogger( System.out ) );
        ClassLoader cl = new IsolatedClassLoader( Thread.currentThread().getContextClassLoader(), false, "role" );
        reflector = new SurefireReflector( cl );
    }

    @Test
    public void shouldProxyConsoleLogger()
    {
        Object mirror = reflector.createConsoleLogger( logger );
        assertThat( mirror, is( notNullValue() ) );
        assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
        assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
        assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
        verify( logger, times( 1 ) ).info( "Hi There!" );
    }
}
\ No newline at end of file
+package org.apache.maven.plugin.surefire;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerDecorator;
import org.apache.maven.plugin.surefire.log.api.PrintStreamLogger;
import org.apache.maven.surefire.booter.IsolatedClassLoader;
import org.apache.maven.surefire.booter.SurefireReflector;
import org.junit.Before;
import org.junit.Test;

import static org.apache.maven.surefire.util.ReflectionUtils.getMethod;
import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @see ConsoleLogger
 * @see SurefireReflector
 * @since 2.20
 */
public class SurefireReflectorTest
{
    private ConsoleLogger logger;
    private SurefireReflector reflector;

    @Before
    public void prepareData()
    {
        logger = spy( new PrintStreamLogger( System.out ) );
        ClassLoader cl = new IsolatedClassLoader( Thread.currentThread().getContextClassLoader(), false, "role" );
        reflector = new SurefireReflector( cl );
    }

    @Test
    public void shouldProxyConsoleLogger()
    {
        Object mirror = reflector.createConsoleLogger( logger );
        assertThat( mirror, is( notNullValue() ) );
        assertThat( mirror.getClass().getInterfaces()[0].getName(), is( ConsoleLogger.class.getName() ) );
        assertThat( mirror, is( not( sameInstance( (Object) logger ) ) ) );
        assertThat( mirror, is( instanceOf( ConsoleLoggerDecorator.class ) ) );
        invokeMethodWithArray( mirror, getMethod( mirror, "info", String.class ), "Hi There!" );
        verify( logger, times( 1 ) ).info( "Hi There!" );
    }
}
\ No newline at end of file
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
index 6759367a1..26b8be7ab 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
@@ -207,7 +207,7 @@ private ProviderConfiguration saveAndReload( ProviderConfiguration booterConfigu
                                                  boolean readTestsFromInStream )
         throws IOException
     {
-        final ForkConfiguration forkConfiguration = ForkConfigurationTest.getForkConfiguration( null, null );
+        final ForkConfiguration forkConfiguration = ForkConfigurationTest.getForkConfiguration( (String) null );
         PropertiesWrapper props = new PropertiesWrapper( new HashMap<String, String>() );
         BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
         Object test;
@@ -220,8 +220,9 @@ private ProviderConfiguration saveAndReload( ProviderConfiguration booterConfigu
             test = "aTest";
         }
         final File propsTest = booterSerializer.serialize( props, booterConfiguration, testProviderConfiguration, test,
-                                                           readTestsFromInStream );
+                                                           readTestsFromInStream, 51L );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
+        assertEquals( 51L, (Object) booterDeserializer.getPluginPid() );
         return booterDeserializer.deserialize();
     }
 
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
index 1ca20d2a3..035add0f4 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
@@ -119,13 +119,14 @@ private StartupConfiguration getReloadedStartupConfiguration()
     private StartupConfiguration saveAndReload( StartupConfiguration startupConfiguration )
         throws IOException
     {
-        final ForkConfiguration forkConfiguration = ForkConfigurationTest.getForkConfiguration( null, null );
+        final ForkConfiguration forkConfiguration = ForkConfigurationTest.getForkConfiguration( (String) null );
         PropertiesWrapper props = new PropertiesWrapper( new HashMap<String, String>() );
         BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
         String aTest = "aTest";
         final File propsTest =
-            booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false );
+            booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false, null );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
+        assertNull( booterDeserializer.getPluginPid() );
         return booterDeserializer.getProviderConfiguration();
     }
 
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
index 4f6267017..b49e16478 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
@@ -19,29 +19,32 @@
  * under the License.
  */
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Properties;
-
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.plugin.surefire.JdkAttributes;
 import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.shared.utils.cli.Commandline;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.junit.Test;
 
-import junit.framework.TestCase;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class ForkConfigurationTest
-    extends TestCase
 {
-
+    @Test
     public void testCreateCommandLine_UseSystemClassLoaderForkOnce_ShouldConstructManifestOnlyJar()
         throws IOException, SurefireBooterForkException
     {
-        ForkConfiguration config = getForkConfiguration( null, "java" );
+        ForkConfiguration config = getForkConfiguration( (String) null );
         File cpElement = getTempClasspathFile();
 
         Commandline cli =
@@ -51,12 +54,13 @@ public void testCreateCommandLine_UseSystemClassLoaderForkOnce_ShouldConstructMa
         assertTrue( line.contains( "-jar" ) );
     }
 
+    @Test
     public void testArglineWithNewline()
         throws IOException, SurefireBooterForkException
     {
         // SUREFIRE-657
         File cpElement = getTempClasspathFile();
-        ForkConfiguration forkConfiguration = getForkConfiguration( "abc\ndef", null );
+        ForkConfiguration forkConfiguration = getForkConfiguration( "abc\ndef" );
 
         final Commandline commandLine =
             forkConfiguration.createCommandLine( Collections.singletonList( cpElement.getAbsolutePath() ), false, false,
@@ -64,18 +68,19 @@ public void testArglineWithNewline()
         assertTrue( commandLine.toString().contains( "abc def" ) );
     }
 
+    @Test
     public void testCurrentWorkingDirectoryPropagationIncludingForkNumberExpansion()
         throws IOException, SurefireBooterForkException
     {
         // SUREFIRE-1136
         File baseDir =
             new File( FileUtils.getTempDirectory(), "SUREFIRE-1136-" + RandomStringUtils.randomAlphabetic( 3 ) );
-        baseDir.mkdirs();
+        assertTrue( baseDir.mkdirs() );
         baseDir.deleteOnExit();
 
         File cwd = new File( baseDir, "fork_${surefire.forkNumber}" );
 
-        ForkConfiguration config = getForkConfiguration( null, "java", cwd.getCanonicalFile() );
+        ForkConfiguration config = getForkConfiguration( null, cwd.getCanonicalFile() );
         Commandline commandLine = config.createCommandLine( Collections.<String>emptyList(), true, false, null, 1 );
 
         File forkDirectory = new File( baseDir, "fork_1" );
@@ -84,20 +89,21 @@ public void testCurrentWorkingDirectoryPropagationIncludingForkNumberExpansion()
             commandLine.getShell().getWorkingDirectory().getCanonicalPath() ) );
     }
 
+    @Test
     public void testExceptionWhenCurrentDirectoryIsNotRealDirectory()
         throws IOException, SurefireBooterForkException
     {
         // SUREFIRE-1136
         File baseDir =
             new File( FileUtils.getTempDirectory(), "SUREFIRE-1136-" + RandomStringUtils.randomAlphabetic( 3 ) );
-        baseDir.mkdirs();
+        assertTrue( baseDir.mkdirs() );
         baseDir.deleteOnExit();
 
         File cwd = new File( baseDir, "cwd.txt" );
         FileUtils.touch( cwd );
         cwd.deleteOnExit();
 
-        ForkConfiguration config = getForkConfiguration( null, "java", cwd.getCanonicalFile() );
+        ForkConfiguration config = getForkConfiguration( null, cwd.getCanonicalFile() );
 
         try
         {
@@ -114,19 +120,20 @@ public void testExceptionWhenCurrentDirectoryIsNotRealDirectory()
         fail();
     }
 
+    @Test
     public void testExceptionWhenCurrentDirectoryCannotBeCreated()
         throws IOException, SurefireBooterForkException
     {
         // SUREFIRE-1136
         File baseDir =
             new File( FileUtils.getTempDirectory(), "SUREFIRE-1136-" + RandomStringUtils.randomAlphabetic( 3 ) );
-        baseDir.mkdirs();
+        assertTrue( baseDir.mkdirs() );
         baseDir.deleteOnExit();
 
         // NULL is invalid for JDK starting from 1.7.60 - https://github.com/openjdk-mirror/jdk/commit/e5389115f3634d25d101e2dcc71f120d4fd9f72f
         // ? character is invalid on Windows, seems to be imposable to create invalid directory using Java on Linux
         File cwd = new File( baseDir, "?\u0000InvalidDirectoryName" );
-        ForkConfiguration config = getForkConfiguration( null, "java", cwd.getAbsoluteFile() );
+        ForkConfiguration config = getForkConfiguration( null, cwd.getAbsoluteFile() );
 
         try
         {
@@ -152,17 +159,31 @@ private File getTempClasspathFile()
         return cpElement;
     }
 
-    public static ForkConfiguration getForkConfiguration( String argLine, String jvm )
+    public static ForkConfiguration getForkConfiguration( File javaExec )
+            throws IOException
+    {
+        return getForkConfiguration( null, javaExec.getAbsolutePath(), new File( "." ).getCanonicalFile() );
+    }
+
+    public static ForkConfiguration getForkConfiguration( String argLine )
         throws IOException
     {
-        return getForkConfiguration( argLine, jvm, new File( "." ).getCanonicalFile() );
+        File jvm = new File( new File( System.getProperty( "java.home" ), "bin" ), "java" );
+        return getForkConfiguration( argLine, jvm.getAbsolutePath(), new File( "." ).getCanonicalFile() );
+    }
+
+    public static ForkConfiguration getForkConfiguration( String argLine, File cwd )
+            throws IOException
+    {
+        File jvm = new File( new File( System.getProperty( "java.home" ), "bin" ), "java" );
+        return getForkConfiguration( argLine, jvm.getAbsolutePath(), cwd );
     }
 
-    public static ForkConfiguration getForkConfiguration( String argLine, String jvm, File cwd )
+    private static ForkConfiguration getForkConfiguration( String argLine, String jvm, File cwd )
         throws IOException
     {
-        return new ForkConfiguration( Classpath.emptyClasspath(), null, null, jvm, cwd, new Properties(), argLine, null,
-                                      false, 1, false );
+        return new ForkConfiguration( Classpath.emptyClasspath(), null, null, new JdkAttributes( jvm, false ),
+                                            cwd, new Properties(), argLine, null, false, 1, false, new Platform() );
     }
 
     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
@@ -170,6 +191,6 @@ private boolean isJavaVersionAtLeast( int major, int update )
     {
         String[] javaVersionElements = System.getProperty( "java.runtime.version" ).split( "\\.|_|-b" );
         return Integer.valueOf( javaVersionElements[1] ) >= major
-            && Integer.valueOf( javaVersionElements[4] ) >= update;
+            && Integer.valueOf( javaVersionElements[3] ) >= update;
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
index 5b8867b54..f7406ded6 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java
@@ -19,19 +19,14 @@
  * under the License.
  */
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.util.List;
-import java.util.Properties;
-import java.util.StringTokenizer;
-
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
-import org.apache.maven.surefire.booter.ForkingRunListener;
-import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
+import org.apache.maven.surefire.booter.ForkingRunListener;
+import org.apache.maven.surefire.report.CategorizedReportEntry;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
 import org.apache.maven.surefire.report.ReportEntry;
@@ -39,13 +34,18 @@
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.hamcrest.MatcherAssert;
 
-import junit.framework.Assert;
-import junit.framework.TestCase;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.StringTokenizer;
 
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
 
 /**
  * @author Kristian Rosenvold
@@ -53,10 +53,9 @@
 public class ForkingRunListenerTest
     extends TestCase
 {
+    private final ByteArrayOutputStream content, anotherContent;
 
-    private final ByteArrayOutputStream content;
-
-    private final PrintStream printStream;
+    private final PrintStream printStream, anotherPrintStream;
 
     final int defaultChannel = 17;
 
@@ -64,8 +63,11 @@
 
     public ForkingRunListenerTest()
     {
-        this.content = new ByteArrayOutputStream();
+        content = new ByteArrayOutputStream();
         printStream = new PrintStream( content );
+
+        anotherContent = new ByteArrayOutputStream();
+        anotherPrintStream = new PrintStream( anotherContent );
     }
 
     private void reset()
@@ -74,7 +76,6 @@ private void reset()
         content.reset();
     }
 
-
     public void testHeaderCreation()
     {
         final byte[] header = ForkingRunListener.createHeader( (byte) 'F', 0xCAFE );
@@ -93,7 +94,7 @@ public void testSetStarting()
         throws ReporterException, IOException
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
-        ReportEntry expected = createDefaultReportEntry();
+        TestSetReportEntry expected = createDefaultReportEntry();
         standardTestRun.run().testSetStarting( expected );
         standardTestRun.assertExpected( MockReporter.SET_STARTING, expected );
     }
@@ -102,7 +103,7 @@ public void testSetCompleted()
         throws ReporterException, IOException
     {
         final StandardTestRun standardTestRun = new StandardTestRun();
-        ReportEntry expected = createDefaultReportEntry();
+        TestSetReportEntry expected = createDefaultReportEntry();
         standardTestRun.run().testSetCompleted( expected );
         standardTestRun.assertExpected( MockReporter.SET_COMPLETED, expected );
     }
@@ -220,27 +221,25 @@ public void testSystemProperties()
         createForkingRunListener( defaultChannel );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
-        final Properties testVmSystemProperties = new Properties();
         NullConsoleLogger log = new NullConsoleLogger();
-        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, testVmSystemProperties,
-                                                      new MockNotifiableTestStream(), log );
+        ForkClient forkStreamClient =
+                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, false, null );
 
-        forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) );
+        forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
 
-        assertThat( testVmSystemProperties.size(), is( greaterThan( 1 ) ) );
+        MatcherAssert.assertThat( forkStreamClient.getTestVmSystemProperties().size(), is( greaterThan( 1 ) ) );
     }
 
     public void testMultipleEntries()
         throws ReporterException, IOException
     {
-
         final StandardTestRun standardTestRun = new StandardTestRun();
         standardTestRun.run();
 
         reset();
         RunListener forkingReporter = createForkingRunListener( defaultChannel );
 
-        ReportEntry reportEntry = createDefaultReportEntry();
+        TestSetReportEntry reportEntry = createDefaultReportEntry();
         forkingReporter.testSetStarting( reportEntry );
         forkingReporter.testStarting( reportEntry );
         forkingReporter.testSucceeded( reportEntry );
@@ -248,12 +247,12 @@ public void testMultipleEntries()
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
         NullConsoleLogger log = new NullConsoleLogger();
-        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(),
-                                                      new MockNotifiableTestStream(), log );
+        ForkClient forkStreamClient =
+                new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, false, null );
 
-        forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) );
+        forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
 
-        final MockReporter reporter = (MockReporter) forkStreamClient.getReporter( defaultChannel );
+        final MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
         final List<String> events = reporter.getEvents();
         assertEquals( MockReporter.SET_STARTING, events.get( 0 ) );
         assertEquals( MockReporter.TEST_STARTING, events.get( 1 ) );
@@ -271,21 +270,24 @@ public void test2DifferentChannels()
         new ForkingRunListener( printStream, defaultChannel, false )
                 .testStarting( expected );
 
-        new ForkingRunListener( printStream, anotherChannel, false )
+        new ForkingRunListener( anotherPrintStream, anotherChannel, false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
+        NotifiableTestStream notifiableTestStream = new MockNotifiableTestStream();
         NullConsoleLogger log = new NullConsoleLogger();
-        final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(),
-                                                            new MockNotifiableTestStream(), log );
-        forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) );
 
-        MockReporter reporter = (MockReporter) forkStreamClient.getReporter( defaultChannel );
+        ForkClient forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, false, null );
+        forkStreamClient.consumeMultiLineContent( content.toString( "UTF-8" ) );
+
+        MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
         Assert.assertEquals( MockReporter.TEST_STARTING, reporter.getFirstEvent() );
         Assert.assertEquals( expected, reporter.getFirstData() );
         Assert.assertEquals( 1, reporter.getEvents().size() );
 
-        MockReporter reporter2 = (MockReporter) forkStreamClient.getReporter( anotherChannel );
+        forkStreamClient = new ForkClient( providerReporterFactory, notifiableTestStream, log, false, null );
+        forkStreamClient.consumeMultiLineContent( anotherContent.toString( "UTF-8" ) );
+        MockReporter reporter2 = (MockReporter) forkStreamClient.getReporter();
         Assert.assertEquals( MockReporter.TEST_SKIPPED, reporter2.getFirstEvent() );
         Assert.assertEquals( secondExpected, reporter2.getFirstData() );
         Assert.assertEquals( 1, reporter2.getEvents().size() );
@@ -331,9 +333,9 @@ private SimpleReportEntry createReportEntryWithSpecialMessage( String message )
         }
     }
 
-    private RunListener createForkingRunListener( Integer testSetCHannel )
+    private RunListener createForkingRunListener( Integer testSetChannel )
     {
-        return new ForkingRunListener( printStream, testSetCHannel, false );
+        return new ForkingRunListener( printStream, testSetChannel, false );
     }
 
     private class StandardTestRun
@@ -352,10 +354,10 @@ public void clientReceiveContent()
         {
             TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
             NullConsoleLogger log = new NullConsoleLogger();
-            final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(),
-                                                                new MockNotifiableTestStream(), log );
+            final ForkClient forkStreamClient =
+                    new ForkClient( providerReporterFactory, new MockNotifiableTestStream(), log, false, null );
             forkStreamClient.consumeMultiLineContent( content.toString( ) );
-            reporter = (MockReporter) forkStreamClient.getReporter( defaultChannel );
+            reporter = (MockReporter) forkStreamClient.getReporter();
         }
 
         public String getFirstEvent()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java
index 12a5f9237..b98aca747 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java
@@ -31,19 +31,28 @@
 final class MockNotifiableTestStream
     implements NotifiableTestStream
 {
+    @Override
     public void provideNewTest()
     {
     }
 
+    @Override
     public void skipSinceNextTest()
     {
     }
 
+    @Override
     public void shutdown( Shutdown shutdownType )
     {
     }
 
+    @Override
     public void noop()
     {
     }
+
+    @Override
+    public void acknowledgeByeEventReceived()
+    {
+    }
 }
\ No newline at end of file
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java
index b8c9ef01c..150d44331 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java
@@ -19,13 +19,15 @@
  * under the License.
  */
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Internal tests use only.
@@ -65,24 +67,28 @@
 
     private final AtomicInteger testFailed = new AtomicInteger();
 
-    public void testSetStarting( ReportEntry report )
+    @Override
+    public void testSetStarting( TestSetReportEntry report )
     {
         events.add( SET_STARTING );
         data.add( report );
     }
 
-    public void testSetCompleted( ReportEntry report )
+    @Override
+    public void testSetCompleted( TestSetReportEntry report )
     {
         events.add( SET_COMPLETED );
         data.add( report );
     }
 
+    @Override
     public void testStarting( ReportEntry report )
     {
         events.add( TEST_STARTING );
         data.add( report );
     }
 
+    @Override
     public void testSucceeded( ReportEntry report )
     {
         events.add( TEST_SUCCEEDED );
@@ -90,6 +96,7 @@ public void testSucceeded( ReportEntry report )
         data.add( report );
     }
 
+    @Override
     public void testError( ReportEntry report )
     {
         events.add( TEST_ERROR );
@@ -97,6 +104,7 @@ public void testError( ReportEntry report )
         testFailed.incrementAndGet();
     }
 
+    @Override
     public void testFailed( ReportEntry report )
     {
         events.add( TEST_FAILED );
@@ -104,6 +112,7 @@ public void testFailed( ReportEntry report )
         testFailed.incrementAndGet();
     }
 
+    @Override
     public void testSkipped( ReportEntry report )
     {
         events.add( TEST_SKIPPED );
@@ -111,6 +120,7 @@ public void testSkipped( ReportEntry report )
         testIgnored.incrementAndGet();
     }
 
+    @Override
     public void testExecutionSkippedByUser()
     {
     }
@@ -140,6 +150,7 @@ public ReportEntry getFirstData()
         return (ReportEntry) data.get( 0 );
     }
 
+    @Override
     public void testAssumptionFailure( ReportEntry report )
     {
         events.add( TEST_ASSUMPTION_FAIL );
@@ -147,38 +158,45 @@ public void testAssumptionFailure( ReportEntry report )
         testIgnored.incrementAndGet();
     }
 
+    @Override
     public void debug( String message )
     {
         events.add( CONSOLE_OUTPUT );
         data.add( message );
     }
 
+    @Override
     public void info( String message )
     {
         events.add( CONSOLE_OUTPUT );
         data.add( message );
     }
 
+    @Override
     public void warning( String message )
     {
         events.add( CONSOLE_OUTPUT );
         data.add( message );
     }
 
+    @Override
     public void error( String message )
     {
         events.add( CONSOLE_OUTPUT );
         data.add( message );
     }
 
+    @Override
     public void error( String message, Throwable t )
     {
     }
 
+    @Override
     public void error( Throwable t )
     {
     }
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
         events.add( stdout ? STDOUT : STDERR );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
index ada54a275..5e4f68208 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
@@ -38,6 +38,7 @@ public TestSetMockReporterFactory()
         super( defaultValue(), new NullConsoleLogger() );
     }
 
+    @Override
     public RunListener createReporter()
     {
         return new MockReporter();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index 6dee0a47c..5d9b5afb9 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -20,10 +20,12 @@
  */
 
 import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
+import java.io.DataInputStream;
 import java.io.IOException;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -31,12 +33,15 @@
 import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
 import static org.apache.maven.surefire.booter.Command.NOOP;
 import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
 import static org.apache.maven.surefire.booter.Shutdown.EXIT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Testing cached and immediate commands in {@link TestLessInputStream}.
@@ -126,4 +131,21 @@ public void combinedCommands()
         e.expect( NoSuchElementException.class );
         is.nextCommand();
     }
+
+    @Test
+    public void shouldDecodeTwoCommands()
+            throws IOException
+    {
+        TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
+        TestLessInputStream pluginIs = builder.build();
+        builder.getImmediateCommands().acknowledgeByeEventReceived();
+        builder.getImmediateCommands().noop();
+        DataInputStream is = new DataInputStream( pluginIs );
+        Command bye = decode( is );
+        assertThat( bye, is( notNullValue() ) );
+        assertThat( bye.getCommandType(), is( BYE_ACK ) );
+        Command noop = decode( is );
+        assertThat( noop, is( notNullValue() ) );
+        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
index 6fc171ef1..3f9403909 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -19,15 +19,21 @@
  * under the License.
  */
 
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
 import org.junit.Test;
 
+import java.io.DataInputStream;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Queue;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
 
@@ -58,6 +64,7 @@ public void emptyStreamShouldWaitUntilClosed()
         final Thread streamThread = Thread.currentThread();
         FutureTask<Thread.State> futureTask = new FutureTask<Thread.State>( new Callable<Thread.State>()
         {
+            @Override
             public Thread.State call()
             {
                 sleep( 1000 );
@@ -82,6 +89,7 @@ public void finishedTestsetShouldNotBlock()
         is.testSetFinished();
         new Thread( new Runnable()
         {
+            @Override
             public void run()
             {
                 is.provideNewTest();
@@ -95,6 +103,7 @@ public void run()
         assertThat( is.read(), is( 0 ) );
         assertThat( is.read(), is( 0 ) );
         assertThat( is.read(), is( 0 ) );
+        is.close();
         assertThat( is.read(), is( -1 ) );
     }
 
@@ -107,6 +116,7 @@ public void shouldReadTest()
         final TestProvidingInputStream is = new TestProvidingInputStream( commands );
         new Thread( new Runnable()
         {
+            @Override
             public void run()
             {
                 is.provideNewTest();
@@ -126,6 +136,22 @@ public void run()
         assertThat( is.read(), is( (int) 't' ) );
     }
 
+    @Test
+    public void shouldDecodeTwoCommands()
+            throws IOException
+    {
+        TestProvidingInputStream pluginIs = new TestProvidingInputStream( new ConcurrentLinkedDeque<String>() );
+        pluginIs.acknowledgeByeEventReceived();
+        pluginIs.noop();
+        DataInputStream is = new DataInputStream( pluginIs );
+        Command bye = decode( is );
+        assertThat( bye, is( notNullValue() ) );
+        assertThat( bye.getCommandType(), is( BYE_ACK ) );
+        Command noop = decode( is );
+        assertThat( noop, is( notNullValue() ) );
+        assertThat( noop.getCommandType(), is( MasterProcessCommand.NOOP ) );
+    }
+
     private static void sleep( long millis )
     {
         try
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
index e51c993aa..67f66d6b1 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
@@ -27,6 +27,7 @@
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.shared.utils.logging.MessageUtils;
 import org.apache.maven.surefire.report.RunStatistics;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
@@ -61,6 +62,7 @@
 
     public void testMergeTestHistoryResult()
     {
+        MessageUtils.setColorEnabled( false );
         File reportsDirectory = new File("target");
         StartupReportConfiguration reportConfig =
                 new StartupReportConfiguration( true, true, "PLAIN", false, false, reportsDirectory, false, null,
@@ -120,7 +122,7 @@ public void testMergeTestHistoryResult()
         // Now test the result will be printed out correctly
         factory.printTestFailures( flake );
         String[] expectedFlakeOutput =
-            { "Flaked tests: ", TEST_FOUR, "  Run 1: " + ASSERTION_FAIL, "  Run 2: PASS", "", TEST_ONE,
+            { "Flakes: ", TEST_FOUR, "  Run 1: " + ASSERTION_FAIL, "  Run 2: PASS", "", TEST_ONE,
                 "  Run 1: " + ERROR, "  Run 2: " + ASSERTION_FAIL, "  Run 3: PASS", "", TEST_TWO, "  Run 1: " + ERROR,
                 "  Run 2: PASS", "" };
         assertEquals( asList( expectedFlakeOutput ), reporter.getMessages() );
@@ -128,7 +130,7 @@ public void testMergeTestHistoryResult()
         reporter.reset();
         factory.printTestFailures( error );
         String[] expectedFailureOutput =
-            { "Tests in error: ", TEST_THREE, "  Run 1: " + ASSERTION_FAIL, "  Run 2: " + ERROR, "  Run 3: " + ERROR, ""
+            { "Errors: ", TEST_THREE, "  Run 1: " + ASSERTION_FAIL, "  Run 2: " + ERROR, "  Run 3: " + ERROR, ""
             };
         assertEquals( asList( expectedFailureOutput ), reporter.getMessages() );
 
@@ -142,31 +144,37 @@ public void testMergeTestHistoryResult()
     {
         private final List<String> messages = new ArrayList<String>();
 
+        @Override
         public void debug( String message )
         {
             messages.add( message );
         }
 
+        @Override
         public void info( String message )
         {
             messages.add( message );
         }
 
+        @Override
         public void warning( String message )
         {
             messages.add( message );
         }
 
+        @Override
         public void error( String message )
         {
             messages.add( message );
         }
 
+        @Override
         public void error( String message, Throwable t )
         {
             messages.add( message );
         }
 
+        @Override
         public void error( Throwable t )
         {
         }
@@ -233,21 +241,25 @@ public DummyStackTraceWriter( String stackTrace )
             this.stackTrace = stackTrace;
         }
 
+        @Override
         public String writeTraceToString()
         {
             return "";
         }
 
+        @Override
         public String writeTrimmedTraceToString()
         {
             return "";
         }
 
+        @Override
         public String smartTrimmedStackTrace()
         {
             return stackTrace;
         }
 
+        @Override
         public SafeThrowable getThrowable()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
index 5b649e3a0..445eaa826 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
@@ -20,7 +20,6 @@
  */
 
 import junit.framework.TestCase;
-
 import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
 import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.shared.utils.xml.Xpp3Dom;
@@ -39,6 +38,9 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
+
 @SuppressWarnings( "ResultOfMethodCallIgnored" )
 public class StatelessXmlReporterTest
     extends TestCase
@@ -89,7 +91,7 @@ public void testFileNameWithoutSuffix()
 
         ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), getClass().getName(), 12 );
         WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null );
+            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
         stats.testSucceeded( testSetReportEntry );
         reporter.testSetCompleted( testSetReportEntry, stats );
 
@@ -104,7 +106,7 @@ public void testAllFieldsSerialized()
     {
         ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 );
         WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null );
+            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
         expectedReportFile = new File( reportDir, "TEST-" + TEST_ONE + ".xml" );
 
         stats.testSucceeded( testSetReportEntry );
@@ -142,7 +144,7 @@ public void testAllFieldsSerialized()
 
         FileInputStream fileInputStream = new FileInputStream( expectedReportFile );
 
-        Xpp3Dom testSuite = Xpp3DomBuilder.build( new InputStreamReader( fileInputStream, "UTF-8") );
+        Xpp3Dom testSuite = Xpp3DomBuilder.build( new InputStreamReader( fileInputStream, UTF_8) );
         assertEquals( "testsuite", testSuite.getName() );
         Xpp3Dom properties = testSuite.getChild( "properties" );
         assertEquals( System.getProperties().size(), properties.getChildCount() );
@@ -176,7 +178,7 @@ public void testOutputRerunFlakyFailure()
         ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 );
 
         WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null );
+            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
         expectedReportFile = new File( reportDir, "TEST-" + TEST_ONE + ".xml" );
 
         stats.testSucceeded( testSetReportEntry );
@@ -224,7 +226,7 @@ public void testOutputRerunFlakyFailure()
 
         FileInputStream fileInputStream = new FileInputStream( expectedReportFile );
 
-        Xpp3Dom testSuite = Xpp3DomBuilder.build( new InputStreamReader( fileInputStream, "UTF-8" ) );
+        Xpp3Dom testSuite = Xpp3DomBuilder.build( new InputStreamReader( fileInputStream, UTF_8 ) );
         assertEquals( "testsuite", testSuite.getName() );
         assertEquals( "0.012", testSuite.getAttribute( "time" ) );
         Xpp3Dom properties = testSuite.getChild( "properties" );
@@ -293,5 +295,4 @@ private Utf8RecodingDeferredFileOutputStream createStdOutput( String content )
     {
 
     }
-
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index d03f1b970..aec256dc4 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -21,6 +21,8 @@
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
+import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest;
+import org.apache.maven.plugin.surefire.SurefireHelperTest;
 import org.apache.maven.plugin.surefire.SurefireReflectorTest;
 import org.apache.maven.plugin.surefire.SurefirePropertiesTest;
 import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderConfigurationTest;
@@ -70,7 +72,9 @@
     TestProvidingInputStreamTest.class,
     TestLessInputStreamBuilderTest.class,
     SPITest.class,
-    SurefireReflectorTest.class
+    SurefireReflectorTest.class,
+    SurefireHelperTest.class,
+    AbstractSurefireMojoTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/CustomizedImpl.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/CustomizedImpl.java
index 70e22553f..e2f654c3c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/CustomizedImpl.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/CustomizedImpl.java
@@ -21,11 +21,12 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public class CustomizedImpl
     implements IDefault
 {
+    @Override
     public boolean isDefault()
     {
         return false;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/DefaultImpl.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/DefaultImpl.java
index e623f0b8a..207f56261 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/DefaultImpl.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/DefaultImpl.java
@@ -21,11 +21,12 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public class DefaultImpl
     implements IDefault
 {
+    @Override
     public boolean isDefault()
     {
         return true;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/EmptyServiceInterface.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/EmptyServiceInterface.java
index 38a186cfa..30abd698f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/EmptyServiceInterface.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/EmptyServiceInterface.java
@@ -22,7 +22,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public interface EmptyServiceInterface
 {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/ExistingServiceInterface.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/ExistingServiceInterface.java
index 9d7fa258e..45e3ab4f8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/ExistingServiceInterface.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/ExistingServiceInterface.java
@@ -22,7 +22,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public interface ExistingServiceInterface
 {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/IDefault.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/IDefault.java
index 53b7b385b..740798de3 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/IDefault.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/IDefault.java
@@ -21,13 +21,13 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public interface IDefault
 {
     /**
-     * @return <tt>true</tt> if SPI implementation vendor is maven-surefire-plugin or maven-failsafe-plugin.
-     * <tt>false</tt> if customized by users.
+     * @return {@code true} if SPI implementation vendor is maven-surefire-plugin or maven-failsafe-plugin.
+     * {@code false} if customized by users.
      */
     boolean isDefault();
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/NoServiceInterface.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/NoServiceInterface.java
index af649b90a..e28a07c10 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/NoServiceInterface.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/NoServiceInterface.java
@@ -22,7 +22,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public interface NoServiceInterface
 {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPITest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPITest.java
index a8058f855..2d00799e0 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPITest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPITest.java
@@ -20,6 +20,7 @@
  */
 
 import org.apache.maven.plugin.surefire.booterclient.ProviderDetector;
+import org.apache.maven.surefire.providerapi.ServiceLoader;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -29,7 +30,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public class SPITest
 {
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl1.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl1.java
index e96cec7ac..d9e6b4d22 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl1.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl1.java
@@ -21,10 +21,11 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public class SPImpl1 implements ExistingServiceInterface
 {
+    @Override
     public String whoAmI()
     {
         return SPImpl1.class.getSimpleName();
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl2.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl2.java
index 75806b927..9ecccef04 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl2.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/spi/SPImpl2.java
@@ -21,10 +21,11 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  */
 public class SPImpl2 implements ExistingServiceInterface
 {
+    @Override
     public String whoAmI()
     {
         return SPImpl2.class.getSimpleName();
diff --git a/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml
index ba4b748df..0faecf60e 100644
--- a/maven-surefire-plugin/pom.xml
+++ b/maven-surefire-plugin/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.maven.plugins</groupId>
@@ -44,27 +44,10 @@
   </properties>
 
   <dependencies>
-    <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-plugin-api</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>maven-surefire-common</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-toolchain</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.plugin-tools</groupId>
-      <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
-    </dependency>
   </dependencies>
 
   <build>
@@ -103,6 +86,7 @@
       </plugin>
       <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
+        <version>2.6</version>
         <executions>
           <execution>
             <id>build-site</id>
@@ -120,32 +104,6 @@
           </execution>
         </executions>
       </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-shade-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-            <configuration>
-              <minimizeJar>true</minimizeJar>
-              <artifactSet>
-                <includes>
-                  <include>org.apache.maven.shared:maven-shared-utils</include>
-                </includes>
-              </artifactSet>
-              <relocations>
-                <relocation>
-                  <pattern>org.apache.maven.shared</pattern>
-                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern>
-                </relocation>
-              </relocations>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
     </plugins>
   </build>
 
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index 97f1a7944..6873ad0d5 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -23,6 +23,8 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import org.apache.maven.artifact.Artifact;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.LifecyclePhase;
@@ -37,7 +39,6 @@
  * Run tests using Surefire.
  *
  * @author Jason van Zyl
- * @noinspection JavaDoc
  */
 @Mojo( name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true,
        requiresDependencyResolution = ResolutionScope.TEST )
@@ -66,25 +67,28 @@
     @Parameter( defaultValue = "${project.build.directory}/surefire-reports" )
     private File reportsDirectory;
 
+    @SuppressWarnings( "checkstyle:linelength" )
     /**
-     * Specify this parameter to run individual tests by file name, overriding the <code>includes/excludes</code>
-     * parameters. Each pattern you specify here will be used to create an include pattern formatted like
-     * <code>**&#47;${test}.java</code>, so you can just type "-Dtest=MyTest" to run a single test called
-     * "foo/MyTest.java". The test patterns prefixed with a <code>!</code> will be excluded.<br/>
-     * This parameter overrides the <code>includes/excludes</code> parameters, and the TestNG <code>suiteXmlFiles</code>
-     * parameter.
-     * <p/>
-     * Since 2.7.3, you can execute a limited number of methods in the test by adding #myMethod or #my*ethod. For
-     * example, "-Dtest=MyTest#myMethod". This is supported for junit 4.x and testNg.<br/>
-     * <br/>
-     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):<br/>
-     * "-Dtest=???Test, !Unstable*, pkg&#47;**&#47;Ci*leTest.java, *Test#test*One+testTwo?????, #fast*+slowTest"<br/>
-     * "-Dtest=Basic*, !%regex[.*.Unstable.*], !%regex[.*.MyTest.class#one.*|two.*], %regex[#fast.*|slow.*]"<br/>
-     * <br/>
-     * The Parameterized JUnit runner <em>describes</em> test methods using an index in brackets, so the non-regex
-     * method pattern would become: <em>#testMethod[*]</em>. If using <em>@Parameters(name="{index}: fib({0})={1}")</em>
-     * and selecting the index e.g. 5 in pattern, the non-regex method pattern would become <em>#testMethod[5:*]</em>.
-     * <br/>
+     * Specify this parameter to run individual tests by file name, overriding the parameter {@code includes} and
+     * {@code excludes}. Each pattern you specify here will be used to create an include pattern formatted like
+     * <code>**{@literal /}${test}.java</code>, so you can just type {@code -Dtest=MyTest} to run a single test called
+     * "foo/MyTest.java". The test patterns prefixed with a <em>!</em> will be excluded.
+     * <br>
+     * This parameter overrides the parameter {@code includes}, {@code excludes}, and the TestNG parameter
+     * {@code suiteXmlFiles}.
+     * <br>
+     * Since 2.7.3, you can execute a limited number of methods in the test by adding <i>#myMethod</i> or
+     * <i>#my*ethod</i>. For example, {@code -Dtest=MyTest#myMethod}. This is supported for junit 4.x and TestNg.<br>
+     * <br>
+     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
+     * <pre><code>"-Dtest=???Test, !Unstable*, pkg{@literal /}**{@literal /}Ci*leTest.java, *Test#test*One+testTwo?????, #fast*+slowTest"</code></pre>
+     * or e.g.
+     * <pre><code>"-Dtest=Basic*, !%regex[.*.Unstable.*], !%regex[.*.MyTest.class#one.*|two.*], %regex[#fast.*|slow.*]"</code></pre>
+     * <br>
+     * The Parameterized JUnit runner {@code describes} test methods using an index in brackets, so the non-regex
+     * method pattern would become: {@code #testMethod[*]}.
+     * If using <code>@Parameters(name="{index}: fib({0})={1}")</code> and selecting the index e.g. 5 in pattern, the
+     * non-regex method pattern would become {@code #testMethod[5:*]}.
      */
     @Parameter( property = "test" )
     private String test;
@@ -109,7 +113,7 @@
     private boolean useFile;
 
     /**
-     * Set this to "true" to cause a failure if the none of the tests specified in -Dtest=... are run. Defaults to
+     * Set this to "true" to cause a failure if none of the tests specified in -Dtest=... are run. Defaults to
      * "true".
      *
      * @since 2.12
@@ -120,7 +124,7 @@
     /**
      * Attach a debugger to the forked JVM. If set to "true", the process will suspend and wait for a debugger to attach
      * on port 5005. If set to some other string, that string will be appended to the argLine, allowing you to configure
-     * arbitrary debuggability options (without overwriting the other options specified through the <code>argLine</code>
+     * arbitrary debuggability options (without overwriting the other options specified through the {@code argLine}
      * parameter).
      *
      * @since 2.4
@@ -140,23 +144,23 @@
     /**
      * Forked process is normally terminated without any significant delay after given tests have completed.
      * If the particular tests started non-daemon Thread(s), the process hangs instead of been properly terminated
-     * by <em>System.exit()</em>. Use this parameter in order to determine the timeout of terminating the process.
+     * by {@code System.exit()}. Use this parameter in order to determine the timeout of terminating the process.
      * <a href="http://maven.apache.org/surefire/maven-surefire-plugin/examples/shutdown.html">see the documentation:
      * http://maven.apache.org/surefire/maven-surefire-plugin/examples/shutdown.html</a>
      * Turns to default fallback value of 30 seconds if negative integer.
      *
-     * @since 2.19.2
+     * @since 2.20
      */
     @Parameter( property = "surefire.exitTimeout", defaultValue = "30" )
     private int forkedProcessExitTimeoutInSeconds;
 
     /**
      * Stop executing queued parallel JUnit tests after a certain number of seconds.
-     * <br/>
-     * Example values: "3.5", "4"<br/>
-     * <br/>
+     * <br>
+     * Example values: "3.5", "4"<br>
+     * <br>
      * If set to 0, wait forever, never timing out.
-     * Makes sense with specified <code>parallel</code> different from "none".
+     * Makes sense with specified {@code parallel} different from "none".
      *
      * @since 2.16
      */
@@ -165,41 +169,45 @@
 
     /**
      * Stop executing queued parallel JUnit tests
-     * and <em>interrupt</em> currently running tests after a certain number of seconds.
-     * <br/>
-     * Example values: "3.5", "4"<br/>
-     * <br/>
+     * and {@code interrupt} currently running tests after a certain number of seconds.
+     * <br>
+     * Example values: "3.5", "4"<br>
+     * <br>
      * If set to 0, wait forever, never timing out.
-     * Makes sense with specified <code>parallel</code> different from "none".
+     * Makes sense with specified {@code parallel} different from "none".
      *
      * @since 2.16
      */
     @Parameter( property = "surefire.parallel.forcedTimeout" )
     private double parallelTestsTimeoutForcedInSeconds;
 
+    @SuppressWarnings( "checkstyle:linelength" )
     /**
-     * A list of &lt;include> elements specifying the tests (by pattern) that should be included in testing. When not
-     * specified and when the <code>test</code> parameter is not specified, the default includes will be <code><br/>
-     * &lt;includes><br/>
-     * &nbsp;&lt;include>**&#47;Test*.java&lt;/include><br/>
-     * &nbsp;&lt;include>**&#47;*Test.java&lt;/include><br/>
-     * &nbsp;&lt;include>**&#47;*Tests.java&lt;/include><br/>
-     * &nbsp;&lt;include>**&#47;*TestCase.java&lt;/include><br/>
-     * &lt;/includes><br/>
-     * </code>
-     * <p/>
-     * Each include item may also contain a comma-separated sublist of items, which will be treated as multiple
-     * &nbsp;&lt;include> entries.<br/>
-     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):<br/>
-     * &nbsp;&lt;include>%regex[.*[Cat|Dog].*], Basic????, !Unstable*&lt;/include><br/>
-     * &nbsp;&lt;include>%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], pkg&#47;**&#47;*Fast*.java&lt;/include><br/>
-     * <p/>
-     * This parameter is ignored if the TestNG <code>suiteXmlFiles</code> parameter is specified.<br/>
-     * <br/>
-     * <em>Notice that</em> these values are relative to the directory containing generated test classes of the project
-     * being tested. This directory is declared by the parameter <code>testClassesDirectory</code> which defaults
-     * to the POM property <code>${project.build.testOutputDirectory}</code>, typically <em>src/test/java</em>
-     * unless overridden.
+     * A list of &lt;include&gt; elements specifying the tests (by pattern) that should be included in testing. When not
+     * specified and when the {@code test} parameter is not specified, the default includes will be
+     * <pre><code>
+     * {@literal <includes>}
+     *     {@literal <include>}**{@literal /}Test*.java{@literal </include>}
+     *     {@literal <include>}**{@literal /}*Test.java{@literal </include>}
+     *     {@literal <include>}**{@literal /}*Tests.java{@literal </include>}
+     *     {@literal <include>}**{@literal /}*TestCase.java{@literal </include>}
+     * {@literal </includes>}
+     * </code></pre>
+     * Each include item may also contain a comma-separated sub-list of items, which will be treated as multiple
+     * &nbsp;&lt;include&gt; entries.<br>
+     * Since 2.19 a complex syntax is supported in one parameter (JUnit 4, JUnit 4.7+, TestNG):
+     * <pre><code>
+     *
+     * </code></pre>
+     * {@literal <include>}%regex[.*[Cat|Dog].*], Basic????, !Unstable*{@literal </include>}
+     * {@literal <include>}%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], pkg{@literal /}**{@literal /}*Fast*.java{@literal </include>}
+     * <br>
+     * This parameter is ignored if the TestNG {@code suiteXmlFiles} parameter is specified.<br>
+     * <br>
+     * <b>Notice that</b> these values are relative to the directory containing generated test classes of the project
+     * being tested. This directory is declared by the parameter {@code testClassesDirectory} which defaults
+     * to the POM property {@code ${project.build.testOutputDirectory}}, typically
+     * <code>{@literal src/test/java}</code> unless overridden.
      */
     @Parameter
     private List<String> includes;
@@ -207,7 +215,7 @@
     /**
      * Option to pass dependencies to the system's classloader instead of using an isolated class loader when forking.
      * Prevents problems with JDKs which implement the service provider lookup mechanism by using the system's
-     * classloader.
+     * ClassLoader.
      *
      * @since 2.3
      */
@@ -220,7 +228,7 @@
      * <a href="http://maven.apache.org/plugins/maven-surefire-plugin/examples/class-loading.html">
      *     http://maven.apache.org/plugins/maven-surefire-plugin/examples/class-loading.html</a>
      * for a more detailed explanation of manifest-only JARs and their benefits.)
-     * <br/>
+     * <br>
      * Beware, setting this to "false" may cause your tests to fail on Windows if your classpath is too long.
      *
      * @since 2.4.3
@@ -238,10 +246,10 @@
     private int rerunFailingTestsCount;
 
     /**
-     * (TestNG) List of &lt;suiteXmlFile> elements specifying TestNG suite xml file locations. Note that
-     * <code>suiteXmlFiles</code> is incompatible with several other parameters of this plugin, like
-     * <code>includes/excludes</code>.<br/>
-     * This parameter is ignored if the <code>test</code> parameter is specified (allowing you to run a single test
+     * (TestNG) List of &lt;suiteXmlFile&gt; elements specifying TestNG suite xml file locations. Note that
+     * {@code suiteXmlFiles} is incompatible with several other parameters of this plugin, like
+     * {@code includes} and {@code excludes}.<br>
+     * This parameter is ignored if the {@code test} parameter is specified (allowing you to run a single test
      * instead of an entire suite).
      *
      * @since 2.2
@@ -250,26 +258,26 @@
     private File[] suiteXmlFiles;
 
     /**
-     * Defines the order the tests will be run in. Supported values are "alphabetical", "reversealphabetical", "random",
-     * "hourly" (alphabetical on even hours, reverse alphabetical on odd hours), "failedfirst", "balanced" and
-     * "filesystem".
-     * <br/>
-     * <br/>
+     * Defines the order the tests will be run in. Supported values are {@code alphabetical},
+     * {@code reversealphabetical}, {@code random}, {@code hourly} (alphabetical on even hours, reverse alphabetical
+     * on odd hours), {@code failedfirst}, {@code balanced} and {@code filesystem}.
+     * <br>
+     * <br>
      * Odd/Even for hourly is determined at the time the of scanning the classpath, meaning it could change during a
      * multi-module build.
-     * <br/>
-     * <br/>
+     * <br>
+     * <br>
      * Failed first will run tests that failed on previous run first, as well as new tests for this run.
-     * <br/>
-     * <br/>
+     * <br>
+     * <br>
      * Balanced is only relevant with parallel=classes, and will try to optimize the run-order of the tests reducing the
      * overall execution time. Initially a statistics file is created and every next test run will reorder classes.
-     * <br/>
-     * <br/>
-     * Note that the statistics are stored in a file named .surefire-XXXXXXXXX beside pom.xml, and should not be checked
-     * into version control. The "XXXXX" is the SHA1 checksum of the entire surefire configuration, so different
-     * configurations will have different statistics files, meaning if you change any config settings you will re-run
-     * once before new statistics data can be established.
+     * <br>
+     * <br>
+     * Note that the statistics are stored in a file named <b>.surefire-XXXXXXXXX</b> beside <i>pom.xml</i> and
+     * should not be checked into version control. The "XXXXX" is the SHA1 checksum of the entire surefire
+     * configuration, so different configurations will have different statistics files, meaning if you change any
+     * configuration settings you will re-run once before new statistics data can be established.
      *
      * @since 2.7
      */
@@ -278,30 +286,34 @@
 
     /**
      * A file containing include patterns. Blank lines, or lines starting with # are ignored. If {@code includes} are
-     * also specified, these patterns are appended. Example with path, simple and regex includes:<br/>
-     * &#042;&#047;test/*<br/>
-     * &#042;&#042;&#047;NotIncludedByDefault.java<br/>
-     * %regex[.*Test.*|.*Not.*]<br/>
+     * also specified, these patterns are appended. Example with path, simple and regex includes:
+     * <pre><code>
+     * *{@literal /}test{@literal /}*
+     * **{@literal /}NotIncludedByDefault.java
+     * %regex[.*Test.*|.*Not.*]
+     * </code></pre>
      */
     @Parameter( property = "surefire.includesFile" )
     private File includesFile;
 
     /**
      * A file containing exclude patterns. Blank lines, or lines starting with # are ignored. If {@code excludes} are
-     * also specified, these patterns are appended. Example with path, simple and regex excludes:<br/>
-     * &#042;&#047;test/*<br/>
-     * &#042;&#042;&#047;DontRunTest.*<br/>
-     * %regex[.*Test.*|.*Not.*]<br/>
+     * also specified, these patterns are appended. Example with path, simple and regex excludes:<br>
+     * <pre><code>
+     * *{@literal /}test{@literal /}*
+     * **{@literal /}DontRunTest.*
+     * %regex[.*Test.*|.*Not.*]
+     * </code></pre>
      */
     @Parameter( property = "surefire.excludesFile" )
     private File excludesFile;
 
     /**
      * Set to error/failure count in order to skip remaining tests.
-     * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br/>
-     * Enable with system property -Dsurefire.skipAfterFailureCount=1 or any number greater than zero.
-     * Defaults to "0".<br/>
-     * See the prerequisites and limitations in documentation:<br/>
+     * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br>
+     * Enable with system property {@code -Dsurefire.skipAfterFailureCount=1} or any number greater than zero.
+     * Defaults to "0".<br>
+     * See the prerequisites and limitations in documentation:<br>
      * <a href="http://maven.apache.org/plugins/maven-surefire-plugin/examples/skip-after-failure.html">
      *     http://maven.apache.org/plugins/maven-surefire-plugin/examples/skip-after-failure.html</a>
      *
@@ -311,50 +323,50 @@
     private int skipAfterFailureCount;
 
     /**
-     * After the plugin process is shutdown by sending SIGTERM signal (CTRL+C), SHUTDOWN command is received by every
-     * forked JVM. By default (shutdown=testset) forked JVM would not continue with new test which means that
-     * the current test may still continue to run.<br/>
-     * The parameter can be configured with other two values "exit" and "kill".<br/>
-     * Using "exit" forked JVM executes System.exit(1) after the plugin process has received SIGTERM signal.<br/>
-     * Using "kill" the JVM executes Runtime.halt(1) and kills itself.
+     * After the plugin process is shutdown by sending <i>SIGTERM signal (CTRL+C)</i>, <i>SHUTDOWN command</i> is
+     * received by every forked JVM.
+     * <br>
+     * By default ({@code shutdown=testset}) forked JVM would not continue with new test which means that
+     * the current test may still continue to run.
+     * <br>
+     * The parameter can be configured with other two values {@code exit} and {@code kill}.
+     * <br>
+     * Using {@code exit} forked JVM executes {@code System.exit(1)} after the plugin process has received
+     * <i>SIGTERM signal</i>.
+     * <br>
+     * Using {@code kill} the JVM executes {@code Runtime.halt(1)} and kills itself.
      *
      * @since 2.19
      */
     @Parameter( property = "surefire.shutdown", defaultValue = "testset" )
     private String shutdown;
 
+    @Override
     protected int getRerunFailingTestsCount()
     {
         return rerunFailingTestsCount;
     }
 
+    @Override
     protected void handleSummary( RunResult summary, Exception firstForkException )
         throws MojoExecutionException, MojoFailureException
     {
-        assertNoException( firstForkException );
-
-        reportExecution( this, summary, getConsoleLogger() );
-    }
-
-    private void assertNoException( Exception firstForkException )
-        throws MojoFailureException
-    {
-        if ( firstForkException != null )
-        {
-            throw new MojoFailureException( firstForkException.getMessage(), firstForkException );
-        }
+        reportExecution( this, summary, getConsoleLogger(), firstForkException );
     }
 
+    @Override
     protected boolean isSkipExecution()
     {
         return isSkip() || isSkipTests() || isSkipExec();
     }
 
+    @Override
     protected String getPluginName()
     {
         return "surefire";
     }
 
+    @Override
     protected String[] getDefaultIncludes()
     {
         return new String[]{ "**/Test*.java", "**/*Test.java", "**/*Tests.java", "**/*TestCase.java" };
@@ -366,219 +378,262 @@ protected String getReportSchemaLocation()
         return "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd";
     }
 
+    @Override
+    protected Artifact getMojoArtifact()
+    {
+        final Map<String, Artifact> pluginArtifactMap = getPluginArtifactMap();
+        return pluginArtifactMap.get( "org.apache.maven.plugins:maven-surefire-plugin" );
+    }
+    
     // now for the implementation of the field accessors
 
+    @Override
     public boolean isSkipTests()
     {
         return skipTests;
     }
 
+    @Override
     public void setSkipTests( boolean skipTests )
     {
         this.skipTests = skipTests;
     }
 
-    /**
-     * @noinspection deprecation
-     */
+    @Override
     public boolean isSkipExec()
     {
         return skipExec;
     }
 
-    /**
-     * @noinspection deprecation
-     */
+    @Override
     public void setSkipExec( boolean skipExec )
     {
         this.skipExec = skipExec;
     }
 
+    @Override
     public boolean isSkip()
     {
         return skip;
     }
 
+    @Override
     public void setSkip( boolean skip )
     {
         this.skip = skip;
     }
 
+    @Override
     public boolean isTestFailureIgnore()
     {
         return testFailureIgnore;
     }
 
+    @Override
     public void setTestFailureIgnore( boolean testFailureIgnore )
     {
         this.testFailureIgnore = testFailureIgnore;
     }
 
+    @Override
     public File getBasedir()
     {
         return basedir;
     }
 
+    @Override
     public void setBasedir( File basedir )
     {
         this.basedir = basedir;
     }
 
+    @Override
     public File getTestClassesDirectory()
     {
         return testClassesDirectory;
     }
 
+    @Override
     public void setTestClassesDirectory( File testClassesDirectory )
     {
         this.testClassesDirectory = testClassesDirectory;
     }
 
+    @Override
     public File getClassesDirectory()
     {
         return classesDirectory;
     }
 
+    @Override
     public void setClassesDirectory( File classesDirectory )
     {
         this.classesDirectory = classesDirectory;
     }
 
+    @Override
     public File getReportsDirectory()
     {
         return reportsDirectory;
     }
 
+    @Override
     public void setReportsDirectory( File reportsDirectory )
     {
         this.reportsDirectory = reportsDirectory;
     }
 
+    @Override
     public String getTest()
     {
         return test;
     }
 
+    @Override
     public boolean isUseSystemClassLoader()
     {
         return useSystemClassLoader;
     }
 
+    @Override
     public void setUseSystemClassLoader( boolean useSystemClassLoader )
     {
         this.useSystemClassLoader = useSystemClassLoader;
     }
 
+    @Override
     public boolean isUseManifestOnlyJar()
     {
         return useManifestOnlyJar;
     }
 
+    @Override
     public void setUseManifestOnlyJar( boolean useManifestOnlyJar )
     {
         this.useManifestOnlyJar = useManifestOnlyJar;
     }
 
+    @Override
     public Boolean getFailIfNoSpecifiedTests()
     {
         return failIfNoSpecifiedTests;
     }
 
+    @Override
     public void setFailIfNoSpecifiedTests( boolean failIfNoSpecifiedTests )
     {
         this.failIfNoSpecifiedTests = failIfNoSpecifiedTests;
     }
 
+    @Override
     public int getSkipAfterFailureCount()
     {
         return skipAfterFailureCount;
     }
 
+    @Override
     public String getShutdown()
     {
         return shutdown;
     }
 
+    @Override
     public boolean isPrintSummary()
     {
         return printSummary;
     }
 
+    @Override
     public void setPrintSummary( boolean printSummary )
     {
         this.printSummary = printSummary;
     }
 
+    @Override
     public String getReportFormat()
     {
         return reportFormat;
     }
 
+    @Override
     public void setReportFormat( String reportFormat )
     {
         this.reportFormat = reportFormat;
     }
 
+    @Override
     public boolean isUseFile()
     {
         return useFile;
     }
 
+    @Override
     public void setUseFile( boolean useFile )
     {
         this.useFile = useFile;
     }
 
+    @Override
     public String getDebugForkedProcess()
     {
         return debugForkedProcess;
     }
 
+    @Override
     public void setDebugForkedProcess( String debugForkedProcess )
     {
         this.debugForkedProcess = debugForkedProcess;
     }
 
+    @Override
     public int getForkedProcessTimeoutInSeconds()
     {
         return forkedProcessTimeoutInSeconds;
     }
 
+    @Override
     public void setForkedProcessTimeoutInSeconds( int forkedProcessTimeoutInSeconds )
     {
         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
     }
 
+    @Override
     public int getForkedProcessExitTimeoutInSeconds()
     {
         return forkedProcessExitTimeoutInSeconds;
     }
 
+    @Override
     public void setForkedProcessExitTimeoutInSeconds( int forkedProcessExitTimeoutInSeconds )
     {
         this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds;
     }
 
+    @Override
     public double getParallelTestsTimeoutInSeconds()
     {
         return parallelTestsTimeoutInSeconds;
     }
 
+    @Override
     public void setParallelTestsTimeoutInSeconds( double parallelTestsTimeoutInSeconds )
     {
         this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds;
     }
 
+    @Override
     public double getParallelTestsTimeoutForcedInSeconds()
     {
         return parallelTestsTimeoutForcedInSeconds;
     }
 
+    @Override
     public void setParallelTestsTimeoutForcedInSeconds( double parallelTestsTimeoutForcedInSeconds )
     {
         this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds;
     }
 
+    @Override
     public void setTest( String test )
     {
         this.test = test;
@@ -596,22 +651,26 @@ public void setIncludes( List<String> includes )
         this.includes = includes;
     }
 
+    @Override
     public File[] getSuiteXmlFiles()
     {
         return suiteXmlFiles.clone();
     }
 
+    @Override
     @SuppressWarnings( "UnusedDeclaration" )
     public void setSuiteXmlFiles( File[] suiteXmlFiles )
     {
         this.suiteXmlFiles = suiteXmlFiles.clone();
     }
 
+    @Override
     public String getRunOrder()
     {
         return runOrder;
     }
 
+    @Override
     @SuppressWarnings( "UnusedDeclaration" )
     public void setRunOrder( String runOrder )
     {
diff --git a/maven-surefire-plugin/src/site/apt/developing.apt.vm b/maven-surefire-plugin/src/site/apt/developing.apt.vm
index fcb193016..680201af6 100644
--- a/maven-surefire-plugin/src/site/apt/developing.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/developing.apt.vm
@@ -114,9 +114,9 @@ ForkedBooter#main
 
 * JDK Versions
 
-  The surefire booter is capable of booting all the way back to jdk1.5. Specifically
+  The surefire booter is capable of booting all the way back to jdk1.6. Specifically
   this means <<<surefire-api>>>, <<<surefire-booter>>>, <<<common-junit3>>> and <<<surefire-junit3>>> are
-  source/target 1.5. The plugin and several providers are 1.5.
+  source/target 1.6. The plugin and several providers are 1.6.
 
 * Provider Isolation
 
diff --git a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
index 2f9429cd5..805d1e1c9 100644
--- a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
@@ -291,6 +291,12 @@ Fork Options and Parallel Test Execution
   <<<-T>>>, the only limitation is that you can not use it together with
   <<<forkCount=0>>>.
 
+  When running parallel maven builds without forks, all system properties
+  are shared among the builder threads and ${thisPlugin.toLowerCase()} executions,
+  therefore the threads will encounter race conditions when setting
+  properties, e.g. <<<baseDir>>>, which may lead to changing system properties
+  and unexpected runtime behaviour.
+
 * Migrating the Deprecated forkMode Parameter to forkCount and reuseForks
 
   ${thisPlugin.toLowerCase()} versions prior 2.14 used the parameter <<<forkMode>>>
diff --git a/maven-surefire-plugin/src/site/apt/examples/inclusion-exclusion.apt.vm b/maven-surefire-plugin/src/site/apt/examples/inclusion-exclusion.apt.vm
index 22b1ce2e9..e5d3408b9 100644
--- a/maven-surefire-plugin/src/site/apt/examples/inclusion-exclusion.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/inclusion-exclusion.apt.vm
@@ -5,7 +5,7 @@
   ------
   2010-01-09
   ------
-  
+
  ~~ Licensed to the Apache Software Foundation (ASF) under one
  ~~ or more contributor license agreements.  See the NOTICE file
  ~~ distributed with this work for additional information
@@ -164,7 +164,16 @@ Inclusions and Exclusions of Tests
 +---+
 
   Note the syntax <<<%regex[expr]>>>, where <<<expr>>> is the actual expression and the rest is just wrapping. Also
-  note that regex matches are done over <<<*.class>>> files and not <<<*.java>>> files.
+  note the following about the use of regular expressions:
+
+   * Regex matches are done over <<<*.class>>> files and not <<<*.java>>> files
+
+   * Regex matches are done over paths using slashes ("<<</>>>") and not package names using dots ("<<<.>>>"), so the
+      "<<<.>>>" in <<<pkg.*Slow.*.class>>> is a regex metacharacter, which happens to match any character, notably
+      the (forward) slashes ("<<</>>>") that make up the path. Slashes here are <forward>, even on Windows
+
+   * The trailing <<<.class>>> is interpreted literally, and not as a regular expression ("<<<\.class>>>" does not
+      work here)
 
 * Multiple Formats in One
 
diff --git a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
index 7e943a5ae..7a7b59e11 100644
--- a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
@@ -39,8 +39,33 @@ Shutdown of Forked JVM
 
 * Pinging forked JVM
 
+  << Since ${thisPlugin} Plugin 2.20.1 ping is platform dependent and fallbacks to old mechanism if PID of Maven
+  process or platform is not recognized, native commands fail in Java. >>
+
+  Simply the mechanism checks the <<< Maven PID >>> is still alive and it is not reused by OS in another application.
+  If Maven process has died, the forked JVM is killed.
+
+  << Implementation: >> The <<< Maven PID >>> is determined by:
+
+   * Java 9 call <<< ProcessHandle.current().pid() >>>, or
+
+   * resolving PID from <<< /proc/self/stat >>> on Linux and <<< /proc/curproc/status >>> on BSD, or
+
+   * the JMX call <<< ManagementFactory.getRuntimeMXBean().getName() >>>.
+
+   []
+
+   On Unix like systems the process' uptime is determined by native command <<< (/usr)/bin/ps -o etime= -p [PID] >>>.
+
+   On Windows the start time is determined using <<< wmic process where (ProcessId=[PID]) get CreationDate >>>
+   in the forked JVM.
+
+
+  << Since ${thisPlugin} Plugin 2.19 the old mechanism is significantly slower: >>
+
   The master process sends NOOP command to a forked JVM every 10 seconds.
-  Forked JVM is waiting for the command every 20 seconds.
+  Forked JVM is waiting for the command every 20 seconds (changed to 30 seconds since version 2.20.1, see
+  {{{https://issues.apache.org/jira/browse/SUREFIRE-1302}SUREFIRE-1302}}).
   If the master process is killed (received SIGKILL signal) or shutdown 
   (pressed CTRL+C, received SIGTERM signal), forked JVM is killed after
   timing out waiting period.
@@ -85,4 +110,11 @@ Shutdown of Forked JVM
   amount of time and the whole plugin fails with the error message:
 
   <<<There was a timeout or other error in the fork>>>
-  
+
+
+* Crashed forked JVM caused listing the crashed test(s)
+
+  After the JVM exited abruptly, the console lists the message <<<Crashed tests:>>> with a list of crashed tests if the
+  entire test-set has not been yet completed. This happens if a test exited, killed JVM or a segmentation fault crashed
+  JVM. In such cases you may be interested in dump files generated in reports directory, see {{{../faq.html#dumpfiles}FAQ}}.
+
diff --git a/maven-surefire-plugin/src/site/apt/index.apt.vm b/maven-surefire-plugin/src/site/apt/index.apt.vm
index 85c4d6632..0ec361cd9 100644
--- a/maven-surefire-plugin/src/site/apt/index.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/index.apt.vm
@@ -31,7 +31,7 @@
 Maven ${thisPlugin} Plugin
 
 
-  Requirements: Maven 2.2.1 or 3.x, and JDK 1.5 or higher. Due to wrong formatting of console text messages
+  Requirements: Maven 2.2.1 or 3.x, and JDK 1.6 or higher. Due to wrong formatting of console text messages
   in Maven Version prior to 3.1.0 it is highly recommended to use Maven 3.1.0 or higher.
 
 #{if}(${project.artifactId}=="maven-surefire-plugin")
@@ -81,7 +81,7 @@ mvn verify
 
   []
 
-  By default, these files are generated at <<<$\{basedir\}/target/${thisPlugin.toLowerCase()}-reports>>>.
+  By default, these files are generated in <<<$\{basedir\}/target/${thisPlugin.toLowerCase()}-reports/TEST-*.xml>>>.
 
   The schema for the ${thisPlugin} XML reports is available at
   {{{./xsd/${thisPlugin.toLowerCase()}-test-report.xsd}${thisPlugin} XML Report Schema}}.
@@ -89,6 +89,11 @@ mvn verify
   For an HTML format of the report, please see the
   {{{http://maven.apache.org/plugins/maven-surefire-report-plugin/}Maven Surefire Report Plugin}}.
 
+#{if}(${project.artifactId}=="maven-failsafe-plugin")
+  By default this plugin generates summary XML file at <<<$\{basedir\}/target/failsafe-reports/failsafe-summary.xml>>>
+  and the schema is available at {{{./xsd/failsafe-summary.xsd}Failsafe XML Summary Schema}}.
+#{end}
+
 * Goals Overview
 
 #{if}(${project.artifactId}=="maven-surefire-plugin")
diff --git a/maven-surefire-plugin/src/site/apt/usage.apt.vm b/maven-surefire-plugin/src/site/apt/usage.apt.vm
index b5dd3bd04..c442e67f3 100644
--- a/maven-surefire-plugin/src/site/apt/usage.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/usage.apt.vm
@@ -281,7 +281,8 @@ mvn verify
             <reports>
               <report>failsafe-report-only</report>
             </reports>
-        </reportSet>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/maven-surefire-plugin/src/site/fml/faq.fml b/maven-surefire-plugin/src/site/fml/faq.fml
index 6be79138d..b73dde63b 100644
--- a/maven-surefire-plugin/src/site/fml/faq.fml
+++ b/maven-surefire-plugin/src/site/fml/faq.fml
@@ -67,6 +67,16 @@ under the License.
         </p>
       </answer>
     </faq>
+    <faq id="crashed-forks">
+      <question>Crashed Surefire or Failsafe plugin must indicate crashed tests</question>
+      <answer>
+        <p>
+        After a forked JVM has crashed the console of forked JVM prints <em>Crashed tests:</em> and lists the last test
+        which has crashed. In the console log you can find the message
+        <em>The forked VM terminated without properly saying goodbye</em>.
+        </p>
+      </answer>
+    </faq>
     <faq id="GWT">
       <question>How can I run GWT tests?</question>
       <answer>
@@ -91,22 +101,60 @@ under the License.
           plugin is run. So Surefire would never see the place-holders in its argLine property.
         </p>
         <p>
-          Using an alternate syntax for these properties, <pre>@{...}</pre> allows late replacement
-          of properties when the plugin is executed, so properties that have been modified by other
+          Since the Version 2.17 using an alternate syntax for these properties, <pre>@{...}</pre> allows late
+          replacement of properties when the plugin is executed, so properties that have been modified by other
           plugins will be picked up correctly.
         </p>
       </answer>
     </faq>
     <faq id="failsafe-jar">
-      <question>How maven-failsafe-plugin allows me to configure the jar file or classes to use</question>
+      <question>How maven-failsafe-plugin allows me to configure the jar file or classes to use?</question>
       <answer>
         <p>
-        By default maven-failsafe-plugin uses project artifact file if packaging is set to "jar" in pom.xml.
-        This can be modified and for instance set to main project classes if you use configuration parameter
+        By default maven-failsafe-plugin uses project artifact file in test classpath if packaging is set to "jar" in
+        pom.xml. This can be modified and for instance set to main project classes if you use configuration parameter
         "classesDirectory". This would mean that you set value "${project.build.outputDirectory}" for the parameter
         "classesDirectory" in the configuration of plugin.
         </p>
       </answer>
     </faq>
+    <faq id="dumpfiles">
+      <question>How to dump fatal errors and stack trace of plugin runtime if it fails?</question>
+      <answer>
+        <p>
+        By default <em>maven-failsafe-plugin</em> and <em>maven-surefire-plugin</em> dumps fatal errors in dump files
+        and these are located in <em>target/failsafe-reports</em> and <em>target/surefire-reports</em>.
+        Names of dump files are formatted as follows:
+        <br/>
+        <code>
+        <![CDATA[ [date]-jvmRun[N].dump]]><br/>
+        <![CDATA[ [date]-jvmRun[N].dumpstream]]><br/>
+        <![CDATA[ [date].dumpstream]]><br/>
+        </code>
+
+        Forked JVM process and plugin process communicate via std/out. If this channel is corrupted, for a whatever
+        reason, the dump of the corrupted stream appears in <em>*.dumpstream</em>.
+        </p>
+      </answer>
+    </faq>
+    <faq id="corruptedstream">
+      <question>Corrupted STDOUT by directly writing to native stream in forked JVM</question>
+      <answer>
+        <p>
+        If your tests use native library which prints to STDOUT this warning message appears because the library
+        corrupted the channel used by the plugin in order to transmit events with test status back to Maven process.
+        It would be even worse if you override the Java stream by <em>System.setOut</em> because the stream is also
+        supposed to be corrupted but the Maven will never see the tests finished and build may hang.
+        <br/>
+        This warning message appears if you use <em>FileDescriptor.out</em> or JVM prints GC summary.
+        <br/>
+        In that case the warning is printed
+        <em>"Corrupted STDOUT by directly writing to native stream in forked JVM"</em>, and a dump file can be found
+        in Reports directory.
+        <br/>
+        If debug level is enabled then messages of corrupted stream appear in the console.
+        </p>
+      </answer>
+    </faq>
   </part>
 </faqs>
diff --git a/maven-surefire-plugin/src/site/markdown/java9.md b/maven-surefire-plugin/src/site/markdown/java9.md
new file mode 100644
index 000000000..e0f758f3f
--- /dev/null
+++ b/maven-surefire-plugin/src/site/markdown/java9.md
@@ -0,0 +1,92 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+Java 9 in JAVA_HOME
+========================
+
+    $ export JAVA_HOME=/path/to/jdk9
+    $ mvn test
+
+The plugin will automatically add `--add-modules java.se.ee` on JVM argument in CLI (unless already specified by user)
+and all Java 9 API is provided to run your tests.
+
+
+Java 9 in configuration of plugin
+========================
+
+The plugin provides you with configuration parameter `jvm` which can point to path of executable Java in JDK, e.g.:
+
+    <configuration>
+        <jvm>/path/to/jdk9/bin/java</jvm>
+    </configuration>
+
+Now you can run the build with tests on the top of Java 9.
+
+
+Maven Toolchains with JDK 9
+========================
+
+This is an example on Windows to run unit tests with custom path to Toolchain **(-t ...)**.
+
+    $ mvn -t D:\.m2\toolchains.xml test
+    
+Without **(-t ...)** the Toolchain should be located in **${user.home}/.m2/toolchains.xml**.
+
+The content of **toolchains.xml** would become as follows however multiple different JDKs can be specified.
+
+    <toolchains xmlns="http://maven.apache.org/POM/4.0.0"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 https://maven.apache.org/xsd/toolchains-1.1.0.xsd">
+      <toolchain>
+        <type>jdk</type>
+        <provides>
+          <version>9</version>
+          <vendor>oracle</vendor>
+          <id>jdk9</id>
+        </provides>
+        <configuration>
+          <jdkHome>/path/to/jdk9</jdkHome>
+        </configuration>
+      </toolchain>
+    </toolchains>
+
+Your POM should specify the plugin which activates only particular JDK in *toolchains.xml* which specifies version **9**:
+
+    <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-toolchains-plugin</artifactId>
+        <version>1.1</version>
+        <executions>
+          <execution>
+            <phase>validate</phase>
+            <goals>
+              <goal>toolchain</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <toolchains>
+            <jdk>
+              <version>9</version>
+            </jdk>
+          </toolchains>
+        </configuration>
+    </plugin>
+
+Now you can run the build with tests on the top of Java 9.
diff --git a/maven-surefire-plugin/src/site/markdown/newerrorsummary.md b/maven-surefire-plugin/src/site/markdown/newerrorsummary.md
index 7467aa930..88cd7c305 100644
--- a/maven-surefire-plugin/src/site/markdown/newerrorsummary.md
+++ b/maven-surefire-plugin/src/site/markdown/newerrorsummary.md
@@ -26,11 +26,11 @@ report of the run or the files on disk.
 
 ### Example output:
 
-    Failed tests:
+    Failures:
       Test1.assertion1:59 Bending maths expected:<[123]> but was:<[312]>
       Test1.assertion2:64 True is false
 
-    Tests in error:
+    Errors:
       Test1.nullPointerInLibrary:38 » NullPointer
       Test1.failInMethod:43->innerFailure:68 NullPointer Fail here
       Test1.failInLibInMethod:48 » NullPointer
diff --git a/maven-surefire-plugin/src/site/resources/xsd/failsafe-summary.xsd b/maven-surefire-plugin/src/site/resources/xsd/failsafe-summary.xsd
new file mode 100644
index 000000000..dca134b09
--- /dev/null
+++ b/maven-surefire-plugin/src/site/resources/xsd/failsafe-summary.xsd
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements.  See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership.  The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License.  You may obtain a copy of the License at
~
~     http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied.  See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<xsd:schema version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="failsafe-summary">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="completed" type="xsd:int"/>
                <xsd:element name="errors" type="xsd:int"/>
                <xsd:element name="failures" type="xsd:int"/>
                <xsd:element name="skipped" type="xsd:int"/>
                <xsd:element name="failureMessage" type="xsd:string" nillable="true"/>
            </xsd:sequence>
            <xsd:attribute name="result" type="errorType" use="optional"/>
            <xsd:attribute name="timeout" type="xsd:boolean" use="required"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:simpleType name="errorType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration id="FAILURE" value="255"/>
            <xsd:enumeration id="NO_TESTS" value="254"/>
        </xsd:restriction>
    </xsd:simpleType>
</xsd:schema>
\ No newline at end of file
diff --git a/maven-surefire-plugin/src/site/site.xml b/maven-surefire-plugin/src/site/site.xml
index 7e8d49cf7..2d2e95ac3 100644
--- a/maven-surefire-plugin/src/site/site.xml
+++ b/maven-surefire-plugin/src/site/site.xml
@@ -56,6 +56,7 @@
       <item name="Fork Options and Parallel Test Execution" href="examples/fork-options-and-parallel-execution.html"/>
       <item name="Using Console Logs" href="examples/logging.html"/>
       <item name="Shutdown of Forked JVM" href="examples/shutdown.html"/>
+      <item name="Run tests with Java 9" href="java9.html"/>
     </menu>
   </body>
 </project>
diff --git a/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java b/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
index 2f8700a2b..5fa6df0a4 100644
--- a/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
+++ b/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
@@ -102,11 +102,13 @@ private Field findField( Class clazz, String fieldName )
     private class MyToolChain
         implements Toolchain
     {
+        @Override
         public String getType()
         {
             return null;
         }
 
+        @Override
         public String findTool( String s )
         {
             return null;
diff --git a/maven-surefire-report-plugin/pom.xml b/maven-surefire-report-plugin/pom.xml
index 81e021495..9cdd56948 100644
--- a/maven-surefire-report-plugin/pom.xml
+++ b/maven-surefire-report-plugin/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.maven.plugins</groupId>
@@ -47,10 +47,6 @@
   </prerequisites>
 
   <dependencies>
-    <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-logger-api</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-project</artifactId>
@@ -136,7 +132,6 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>build-helper-maven-plugin</artifactId>
-        <version>1.12</version>
         <executions>
           <execution>
             <id>add-source</id>
diff --git a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/AbstractSurefireReportMojo.java b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/AbstractSurefireReportMojo.java
index 9621fa117..ab27b1826 100644
--- a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/AbstractSurefireReportMojo.java
+++ b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/AbstractSurefireReportMojo.java
@@ -48,16 +48,12 @@
 
     /**
      * If set to false, only failures are shown.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "true", required = true, property = "showSuccess" )
     private boolean showSuccess;
 
     /**
      * Directories containing the XML Report files that will be parsed and rendered to HTML format.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter
     private File[] reportsDirectories;
@@ -65,8 +61,6 @@
     /**
      * (Deprecated, use reportsDirectories) This directory contains the XML Report files that will be parsed and
      * rendered to HTML format.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Deprecated
     @Parameter
@@ -74,32 +68,24 @@
 
     /**
      * The projects in the reactor for aggregation report.
-     *
-     * @noinspection MismatchedQueryAndUpdateOfCollection, UnusedDeclaration
      */
     @Parameter( defaultValue = "${reactorProjects}", readonly = true )
     private List<MavenProject> reactorProjects;
 
     /**
      * Location of the Xrefs to link.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref-test" )
     private File xrefLocation;
 
     /**
      * Whether to link the XRef if found.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "true", property = "linkXRef" )
     private boolean linkXRef;
 
     /**
      * Whether to build an aggregated report at the root, or build individual reports.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "false", property = "aggregate" )
     private boolean aggregate;
@@ -131,6 +117,7 @@ protected boolean isGeneratedWhenNoResults()
     /**
      * {@inheritDoc}
      */
+    @Override
     public void executeReport( Locale locale )
         throws MavenReportException
     {
@@ -312,6 +299,7 @@ private String determineXrefLocation()
     /**
      * {@inheritDoc}
      */
+    @Override
     public String getName( Locale locale )
     {
         return getBundle( locale ).getString( "report.surefire.name" );
@@ -320,6 +308,7 @@ public String getName( Locale locale )
     /**
      * {@inheritDoc}
      */
+    @Override
     public String getDescription( Locale locale )
     {
         return getBundle( locale ).getString( "report.surefire.description" );
@@ -328,6 +317,7 @@ public String getDescription( Locale locale )
     /**
      * {@inheritDoc}
      */
+    @Override
     public abstract String getOutputName();
 
     private ResourceBundle getBundle( Locale locale )
diff --git a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/FailsafeReportMojo.java b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/FailsafeReportMojo.java
index b208c4f80..c8fca02fd 100644
--- a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/FailsafeReportMojo.java
+++ b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/FailsafeReportMojo.java
@@ -43,17 +43,13 @@
 
     /**
      * The filename to use for the report.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "failsafe-report", property = "outputName", required = true )
     private String outputName;
 
     /**
      * If set to true the failsafe report will be generated even when there are no failsafe result files.
-     * Defaults to <code>false</code> to preserve legacy behaviour pre 2.10
-     *
-     * @noinspection UnusedDeclaration
+     * Defaults to {@code false} to preserve legacy behaviour pre 2.10.
      * @since 2.11
      */
     @Parameter( defaultValue = "false", property = "alwaysGenerateFailsafeReport" )
@@ -61,29 +57,31 @@
 
     /**
      * If set to true the failsafe report generation will be skipped.
-     *
-     * @noinspection UnusedDeclaration
      * @since 2.11
      */
     @Parameter( defaultValue = "false", property = "skipFailsafeReport" )
     private boolean skipFailsafeReport;
 
+    @Override
     protected File getSurefireReportsDirectory( MavenProject subProject )
     {
         String buildDir = subProject.getBuild().getDirectory();
         return new File( buildDir + "/failsafe-reports" );
     }
 
+    @Override
     public String getOutputName()
     {
         return outputName;
     }
 
+    @Override
     protected boolean isSkipped()
     {
         return skipFailsafeReport;
     }
 
+    @Override
     protected boolean isGeneratedWhenNoResults()
     {
         return alwaysGenerateFailsafeReport;
@@ -92,6 +90,7 @@ protected boolean isGeneratedWhenNoResults()
     /**
      * {@inheritDoc}
      */
+    @Override
     public String getName( Locale locale )
     {
         return getBundle( locale ).getString( "report.failsafe.name" );
@@ -100,6 +99,7 @@ public String getName( Locale locale )
     /**
      * {@inheritDoc}
      */
+    @Override
     public String getDescription( Locale locale )
     {
         return getBundle( locale ).getString( "report.failsafe.description" );
diff --git a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/PluginConsoleLogger.java b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/PluginConsoleLogger.java
index ba83734ec..3a75037a5 100644
--- a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/PluginConsoleLogger.java
+++ b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/PluginConsoleLogger.java
@@ -31,7 +31,7 @@
  * Calling {@link Log#isInfoEnabled()} before {@link Log#info(CharSequence)} due to Maven 2.2.1.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.19.2
+ * @since 2.20
  * @see ConsoleLogger
  */
 final class PluginConsoleLogger
@@ -49,6 +49,7 @@ public boolean isDebugEnabled()
         return mojoLogger.isDebugEnabled();
     }
 
+    @Override
     public void debug( String message )
     {
         if ( mojoLogger.isDebugEnabled() )
@@ -70,6 +71,7 @@ public boolean isInfoEnabled()
         return mojoLogger.isInfoEnabled();
     }
 
+    @Override
     public void info( String message )
     {
         if ( mojoLogger.isInfoEnabled() )
@@ -83,6 +85,7 @@ public boolean isWarnEnabled()
         return mojoLogger.isWarnEnabled();
     }
 
+    @Override
     public void warning( String message )
     {
         if ( mojoLogger.isWarnEnabled() )
@@ -104,6 +107,7 @@ public boolean isErrorEnabled()
         return mojoLogger.isErrorEnabled();
     }
 
+    @Override
     public void error( String message )
     {
         if ( mojoLogger.isErrorEnabled() )
@@ -112,6 +116,7 @@ public void error( String message )
         }
     }
 
+    @Override
     public void error( String message, Throwable t )
     {
         if ( mojoLogger.isErrorEnabled() )
@@ -120,6 +125,7 @@ public void error( String message, Throwable t )
         }
     }
 
+    @Override
     public void error( Throwable t )
     {
         if ( mojoLogger.isErrorEnabled() )
diff --git a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportMojo.java b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportMojo.java
index 964c1c45f..906feb945 100644
--- a/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportMojo.java
+++ b/maven-surefire-report-plugin/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportMojo.java
@@ -40,17 +40,13 @@
 
     /**
      * The filename to use for the report.
-     *
-     * @noinspection UnusedDeclaration
      */
     @Parameter( defaultValue = "surefire-report", property = "outputName", required = true )
     private String outputName;
 
     /**
      * If set to true the surefire report will be generated even when there are no surefire result files.
-     * Defaults to <code>true</code> to preserve legacy behaviour pre 2.10.
-     *
-     * @noinspection UnusedDeclaration
+     * Defaults to {@code true} to preserve legacy behaviour pre 2.10.
      * @since 2.11
      */
     @Parameter( defaultValue = "true", property = "alwaysGenerateSurefireReport" )
@@ -58,29 +54,31 @@
 
     /**
      * If set to true the surefire report generation will be skipped.
-     *
-     * @noinspection UnusedDeclaration
      * @since 2.11
      */
     @Parameter( defaultValue = "false", property = "skipSurefireReport" )
     private boolean skipSurefireReport;
 
+    @Override
     protected File getSurefireReportsDirectory( MavenProject subProject )
     {
         String buildDir = subProject.getBuild().getDirectory();
         return new File( buildDir + "/surefire-reports" );
     }
 
+    @Override
     public String getOutputName()
     {
         return outputName;
     }
 
+    @Override
     protected boolean isSkipped()
     {
         return skipSurefireReport;
     }
 
+    @Override
     protected boolean isGeneratedWhenNoResults()
     {
         return alwaysGenerateSurefireReport;
diff --git a/maven-surefire-report-plugin/src/main/resources/surefire-report_sv.properties b/maven-surefire-report-plugin/src/main/resources/surefire-report_sv.properties
index bdf8361c6..3664c6bce 100644
--- a/maven-surefire-report-plugin/src/main/resources/surefire-report_sv.properties
+++ b/maven-surefire-report-plugin/src/main/resources/surefire-report_sv.properties
@@ -35,3 +35,5 @@ report.surefire.label.failuredetails=Detaljer om misslyckade tester
 report.surefire.text.note1=Notera: misslyckade tester \u00e4r f\u00f6rv\u00e4ntade och har kontrollerats med assertions medan felaktiga tester \u00e4r ov\u00e4ntade.
 report.surefire.text.note2=Notera: paketstatistiken ber\u00e4knas inte rekursivt, den summerar bara alla testsviters antal.
 
+report.failsafe.name=Failsafe-rapport
+report.failsafe.description=Rapport om integration testresultaten f\u00f6r projektet.
diff --git a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/Utils.java b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/Utils.java
index 8fd91bf24..d2cb04e5c 100644
--- a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/Utils.java
+++ b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/Utils.java
@@ -23,6 +23,7 @@
 {
     private Utils()
     {
+        throw new IllegalStateException( "no instantiable constructor" );
     }
 
     public static String toSystemNewLine( String s )
diff --git a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub.java b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub.java
index af02470a0..155b68362 100644
--- a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub.java
+++ b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub.java
@@ -34,6 +34,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public List getReportPlugins()
     {
         Reporting reporting = new Reporting();
diff --git a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub2.java b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub2.java
index 18277ca68..23b1e4e7c 100644
--- a/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub2.java
+++ b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/stubs/SurefireRepMavenProjectStub2.java
@@ -32,6 +32,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public List getReportPlugins()
     {
         return new ArrayList();
diff --git a/pom.xml b/pom.xml
index 0f9cc1212..4f0bf5812 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,13 +23,13 @@
   <parent>
     <artifactId>maven-parent</artifactId>
     <groupId>org.apache.maven</groupId>
-    <version>26</version>
+    <version>30</version>
     <relativePath>../pom/maven/pom.xml</relativePath>
   </parent>
 
   <groupId>org.apache.maven.surefire</groupId>
   <artifactId>surefire</artifactId>
-  <version>2.19.2-SNAPSHOT</version>
+  <version>2.21.0-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Apache Maven Surefire</name>
@@ -76,7 +76,7 @@
   </issueManagement>
   <ciManagement>
     <system>Jenkins</system>
-    <url>https://builds.apache.org/job/maven-surefire/</url>
+    <url>https://builds.apache.org/view/M-R/view/Maven/job/maven-surefire-pipeline/job/master/</url>
   </ciManagement>
   <distributionManagement>
     <site>
@@ -89,8 +89,13 @@
     <mavenVersion>2.2.1</mavenVersion>
     <!-- <shadedVersion>2.12.4</shadedVersion> commented out due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
     <mavenPluginPluginVersion>3.3</mavenPluginPluginVersion>
+    <commonsLang3Version>3.5</commonsLang3Version>
+    <commonsIoVersion>2.5</commonsIoVersion>
+    <mavenSharedUtilsVersion>0.9</mavenSharedUtilsVersion>
     <maven.surefire.scm.devConnection>scm:git:https://git-wip-us.apache.org/repos/asf/maven-surefire.git</maven.surefire.scm.devConnection>
     <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path>
+    <!-- Override with Jigsaw JRE 9 -->
+    <jdk.home>${java.home}/..</jdk.home>
   </properties>
 
   <dependencyManagement>
@@ -103,12 +108,12 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.1</version>
+        <version>${commonsLang3Version}</version>
       </dependency>
       <dependency>
         <groupId>commons-io</groupId>
         <artifactId>commons-io</artifactId>
-        <version>2.2</version>
+        <version>${commonsIoVersion}</version>
       </dependency>
       <dependency>
         <groupId>org.apache.maven.surefire</groupId>
@@ -210,10 +215,21 @@
         <artifactId>maven-toolchain</artifactId>
         <version>${mavenVersion}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.maven</groupId>
+        <artifactId>maven-settings</artifactId>
+        <version>${mavenVersion}</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.maven.shared</groupId>
         <artifactId>maven-shared-utils</artifactId>
-        <version>0.9</version>
+        <version>${mavenSharedUtilsVersion}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+          </exclusion>
+        </exclusions>
       </dependency>
       <dependency>
         <groupId>org.apache.maven.shared</groupId>
@@ -224,6 +240,28 @@
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
         <version>1.10.19</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>org.powermock</groupId>
+        <artifactId>powermock-mockito-release-full</artifactId>
+        <version>1.6.4</version>
+        <classifier>full</classifier>
+        <exclusions>
+          <exclusion>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+          </exclusion>
+        </exclusions>
       </dependency>
       <dependency>
         <groupId>junit</groupId>
@@ -253,6 +291,12 @@
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.hamcrest</groupId>
+          <artifactId>hamcrest-core</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.hamcrest</groupId>
@@ -269,15 +313,66 @@
   <build>
     <pluginManagement>
       <plugins>
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>build-helper-maven-plugin</artifactId>
+          <version>1.12</version>
+        </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.6.1</version>
+          <executions>
+            <execution>
+              <id>compile-generated</id>
+              <phase>process-sources</phase>
+              <goals>
+                <goal>compile</goal>
+              </goals>
+              <configuration>
+                <includes>
+                  <include>org/apache/maven/shared/utils/logging/*.java</include>
+                  <include>HelpMojo.java</include>
+                  <include>**/HelpMojo.java</include>
+                  <include>org/apache/maven/plugin/failsafe/xmlsummary/*.java</include>
+                </includes>
+                <compilerArgs>
+                  <!-- FIXME: maven-plugin-plugin therefore used -syntax or none due to HelpMojo -->
+                  <arg>-Xdoclint:none</arg>
+                </compilerArgs>
+              </configuration>
+            </execution>
+            <execution>
+              <id>default-compile</id>
+              <phase>compile</phase>
+              <goals>
+                <goal>compile</goal>
+              </goals>
+              <configuration>
+                <excludes>
+                  <exclude>org/apache/maven/shared/utils/logging/*.java</exclude>
+                  <exclude>HelpMojo.java</exclude>
+                  <exclude>**/HelpMojo.java</exclude>
+                  <exclude>org/apache/maven/plugin/failsafe/xmlsummary/*.java</exclude>
+                </excludes>
+                <compilerArgs>
+                  <arg>-Xdoclint:all</arg>
+                </compilerArgs>
+              </configuration>
+            </execution>
+          </executions>
+          <configuration>
+            <fork>true</fork>
+            <compilerArgs>
+              <arg>-Xdoclint:all</arg>
+            </compilerArgs>
+          </configuration>
         </plugin>
         <!-- NOTE: animal sniffer does not check test classes: https://jira.codehaus.org/browse/MANIMALSNIFFER-40 -->
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>animal-sniffer-maven-plugin</artifactId>
-          <version>1.11</version>
+          <version>1.15</version>
           <executions>
             <execution>
               <id>signature-check</id>
@@ -289,8 +384,8 @@
           <configuration>
             <signature>
               <groupId>org.codehaus.mojo.signature</groupId>
-              <artifactId>java15</artifactId>
-              <version>1.0</version>
+              <artifactId>java16</artifactId>
+              <version>1.1</version>
             </signature>
           </configuration>
         </plugin>
@@ -300,7 +395,7 @@
           <configuration>
             <!-- NOTE: Be sure to isolate the Surefire version under test from the version running the tests! -->
             <useSystemClassLoader>false</useSystemClassLoader>
-            <argLine>${jacoco.agent}</argLine>
+            <argLine>-Xms128m -Xmx128m</argLine>
           </configuration>
         </plugin>
         <plugin>
@@ -308,11 +403,12 @@
           <configuration>
             <autoVersionSubmodules>true</autoVersionSubmodules>
             <preparationGoals>clean install</preparationGoals>
+            <commitByProject>true</commitByProject>
           </configuration>
         </plugin>
         <plugin>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>1.5</version>
+          <version>3.0.0</version>
         </plugin>
         <plugin>
           <artifactId>maven-plugin-plugin</artifactId>
@@ -323,12 +419,12 @@
         </plugin>
         <plugin>
           <artifactId>maven-invoker-plugin</artifactId>
-          <version>1.10</version>
+          <version>3.0.1</version>
         </plugin>
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.7.7.201606060606</version>
+          <version>0.7.9</version>
           <configuration>
             <includes>
               <include>**/failsafe/*</include>
@@ -338,6 +434,11 @@
             </includes>
           </configuration>
         </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>3.4</version>
+        </plugin>
       </plugins>
     </pluginManagement>
     <plugins>
@@ -383,6 +484,7 @@
         <plugin>
           <groupId>org.apache.rat</groupId>
           <artifactId>apache-rat-plugin</artifactId>
+          <version>0.12</version>
           <executions>
             <execution>
               <id>rat-check</id>
@@ -391,6 +493,7 @@
               </goals>
               <configuration>
                 <excludes combine.children="append">
+                  <exclude>Jenkinsfile</exclude>
                   <exclude>README.md</exclude>
                   <exclude>.gitignore</exclude>
                   <exclude>.git/**/*</exclude>
@@ -553,6 +656,12 @@
               </execution>
             </executions>
           </plugin>
+          <plugin>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <argLine>-Xms128m -Xmx144m ${jacoco.agent}</argLine>
+            </configuration>
+          </plugin>
         </plugins>
       </build>
     </profile>
diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml
index a35f98366..00ef75827 100644
--- a/surefire-api/pom.xml
+++ b/surefire-api/pom.xml
@@ -23,7 +23,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>surefire-api</artifactId>
@@ -40,6 +40,11 @@
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-shared-utils</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -47,6 +52,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <jvm>${jdk.home}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
@@ -74,7 +80,6 @@
               <artifactSet>
                 <includes>
                   <include>org.apache.maven.shared:maven-shared-utils</include>
-                  <include>commons-lang:commons-lang</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -82,10 +87,6 @@
                   <pattern>org.apache.maven.shared</pattern>
                   <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern>
                 </relocation>
-                <relocation>
-                  <pattern>org.apache.commons.lang</pattern>
-                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern>
-                </relocation>
               </relocations>
             </configuration>
           </execution>
diff --git a/surefire-api/src/main/appended-resources/META-INF/NOTICE b/surefire-api/src/main/appended-resources/META-INF/NOTICE
deleted file mode 100644
index 9d9184d27..000000000
--- a/surefire-api/src/main/appended-resources/META-INF/NOTICE
+++ /dev/null
@@ -1,3 +0,0 @@
-
-This product includes software developed by the Spring Framework
-Project (http://www.springframework.org).
diff --git a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
index d47e8039b..dc871a569 100644
--- a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
+++ b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
@@ -150,6 +150,7 @@ public void add( RunEntryStatistics item )
     static final class RunCountComparator
         implements Comparator<RunEntryStatistics>
     {
+        @Override
         public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
         {
             int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
@@ -238,6 +239,7 @@ private Map getPriorities( Comparator<Priority> priorityComparator )
     static final class PrioritizedTestComparator
         implements Comparator<PrioritizedTest>
     {
+        @Override
         public int compare( PrioritizedTest o, PrioritizedTest o1 )
         {
             return o.getPriority() - o1.getPriority();
@@ -247,6 +249,7 @@ public int compare( PrioritizedTest o, PrioritizedTest o1 )
     static final class TestRuntimeComparator
         implements Comparator<Priority>
     {
+        @Override
         public int compare( Priority o, Priority o1 )
         {
             return o1.getTotalRuntime() - o.getTotalRuntime();
@@ -256,6 +259,7 @@ public int compare( Priority o, Priority o1 )
     static final class LeastFailureComparator
         implements Comparator<Priority>
     {
+        @Override
         public int compare( Priority o, Priority o1 )
         {
             return o.getMinSuccessRate() - o1.getMinSuccessRate();
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/NonAbstractClassFilter.java b/surefire-api/src/main/java/org/apache/maven/surefire/NonAbstractClassFilter.java
index cc3897707..da3badd58 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/NonAbstractClassFilter.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/NonAbstractClassFilter.java
@@ -28,6 +28,7 @@
 public class NonAbstractClassFilter
     implements ScannerFilter
 {
+    @Override
     public boolean accept( Class testClass )
     {
         return !Modifier.isAbstract( testClass.getModifiers() );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/SpecificTestClassFilter.java b/surefire-api/src/main/java/org/apache/maven/surefire/SpecificTestClassFilter.java
index ca98b8c63..cfe7258e2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/SpecificTestClassFilter.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/SpecificTestClassFilter.java
@@ -48,6 +48,7 @@ public SpecificTestClassFilter( String[] classNames )
         }
     }
 
+    @Override
     public boolean accept( Class testClass )
     {
         // If the tests enumeration is empty, allow anything.
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index 2a713ef0a..2b329ee5d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -85,6 +85,7 @@ public BaseProviderFactory( ReporterFactory reporterFactory, boolean insideFork
         this.insideFork = insideFork;
     }
 
+    @Override
     @Deprecated
     public DirectoryScanner getDirectoryScanner()
     {
@@ -95,6 +96,7 @@ public DirectoryScanner getDirectoryScanner()
                                             directoryScannerParameters.getSpecificTests() );
     }
 
+    @Override
     public ScanResult getScanResult()
     {
         return DefaultScanResult.from( providerProperties );
@@ -106,32 +108,38 @@ private int getThreadCount()
         return threadcount == null ? 1 : Math.max( Integer.parseInt( threadcount ), 1 );
     }
 
+    @Override
     public RunOrderCalculator getRunOrderCalculator()
     {
         return directoryScannerParameters == null
                 ? null : new DefaultRunOrderCalculator( runOrderParameters, getThreadCount() );
     }
 
+    @Override
     public ReporterFactory getReporterFactory()
     {
         return reporterFactory;
     }
 
+    @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScannerParameters )
     {
         this.directoryScannerParameters = directoryScannerParameters;
     }
 
+    @Override
     public void setReporterConfiguration( ReporterConfiguration reporterConfiguration )
     {
         this.reporterConfiguration = reporterConfiguration;
     }
 
+    @Override
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
     }
 
+    @Override
     public ConsoleStream getConsoleLogger()
     {
         boolean trim = reporterConfiguration.isTrimStackTrace();
@@ -139,91 +147,109 @@ public ConsoleStream getConsoleLogger()
         return insideFork ? new ForkingRunListener( out, ROOT_CHANNEL, trim ) : new DefaultDirectConsoleReporter( out );
     }
 
+    @Override
     public void setTestRequest( TestRequest testRequest )
     {
         this.testRequest = testRequest;
     }
 
+    @Override
     public DirectoryScannerParameters getDirectoryScannerParameters()
     {
         return directoryScannerParameters;
     }
 
+    @Override
     public ReporterConfiguration getReporterConfiguration()
     {
         return reporterConfiguration;
     }
 
+    @Override
     public TestRequest getTestRequest()
     {
         return testRequest;
     }
 
+    @Override
     public ClassLoader getTestClassLoader()
     {
         return testClassLoader;
     }
 
+    @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
         this.providerProperties = providerProperties;
     }
 
+    @Override
     public Map<String, String> getProviderProperties()
     {
         return providerProperties;
     }
 
+    @Override
     public TestArtifactInfo getTestArtifactInfo()
     {
         return testArtifactInfo;
     }
 
+    @Override
     public void setTestArtifactInfo( TestArtifactInfo testArtifactInfo )
     {
         this.testArtifactInfo = testArtifactInfo;
     }
 
+    @Override
     public void setRunOrderParameters( RunOrderParameters runOrderParameters )
     {
         this.runOrderParameters = runOrderParameters;
     }
 
+    @Override
     public List<CommandLineOption> getMainCliOptions()
     {
         return mainCliOptions;
     }
 
+    @Override
     public void setMainCliOptions( List<CommandLineOption> mainCliOptions )
     {
         this.mainCliOptions = mainCliOptions == null ? Collections.<CommandLineOption>emptyList() : mainCliOptions;
     }
 
+    @Override
     public int getSkipAfterFailureCount()
     {
         return skipAfterFailureCount;
     }
 
+    @Override
     public void setSkipAfterFailureCount( int skipAfterFailureCount )
     {
         this.skipAfterFailureCount = skipAfterFailureCount;
     }
 
+    @Override
     public boolean isInsideFork()
     {
         return insideFork;
     }
 
+    @Override
     public Shutdown getShutdown()
     {
         return shutdown;
     }
 
+    @Override
     public void setShutdown( Shutdown shutdown )
     {
         this.shutdown = shutdown;
     }
 
+    @Override
     public Integer getSystemExitTimeout()
     {
         return systemExitTimeout;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
index 49ae52d92..502499292 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
@@ -36,6 +36,7 @@
     public static final Command TEST_SET_FINISHED = new Command( MasterProcessCommand.TEST_SET_FINISHED );
     public static final Command SKIP_SINCE_NEXT_TEST = new Command( MasterProcessCommand.SKIP_SINCE_NEXT_TEST );
     public static final Command NOOP = new Command( MasterProcessCommand.NOOP );
+    public static final Command BYE_ACK = new Command( MasterProcessCommand.BYE_ACK );
 
     private final MasterProcessCommand command;
     private final String data;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index bdd609211..c3d80eaf2 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -42,6 +42,7 @@
 import static java.lang.StrictMath.max;
 import static org.apache.maven.surefire.booter.Command.toShutdown;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
 import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN;
@@ -123,6 +124,7 @@ public boolean awaitStarted()
             }
             catch ( InterruptedException e )
             {
+                DumpErrorSingleton.getSingleton().dumpException( e );
                 throw new TestSetFailedException( e.getLocalizedMessage() );
             }
         }
@@ -133,7 +135,7 @@ public boolean awaitStarted()
     }
 
     /**
-     * @param listener listener called with <em>Any</em> {@link MasterProcessCommand command type}
+     * @param listener listener called with <b>Any</b> {@link MasterProcessCommand command type}
      */
     public void addListener( CommandListener listener )
     {
@@ -165,6 +167,11 @@ public void addNoopListener( CommandListener listener )
         addListener( NOOP, listener );
     }
 
+    public void addByeAckListener( CommandListener listener )
+    {
+        addListener( BYE_ACK, listener );
+    }
+
     private void addListener( MasterProcessCommand cmd, CommandListener listener )
     {
         listeners.add( new BiProperty<MasterProcessCommand, CommandListener>( cmd, listener ) );
@@ -218,7 +225,7 @@ private boolean isStopped()
     }
 
     /**
-     * @return <tt>true</tt> if {@link #LAST_TEST_SYMBOL} found at the last index in {@link #testClasses}.
+     * @return {@code true} if {@link #LAST_TEST_SYMBOL} found at the last index in {@link #testClasses}.
      */
     private boolean isQueueFull()
     {
@@ -254,6 +261,7 @@ private boolean insertToQueue( String test )
             this.originalOutStream = originalOutStream;
         }
 
+        @Override
         public Iterator<String> iterator()
         {
             return new ClassesIterator( originalOutStream );
@@ -274,12 +282,14 @@ private ClassesIterator( PrintStream originalOutStream )
             this.originalOutStream = originalOutStream;
         }
 
+        @Override
         public boolean hasNext()
         {
             popUnread();
             return isNotBlank( clazz );
         }
 
+        @Override
         public String next()
         {
             popUnread();
@@ -300,6 +310,7 @@ public String next()
             }
         }
 
+        @Override
         public void remove()
         {
             throw new UnsupportedOperationException();
@@ -335,7 +346,11 @@ private void popUnread()
         private void requestNextTest()
         {
             byte[] encoded = encodeStringForForkCommunication( ( (char) BOOTERCODE_NEXT_TEST ) + ",0,want more!\n" );
-            originalOutStream.write( encoded, 0, encoded.length );
+            synchronized ( originalOutStream )
+            {
+                originalOutStream.write( encoded, 0, encoded.length );
+                originalOutStream.flush();
+            }
         }
 
         private boolean shouldFinish()
@@ -363,6 +378,7 @@ private void wakeupIterator()
     private final class CommandRunnable
         implements Runnable
     {
+        @Override
         public void run()
         {
             CommandReader.this.startMonitor.countDown();
@@ -375,7 +391,9 @@ public void run()
                     Command command = decode( stdIn );
                     if ( command == null )
                     {
-                        logger.error( "[SUREFIRE] std/in stream corrupted: first sequence not recognized" );
+                        String errorMessage = "[SUREFIRE] std/in stream corrupted: first sequence not recognized";
+                        DumpErrorSingleton.getSingleton().dumpStreamText( errorMessage );
+                        logger.error( errorMessage );
                         break;
                     }
                     else
@@ -414,8 +432,13 @@ public void run()
                 CommandReader.this.state.set( TERMINATED );
                 if ( !isTestSetFinished )
                 {
+                    String msg = "TestSet has not finished before stream error has appeared >> "
+                                         + "initializing exit by non-null configuration: "
+                                         + CommandReader.this.shutdown;
+                    DumpErrorSingleton.getSingleton().dumpStreamException( e, msg );
+
                     exitByConfiguration();
-                    // does not go to finally
+                    // does not go to finally for non-default config: Shutdown.EXIT or Shutdown.KILL
                 }
             }
             catch ( IOException e )
@@ -424,7 +447,9 @@ public void run()
                 // If #stop() method is called, reader thread is interrupted and cause is InterruptedException.
                 if ( !( e.getCause() instanceof InterruptedException ) )
                 {
-                    logger.error( "[SUREFIRE] std/in stream corrupted", e );
+                    String msg = "[SUREFIRE] std/in stream corrupted";
+                    DumpErrorSingleton.getSingleton().dumpStreamException( e, msg );
+                    logger.error( msg, e );
                 }
             }
             finally
@@ -460,17 +485,15 @@ private void exitByConfiguration()
                 CommandReader.this.makeQueueFull();
                 CommandReader.this.wakeupIterator();
                 insertToListeners( toShutdown( shutdown ) );
-                switch ( shutdown )
+                if ( shutdown.isExit() )
                 {
-                    case EXIT:
-                        System.exit( 1 );
-                    case KILL:
-                        Runtime.getRuntime().halt( 1 );
-                    case DEFAULT:
-                    default:
-                        // should not happen; otherwise you missed enum case
-                        break;
+                    System.exit( 1 );
+                }
+                else if ( shutdown.isKill() )
+                {
+                    Runtime.getRuntime().halt( 1 );
                 }
+                // else is default: other than Shutdown.DEFAULT should not happen; otherwise you missed enum case
             }
         }
     }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
index 54ff6caf7..cefeb33cc 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/DirectoryScannerParametersAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface DirectoryScannerParametersAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/DumpErrorSingleton.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/DumpErrorSingleton.java
new file mode 100644
index 000000000..e287f05f8
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/DumpErrorSingleton.java
@@ -0,0 +1,99 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.ReporterConfiguration;
+import org.apache.maven.surefire.util.internal.DumpFileUtils;
+
+import java.io.File;
+
+import static org.apache.maven.surefire.util.internal.DumpFileUtils.newDumpFile;
+
+/**
+ * Dumps lost commands and caused exceptions in forked JVM. <br>
+ * Fail-safe.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public final class DumpErrorSingleton
+{
+    public static final String DUMP_FILE_EXT = ".dump";
+    public static final String DUMPSTREAM_FILE_EXT = ".dumpstream";
+    private static final DumpErrorSingleton SINGLETON = new DumpErrorSingleton();
+
+    private File dumpFile;
+    private File dumpStreamFile;
+
+    private DumpErrorSingleton()
+    {
+    }
+
+    public static DumpErrorSingleton getSingleton()
+    {
+        return SINGLETON;
+    }
+
+    public synchronized void init( String dumpFileName, ReporterConfiguration configuration )
+    {
+        dumpFile = createDumpFile( dumpFileName, configuration );
+        dumpStreamFile = createDumpStreamFile( dumpFileName, configuration );
+    }
+
+    public synchronized void dumpException( Throwable t, String msg )
+    {
+        DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpFile );
+    }
+
+    public synchronized void dumpException( Throwable t )
+    {
+        DumpFileUtils.dumpException( t, dumpFile );
+    }
+
+    public synchronized void dumpText( String msg )
+    {
+        DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpFile );
+    }
+
+    public synchronized void dumpStreamException( Throwable t, String msg )
+    {
+        DumpFileUtils.dumpException( t, msg == null ? "null" : msg, dumpStreamFile );
+    }
+
+    public synchronized void dumpStreamException( Throwable t )
+    {
+        DumpFileUtils.dumpException( t, dumpStreamFile );
+    }
+
+    public synchronized void dumpStreamText( String msg )
+    {
+        DumpFileUtils.dumpText( msg == null ? "null" : msg, dumpStreamFile );
+    }
+
+    private File createDumpFile( String dumpFileName, ReporterConfiguration configuration )
+    {
+        return newDumpFile( dumpFileName + DUMP_FILE_EXT, configuration );
+    }
+
+    private File createDumpStreamFile( String dumpFileName, ReporterConfiguration configuration )
+    {
+        return newDumpFile( dumpFileName + DUMPSTREAM_FILE_EXT, configuration );
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
index 06e47bad9..994b60d80 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java
@@ -20,7 +20,7 @@
  */
 
 /**
- * See the plugin configuration parameter <em>skipAfterFailureCount</em>.
+ * See the plugin configuration parameter {@code skipAfterFailureCount}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index def345d24..7459ad984 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -31,9 +31,7 @@
  * This factory is only used inside forks.
  *
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
-
 public class ForkingReporterFactory
     implements ReporterFactory
 {
@@ -49,11 +47,13 @@ public ForkingReporterFactory( boolean trimstackTrace, PrintStream originalSyste
         this.originalSystemOut = originalSystemOut;
     }
 
+    @Override
     public RunListener createReporter()
     {
         return new ForkingRunListener( originalSystemOut, testSetChannelId.getAndIncrement(), isTrimstackTrace );
     }
 
+    @Override
     public RunResult close()
     {
         return new RunResult( 17, 17, 17, 17 );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 282c4d43c..7eb7fa85a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -19,10 +19,6 @@
  * under the License.
  */
 
-import java.io.PrintStream;
-import java.util.Enumeration;
-import java.util.Properties;
-
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
@@ -32,23 +28,29 @@
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+
+import java.io.PrintStream;
+import java.util.Map.Entry;
 
 import static java.lang.Integer.toHexString;
 import static java.nio.charset.Charset.defaultCharset;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.useNonNull;
 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
 import static org.apache.maven.surefire.util.internal.StringUtils.escapeBytesToPrintable;
 import static org.apache.maven.surefire.util.internal.StringUtils.escapeToPrintable;
 
 /**
  * Encodes the full output of the test run to the stdout stream.
- * <p/>
+ * <br>
  * This class and the ForkClient contain the full definition of the
  * "wire-level" protocol used by the forked process. The protocol
  * is *not* part of any public api and may change without further
  * notice.
- * <p/>
+ * <br>
  * This class is threadsafe.
- * <p/>
+ * <br>
  * The synchronization in the underlying PrintStream (target instance)
  * is used to preserve thread safety of the output stream. To perform
  * multiple writes/prints for a single request, they must
@@ -132,46 +134,55 @@ public ForkingRunListener( PrintStream target, int testSetChannelId, boolean tri
         sendProps();
     }
 
-    public void testSetStarting( ReportEntry report )
+    @Override
+    public void testSetStarting( TestSetReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_STARTING, report, testSetChannelId ) );
     }
 
-    public void testSetCompleted( ReportEntry report )
+    @Override
+    public void testSetCompleted( TestSetReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_COMPLETED, report, testSetChannelId ) );
     }
 
+    @Override
     public void testStarting( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_STARTING, report, testSetChannelId ) );
     }
 
+    @Override
     public void testSucceeded( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SUCCEEDED, report, testSetChannelId ) );
     }
 
+    @Override
     public void testAssumptionFailure( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ASSUMPTIONFAILURE, report, testSetChannelId ) );
     }
 
+    @Override
     public void testError( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ERROR, report, testSetChannelId ) );
     }
 
+    @Override
     public void testFailed( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_FAILED, report, testSetChannelId ) );
     }
 
+    @Override
     public void testSkipped( ReportEntry report )
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) );
     }
 
+    @Override
     public void testExecutionSkippedByUser()
     {
         encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
@@ -179,19 +190,14 @@ public void testExecutionSkippedByUser()
 
     void sendProps()
     {
-        Properties systemProperties = System.getProperties();
-
-        if ( systemProperties != null )
+        for ( Entry<String, String> entry : systemProps().entrySet() )
         {
-            for ( Enumeration<?> propertyKeys = systemProperties.propertyNames(); propertyKeys.hasMoreElements(); )
-            {
-                String key = (String) propertyKeys.nextElement();
-                String value = systemProperties.getProperty( key );
-                encodeAndWriteToTarget( toPropertyString( key, value == null ? "null" : value ) );
-            }
+            String value = entry.getValue();
+            encodeAndWriteToTarget( toPropertyString( entry.getKey(), useNonNull( value, "null" ) ) );
         }
     }
 
+    @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
         byte[] header = stdout ? stdOutHeader : stdErrHeader;
@@ -206,6 +212,14 @@ public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
         {
             target.write( encodeBytes, 0, encodeBytes.length );
+            target.flush();
+            if ( target.checkError() )
+            {
+                // We MUST NOT throw any exception from this method; otherwise we are in loop and CPU goes up:
+                // ForkingRunListener -> Exception -> JUnit Notifier and RunListener -> ForkingRunListener -> Exception
+                DumpErrorSingleton.getSingleton()
+                        .dumpStreamText( "Unexpected IOException with stream: " + new String( buf, off, len ) );
+            }
         }
     }
 
@@ -232,31 +246,37 @@ private void log( byte bootCode, String message )
         }
     }
 
+    @Override
     public void debug( String message )
     {
         log( BOOTERCODE_DEBUG, message );
     }
 
+    @Override
     public void info( String message )
     {
         log( BOOTERCODE_CONSOLE, message );
     }
 
+    @Override
     public void warning( String message )
     {
         log( BOOTERCODE_WARNING, message );
     }
 
+    @Override
     public void error( String message )
     {
         log( BOOTERCODE_ERROR, message );
     }
 
+    @Override
     public void error( String message, Throwable t )
     {
         error( ConsoleLoggerUtils.toString( message, t ) );
     }
 
+    @Override
     public void error( Throwable t )
     {
         error( null, t );
@@ -268,6 +288,13 @@ private void encodeAndWriteToTarget( String string )
         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
         {
             target.write( encodeBytes, 0, encodeBytes.length );
+            target.flush();
+            if ( target.checkError() )
+            {
+                // We MUST NOT throw any exception from this method; otherwise we are in loop and CPU goes up:
+                // ForkingRunListener -> Exception -> JUnit Notifier and RunListener -> ForkingRunListener -> Exception
+                DumpErrorSingleton.getSingleton().dumpStreamText( "Unexpected IOException: " + string );
+            }
         }
     }
 
@@ -342,7 +369,7 @@ private String encode( String source )
 
     private static void nullableEncoding( StringBuilder stringBuilder, String source )
     {
-        if ( source == null || source.length() == 0 )
+        if ( source == null || source.isEmpty() )
         {
             stringBuilder.append( "null" );
         }
@@ -378,12 +405,14 @@ public static void encode( StringBuilder stringBuilder, StackTraceWriter stackTr
         }
     }
 
+    @Override
     public void println( String message )
     {
         byte[] buf = message.getBytes();
         println( buf, 0, buf.length );
     }
 
+    @Override
     public void println( byte[] buf, int off, int len )
     {
         writeTestOutput( buf, off, len, true );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
index a53a04672..5d4721222 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
@@ -19,18 +19,14 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.StringUtils;
-
 import java.io.DataInputStream;
-import java.io.EOFException;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
 
-import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
-import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
-import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
 import static java.lang.String.format;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
+import static org.apache.maven.surefire.util.internal.StringUtils.US_ASCII;
+import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
 
 /**
  * Commands which are sent from plugin to the forked jvm.
@@ -47,9 +43,8 @@
     SHUTDOWN( 3, String.class ),
 
     /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
-    NOOP( 4, Void.class );
-
-    private static final Charset ASCII = Charset.forName( "ASCII" );
+    NOOP( 4, Void.class ),
+    BYE_ACK( 5, Void.class );
 
     private final int id;
 
@@ -89,12 +84,15 @@ public boolean hasDataType()
             throw new IllegalArgumentException( "Data type can be only " + String.class );
         }
 
-        byte[] dataBytes = fromDataType( data );
-        byte[] encoded = new byte[8 + dataBytes.length];
-        int command = getId();
-        int len = dataBytes.length;
+        final byte[] dataBytes = fromDataType( data );
+        final int len = dataBytes.length;
+
+        final byte[] encoded = new byte[8 + len];
+
+        final int command = getId();
         setCommandAndDataLength( command, len, encoded );
-        System.arraycopy( dataBytes, 0, encoded, 8, dataBytes.length );
+        System.arraycopy( dataBytes, 0, encoded, 8, len );
+
         return encoded;
     }
 
@@ -124,33 +122,15 @@ public static Command decode( DataInputStream is )
             int dataLength = is.readInt();
             if ( dataLength > 0 )
             {
-                byte[] buffer = new byte[dataLength];
-                int read = 0;
-                int total = 0;
-                do
-                {
-                    total += read;
-                    read = is.read( buffer, total, dataLength - total );
-                } while ( read > 0 );
+                byte[] buffer = new byte[ dataLength ];
+                is.readFully( buffer );
 
                 if ( command.getDataType() == Void.class )
                 {
-                    // must read entire sequence to get to the next command; cannot be above the loop
                     throw new IOException( format( "Command %s unexpectedly read Void data with length %d.",
                                                    command, dataLength ) );
                 }
 
-                if ( total != dataLength )
-                {
-                    if ( read == -1 )
-                    {
-                        throw new EOFException( "stream closed" );
-                    }
-
-                    throw new IOException( format( "%s read %d out of %d bytes",
-                                                    MasterProcessCommand.class, total, dataLength ) );
-                }
-
                 String data = command.toDataTypeAsString( buffer );
                 return new Command( command, data );
             }
@@ -163,21 +143,14 @@ public static Command decode( DataInputStream is )
 
     String toDataTypeAsString( byte... data )
     {
-        try
-        {
-            switch ( this )
-            {
-                case RUN_CLASS:
-                    return new String( data, FORK_STREAM_CHARSET_NAME );
-                case SHUTDOWN:
-                    return StringUtils.decode( data, ASCII );
-                default:
-                    return null;
-            }
-        }
-        catch ( UnsupportedEncodingException e )
+        switch ( this )
         {
-            throw new IllegalStateException( e );
+            case RUN_CLASS:
+                return new String( data, ISO_8859_1 );
+            case SHUTDOWN:
+                return new String( data, US_ASCII );
+            default:
+                return null;
         }
     }
 
@@ -188,7 +161,7 @@ String toDataTypeAsString( byte... data )
             case RUN_CLASS:
                 return encodeStringForForkCommunication( data );
             case SHUTDOWN:
-                return StringUtils.encode( data, ASCII );
+                return data.getBytes( US_ASCII );
             default:
                 return new byte[0];
         }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
index 373e925c5..caedb988a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderPropertiesAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface ProviderPropertiesAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
index 0359636eb..8c65be3f9 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ReporterConfigurationAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface ReporterConfigurationAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
index 370c6a6a1..3bee07d69 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/RunOrderParametersAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface RunOrderParametersAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java
index 262cb2972..77a09cfcf 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java
@@ -44,6 +44,21 @@ public String getParam()
         return param;
     }
 
+    public boolean isKill()
+    {
+        return this == KILL;
+    }
+
+    public boolean isExit()
+    {
+        return this == EXIT;
+    }
+
+    public boolean isDefaultShutdown()
+    {
+        return this == DEFAULT;
+    }
+
     public static boolean isKnown( String param )
     {
         for ( Shutdown shutdown : values() )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
index a843b27e6..0bfcdb8f3 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java
@@ -20,7 +20,7 @@
  */
 
 /**
- * See the plugin configuration parameter <em>shutdown</em>.
+ * See the plugin configuration parameter {@code shutdown}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
index 351bb51ae..c2f5d99c5 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireClassLoadersAware.java
@@ -21,7 +21,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface SurefireClassLoadersAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
index 528589c85..097a3fa7a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java
@@ -58,7 +58,7 @@
 
 /**
  * Does reflection based invocation of the surefire methods.
- * <p/>
+ * <br>
  * This is to avoid complications with linkage issues
  *
  * @author Kristian Rosenvold
@@ -152,9 +152,6 @@ public Object convertIfRunResult( Object result )
 
     }
 
-    /**
-     * @noinspection UnusedDeclaration
-     */
     class ClassLoaderProxy
         implements InvocationHandler
     {
@@ -162,13 +159,13 @@ public Object convertIfRunResult( Object result )
 
         /**
          * @param delegate a target
-         * @noinspection UnusedDeclaration
          */
         public ClassLoaderProxy( Object delegate )
         {
             this.target = delegate;
         }
 
+        @Override
         public Object invoke( Object proxy, Method method, Object[] args )
             throws Throwable
         {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
index 2b7065e3f..98980615e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestArtifactInfoAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface TestArtifactInfoAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
index d2d6d298a..3e98b92c3 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TestRequestAware.java
@@ -23,7 +23,6 @@
 
 /**
  * @author Kristian Rosenvold
- * @noinspection UnusedDeclaration
  */
 interface TestRequestAware
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/cli/CommandLineOption.java b/surefire-api/src/main/java/org/apache/maven/surefire/cli/CommandLineOption.java
index 2f82814f2..a806370d5 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/cli/CommandLineOption.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/cli/CommandLineOption.java
@@ -28,7 +28,7 @@
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
- * @see http://books.sonatype.com/mvnref-book/reference/running-sect-options.html
+ * @see <a href="http://books.sonatype.com/mvnref-book/reference/running-sect-options.html">command line options</a>
  */
 public enum CommandLineOption
 {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/AbstractProvider.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/AbstractProvider.java
index 19d1da3c3..eca390936 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/AbstractProvider.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/AbstractProvider.java
@@ -29,6 +29,7 @@
 {
     private final Thread creatingThread = Thread.currentThread();
 
+    @Override
     public void cancel()
     {
         synchronized ( creatingThread )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
index 9ef7a94bd..b487f0658 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
@@ -36,10 +36,10 @@
 
 /**
  * Injected into the providers upon provider construction. Allows the provider to request services and data it needs.
- * <p/>
+ * <br>
  * NOTE: This class is part of the proposed public api for surefire providers from 2.7 and up. It may
  * still be subject to changes, even for minor revisions.
- * <p/>
+ * <br>
  * The api covers this interface and all the types reachable from it. And nothing else.
  *
  * @author Kristian Rosenvold
@@ -80,7 +80,7 @@
 
     /**
      * Gets a logger intended for console output.
-     * <p/>
+     * <br>
      * This output is intended for provider-oriented messages that are not attached to a single test-set
      * and will normally be written to something console-like immediately.
      *
@@ -136,7 +136,7 @@
     List<CommandLineOption> getMainCliOptions();
 
     /**
-     * Defaults to 0. Configured with parameter <em>skipAfterFailureCount</em> in POM.
+     * @return Defaults to 0. Configured with parameter {@code skipAfterFailureCount} in POM.
      */
     int getSkipAfterFailureCount();
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/SurefireProvider.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/SurefireProvider.java
index 9b27c1c8e..9eccf6b31 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/SurefireProvider.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/SurefireProvider.java
@@ -26,13 +26,13 @@
 
 /**
  * Interface to be implemented by all Surefire providers.
- * <p/>
+ * <br>
  * NOTE: This class is part of the proposed public api for surefire providers for 2.7. It may
  * still be subject to changes, even for minor revisions.
- * <p/>
+ * <br>
  * The api covers this interface and all the types reachable from it. And nothing else.
- * <p/>
- * <p/>
+ * <br>
+ * <br>
  * Called in one of three ways:
  * Forkmode = never: getSuites is not called, invoke is called with null parameter
  * Forkmode = once: getSuites is not called, invoke is called with null parameter
@@ -45,7 +45,7 @@
 {
     /**
      * Determines the number of forks.
-     * <p/>
+     * <br>
      * Called when forkmode is different from "never" or "always", allows the provider to define
      * how to behave for the fork.
      *
@@ -64,6 +64,7 @@
      *          When reporting fails
      * @throws org.apache.maven.surefire.testset.TestSetFailedException
      *          When testset fails
+     * @throws InvocationTargetException fails in {@code ProviderFactory}
      */
     @SuppressWarnings( "checkstyle:redundantthrows" )
     RunResult invoke( Object forkTestSet )
@@ -72,14 +73,14 @@ RunResult invoke( Object forkTestSet )
     /**
      * Makes an attempt at cancelling the current run, giving the provider a chance to notify
      * reporting that the remaining tests have been cancelled due to timeout.
-     * <p/>
+     * <br>
      * If the provider thinks it can terminate properly it is the responsibility of
      * the invoke method to return a RunResult with a booter code of failure.
-     * <p/>
+     * <br>
      * It is up to the provider to find out how to implement this method properly.
      * A provider may also choose to not do anything at all in this method,
      * which means surefire will kill the forked process soon afterwards anyway.
-     * <p/>
+     * <br>
      * Will be called on a different thread than the one calling invoke.
      */
     // Todo: Need to think a lot closer about how/if this works and if there is a use case for it.
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/CategorizedReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/CategorizedReportEntry.java
index 77cfaf363..47b0c48b0 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/CategorizedReportEntry.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/CategorizedReportEntry.java
@@ -19,6 +19,9 @@
  * under the License.
  */
 
+import java.util.Collections;
+import java.util.Map;
+
 /**
  * @author Kristian Rosenvold
  */
@@ -47,18 +50,26 @@ public CategorizedReportEntry( String source, String name, String group, StackTr
     public CategorizedReportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
                                    Integer elapsed, String message )
     {
-        super( source, name, stackTraceWriter, elapsed, message );
+        this( source, name, group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+    }
+
+    public CategorizedReportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
+                                   Integer elapsed, String message, Map<String, String> systemProperties )
+    {
+        super( source, name, stackTraceWriter, elapsed, message, systemProperties );
         this.group = group;
     }
 
-    public static ReportEntry reportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
-                                           Integer elapsed, String message )
+    public static TestSetReportEntry reportEntry( String source, String name, String group,
+                                                  StackTraceWriter stackTraceWriter, Integer elapsed, String message,
+                                                  Map<String, String> systemProperties )
     {
         return group != null
-            ? new CategorizedReportEntry( source, name, group, stackTraceWriter, elapsed, message )
-            : new SimpleReportEntry( source, name, stackTraceWriter, elapsed, message );
+            ? new CategorizedReportEntry( source, name, group, stackTraceWriter, elapsed, message, systemProperties )
+            : new SimpleReportEntry( source, name, stackTraceWriter, elapsed, message, systemProperties );
     }
 
+    @Override
     public String getGroup()
     {
         return group;
@@ -67,19 +78,10 @@ public String getGroup()
     @Override
     public String getNameWithGroup()
     {
-        StringBuilder result = new StringBuilder();
-        result.append( getName() );
-
-        if ( getGroup() != null && !getName().equals( getGroup() ) )
-        {
-            result.append( GROUP_PREFIX );
-            result.append( getGroup() );
-            result.append( GROUP_SUFIX );
-        }
-
-        return result.toString();
+        return isNameWithGroup() ? getName() + GROUP_PREFIX + getGroup() + GROUP_SUFIX : getName();
     }
 
+    @Override
     public boolean equals( Object o )
     {
         if ( this == o )
@@ -101,10 +103,16 @@ public boolean equals( Object o )
 
     }
 
+    @Override
     public int hashCode()
     {
         int result = super.hashCode();
         result = 31 * result + ( group != null ? group.hashCode() : 0 );
         return result;
     }
+
+    private boolean isNameWithGroup()
+    {
+        return getGroup() != null && !getGroup().equals( getName() );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/ConsoleOutputCapture.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/ConsoleOutputCapture.java
index b5835936d..d4af67967 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/ConsoleOutputCapture.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/ConsoleOutputCapture.java
@@ -29,7 +29,7 @@
 
 /**
  * Deals with system.out/err.
- * <p/>
+ * <br>
  */
 public class ConsoleOutputCapture
 {
@@ -52,6 +52,7 @@ public static void startCapture( ConsoleOutputReceiver target )
             this.target = target;
         }
 
+        @Override
         public void write( byte[] buf, int off, int len )
         {
             // Note: At this point the supplied "buf" instance is reused, which means
@@ -59,12 +60,14 @@ public void write( byte[] buf, int off, int len )
             target.writeTestOutput( buf, off, len, isStdout );
         }
 
+        @Override
         public void write( byte[] b )
             throws IOException
         {
             target.writeTestOutput( b, 0, b.length, isStdout );
         }
 
+        @Override
         public void write( int b )
         {
             byte[] buf = new byte[1];
@@ -79,6 +82,7 @@ public void write( int b )
             }
         }
 
+        @Override
         public void println( String s )
         {
             if ( s == null )
@@ -89,10 +93,12 @@ public void println( String s )
             target.writeTestOutput( bytes, 0, bytes.length, isStdout );
         }
 
+        @Override
         public void close()
         {
         }
 
+        @Override
         public void flush()
         {
         }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/DefaultDirectConsoleReporter.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/DefaultDirectConsoleReporter.java
index f1e0f48c8..5298ad95b 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/DefaultDirectConsoleReporter.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/DefaultDirectConsoleReporter.java
@@ -34,11 +34,13 @@ public DefaultDirectConsoleReporter( PrintStream systemOut )
         this.systemOut = systemOut;
     }
 
+    @Override
     public void println( String message )
     {
         systemOut.println( message );
     }
 
+    @Override
     public void println( byte[] buf, int off, int len )
     {
         println( new String( buf, off, len ) );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriter.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriter.java
index 6ff45dee8..dee095c62 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriter.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriter.java
@@ -29,7 +29,6 @@
  * Write the trace out for a POJO test. Java 1.5 compatible.
  *
  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
- * @noinspection ThrowableResultOfMethodCallIgnored
  */
 public class LegacyPojoStackTraceWriter
     implements StackTraceWriter
@@ -49,6 +48,7 @@ public LegacyPojoStackTraceWriter( String testClass, String testMethod, Throwabl
         this.t = t;
     }
 
+    @Override
     public String writeTraceToString()
     {
         if ( t != null )
@@ -69,7 +69,7 @@ public String writeTraceToString()
             {
                 // SUREFIRE-986
                 String exc = t.getClass().getName() + ": ";
-                if ( builder.toString().startsWith( exc ) )
+                if ( StringUtils.startsWith( builder, exc ) )
                 {
                     builder.insert( exc.length(), '\n' );
                 }
@@ -79,6 +79,7 @@ public String writeTraceToString()
         return "";
     }
 
+    @Override
     public String smartTrimmedStackTrace()
     {
         StringBuilder result = new StringBuilder();
@@ -147,6 +148,7 @@ private static String getTruncatedMessage( String msg, int i )
     }
 
 
+    @Override
     public String writeTrimmedTraceToString()
     {
         String text = writeTraceToString();
@@ -189,6 +191,7 @@ else if ( line.startsWith( "Caused by" ) )
         return trace.toString();
     }
 
+    @Override
     public SafeThrowable getThrowable()
     {
         return new SafeThrowable( t );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/ReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/ReportEntry.java
index 2bc3fcd93..ec0f782e0 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/ReportEntry.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/ReportEntry.java
@@ -55,8 +55,9 @@
 
     /**
      * Gets the runtime for the item. Optional parameter. If the value is not set, it will be determined within
-     * the reporting subsustem. Some providers like to calculate this value themselves, and it gets the
+     * the reporting subsystem. Some providers like to calculate this value themselves, and it gets the
      * most accurate value.
+     * @return duration of a test in milli seconds
      */
     Integer getElapsed();
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/ReporterConfiguration.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/ReporterConfiguration.java
index 52384731c..d2bfb13d9 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/ReporterConfiguration.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/ReporterConfiguration.java
@@ -24,7 +24,7 @@
 
 /**
  * Bits and pieces of reporting configuration that seem to be necessary on the provider side.
- * <p/>
+ * <br>
  * Todo: Consider moving these fields elsewhere, this concept does not smell too good
  *
  * @author Kristian Rosenvold
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
index b9644306c..32c0abd1f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java
@@ -22,7 +22,7 @@
 /**
  * Used by providers to report results.
  * Using this interface integrates the providers together into a common reporting infrastructure.
- * <p/>
+ * <br>
  * An instance of a reporter is not guaranteed to be thread-safe and concurrent test frameworks
  * must request an instance of a reporter per-thread from the ReporterFactory.
  */
@@ -34,7 +34,7 @@
      * @param report the report entry describing the testset
      * @throws ReporterException When reporting fails
      */
-    void testSetStarting( ReportEntry report );
+    void testSetStarting( TestSetReportEntry report );
 
     /**
      * Indicates end of a given test-set
@@ -42,7 +42,7 @@
      * @param report the report entry describing the testset
      * @throws ReporterException When reporting fails
      */
-    void testSetCompleted( ReportEntry report );
+    void testSetCompleted( TestSetReportEntry report );
 
     /**
      * Event fired when a test is about to start
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/SafeThrowable.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/SafeThrowable.java
index 60c78975a..b3b86f276 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/SafeThrowable.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/SafeThrowable.java
@@ -31,6 +31,11 @@ public SafeThrowable( Throwable target )
         this.target = target;
     }
 
+    public SafeThrowable( String message )
+    {
+        this( new Throwable( message ) );
+    }
+
     public String getLocalizedMessage()
     {
         try
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
index 4715b248a..1013f397d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java
@@ -19,12 +19,19 @@
  * under the License.
  */
 
+import org.apache.maven.surefire.util.internal.ImmutableMap;
+
+import java.util.Collections;
+import java.util.Map;
+
 /**
  * @author Kristian Rosenvold
  */
 public class SimpleReportEntry
-    implements ReportEntry
+    implements TestSetReportEntry
 {
+    private final Map<String, String> systemProperties;
+
     private final String source;
 
     private final String name;
@@ -45,6 +52,11 @@ public SimpleReportEntry( String source, String name )
         this( source, name, null, null );
     }
 
+    public SimpleReportEntry( String source, String name, Map<String, String> systemProperties )
+    {
+        this( source, name, null, null, systemProperties );
+    }
+
     private SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter )
     {
         this( source, name, stackTraceWriter, null );
@@ -57,11 +69,11 @@ public SimpleReportEntry( String source, String name, Integer elapsed )
 
     public SimpleReportEntry( String source, String name, String message )
     {
-        this( source, name, null, null, message );
+        this( source, name, null, null, message, Collections.<String, String>emptyMap() );
     }
 
     protected SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed,
-                                 String message )
+                                 String message, Map<String, String> systemProperties )
     {
         if ( source == null )
         {
@@ -81,13 +93,19 @@ protected SimpleReportEntry( String source, String name, StackTraceWriter stackT
         this.message = message;
 
         this.elapsed = elapsed;
-    }
 
+        this.systemProperties = new ImmutableMap<String, String>( systemProperties );
+    }
 
     public SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed )
     {
-        //noinspection ThrowableResultOfMethodCallIgnored
-        this( source, name, stackTraceWriter, elapsed, safeGetMessage( stackTraceWriter ) );
+        this( source, name, stackTraceWriter, elapsed, Collections.<String, String>emptyMap() );
+    }
+
+    public SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed,
+                              Map<String, String> systemProperties )
+    {
+        this( source, name, stackTraceWriter, elapsed, safeGetMessage( stackTraceWriter ), systemProperties );
     }
 
     public static SimpleReportEntry assumption( String source, String name, String message )
@@ -118,45 +136,50 @@ private static String safeGetMessage( StackTraceWriter stackTraceWriter )
         }
     }
 
+    @Override
     public String getSourceName()
     {
         return source;
     }
 
+    @Override
     public String getName()
     {
         return name;
     }
 
+    @Override
     public String getGroup()
     {
         return null;
     }
 
+    @Override
     public StackTraceWriter getStackTraceWriter()
     {
         return stackTraceWriter;
     }
 
+    @Override
     public Integer getElapsed()
     {
         return elapsed;
     }
 
+    @Override
     public String toString()
     {
         return "ReportEntry{" + "source='" + source + '\'' + ", name='" + name + '\'' + ", stackTraceWriter="
             + stackTraceWriter + ", elapsed=" + elapsed + ",message=" + message + '}';
     }
 
+    @Override
     public String getMessage()
     {
         return message;
     }
 
-    /**
-     * @noinspection RedundantIfStatement
-     */
+    @Override
     public boolean equals( Object o )
     {
         if ( this == o )
@@ -169,29 +192,10 @@ public boolean equals( Object o )
         }
 
         SimpleReportEntry that = (SimpleReportEntry) o;
-
-        if ( elapsed != null ? !elapsed.equals( that.elapsed ) : that.elapsed != null )
-        {
-            return false;
-        }
-        if ( name != null ? !name.equals( that.name ) : that.name != null )
-        {
-            return false;
-        }
-        if ( source != null ? !source.equals( that.source ) : that.source != null )
-        {
-            return false;
-        }
-        if ( stackTraceWriter != null
-            ? !stackTraceWriter.equals( that.stackTraceWriter )
-            : that.stackTraceWriter != null )
-        {
-            return false;
-        }
-
-        return true;
+        return isElapsedTimeEqual( that ) && isNameEqual( that ) && isSourceEqual( that ) && isStackEqual( that );
     }
 
+    @Override
     public int hashCode()
     {
         int result = source != null ? source.hashCode() : 0;
@@ -201,8 +205,35 @@ public int hashCode()
         return result;
     }
 
+    @Override
     public String getNameWithGroup()
     {
         return getName();
     }
+
+    @Override
+    public Map<String, String> getSystemProperties()
+    {
+        return systemProperties;
+    }
+
+    private boolean isElapsedTimeEqual( SimpleReportEntry en )
+    {
+        return elapsed != null ? elapsed.equals( en.elapsed ) : en.elapsed == null;
+    }
+
+    private boolean isNameEqual( SimpleReportEntry en )
+    {
+        return name != null ? name.equals( en.name ) : en.name == null;
+    }
+
+    private boolean isSourceEqual( SimpleReportEntry en )
+    {
+        return source != null ? source.equals( en.source ) : en.source == null;
+    }
+
+    private boolean isStackEqual( SimpleReportEntry en )
+    {
+        return stackTraceWriter != null ? stackTraceWriter.equals( en.stackTraceWriter ) : en.stackTraceWriter == null;
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/TestSetReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/TestSetReportEntry.java
new file mode 100644
index 000000000..02d8669ea
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/TestSetReportEntry.java
@@ -0,0 +1,35 @@
+package org.apache.maven.surefire.report;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+
+/**
+ * Describes test-set when started and finished.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see RunListener#testSetStarting(TestSetReportEntry)
+ * @see RunListener#testSetCompleted(TestSetReportEntry)
+ * @since 2.20.1
+ */
+public interface TestSetReportEntry extends ReportEntry
+{
+    Map<String, String> getSystemProperties();
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java b/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
index 50102e0f2..e318e1fb4 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/suite/RunResult.java
@@ -19,26 +19,12 @@
  * under the License.
  */
 
-import org.apache.maven.shared.utils.StringUtils;
-import org.apache.maven.shared.utils.io.IOUtil;
-import org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter;
-import org.apache.maven.shared.utils.xml.Xpp3Dom;
-import org.apache.maven.shared.utils.xml.Xpp3DomBuilder;
-import org.apache.maven.shared.utils.xml.Xpp3DomWriter;
-
-import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
 
 /**
  * Represents a test-run-result; this may be from a single test run or an aggregated result.
- * <p/>
+ * <br>
  * In the case of timeout==true, the run-counts reflect the state of the test-run at the time
  * of the timeout.
  *
@@ -79,8 +65,7 @@ public static RunResult failure( RunResult accumulatedAtTimeout, Exception cause
     private static RunResult errorCode( RunResult other, String failure, boolean timeout )
     {
         return new RunResult( other.getCompletedCount(), other.getErrors(), other.getFailures(), other.getSkipped(),
-                              failure, timeout );
-
+                                    failure, timeout );
     }
 
     public RunResult( int completedCount, int errors, int failures, int skipped )
@@ -171,13 +156,18 @@ public Integer getFailsafeCode()  // Only used for compatibility reasons.
     /* Indicates if the tests are error free */
     public boolean isErrorFree()
     {
-        return getFailures() == 0 && getErrors() == 0;
+        return getFailures() == 0 && getErrors() == 0 && !isFailure();
+    }
+
+    public boolean isInternalError()
+    {
+        return getFailures() == 0 && getErrors() == 0 && isFailure();
     }
 
     /* Indicates test timeout or technical failure */
     public boolean isFailureOrTimeout()
     {
-        return this.timeout || isFailure();
+        return isTimeout() || isFailure();
     }
 
     public boolean isFailure()
@@ -195,7 +185,6 @@ public boolean isTimeout()
         return timeout;
     }
 
-
     public RunResult aggregate( RunResult other )
     {
         String failureMessage = getFailure() != null ? getFailure() : other.getFailure();
@@ -213,83 +202,7 @@ public static RunResult noTestsRun()
         return new RunResult( 0, 0, 0, 0 );
     }
 
-    private Xpp3Dom create( String node, String value )
-    {
-        Xpp3Dom dom = new Xpp3Dom( node );
-        dom.setValue( value );
-        return dom;
-    }
-
-    private Xpp3Dom create( String node, int value )
-    {
-        return create( node, Integer.toString( value ) );
-    }
-
-    Xpp3Dom asXpp3Dom()
-    {
-        Xpp3Dom dom = new Xpp3Dom( "failsafe-summary" );
-        Integer failsafeCode = getFailsafeCode();
-        if ( failsafeCode != null )
-        {
-            dom.setAttribute( "result", Integer.toString( failsafeCode ) );
-        }
-        dom.setAttribute( "timeout", Boolean.toString( this.timeout ) );
-        dom.addChild( create( "completed", this.completedCount ) );
-        dom.addChild( create( "errors", this.errors ) );
-        dom.addChild( create( "failures", this.failures ) );
-        dom.addChild( create( "skipped", this.skipped ) );
-        dom.addChild( create( "failureMessage", this.failure ) );
-        return dom;
-    }
-
-    public static RunResult fromInputStream( InputStream inputStream, String encoding )
-        throws FileNotFoundException
-    {
-        Xpp3Dom dom = Xpp3DomBuilder.build( inputStream, encoding );
-        boolean timeout = Boolean.parseBoolean( dom.getAttribute( "timeout" ) );
-        int completed = Integer.parseInt( dom.getChild( "completed" ).getValue() );
-        int errors = Integer.parseInt( dom.getChild( "errors" ).getValue() );
-        int failures = Integer.parseInt( dom.getChild( "failures" ).getValue() );
-        int skipped = Integer.parseInt( dom.getChild( "skipped" ).getValue() );
-        String failureMessage1 = dom.getChild( "failureMessage" ).getValue();
-        String failureMessage = StringUtils.isEmpty( failureMessage1 ) ? null : failureMessage1;
-        return new RunResult( completed, errors, failures, skipped, failureMessage, timeout );
-    }
-
-    public void writeSummary( File summaryFile, boolean inProgress, String encoding )
-        throws IOException
-    {
-        if ( !summaryFile.getParentFile().isDirectory() )
-        {
-            //noinspection ResultOfMethodCallIgnored
-            summaryFile.getParentFile().mkdirs();
-        }
-
-        FileInputStream fin = null;
-        FileWriter writer = null;
-        try
-        {
-            RunResult mergedSummary = this;
-            if ( summaryFile.exists() && inProgress )
-            {
-                fin = new FileInputStream( summaryFile );
-
-                RunResult runResult = RunResult.fromInputStream( new BufferedInputStream( fin ), encoding );
-                mergedSummary = mergedSummary.aggregate( runResult );
-            }
-
-            writer = new FileWriter( summaryFile );
-            writer.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
-            PrettyPrintXMLWriter prettyPrintXMLWriter = new PrettyPrintXMLWriter( writer );
-            Xpp3DomWriter.write( prettyPrintXMLWriter, mergedSummary.asXpp3Dom() );
-        }
-        finally
-        {
-            IOUtil.close( fin );
-            IOUtil.close( writer );
-        }
-    }
-
+    @Override
     @SuppressWarnings( "RedundantIfStatement" )
     public boolean equals( Object o )
     {
@@ -332,6 +245,7 @@ public boolean equals( Object o )
         return true;
     }
 
+    @Override
     public int hashCode()
     {
         int result = completedCount;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/ResolvedTest.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/ResolvedTest.java
index 8e46ccb5a..56bc27927 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/ResolvedTest.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/ResolvedTest.java
@@ -73,12 +73,12 @@
     /**
      * '*' means zero or more characters<br>
      * '?' means one and only one character
-     * The pattern %regex[] prefix and suffix does not appear. The regex <code>pattern</code> is always
+     * The pattern %regex[] prefix and suffix does not appear. The regex <i>pattern</i> is always
      * unwrapped by the caller.
      *
      * @param classPattern     test class file pattern
      * @param methodPattern    test method
-     * @param isRegex          {@code true} if regex
+     * @param isRegex          {@code true} if pattern is regex
      */
     public ResolvedTest( String classPattern, String methodPattern, boolean isRegex )
     {
@@ -104,7 +104,11 @@ public ResolvedTest( String classPattern, String methodPattern, boolean isRegex
     }
 
     /**
-     * The regex <code>pattern</code> is always unwrapped.
+     * The regex {@code pattern} is always unwrapped.
+     *
+     * @param type class or method
+     * @param pattern pattern or regex
+     * @param isRegex {@code true} if pattern is regex
      */
     public ResolvedTest( Type type, String pattern, boolean isRegex )
     {
@@ -123,12 +127,14 @@ public ResolvedTest( Type type, String pattern, boolean isRegex )
     }
 
     /**
-     * Test class file pattern, e.g. org&#47;**&#47;Cat*.class<br/>, or null if not any
+     * Test class file pattern, e.g. org&#47;**&#47;Cat*.class<br>, or null if not any
      * and {@link #hasTestClassPattern()} returns false.
-     * Other examples: org&#47;animals&#47;Cat*, org&#47;animals&#47;Ca?.class, %regex[Cat.class|Dog.*]<br/>
-     * <br/>
+     * Other examples: org&#47;animals&#47;Cat*, org&#47;animals&#47;Ca?.class, %regex[Cat.class|Dog.*]<br>
+     * <br>
      * '*' means zero or more characters<br>
      * '?' means one and only one character
+     *
+     * @return class pattern or regex
      */
     public String getTestClassPattern()
     {
@@ -141,11 +147,13 @@ public boolean hasTestClassPattern()
     }
 
     /**
-     * Test method, e.g. "realTestMethod".<br/>, or null if not any and {@link #hasTestMethodPattern()} returns false.
-     * Other examples: test* or testSomethin? or %regex[testOne|testTwo] or %ant[testOne|testTwo]<br/>
-     * <br/>
+     * Test method, e.g. "realTestMethod".<br>, or null if not any and {@link #hasTestMethodPattern()} returns false.
+     * Other examples: test* or testSomethin? or %regex[testOne|testTwo] or %ant[testOne|testTwo]<br>
+     * <br>
      * '*' means zero or more characters<br>
      * '?' means one and only one character
+     *
+     * @return method pattern or regex
      */
     public String getTestMethodPattern()
     {
@@ -266,6 +274,9 @@ private boolean canMatchExclusiveAll( String testClassFile, String methodName )
 
     /**
      * Prevents {@link #match(String, String)} from throwing NPE in situations when inclusive returns true.
+     *
+     * @param testClassFile    path to class file
+     * @return {@code true} if examined class in null and class pattern exists
      */
     private boolean alwaysInclusiveQuietly( String testClassFile )
     {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
index f43d51eb0..b860c3b47 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
@@ -35,11 +35,12 @@
 import static org.apache.maven.surefire.testset.ResolvedTest.Type.CLASS;
 import static org.apache.maven.surefire.testset.ResolvedTest.Type.METHOD;
 
+// TODO In Surefire 3.0 see SUREFIRE-1309 and use normal fully qualified class name regex instead.
 /**
  * Resolved multi pattern filter e.g. -Dtest=MyTest#test,!AnotherTest#otherTest into an object model
- * composed of included and excluded tests.<br/>
+ * composed of included and excluded tests.<br>
  * The methods {@link #shouldRun(String, String)} are filters easily used in JUnit filter or TestNG.
- * This class is independent of JUnit and TestNG API.<br/>
+ * This class is independent of JUnit and TestNG API.<br>
  * It is accessed by Java Reflection API in {@link org.apache.maven.surefire.booter.SurefireReflector}
  * using specific ClassLoader.
  */
@@ -73,7 +74,7 @@ public TestListResolver( Collection<String> tests )
                 for ( String request : split( csvTests, "," ) )
                 {
                     request = request.trim();
-                    if ( request.length() != 0 && !request.equals( "!" ) )
+                    if ( !request.isEmpty() && !request.equals( "!" ) )
                     {
                         resolveTestRequest( request, patterns, includedFilters, excludedFilters );
                     }
@@ -116,16 +117,19 @@ public static TestListResolver newTestListResolver( Set<ResolvedTest> includedPa
                                      includedPatterns, excludedPatterns );
     }
 
+    @Override
     public boolean hasIncludedMethodPatterns()
     {
         return hasIncludedMethodPatterns;
     }
 
+    @Override
     public boolean hasExcludedMethodPatterns()
     {
         return hasExcludedMethodPatterns;
     }
 
+    @Override
     public boolean hasMethodPatterns()
     {
         return hasIncludedMethodPatterns() || hasExcludedMethodPatterns();
@@ -135,7 +139,7 @@ public boolean hasMethodPatterns()
      *
      * @param resolver    filter possibly having method patterns
      * @return {@code resolver} if {@link TestListResolver#hasMethodPatterns() resolver.hasMethodPatterns()}
-     * returns <tt>true</tt>; Otherwise wildcard filter <em>*.class</em> is returned.
+     * returns {@code true}; Otherwise wildcard filter {@code *.class} is returned.
      */
     public static TestListResolver optionallyWildcardFilter( TestListResolver resolver )
     {
@@ -161,6 +165,7 @@ public final boolean isWildcard()
     {
         return new TestFilter<String, String>()
         {
+            @Override
             public boolean shouldRun( String testClass, String methodName )
             {
                 return TestListResolver.this.shouldRun( testClass, methodName )
@@ -173,6 +178,7 @@ public boolean shouldRun( String testClass, String methodName )
     {
         return new TestFilter<String, String>()
         {
+            @Override
             public boolean shouldRun( String testClass, String methodName )
             {
                 return TestListResolver.this.shouldRun( testClass, methodName )
@@ -192,6 +198,7 @@ public boolean shouldRun( Class<?> testClass, String methodName )
      * @param testClassFile format must be e.g. "my/package/MyTest.class" including class extension; or null
      * @param methodName real test-method name; or null
      */
+    @Override
     public boolean shouldRun( String testClassFile, String methodName )
     {
         if ( isEmpty() || isBlank( testClassFile ) && isBlank( methodName ) )
@@ -233,11 +240,13 @@ public boolean shouldRun( String testClassFile, String methodName )
         }
     }
 
+    @Override
     public boolean isEmpty()
     {
         return equals( EMPTY );
     }
 
+    @Override
     public String getPluginParameterTest()
     {
         String aggregatedTest = aggregatedTest( "", getIncludedPatterns() );
@@ -251,11 +260,13 @@ public String getPluginParameterTest()
         return aggregatedTest.length() == 0 ? "" : aggregatedTest;
     }
 
+    @Override
     public Set<ResolvedTest> getIncludedPatterns()
     {
         return includedPatterns;
     }
 
+    @Override
     public Set<ResolvedTest> getExcludedPatterns()
     {
         return excludedPatterns;
@@ -308,7 +319,7 @@ public static String toClassFileName( String fullyQualifiedTestClass )
 
     static String removeExclamationMark( String s )
     {
-        return s.length() != 0 && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;
+        return !s.isEmpty() && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;
     }
 
     private static void updatedFilters( boolean isExcluded, ResolvedTest test, IncludedExcludedPatterns patterns,
@@ -333,7 +344,7 @@ private static String aggregatedTest( String testPrefix, Set<ResolvedTest> tests
         for ( ResolvedTest test : tests )
         {
             String readableTest = test.toString();
-            if ( readableTest.length() != 0 )
+            if ( !readableTest.isEmpty() )
             {
                 if ( aggregatedTest.length() != 0 )
                 {
@@ -356,7 +367,7 @@ private static String aggregatedTest( String testPrefix, Set<ResolvedTest> tests
             if ( exc != null )
             {
                 exc = exc.trim();
-                if ( exc.length() != 0 )
+                if ( !exc.isEmpty() )
                 {
                     if ( exc.contains( "!" ) )
                     {
@@ -445,7 +456,7 @@ static void nonRegexClassAndMethods( String clazz, String methods, boolean isExc
     }
 
     /**
-     * Requires trimmed <code>request</code> been not equal to "!".
+     * Requires trimmed {@code request} been not equal to "!".
      */
     static void resolveTestRequest( String request, IncludedExcludedPatterns patterns,
                                     Collection<ResolvedTest> includedFilters, Collection<ResolvedTest> excludedFilters )
@@ -456,8 +467,8 @@ static void resolveTestRequest( String request, IncludedExcludedPatterns pattern
         if ( isRegexPrefixedPattern( request ) )
         {
             final String[] unwrapped = unwrapRegex( request );
-            final boolean hasClass = unwrapped[0].length() != 0;
-            final boolean hasMethod = unwrapped[1].length() != 0;
+            final boolean hasClass = !unwrapped[0].isEmpty();
+            final boolean hasMethod = !unwrapped[1].isEmpty();
             if ( hasClass && hasMethod )
             {
                 test = new ResolvedTest( unwrapped[0], unwrapped[1], true );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestRequest.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestRequest.java
index a1c931585..ef8d66682 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestRequest.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestRequest.java
@@ -74,6 +74,8 @@ public File getTestSourceDirectory()
 
     /**
      * A specific test request issued with -Dtest= from the command line.
+     *
+     * @return filter
      */
     public TestListResolver getTestListResolver()
     {
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestSetFailedException.java b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestSetFailedException.java
index e0b03aaa0..f6966b0b5 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestSetFailedException.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestSetFailedException.java
@@ -30,11 +30,11 @@
 {
 
     /**
-     * Create a <code>TestFailedException</code> with a detail message.
+     * Creates {@code TestSetFailedException} with a detail message.
      *
-     * @param message A detail message for this <code>TestFailedException</code>, or
-     *                <code>null</code>. If <code>null</code> is passed, the <code>getMessage</code>
-     *                method will return an empty <code>String</code>.
+     * @param message A detail message for this {@code TestSetFailedException}, or
+     *                {@code null}. If {@code null} is passed, the {@link #getMessage}
+     *                method will return an empty {@link String string}.
      */
     public TestSetFailedException( String message )
     {
@@ -42,15 +42,15 @@ public TestSetFailedException( String message )
     }
 
     /**
-     * Create a <code>TestFailedException</code> with the specified detail
+     * Creates {@code TestSetFailedException} with the specified detail
      * message and cause.
-     * <p/>
+     * <br>
      * <p>Note that the detail message associated with cause is
-     * <em>not</em> automatically incorporated in this throwable's detail
+     * <b>NOT</b> automatically incorporated in this throwable's detail
      * message.
      *
-     * @param message A detail message for this <code>TestFailedException</code>, or <code>null</code>.
-     * @param cause   the cause, which is saved for later retrieval by the <code>getCause</code> method.
+     * @param message A detail message for this {@code TestSetFailedException}, or {@code null}.
+     * @param cause   the cause, which is saved for later retrieval by the {@link #getCause} method.
      *                (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
      */
     public TestSetFailedException( String message, Throwable cause )
@@ -59,9 +59,8 @@ public TestSetFailedException( String message, Throwable cause )
     }
 
     /**
-     * Create a <code>TestFailedException</code> with the specified cause.  The
-     * <code>getMessage</code> method of this exception object will return
-     * <code>(cause == null ? "" : cause.toString())</code>.
+     * Creates {@code TestSetFailedException} with the specified cause. The mthod {@link #getMessage} method of this
+     * exception object will return {@code cause == null ? "" : cause.toString()}.
      *
      * @param cause The cause
      */
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/CloseableIterator.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/CloseableIterator.java
index 52b5b65d5..eec185982 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/CloseableIterator.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/CloseableIterator.java
@@ -1 +1,100 @@
-package org.apache.maven.surefire.util;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * This iterator is marked as stopped if {@link #isClosed()} returns {@code true}.
 * If the iterator has been closed before calling {@link #hasNext()} then the method returns {@code false}.
 * If the iterator was closed after {@link #hasNext() hasNext returns true} but before {@link #next()}, the
 * method {@link #next()} throws {@link java.util.NoSuchElementException}.
 * The method {@link #remove()} throws {@link IllegalStateException} if the iterator has been closed.
 *
 * @param <T> the type of elements returned by this iterator
 *
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @since 2.19.1
 */
public abstract class CloseableIterator<T>
        implements Iterator<T>
{
    private Boolean finishCurrentIteration;

    protected abstract boolean isClosed();
    protected abstract boolean doHasNext();
    protected abstract T doNext();
    protected abstract void doRemove();

    public boolean hasNext()
    {
        popMarker();
        return !finishCurrentIteration && doHasNext();
    }

    public T next()
    {
        try
        {
            if ( popMarker() && finishCurrentIteration )
            {
                throw new NoSuchElementException( "iterator closed" );
            }
            return doNext();
        }
        finally
        {
            finishCurrentIteration = null;
        }
    }

    public void remove()
    {
        try
        {
            if ( popMarker() && finishCurrentIteration )
            {
                throw new IllegalStateException( "iterator closed" );
            }
            doRemove();
        }
        finally
        {
            finishCurrentIteration = null;
        }
    }

    /**
     * @return {@code true} if marker changed from NULL to anything
     */
    private boolean popMarker()
    {
        if ( finishCurrentIteration == null )
        {
            finishCurrentIteration = isClosed();
            return true;
        }
        return false;
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * This iterator is marked as stopped if {@link #isClosed()} returns {@code true}.
+ * If the iterator has been closed before calling {@link #hasNext()} then the method returns {@code false}.
+ * If the iterator was closed after {@link #hasNext() hasNext returns true} but before {@link #next()}, the
+ * method {@link #next()} throws {@link java.util.NoSuchElementException}.
+ * The method {@link #remove()} throws {@link IllegalStateException} if the iterator has been closed.
+ *
+ * @param <T> the type of elements returned by this iterator
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.19.1
+ */
+public abstract class CloseableIterator<T>
+        implements Iterator<T>
+{
+    private Boolean finishCurrentIteration;
+
+    protected abstract boolean isClosed();
+    protected abstract boolean doHasNext();
+    protected abstract T doNext();
+    protected abstract void doRemove();
+
+    @Override
+    public boolean hasNext()
+    {
+        popMarker();
+        return !finishCurrentIteration && doHasNext();
+    }
+
+    @Override
+    public T next()
+    {
+        try
+        {
+            if ( popMarker() && finishCurrentIteration )
+            {
+                throw new NoSuchElementException( "iterator closed" );
+            }
+            return doNext();
+        }
+        finally
+        {
+            finishCurrentIteration = null;
+        }
+    }
+
+    @Override
+    public void remove()
+    {
+        try
+        {
+            if ( popMarker() && finishCurrentIteration )
+            {
+                throw new IllegalStateException( "iterator closed" );
+            }
+            doRemove();
+        }
+        finally
+        {
+            finishCurrentIteration = null;
+        }
+    }
+
+    /**
+     * @return {@code true} if marker changed from NULL to anything
+     */
+    private boolean popMarker()
+    {
+        if ( finishCurrentIteration == null )
+        {
+            finishCurrentIteration = isClosed();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultDirectoryScanner.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultDirectoryScanner.java
index 7353165ab..b8be70f3e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultDirectoryScanner.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultDirectoryScanner.java
@@ -64,6 +64,7 @@ public DefaultDirectoryScanner( File basedir, List<String> includes, List<String
         this.specificTests = specificTests;
     }
 
+    @Override
     public TestsToRun locateTestClasses( ClassLoader classLoader, ScannerFilter scannerFilter )
     {
         String[] testClassNames = collectTests();
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultRunOrderCalculator.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultRunOrderCalculator.java
index 7f8d9e10b..dea10a69a 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultRunOrderCalculator.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultRunOrderCalculator.java
@@ -53,6 +53,7 @@ public DefaultRunOrderCalculator( RunOrderParameters runOrderParameters, int thr
         this.sortOrder = this.runOrder.length > 0 ? getSortOrderComparator( this.runOrder[0] ) : null;
     }
 
+    @Override
     @SuppressWarnings( "checkstyle:magicnumber" )
     public TestsToRun orderTestClasses( TestsToRun scannedClasses )
     {
@@ -120,6 +121,7 @@ else if ( RunOrder.HOURLY.equals( runOrder ) )
     {
         return new Comparator<Class>()
         {
+            @Override
             public int compare( Class o1, Class o2 )
             {
                 return o2.getName().compareTo( o1.getName() );
@@ -131,6 +133,7 @@ public int compare( Class o1, Class o2 )
     {
         return new Comparator<Class>()
         {
+            @Override
             public int compare( Class o1, Class o2 )
             {
                 return o1.getName().compareTo( o2.getName() );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultScanResult.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultScanResult.java
index ff75508b6..a00a3b56d 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultScanResult.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/DefaultScanResult.java
@@ -41,16 +41,19 @@ public DefaultScanResult( List<String> files )
         this.files = Collections.unmodifiableList( files );
     }
 
+    @Override
     public int size()
     {
         return files.size();
     }
 
+    @Override
     public String getClassName( int index )
     {
         return files.get( index );
     }
 
+    @Override
     public void writeTo( Map<String, String> properties )
     {
         for ( int i = 0, size = files.size(); i < size; i++ )
@@ -84,6 +87,7 @@ public List getFiles()
         return files;
     }
 
+    @Override
     public TestsToRun applyFilter( ScannerFilter scannerFilter, ClassLoader testClassLoader )
     {
         Set<Class<?>> result = new LinkedHashSet<Class<?>>();
@@ -104,6 +108,7 @@ public TestsToRun applyFilter( ScannerFilter scannerFilter, ClassLoader testClas
         return new TestsToRun( result );
     }
 
+    @Override
     public List<Class<?>> getClassesSkippedByValidation( ScannerFilter scannerFilter, ClassLoader testClassLoader )
     {
         List<Class<?>> result = new ArrayList<Class<?>>();
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
index 6844dda3e..49f8f096e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
@@ -28,9 +28,8 @@
  */
 public final class ReflectionUtils
 {
-    private static final Class[] NO_ARGS = new Class[0];
-
-    private static final Object[] NO_ARGS_VALUES = new Object[0];
+    private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 
     private ReflectionUtils()
     {
@@ -68,8 +67,13 @@ public static Method tryGetMethod( Class<?> clazz, String methodName, Class<?>..
 
     public static Object invokeGetter( Object instance, String methodName )
     {
-        final Method method = getMethod( instance, methodName, NO_ARGS );
-        return invokeMethodWithArray( instance, method, NO_ARGS_VALUES );
+        return invokeGetter( instance.getClass(), instance, methodName );
+    }
+
+    public static Object invokeGetter( Class<?> instanceType, Object instance, String methodName )
+    {
+        Method method = getMethod( instanceType, methodName );
+        return invokeMethodWithArray( instance, method );
     }
 
     public static Constructor getConstructor( Class<?> clazz, Class<?>... arguments )
@@ -245,4 +249,66 @@ public static Object instantiateObject( String className, Class[] types, Object[
             throw new SurefireReflectionException( e );
         }
     }
+
+    /**
+     * Invoker of public static no-argument method.
+     *
+     * @param clazz         class on which public static no-argument {@code methodName} is invoked
+     * @param methodName    public static no-argument method to be called
+     * @param parameterTypes    method parameter types
+     * @param parameters    method parameters
+     * @return value returned by {@code methodName}
+     * @throws RuntimeException if no such method found
+     * @throws SurefireReflectionException if the method could not be called or threw an exception.
+     * It has original cause Exception.
+     */
+    public static Object invokeStaticMethod( Class<?> clazz, String methodName,
+                                             Class<?>[] parameterTypes, Object[] parameters )
+    {
+        if ( parameterTypes.length != parameters.length )
+        {
+            throw new IllegalArgumentException( "arguments length do not match" );
+        }
+        Method method = getMethod( clazz, methodName, parameterTypes );
+        return invokeMethodWithArray( null, method, parameters );
+    }
+
+    /**
+     * Method chain invoker.
+     *
+     * @param classesChain        classes to invoke on method chain
+     * @param noArgMethodNames    chain of public methods to call
+     * @param fallback            returned value if a chain could not be invoked due to an error
+     * @return successfully returned value from the last method call; {@code fallback} otherwise
+     * @throws IllegalArgumentException if {@code classes} and {@code noArgMethodNames} have different array length
+     */
+    public static Object invokeMethodChain( Class<?>[] classesChain, String[] noArgMethodNames, Object fallback )
+    {
+        if ( classesChain.length != noArgMethodNames.length )
+        {
+            throw new IllegalArgumentException( "arrays must have the same length" );
+        }
+        Object obj = null;
+        try
+        {
+            for ( int i = 0, len = noArgMethodNames.length; i < len; i++ )
+            {
+                if ( i == 0 )
+                {
+                    obj = invokeStaticMethod( classesChain[i], noArgMethodNames[i],
+                                                    EMPTY_CLASS_ARRAY, EMPTY_OBJECT_ARRAY );
+                }
+                else
+                {
+                    Method method = getMethod( classesChain[i], noArgMethodNames[i] );
+                    obj = invokeMethodWithArray( obj, method );
+                }
+            }
+            return obj;
+        }
+        catch ( RuntimeException e )
+        {
+            return fallback;
+        }
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/RunOrder.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/RunOrder.java
index ebbec7063..79c52357f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/RunOrder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/RunOrder.java
@@ -143,6 +143,7 @@ public String name()
         return name;
     }
 
+    @Override
     public String toString()
     {
         return name;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/SurefireReflectionException.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/SurefireReflectionException.java
index ca5d2beb5..eb3b04aee 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/SurefireReflectionException.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/SurefireReflectionException.java
@@ -30,9 +30,8 @@
     extends RuntimeException
 {
     /**
-     * Create a <code>SurefireReflectionException</code> with the specified cause.  The
-     * <code>getMessage</code> method of this exception object will return
-     * <code>(cause == null ? "" : cause.toString())</code>.
+     * Create a {@link SurefireReflectionException} with the specified cause. The method {@link #getMessage} of this
+     * exception object will return {@code cause == null ? "" : cause.toString()}.
      *
      * @param cause The cause of this exception
      */
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/TestsToRun.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/TestsToRun.java
index 204bed862..8a264ca8f 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/TestsToRun.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/TestsToRun.java
@@ -73,6 +73,7 @@ public static TestsToRun fromClass( Class<?> clazz )
      *
      * @return an unmodifiable iterator
      */
+    @Override
     public Iterator<Class<?>> iterator()
     {
         return new ClassesIterator();
@@ -127,6 +128,7 @@ public final boolean isFinished()
         return finished;
     }
 
+    @Override
     public String toString()
     {
         StringBuilder sb = new StringBuilder( "TestsToRun: [" );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ByteBuffer.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ByteBuffer.java
deleted file mode 100644
index 69da150bc..000000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ByteBuffer.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package org.apache.maven.surefire.util.internal;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * @author Kristian Rosenvold
- */
-public class ByteBuffer
-{
-    private final byte[] data;
-
-    private int position;
-
-    public ByteBuffer( int length )
-    {
-        this.data = new byte[length];
-    }
-
-    public ByteBuffer( byte[] buf, int off, int len )
-    {
-        this.data = new byte[len];
-        append( buf, off, len );
-    }
-
-
-    public void append( char chararcter )
-    {
-        data[position++] = (byte) chararcter;
-    }
-
-    public void append( byte chararcter )
-    {
-        data[position++] = chararcter;
-    }
-
-    private static final byte COMMA = (byte) ',';
-
-    public void comma()
-    {
-        data[position++] = COMMA;
-    }
-
-
-    public void advance( int i )
-    { // Oooh nice break of encapsulation
-        position += i;
-    }
-
-    public void append( Integer integer )
-    {
-        toHex( integer );
-    }
-
-    /**
-     * Convert the integer to an unsigned number.
-     *
-     * @param i the value
-     */
-    private void toHex( int i )
-    {
-        byte[] buf = new byte[32];
-        int charPos = 32;
-        int radix = 1 << 4;
-        int mask = radix - 1;
-        do
-        {
-            buf[--charPos] = (byte) DIGITS[i & mask];
-            i >>>= 4;
-        }
-        while ( i != 0 );
-
-        append( buf, charPos, ( 32 - charPos ) );
-    }
-
-    private static final char[] DIGITS =
-        { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
-            'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
-
-
-    public byte[] getData()
-    {
-        return data;
-    }
-
-    public int getlength()
-    {
-        return position;
-    }
-
-    public String toString()
-    {
-        return new String( data, 0, position );
-    }
-
-    public static byte[] copy( byte[] src1, int off1, int len1 )
-    {
-        byte[] combined = new byte[len1];
-        int pos = 0;
-        for ( int i = off1; i < off1 + len1; i++ )
-        {
-            combined[pos++] = src1[i];
-        }
-        return combined;
-    }
-
-    void append( byte[] src1, int off1, int len1 )
-    {
-        for ( int i = off1; i < off1 + len1; i++ )
-        {
-            data[position++] = src1[i];
-        }
-    }
-
-    public static byte[] join( byte[] src1, int off1, int len1, byte[] src2, int off2, int len2 )
-    {
-        byte[] combined = new byte[len1 + len2];
-        int pos = 0;
-        for ( int i = off1; i < off1 + len1; i++ )
-        {
-            combined[pos++] = src1[i];
-        }
-        for ( int i = off2; i < off2 + len2; i++ )
-        {
-            combined[pos++] = src2[i];
-        }
-        return combined;
-    }
-
-}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
index 29eb18d85..3610a4b69 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DaemonThreadFactory.java
@@ -43,6 +43,7 @@ private DaemonThreadFactory()
         namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
     }
 
+    @Override
     public Thread newThread( Runnable r )
     {
         Thread t = new Thread( group, r, namePrefix + threadNumber.getAndIncrement() );
@@ -56,6 +57,7 @@ public Thread newThread( Runnable r )
 
     /**
      * Should be used by thread pools.
+     * @return new instance of {@link ThreadFactory} where each {@link Thread thread} is daemon
      */
     public static ThreadFactory newDaemonThreadFactory()
     {
@@ -104,6 +106,7 @@ private NamedThreadFactory( String name )
             this.name = name;
         }
 
+        @Override
         public Thread newThread( Runnable r )
         {
             return newDaemonThread( r, name );
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DumpFileUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DumpFileUtils.java
new file mode 100644
index 000000000..302358c23
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/DumpFileUtils.java
@@ -0,0 +1,130 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.report.ReporterConfiguration;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
+
+/**
+ * Dumps a text or exception in dump file.
+ * Each call logs a date when it was written to the dump file.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public final class DumpFileUtils
+{
+    private DumpFileUtils()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    /**
+     * New dump file. Synchronized object appears in main memory and perfectly visible in other threads.
+     *
+     * @param dumpFileName    dump file name
+     * @param configuration    only report directory
+     */
+    public static synchronized File newDumpFile( String dumpFileName, ReporterConfiguration configuration )
+    {
+        return new File( configuration.getReportsDirectory(), dumpFileName );
+    }
+
+    public static void dumpException( Throwable t, File dumpFile )
+    {
+        dumpException( t, null, dumpFile );
+    }
+
+    public static void dumpException( Throwable t, String msg, File dumpFile )
+    {
+        try
+        {
+            if ( t != null && dumpFile != null
+                         && ( dumpFile.exists() || mkdirs( dumpFile ) && dumpFile.createNewFile() ) )
+            {
+                Writer fw = createWriter( dumpFile );
+                if ( msg != null )
+                {
+                    fw.append( msg )
+                            .append( StringUtils.NL );
+                }
+                PrintWriter pw = new PrintWriter( fw );
+                t.printStackTrace( pw );
+                pw.flush();
+                fw.append( StringUtils.NL )
+                  .append( StringUtils.NL )
+                  .close();
+            }
+        }
+        catch ( Exception e )
+        {
+            // do nothing
+        }
+    }
+
+    public static void dumpText( String msg, File dumpFile )
+    {
+        try
+        {
+            if ( msg != null && dumpFile != null
+                         && ( dumpFile.exists() || mkdirs( dumpFile ) && dumpFile.createNewFile() ) )
+            {
+                createWriter( dumpFile )
+                        .append( msg )
+                        .append( StringUtils.NL )
+                        .append( StringUtils.NL )
+                        .close();
+            }
+        }
+        catch ( Exception e )
+        {
+            // do nothing
+        }
+    }
+
+    public static String newFormattedDateFileName()
+    {
+        return new SimpleDateFormat( "yyyy-MM-dd'T'HH-mm-ss_SSS" ).format( new Date() );
+    }
+
+    private static Writer createWriter( File dumpFile ) throws IOException
+    {
+        return new OutputStreamWriter( new FileOutputStream( dumpFile, true ), UTF_8 )
+                       .append( "# Created on " )
+                       .append( new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS" ).format( new Date() ) )
+                       .append( StringUtils.NL );
+    }
+
+    private static boolean mkdirs( File dumpFile )
+    {
+        File dir = dumpFile.getParentFile();
+        return dir.exists() || dir.mkdirs();
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ImmutableMap.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ImmutableMap.java
new file mode 100644
index 000000000..003d88444
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ImmutableMap.java
@@ -0,0 +1,134 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.AbstractMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Copies input map in {@link #ImmutableMap(Map) constructor}, and Entries are linked and thread-safe.
+ * The map is immutable with linear list of entries.
+ *
+ * @param <K> key
+ * @param <V> value
+ * @since 2.20
+ */
+public final class ImmutableMap<K, V>
+        extends AbstractMap<K, V>
+{
+    private final Node<K, V> first;
+
+    public ImmutableMap( Map<K, V> map )
+    {
+        Node<K, V> first = null;
+        Node<K, V> previous = null;
+        for ( Entry<K, V> e : map.entrySet() )
+        {
+            Node<K, V> node = new Node<K, V>( e.getKey(), e.getValue() );
+            if ( first == null )
+            {
+                first = node;
+            }
+            else
+            {
+                previous.next = node;
+            }
+            previous = node;
+        }
+        this.first = first;
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet()
+    {
+        Set<Entry<K, V>> entries = new LinkedHashSet<Entry<K, V>>();
+        Node<K, V> node = first;
+        while ( node != null )
+        {
+            entries.add( node );
+            node = node.next;
+        }
+        return unmodifiableSet( entries );
+    }
+
+    static final class Node<K, V>
+            implements Entry<K, V>
+    {
+        final K key;
+        final V value;
+        volatile Node<K, V> next;
+
+        Node( K key, V value )
+        {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public K getKey()
+        {
+            return key;
+        }
+
+        @Override
+        public V getValue()
+        {
+            return value;
+        }
+
+        @Override
+        public V setValue( V value )
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean equals( Object o )
+        {
+            if ( this == o )
+            {
+                return true;
+            }
+
+            if ( o == null || getClass() != o.getClass() )
+            {
+                return false;
+            }
+
+            Node<?, ?> node = (Node<?, ?>) o;
+
+            return getKey() != null ? getKey().equals( node.getKey() ) : node.getKey() == null
+                           && getValue() != null ? getValue().equals( node.getValue() ) : node.getValue() == null;
+
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = getKey() != null ? getKey().hashCode() : 0;
+            result = 31 * result + ( getValue() != null ? getValue().hashCode() : 0 );
+            return result;
+        }
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ObjectUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ObjectUtils.java
index 93d914df1..bc4fdd953 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ObjectUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ObjectUtils.java
@@ -1 +1,87 @@
-package org.apache.maven.surefire.util.internal;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * Similar to Java 7 java.util.Objects.
 *
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @since 2.19.2
 */
public final class ObjectUtils
{
    private ObjectUtils()
    {
        throw new IllegalStateException( "no instantiable constructor" );
    }

    public static <T> T useNonNull( T target, T fallback )
    {
        return isNull( target ) ? fallback : target;
    }

    /*
    * In JDK7 use java.util.Objects instead.
    * todo
    * */
    public static boolean isNull( Object target )
    {
        return target == null;
    }

    /*
    * In JDK7 use java.util.Objects instead.
    * todo
    * */
    public static boolean nonNull( Object target )
    {
        return !isNull( target );
    }

    /*
    * In JDK7 use java.util.Objects instead.
    * todo
    * */
    public static <T> T requireNonNull( T obj, String message )
    {
        if ( isNull( obj ) )
        {
            throw new NullPointerException( message );
        }
        return obj;
    }

    /*
    * In JDK7 use java.util.Objects instead.
    * todo
    * */
    public static <T> T requireNonNull( T obj )
    {
        return requireNonNull( obj, null );
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.lang.management.ManagementFactory;
+import java.util.Map;
+
+/**
+ * Similar to Java 7 java.util.Objects.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public final class ObjectUtils
+{
+    private ObjectUtils()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    public static <T> T useNonNull( T target, T fallback )
+    {
+        return isNull( target ) ? fallback : target;
+    }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * todo
+    * */
+    public static boolean isNull( Object target )
+    {
+        return target == null;
+    }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * todo
+    * */
+    public static boolean nonNull( Object target )
+    {
+        return !isNull( target );
+    }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * todo
+    * */
+    public static <T> T requireNonNull( T obj, String message )
+    {
+        if ( isNull( obj ) )
+        {
+            throw new NullPointerException( message );
+        }
+        return obj;
+    }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * todo
+    * */
+    public static <T> T requireNonNull( T obj )
+    {
+        return requireNonNull( obj, null );
+    }
+
+    public static Map<String, String> systemProps()
+    {
+        return ManagementFactory.getRuntimeMXBean().getSystemProperties();
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
index a95518386..b32d5d03e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
@@ -19,7 +19,6 @@
  * under the License.
  */
 
-import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
@@ -28,12 +27,14 @@
 
 /**
  * <p>
- * Common <code>String</code> manipulation routines.
+ * Common {@link String java.lang.String} manipulation routines.
  * </p>
- * <p/>
+ * <br>
  * <p>
  * Originally from <a href="http://jakarta.apache.org/turbine/">Turbine</a> and the GenerationJavaCore library.
  * </p>
+ * <br>
+ * NOTE: This class is not part of any api and is public purely for technical reasons !
  *
  * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
@@ -47,26 +48,27 @@
  * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
  * @version $Id: StringUtils.java 8001 2009-01-03 13:17:09Z vsiveton $
- * @noinspection JavaDoc
- *               <p/>
- *               A quick borrow from plexus-utils by Kristian Rosenvold, to restore jdk1.3 compat Threw away all the
- *               unused stuff.
- *               <p/>
- *               NOTE: This class is not part of any api and is public purely for technical reasons !
  * @since 1.0
  */
 public final class StringUtils
 {
     public static final String NL = System.getProperty( "line.separator" );
 
-    private static final byte[] HEX_CHARS = new byte[] {
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-        'A', 'B', 'C', 'D', 'E', 'F' };
+    private static final byte[] HEX_CHARS = {
+                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
 
     private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
 
+    /**
+     * TODO
+     * Use JDK7 StandardCharsets
+     */
+    public static final Charset US_ASCII = Charset.forName( "US-ASCII" );
+
     // 8-bit charset Latin-1
-    public static final String FORK_STREAM_CHARSET_NAME = "ISO-8859-1";
+    public static final Charset ISO_8859_1 = Charset.forName( "ISO-8859-1" );
+
+    public static final Charset UTF_8 = Charset.forName( "UTF-8" );
 
     private StringUtils()
     {
@@ -75,8 +77,7 @@ private StringUtils()
 
     public static String[] split( String text, String separator )
     {
-        int max = -1;
-        StringTokenizer tok;
+        final StringTokenizer tok;
         if ( separator == null )
         {
             // Null separator means we're using StringTokenizer's default
@@ -88,60 +89,34 @@ private StringUtils()
             tok = new StringTokenizer( text, separator );
         }
 
-        int listSize = tok.countTokens();
-        if ( max > 0 && listSize > max )
+        String[] list = new String[tok.countTokens()];
+        for ( int i = 0; tok.hasMoreTokens(); i++ )
         {
-            listSize = max;
-        }
-
-        String[] list = new String[listSize];
-        int i = 0;
-        int lastTokenBegin;
-        int lastTokenEnd = 0;
-        while ( tok.hasMoreTokens() )
-        {
-            if ( max > 0 && i == listSize - 1 )
-            {
-                // In the situation where we hit the max yet have
-                // tokens left over in our input, the last list
-                // element gets all remaining text.
-                String endToken = tok.nextToken();
-                lastTokenBegin = text.indexOf( endToken, lastTokenEnd );
-                list[i] = text.substring( lastTokenBegin );
-                break;
-            }
-            else
-            {
-                list[i] = tok.nextToken();
-                lastTokenBegin = text.indexOf( list[i], lastTokenEnd );
-                lastTokenEnd = lastTokenBegin + list[i].length();
-            }
-            i++;
+            list[i] = tok.nextToken();
         }
         return list;
     }
 
     /**
      * <p>
-     * Checks if a (trimmed) String is <code>null</code> or blank.
+     * Checks if a (trimmed) String is {@code null} or blank.
      * </p>
      *
      * @param str the String to check
-     * @return <code>true</code> if the String is <code>null</code>, or length zero once trimmed
+     * @return {@code true} if the String is {@code null}, or length zero once trimmed
      */
     public static boolean isBlank( String str )
     {
-        return str == null || str.trim().length() == 0;
+        return str == null || str.trim().isEmpty();
     }
 
     /**
      * <p>
-     * Checks if a (trimmed) String is not <code>null</code> and not blank.
+     * Checks if a (trimmed) String is not {@code null} and not blank.
      * </p>
      *
      * @param str the String to check
-     * @return <code>true</code> if the String is not <code>null</code> and length of trimmed
-     * <code>str</code> is not zero.
+     * @return {@code true} if the String is not {@code null} and length of trimmed {@code str} is not zero.
      */
     public static boolean isNotBlank( String str )
     {
@@ -249,7 +224,7 @@ else if ( ch >= 'A' )
      * <p>
      * A save length of {@code out} is {@code len * 3 + outoff}.
      * <p>
-     * The reverse-method is {@link #unescapeBytes(byte[], String)}.
+     * The reverse-method is {@link #unescapeBytes(String, String)}.
      *
      * @param out output buffer
      * @param outoff offset in the output buffer
@@ -333,9 +308,7 @@ public static ByteBuffer unescapeBytes( String str, String charsetName  )
             try
             {
                 decodedFromSourceCharset = sourceCharset.newDecoder().decode( ByteBuffer.wrap( out, 0, outPos ) );
-                ByteBuffer defaultEncoded = DEFAULT_CHARSET.encode( decodedFromSourceCharset );
-
-                return defaultEncoded;
+                return DEFAULT_CHARSET.encode( decodedFromSourceCharset );
             }
             catch ( CharacterCodingException e )
             {
@@ -346,41 +319,34 @@ public static ByteBuffer unescapeBytes( String str, String charsetName  )
         return ByteBuffer.wrap( out, 0, outPos );
     }
 
-    public static String decode( byte[] toDecode, Charset charset )
-    {
-        try
-        {
-            // @todo use new JDK 1.6 constructor String(byte bytes[], Charset charset)
-            return new String( toDecode, charset.name() );
-        }
-        catch ( UnsupportedEncodingException e )
-        {
-            throw new RuntimeException( "The JVM must support Charset " + charset, e );
-        }
-    }
-
-    public static byte[] encode( String toEncode, Charset charset )
+    public static byte[] encodeStringForForkCommunication( String string )
     {
-        try
-        {
-            // @todo use new JDK 1.6 method getBytes(Charset charset)
-            return toEncode.getBytes( charset.name() );
-        }
-        catch ( UnsupportedEncodingException e )
-        {
-            throw new RuntimeException( "The JVM must support Charset " + charset, e );
-        }
+        return string.getBytes( ISO_8859_1 );
     }
 
-    public static byte[] encodeStringForForkCommunication( String string )
+    /**
+     * Determines if {@code buffer} starts with specific literal(s).
+     *
+     * @param buffer     Examined StringBuffer
+     * @param pattern    a pattern which should start in {@code buffer}
+     * @return    {@code true} if buffer's literal starts with given {@code pattern}, or both are empty.
+     */
+    public static boolean startsWith( StringBuffer buffer, String pattern )
     {
-        try
+        if ( buffer.length() < pattern.length() )
         {
-            return string.getBytes( FORK_STREAM_CHARSET_NAME );
+            return false;
         }
-        catch ( UnsupportedEncodingException e )
+        else
         {
-           throw new RuntimeException( "The JVM must support Charset " + FORK_STREAM_CHARSET_NAME, e );
+            for ( int i = 0, len = pattern.length(); i < len; i++ )
+            {
+                if ( buffer.charAt( i ) != pattern.charAt( i ) )
+                {
+                    return false;
+                }
+            }
+            return true;
         }
     }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java
new file mode 100644
index 000000000..23e72e101
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java
@@ -0,0 +1,56 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * JUnit Description parser.
+ * Used by JUnit Version lower than 4.7.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public final class TestClassMethodNameUtils
+{
+    /**
+     * This pattern is verbatim copy from JUnit's code in class {@code Description}.
+     * Parsing class and method from junit description would provide identical result to JUnit internal parser.
+     */
+    private static final Pattern METHOD_CLASS_PATTERN = Pattern.compile( "([\\s\\S]*)\\((.*)\\)" );
+
+    private TestClassMethodNameUtils()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    public static String extractClassName( String displayName )
+    {
+        Matcher m = METHOD_CLASS_PATTERN.matcher( displayName );
+        return m.matches() ? m.group( 2 ) : displayName;
+    }
+
+    public static String extractMethodName( String displayName )
+    {
+        Matcher m = METHOD_CLASS_PATTERN.matcher( displayName );
+        return m.matches() ? m.group( 1 ) : displayName;
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/UrlUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/UrlUtils.java
similarity index 55%
rename from surefire-api/src/main/java/org/apache/maven/surefire/util/UrlUtils.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/util/internal/UrlUtils.java
index 34dfe3d96..dd96f28fc 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/UrlUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/UrlUtils.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.util;
+package org.apache.maven.surefire.util.internal;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -20,15 +20,16 @@
  */
 
 import java.io.File;
-import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.BitSet;
 
+import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
+
 /**
  * Utility for dealing with URLs in pre-JDK 1.4.
  */
-public class UrlUtils
+public final class UrlUtils
 {
     private static final BitSet UNRESERVED = new BitSet( Byte.MAX_VALUE - Byte.MIN_VALUE + 1 );
 
@@ -38,28 +39,19 @@
 
     private UrlUtils()
     {
+        throw new IllegalStateException( "no instantiable constructor" );
     }
 
-    private static final String ENCODING = "UTF-8";
-
     static
     {
-        try
-        {
-            byte[] bytes =
-                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'():/".getBytes( ENCODING );
-            for ( byte aByte : bytes )
-            {
-                UNRESERVED.set( aByte );
-            }
-        }
-        catch ( UnsupportedEncodingException e )
+        byte[] bytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'():/".getBytes( UTF_8 );
+        for ( byte aByte : bytes )
         {
-            // can't happen as UTF-8 must be present
+            UNRESERVED.set( aByte );
         }
     }
 
-    public static URL getURL( File file )
+    public static URL toURL( File file )
         throws MalformedURLException
     {
         // with JDK 1.4+, code would be: return new URL( file.toURI().toASCIIString() );
@@ -67,29 +59,21 @@ public static URL getURL( File file )
         URL url = file.toURL();
         // encode any characters that do not comply with RFC 2396
         // this is primarily to handle Windows where the user's home directory contains spaces
-        try
+        byte[] bytes = url.toString().getBytes( UTF_8 );
+        StringBuilder buf = new StringBuilder( bytes.length );
+        for ( byte b : bytes )
         {
-            byte[] bytes = url.toString().getBytes( ENCODING );
-            StringBuilder buf = new StringBuilder( bytes.length );
-            for ( byte b : bytes )
+            if ( b > 0 && UNRESERVED.get( b ) )
             {
-                if ( b > 0 && UNRESERVED.get( b ) )
-                {
-                    buf.append( (char) b );
-                }
-                else
-                {
-                    buf.append( '%' );
-                    buf.append( Character.forDigit( b >>> 4 & MASK, RADIX ) );
-                    buf.append( Character.forDigit( b & MASK, RADIX ) );
-                }
+                buf.append( (char) b );
+            }
+            else
+            {
+                buf.append( '%' );
+                buf.append( Character.forDigit( b >>> 4 & MASK, RADIX ) );
+                buf.append( Character.forDigit( b & MASK, RADIX ) );
             }
-            return new URL( buf.toString() );
-        }
-        catch ( UnsupportedEncodingException e )
-        {
-            // should not happen as UTF-8 must be present
-            throw new RuntimeException( e );
         }
+        return new URL( buf.toString() );
     }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index c43a3a660..66cca64b8 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -32,13 +32,14 @@
 import org.apache.maven.surefire.testset.ResolvedTestTest;
 import org.apache.maven.surefire.testset.TestListResolverTest;
 import org.apache.maven.surefire.util.DefaultDirectoryScannerTest;
+import org.apache.maven.surefire.util.ReflectionUtilsTest;
 import org.apache.maven.surefire.util.RunOrderCalculatorTest;
 import org.apache.maven.surefire.util.RunOrderTest;
 import org.apache.maven.surefire.util.ScanResultTest;
 import org.apache.maven.surefire.util.TestsToRunTest;
 import org.apache.maven.surefire.util.UrlUtilsTest;
-import org.apache.maven.surefire.util.internal.ByteBufferTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
+import org.apache.maven.surefire.util.internal.ImmutableMapTest;
 import org.apache.maven.surefire.util.internal.StringUtilsTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -58,7 +59,6 @@
     RunResultTest.class,
     ResolvedTestTest.class,
     TestListResolverTest.class,
-    ByteBufferTest.class,
     ConcurrencyUtilsTest.class,
     StringUtilsTest.class,
     DefaultDirectoryScannerTest.class,
@@ -68,7 +68,9 @@
     TestsToRunTest.class,
     UrlUtilsTest.class,
     SpecificTestClassFilterTest.class,
-    FundamentalFilterTest.class
+    FundamentalFilterTest.class,
+    ImmutableMapTest.class,
+    ReflectionUtilsTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
index 8396d8b93..19740fdbf 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
@@ -117,6 +117,13 @@ public void testDataToByteArrayAndBack()
                     decoded = command.toDataTypeAsString( encoded );
                     assertNull( decoded );
                     break;
+                case  BYE_ACK:
+                    assertEquals( Void.class, command.getDataType() );
+                    encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 0 ) );
+                    decoded = command.toDataTypeAsString( encoded );
+                    assertNull( decoded );
+                    break;
                 default:
                     fail();
             }
@@ -150,4 +157,20 @@ public void testEncodedDecodedIsSameForRunClass()
         assertThat( command.getCommandType(), is( RUN_CLASS ) );
         assertThat( command.getData(), is( "pkg.Test" ) );
     }
+
+    public void testShouldDecodeTwoCommands() throws IOException
+    {
+        byte[] cmd1 = BYE_ACK.encode();
+        byte[] cmd2 = NOOP.encode();
+        byte[] stream = new byte[cmd1.length + cmd2.length];
+        System.arraycopy( cmd1, 0, stream, 0, cmd1.length );
+        System.arraycopy( cmd2, 0, stream, cmd1.length, cmd2.length );
+        DataInputStream is = new DataInputStream( new ByteArrayInputStream( stream ) );
+        Command bye = decode( is );
+        assertNotNull( bye );
+        assertThat( bye.getCommandType(), is( BYE_ACK ) );
+        Command noop = decode( is );
+        assertNotNull( noop );
+        assertThat( noop.getCommandType(), is( NOOP ) );
+    }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
index 84a445ddd..8394e00a1 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
@@ -29,10 +29,12 @@
     public void testShouldCreateFactoryWithoutException()
     {
         ReporterFactory factory = new ReporterFactory() {
+            @Override
             public RunListener createReporter() {
                 return null;
             }
 
+            @Override
             public RunResult close() {
                 return null;
             }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriterTest.java
index eb8187118..d81e8165b 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/report/LegacyPojoStackTraceWriterTest.java
@@ -136,6 +136,7 @@ public MockThrowable( String stackTrace )
             this.stackTrace = stackTrace;
         }
 
+        @Override
         public void printStackTrace( PrintWriter s )
         {
             s.write( stackTrace );
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/suite/RunResultTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/suite/RunResultTest.java
index 007a26cda..e888ca259 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/suite/RunResultTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/suite/RunResultTest.java
@@ -19,22 +19,13 @@
  * under the License.
  */
 
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.StringWriter;
-import org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter;
-import org.apache.maven.shared.utils.xml.Xpp3DomWriter;
-
 import junit.framework.TestCase;
 
 /**
  * @author Kristian Rosenvold
  */
 public class RunResultTest
-    extends TestCase
+        extends TestCase
 {
 
     public void testEmptySummaryShouldBeErrorFree()
@@ -49,76 +40,4 @@ public void testFailuresInFirstRun()
         RunResult resultTwo = new RunResult( 20, 0, 0, 0 );
         assertFalse( resultOne.aggregate( resultTwo ).isErrorFree() );
     }
-
-
-    public void testAggregatedValues()
-    {
-        RunResult simple = getSimpleAggregate();
-        assertEquals( 20, simple.getCompletedCount() );
-        assertEquals( 3, simple.getErrors() );
-        assertEquals( 7, simple.getFailures() );
-        assertEquals( 4, simple.getSkipped() );
-        assertEquals( 2, simple.getFlakes() );
-
-    }
-
-    public void testSerialization()
-        throws FileNotFoundException
-    {
-        writeReadCheck( getSimpleAggregate() );
-    }
-
-    public void testFailures()
-        throws FileNotFoundException
-    {
-        writeReadCheck( new RunResult( 0, 1, 2, 3, "stacktraceHere", false ) );
-    }
-
-    public void testSkipped()
-        throws FileNotFoundException
-    {
-        writeReadCheck( new RunResult( 3, 2, 1, 0, null, true ) );
-    }
-
-    public void testAppendSerialization()
-        throws IOException
-    {
-        RunResult simpleAggregate = getSimpleAggregate();
-        RunResult additional = new RunResult( 2, 1, 2, 2, null, true );
-        File summary = File.createTempFile( "failsafe", "test" );
-        simpleAggregate.writeSummary( summary, false, "utf-8" );
-        additional.writeSummary( summary, true, "utf-8" );
-
-        RunResult actual = RunResult.fromInputStream( new FileInputStream( summary ), "utf-8" );
-        RunResult expected = simpleAggregate.aggregate( additional );
-        assertEquals( expected, actual );
-        //noinspection ResultOfMethodCallIgnored
-        summary.delete();
-
-    }
-
-    private void writeReadCheck( RunResult simpleAggregate )
-        throws FileNotFoundException
-    {
-        StringWriter writer = getStringWriter( simpleAggregate );
-
-        RunResult actual =
-            RunResult.fromInputStream( new ByteArrayInputStream( writer.getBuffer().toString().getBytes() ), "UTF-8" );
-        assertEquals( simpleAggregate, actual );
-    }
-
-    private StringWriter getStringWriter( RunResult simpleAggregate )
-    {
-        StringWriter writer = new StringWriter();
-        PrettyPrintXMLWriter wr = new PrettyPrintXMLWriter( writer );
-        Xpp3DomWriter.write( wr, simpleAggregate.asXpp3Dom() );
-        return writer;
-    }
-
-    private RunResult getSimpleAggregate()
-    {
-        RunResult resultOne = new RunResult( 10, 1, 3, 2, 1 );
-        RunResult resultTwo = new RunResult( 10, 2, 4, 2, 1 );
-        return resultOne.aggregate( resultTwo );
-    }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/testset/FundamentalFilterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/testset/FundamentalFilterTest.java
index 911844837..b99d4f98c 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/testset/FundamentalFilterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/testset/FundamentalFilterTest.java
@@ -23,6 +23,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
 
+@SuppressWarnings( { "javadoc", "checkstyle:javadoctype" } )
 /**
  * Inclusive test patters:<p>
  *
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
new file mode 100644
index 000000000..5440d6eab
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
@@ -0,0 +1,116 @@
+package org.apache.maven.surefire.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Unit test for {@link ReflectionUtils}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class ReflectionUtilsTest
+{
+    @Test(expected = RuntimeException.class)
+    public void shouldNotInvokeStaticMethod()
+    {
+        ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "notCallable",
+                                                  new Class<?>[0], new Object[0] );
+    }
+
+    @Test
+    public void shouldInvokeStaticMethod()
+    {
+        Object o = ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "callable",
+                                                             new Class<?>[0], new Object[0] );
+        assertThat( o )
+                .isEqualTo( 3L );
+    }
+
+    @Test
+    public void shouldInvokeMethodChain()
+    {
+        Class<?>[] classes1 = { A.class, A.class };
+        String[] chain = { "current", "pid" };
+        Object o = ReflectionUtils.invokeMethodChain( classes1, chain, null );
+        assertThat( o )
+                .isEqualTo( 3L );
+
+        Class<?>[] classes2 = { A.class, A.class, B.class };
+        String[] longChain = { "current", "createB", "pid" };
+        o = ReflectionUtils.invokeMethodChain( classes2, longChain, null );
+        assertThat( o )
+                .isEqualTo( 1L );
+    }
+
+    @Test
+    public void shouldInvokeFallbackOnMethodChain()
+    {
+        Class<?>[] classes1 = { A.class, A.class };
+        String[] chain = { "current", "abc" };
+        Object o = ReflectionUtils.invokeMethodChain( classes1, chain, 5L );
+        assertThat( o )
+                .isEqualTo( 5L );
+
+        Class<?>[] classes2 = { A.class, B.class, B.class };
+        String[] longChain = { "current", "createB", "abc" };
+        o = ReflectionUtils.invokeMethodChain( classes2, longChain, 6L );
+        assertThat( o )
+                .isEqualTo( 6L );
+    }
+
+    private static void notCallable()
+    {
+    }
+
+    public static long callable()
+    {
+        return 3L;
+    }
+
+    public static class A
+    {
+        public static A current()
+        {
+            return new A();
+        }
+
+        public long pid()
+        {
+            return 3L;
+        }
+
+        public B createB()
+        {
+            return new B();
+        }
+    }
+
+    public static class B
+    {
+        public long pid()
+        {
+            return 1L;
+        }
+    }
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/UrlUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/UrlUtilsTest.java
index a9d7df060..d5ecc4681 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/UrlUtilsTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/UrlUtilsTest.java
@@ -19,12 +19,13 @@
  * under the License.
  */
 
+import junit.framework.TestCase;
+
 import java.io.File;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
+import java.net.URI;
 import java.net.URL;
 
-import junit.framework.TestCase;
+import static org.apache.maven.surefire.util.internal.UrlUtils.toURL;
 
 /**
  * Test the URL utilities.
@@ -34,6 +35,7 @@
 {
     private String homeDir;
 
+    @Override
     public void setUp()
         throws Exception
     {
@@ -55,28 +57,14 @@ private void verifyFileName( String fileName, String expectedFileName )
         throws Exception
     {
         File f = new File( homeDir, fileName );
-        URL u = UrlUtils.getURL( f );
+        URL u = toURL( f );
+        URI uri = u.toURI();
+        File urlFile = new File( uri );
         String url = u.toString();
+
         assertStartsWith( url, "file:" );
         assertEndsWith( url, expectedFileName );
-
-        try
-        {
-            // use reflection to do "URI uri = u.toURI()" if JDK 1.5+
-            Method toURI = URL.class.getMethod( "toURI", null );
-            Object uri = toURI.invoke( u, null );
-
-            // use reflection to do "File urlFile = new File( uri )" if JDK 1.4+
-            Constructor newFile = File.class.getConstructor( new Class[]{ uri.getClass() } );
-            File urlFile = (File) newFile.newInstance( uri );
-
-            assertEquals( f, urlFile );
-        }
-        catch ( NoSuchMethodException e )
-        {
-            // URL.toURI() method in JDK 1.5+, not available currently
-            // we won't be able to check for file equality...
-        }
+        assertEquals( f, urlFile );
     }
 
     private void assertStartsWith( String string, String substring )
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ConcurrencyUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ConcurrencyUtilsTest.java
index 516f8850d..7e76fbfa3 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ConcurrencyUtilsTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ConcurrencyUtilsTest.java
@@ -81,6 +81,7 @@ public void countDownShouldBeDecreasedByTwoThreadsModification()
 
         FutureTask<Boolean> task = new FutureTask<Boolean>( new Callable<Boolean>()
         {
+            @Override
             public Boolean call()
                 throws Exception
             {
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ImmutableMapTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ImmutableMapTest.java
new file mode 100644
index 000000000..b104cc35b
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ImmutableMapTest.java
@@ -0,0 +1,86 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.util.internal.ImmutableMap.Node;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @since 2.20
+ */
+public class ImmutableMapTest
+{
+    private ImmutableMap<String, String> map;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        Map<String, String> backingMap = new HashMap<String, String>();
+        backingMap.put( "a", "1" );
+        backingMap.put( "x", null );
+        backingMap.put( "b", "2" );
+        backingMap.put( "c", "3" );
+        backingMap.put( "", "" );
+        backingMap.put( null, "1" );
+        map = new ImmutableMap<String, String>( backingMap );
+    }
+
+    @Test
+    public void testEntrySet() throws Exception
+    {
+        Set<Entry<String, String>> entries = map.entrySet();
+        assertThat( entries, hasSize( 6 ) );
+        assertThat( entries, hasItem( new Node<String, String>( "a", "1" ) ) );
+        assertThat( entries, hasItem( new Node<String, String>( "x", null ) ) );
+        assertThat( entries, hasItem( new Node<String, String>( "b", "2" ) ) );
+        assertThat( entries, hasItem( new Node<String, String>( "c", "3" ) ) );
+        assertThat( entries, hasItem( new Node<String, String>( "", "" ) ) );
+        assertThat( entries, hasItem( new Node<String, String>( null, "1" ) ) );
+    }
+
+    @Test
+    public void testGetter()
+    {
+        assertThat( map.size(), is( 6 ) );
+        assertThat( map.get( "a" ), is( "1" ) );
+        assertThat( map.get( "x" ), is( (String) null ) );
+        assertThat( map.get( "b" ), is( "2" ) );
+        assertThat( map.get( "c" ), is( "3" ) );
+        assertThat( map.get( "" ), is( "" ) );
+        assertThat( map.get( null ), is( "1" ) );
+    }
+
+    @Test( expected = UnsupportedOperationException.class )
+    public void shouldNotModifyEntries()
+    {
+        map.entrySet().clear();
+    }
+}
\ No newline at end of file
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 4ded62878..d20fbddde 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -23,7 +23,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>surefire-booter</artifactId>
@@ -35,11 +35,52 @@
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>surefire-api</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.maven.shared</groupId>
+          <artifactId>maven-shared-utils</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-shared-utils</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-mockito-release-full</artifactId>
+      <classifier>full</classifier>
+      <scope>test</scope>
     </dependency>
   </dependencies>
 
   <build>
     <plugins>
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>build-test-classpath</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>build-classpath</goal>
+            </goals>
+            <configuration>
+              <includeScope>test</includeScope>
+              <outputFile>target/test-classpath/cp.txt</outputFile>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <dependencies>
@@ -50,6 +91,7 @@
           </dependency>
         </dependencies>
         <configuration>
+          <jvm>${jdk.home}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
@@ -69,13 +111,18 @@
               <minimizeJar>true</minimizeJar>
               <artifactSet>
                 <includes>
-                  <include>commons-lang:commons-lang</include>
+                  <include>org.apache.commons:commons-lang3</include>
+                  <include>commons-io:commons-io</include>
                 </includes>
               </artifactSet>
               <relocations>
                 <relocation>
-                  <pattern>org.apache.commons.lang</pattern>
-                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern>
+                  <pattern>org.apache.commons.lang3</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.io</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern>
                 </relocation>
               </relocations>
             </configuration>
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
index c21edf802..3551910aa 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
@@ -56,4 +56,5 @@ private BooterConstants()
     public static final String FAIL_FAST_COUNT = "failFastCount";
     public static final String SHUTDOWN = "shutdown";
     public static final String SYSTEM_EXIT_TIMEOUT = "systemExitTimeout";
+    public static final String PLUGIN_PID = "pluginPid";
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
index bb641158c..75aad1f52 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
@@ -37,12 +37,12 @@
 
 /**
  * Knows how to serialize and deserialize the booter configuration.
- * <p/>
+ * <br>
  * The internal serialization format is through a properties file. The long-term goal of this
  * class is not to expose this implementation information to its clients. This still leaks somewhat,
  * and there are some cases where properties are being accessed as "Properties" instead of
  * more representative domain objects.
- * <p/>
+ * <br>
  *
  * @author Jason van Zyl
  * @author Emmanuel Venisse
@@ -58,6 +58,14 @@ public BooterDeserializer( InputStream inputStream )
         properties = SystemPropertyManager.loadProperties( inputStream );
     }
 
+    /**
+     * @return PID of Maven process where plugin is executed; or null if PID could not be determined.
+     */
+    public Long getPluginPid()
+    {
+        return properties.getLongProperty( PLUGIN_PID );
+    }
+
     public ProviderConfiguration deserialize()
     {
         final File reportsDirectory = new File( properties.getProperty( REPORTSDIRECTORY ) );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
index d03fed1a2..7b22712b3 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
@@ -19,8 +19,6 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.util.UrlUtils;
-
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -32,6 +30,7 @@
 import java.util.List;
 
 import static java.io.File.pathSeparatorChar;
+import static org.apache.maven.surefire.util.internal.UrlUtils.toURL;
 
 /**
  * An ordered list of classpath elements with set behaviour
@@ -85,7 +84,7 @@ public Classpath( Collection<String> elements )
         for ( String element : elements )
         {
             element = element.trim();
-            if ( element.length() != 0 )
+            if ( !element.isEmpty() )
             {
                 newCp.add( element );
             }
@@ -115,6 +114,9 @@ public Classpath addClassPathElementUrl( String path )
     /**
      * @deprecated this should be package private method which returns List of Files. It will be
      * removed in the next major version.
+     *
+     * @return list of {@link URL jar files paths} with {@code file} protocol in URL.
+     * @throws MalformedURLException if {@link URL} could not be created upon given class-path element(s)
      */
     @Deprecated
     public List<URL> getAsUrlList()
@@ -124,7 +126,7 @@ public Classpath addClassPathElementUrl( String path )
         for ( String url : unmodifiableElements )
         {
             File f = new File( url );
-            urls.add( UrlUtils.getURL( f ) );
+            urls.add( toURL( f ) );
         }
         return urls;
     }
@@ -140,6 +142,7 @@ public void writeToSystemProperty( String propertyName )
         System.setProperty( propertyName, sb.toString() );
     }
 
+    @Override
     public boolean equals( Object o )
     {
         if ( this == o )
@@ -156,16 +159,16 @@ public boolean equals( Object o )
         return unmodifiableElements.equals( classpath.unmodifiableElements );
     }
 
-    public ClassLoader createClassLoader( ClassLoader parent, boolean childDelegation, boolean enableAssertions,
-                                          String roleName )
+    public ClassLoader createClassLoader( boolean childDelegation, boolean enableAssertions, String roleName )
         throws SurefireExecutionException
     {
         try
         {
+            ClassLoader parent = SystemUtils.platformClassLoader();
             IsolatedClassLoader classLoader = new IsolatedClassLoader( parent, childDelegation, roleName );
-            for ( URL url : getAsUrlList() )
+            for ( String classPathElement : unmodifiableElements )
             {
-                classLoader.addURL( url );
+                classLoader.addURL( new File( classPathElement ).toURL() );
             }
             if ( parent != null )
             {
@@ -180,7 +183,7 @@ public ClassLoader createClassLoader( ClassLoader parent, boolean childDelegatio
         }
     }
 
-
+    @Override
     public int hashCode()
     {
         return unmodifiableElements.hashCode();
@@ -225,6 +228,7 @@ public String getCompactLogMessage( String descriptor )
         return result.toString();
     }
 
+    @Override
     public Iterator<String> iterator()
     {
         return unmodifiableElements.iterator();
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ClasspathConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ClasspathConfiguration.java
index d582f3c7e..0e843150d 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ClasspathConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ClasspathConfiguration.java
@@ -21,7 +21,7 @@
 
 /**
  * Represents the classpaths for the BooterConfiguration.
- * <p/>
+ * <br>
  *
  * @author Jason van Zyl
  * @author Emmanuel Venisse
@@ -82,7 +82,7 @@ public ClassLoader createMergedClassLoader()
         throws SurefireExecutionException
     {
         return Classpath.join( inprocClasspath, classpathUrls )
-            .createClassLoader( null, this.childDelegation, enableAssertions, "test" );
+            .createClassLoader( childDelegation, enableAssertions, "test" );
     }
 
     public Classpath getProviderClasspath()
@@ -90,8 +90,7 @@ public Classpath getProviderClasspath()
         return surefireClasspathUrls;
     }
 
-
-        public Classpath getTestClasspath()
+    public Classpath getTestClasspath()
     {
         return classpathUrls;
     }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 98040db30..d00abc55c 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -19,33 +19,34 @@
  * under the License.
  */
 
+import org.apache.maven.surefire.providerapi.ProviderParameters;
+import org.apache.maven.surefire.providerapi.SurefireProvider;
+import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.TestSetFailedException;
+
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.lang.management.ManagementFactory;
 import java.lang.reflect.InvocationTargetException;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.maven.surefire.providerapi.ProviderParameters;
-import org.apache.maven.surefire.providerapi.SurefireProvider;
-import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
-import org.apache.maven.surefire.report.ReporterFactory;
-import org.apache.maven.surefire.report.StackTraceWriter;
-import org.apache.maven.surefire.suite.RunResult;
-import org.apache.maven.surefire.testset.TestSetFailedException;
-
-import static java.lang.System.err;
-import static java.lang.System.out;
-import static java.lang.System.setErr;
-import static java.lang.System.setOut;
+import static java.lang.Math.max;
 import static java.lang.Thread.currentThread;
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
-import static org.apache.maven.surefire.booter.Shutdown.EXIT;
-import static org.apache.maven.surefire.booter.Shutdown.KILL;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR;
 import static org.apache.maven.surefire.booter.ForkingRunListener.encode;
@@ -53,13 +54,12 @@
 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg;
 import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
-import static java.util.concurrent.TimeUnit.SECONDS;
 
 /**
  * The part of the booter that is unique to a forked vm.
- * <p/>
+ * <br>
  * Deals with deserialization of the booter wire-level protocol
- * <p/>
+ * <br>
  *
  * @author Jason van Zyl
  * @author Emmanuel Venisse
@@ -67,121 +67,164 @@
  */
 public final class ForkedBooter
 {
-    private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30;
-    private static final long PING_TIMEOUT_IN_SECONDS = 20;
-    private static final ScheduledExecutorService JVM_TERMINATOR = createJvmTerminator();
+    private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30L;
+    private static final long PING_TIMEOUT_IN_SECONDS = 30L;
+    private static final long ONE_SECOND_IN_MILLIS = 1000L;
 
-    private static volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
+    private final CommandReader commandReader = CommandReader.getReader();
+    private final PrintStream originalOut = System.out;
 
-    /**
-     * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
-     * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
-     *
-     * @param args Commandline arguments
-     */
-    public static void main( String... args )
+    private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
+    private volatile PingScheduler pingScheduler;
+
+    private ScheduledThreadPoolExecutor jvmTerminator;
+    private ProviderConfiguration providerConfiguration;
+    private StartupConfiguration startupConfiguration;
+    private Object testSet;
+
+    private void setupBooter( String tmpDir, String dumpFileName, String surefirePropsFileName,
+                              String effectiveSystemPropertiesFileName )
+            throws IOException, SurefireExecutionException
     {
-        final CommandReader reader = startupMasterProcessReader();
-        final ScheduledFuture<?> pingScheduler = listenToShutdownCommands( reader );
-        final PrintStream originalOut = out;
-        try
-        {
-            if ( args.length > 1 )
-            {
-                setSystemProperties( new File( args[1] ) );
-            }
+        BooterDeserializer booterDeserializer =
+                new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) );
+        // todo: print PID in debug console logger in version 2.20.2
+        pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() );
+        setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) );
 
-            File surefirePropertiesFile = new File( args[0] );
-            InputStream stream = surefirePropertiesFile.exists() ? new FileInputStream( surefirePropertiesFile ) : null;
-            BooterDeserializer booterDeserializer = new BooterDeserializer( stream );
-            ProviderConfiguration providerConfiguration = booterDeserializer.deserialize();
-            final StartupConfiguration startupConfiguration = booterDeserializer.getProviderConfiguration();
-            systemExitTimeoutInSeconds =
-                    providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
-            TypeEncodedValue forkedTestSet = providerConfiguration.getTestForFork();
-            boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream();
-
-            final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
-            if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
-            {
-                classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
-            }
+        providerConfiguration = booterDeserializer.deserialize();
+        DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() );
 
-            ClassLoader classLoader = currentThread().getContextClassLoader();
-            classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
-            startupConfiguration.writeSurefireTestClasspathProperty();
+        startupConfiguration = booterDeserializer.getProviderConfiguration();
+        systemExitTimeoutInSeconds =
+                providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
 
-            final Object testSet;
-            if ( forkedTestSet != null )
-            {
-                testSet = forkedTestSet.getDecodedValue( classLoader );
-            }
-            else if ( readTestsFromInputStream )
-            {
-                testSet = new LazyTestsToRun( originalOut );
-            }
-            else
-            {
-                testSet = null;
-            }
+        ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
+        if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
+        {
+            classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
+        }
 
-            try
-            {
-                runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut );
-            }
-            catch ( InvocationTargetException t )
-            {
-                StackTraceWriter stackTraceWriter =
+        ClassLoader classLoader = currentThread().getContextClassLoader();
+        classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
+        startupConfiguration.writeSurefireTestClasspathProperty();
+        testSet = createTestSet( providerConfiguration.getTestForFork(),
+                                       providerConfiguration.isReadTestsFromInStream(), classLoader );
+    }
+
+    private void execute()
+    {
+        try
+        {
+            runSuitesInProcess();
+        }
+        catch ( InvocationTargetException t )
+        {
+            DumpErrorSingleton.getSingleton().dumpException( t );
+            StackTraceWriter stackTraceWriter =
                     new LegacyPojoStackTraceWriter( "test subsystem", "no method", t.getTargetException() );
-                StringBuilder stringBuilder = new StringBuilder();
-                encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut );
-            }
-            catch ( Throwable t )
-            {
-                StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
-                StringBuilder stringBuilder = new StringBuilder();
-                encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut );
-            }
-            // Say bye.
-            encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
-            originalOut.flush();
-            // noinspection CallToSystemExit
-            exit( 0, EXIT, reader, false );
+            StringBuilder stringBuilder = new StringBuilder();
+            encode( stringBuilder, stackTraceWriter, false );
+            encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
         }
         catch ( Throwable t )
         {
-            // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
-            // noinspection UseOfSystemOutOrSystemErr
-            t.printStackTrace( err );
-            // noinspection ProhibitedExceptionThrown,CallToSystemExit
-            exit( 1, EXIT, reader, false );
+            DumpErrorSingleton.getSingleton().dumpException( t );
+            StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
+            StringBuilder stringBuilder = new StringBuilder();
+            encode( stringBuilder, stackTraceWriter, false );
+            encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
+        }
+        acknowledgedExit();
+    }
+
+    private Object createTestSet( TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl )
+    {
+        if ( forkedTestSet != null )
+        {
+            return forkedTestSet.getDecodedValue( cl );
         }
-        finally
+        else if ( readTestsFromCommandReader )
         {
-            pingScheduler.cancel( true );
+            return new LazyTestsToRun( originalOut );
         }
+        return null;
     }
 
-    private static CommandReader startupMasterProcessReader()
+    private void cancelPingScheduler()
     {
-        return getReader();
+        if ( pingScheduler != null )
+        {
+            try
+            {
+                AccessController.doPrivileged( new PrivilegedAction<Object>()
+                                               {
+                                                   @Override
+                                                   public Object run()
+                                                   {
+                                                       pingScheduler.shutdown();
+                                                       return null;
+                                                   }
+                                               }
+                );
+            }
+            catch ( AccessControlException e )
+            {
+                // ignore
+            }
+        }
     }
 
-    private static ScheduledFuture<?> listenToShutdownCommands( CommandReader reader )
+    private PingScheduler listenToShutdownCommands( Long pluginPid )
     {
-        reader.addShutdownListener( createExitHandler( reader ) );
+        commandReader.addShutdownListener( createExitHandler() );
         AtomicBoolean pingDone = new AtomicBoolean( true );
-        reader.addNoopListener( createPingHandler( pingDone ) );
-        return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone, reader ),
-                                                   0, PING_TIMEOUT_IN_SECONDS, SECONDS );
+        commandReader.addNoopListener( createPingHandler( pingDone ) );
+
+        PingScheduler pingMechanisms = new PingScheduler( createPingScheduler(),
+                                                          pluginPid == null ? null : new PpidChecker( pluginPid ) );
+        if ( pingMechanisms.pluginProcessChecker != null )
+        {
+            Runnable checkerJob = processCheckerJob( pingMechanisms );
+            pingMechanisms.pingScheduler.scheduleWithFixedDelay( checkerJob, 0L, 1L, SECONDS );
+        }
+        Runnable pingJob = createPingJob( pingDone, pingMechanisms.pluginProcessChecker );
+        pingMechanisms.pingScheduler.scheduleAtFixedRate( pingJob, 0L, PING_TIMEOUT_IN_SECONDS, SECONDS );
+
+        return pingMechanisms;
+    }
+
+    private Runnable processCheckerJob( final PingScheduler pingMechanism )
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    if ( pingMechanism.pluginProcessChecker.canUse()
+                                 && !pingMechanism.pluginProcessChecker.isProcessAlive()
+                                 && !pingMechanism.pingScheduler.isShutdown() )
+                    {
+                        DumpErrorSingleton.getSingleton().dumpText( "Killing self fork JVM. Maven process died." );
+                        kill();
+                    }
+                }
+                catch ( Exception e )
+                {
+                    DumpErrorSingleton.getSingleton()
+                            .dumpText( "System.exit() or native command error interrupted process checker." );
+                }
+            }
+        };
     }
 
-    private static CommandListener createPingHandler( final AtomicBoolean pingDone )
+    private CommandListener createPingHandler( final AtomicBoolean pingDone )
     {
         return new CommandListener()
         {
+            @Override
             public void update( Command command )
             {
                 pingDone.set( true );
@@ -189,132 +232,150 @@ public void update( Command command )
         };
     }
 
-    private static CommandListener createExitHandler( final CommandReader reader )
+    private CommandListener createExitHandler()
     {
         return new CommandListener()
         {
+            @Override
             public void update( Command command )
             {
-                exit( 1, command.toShutdownData(), reader, true );
+                Shutdown shutdown = command.toShutdownData();
+                if ( shutdown.isKill() )
+                {
+                    DumpErrorSingleton.getSingleton()
+                            .dumpText( "Killing self fork JVM. Received SHUTDOWN command from Maven shutdown hook." );
+                    kill();
+                }
+                else if ( shutdown.isExit() )
+                {
+                    cancelPingScheduler();
+                    DumpErrorSingleton.getSingleton()
+                            .dumpText( "Exiting self fork JVM. Received SHUTDOWN command from Maven shutdown hook." );
+                    exit( 1 );
+                }
+                // else refers to shutdown=testset, but not used now, keeping reader open
             }
         };
     }
 
-    private static Runnable createPingJob( final AtomicBoolean pingDone, final CommandReader reader  )
+    private Runnable createPingJob( final AtomicBoolean pingDone, final PpidChecker pluginProcessChecker  )
     {
         return new Runnable()
         {
+            @Override
             public void run()
             {
-                boolean hasPing = pingDone.getAndSet( false );
-                if ( !hasPing )
+                if ( !canUseNewPingMechanism( pluginProcessChecker ) )
                 {
-                    exit( 1, KILL, reader, true );
+                    boolean hasPing = pingDone.getAndSet( false );
+                    if ( !hasPing )
+                    {
+                        DumpErrorSingleton.getSingleton().dumpText( "Killing self fork JVM. PING timeout elapsed." );
+                        kill();
+                    }
                 }
             }
         };
     }
 
-    private static void encodeAndWriteToOutput( String string, PrintStream out )
+    private void encodeAndWriteToOutput( String string )
     {
         byte[] encodeBytes = encodeStringForForkCommunication( string );
-        out.write( encodeBytes, 0, encodeBytes.length );
+        //noinspection SynchronizationOnLocalVariableOrMethodParameter
+        synchronized ( originalOut )
+        {
+            originalOut.write( encodeBytes, 0, encodeBytes.length );
+            originalOut.flush();
+        }
     }
 
-    private static void exit( int returnCode, Shutdown shutdownType, CommandReader reader, boolean stopReaderOnExit )
+    private void kill()
     {
-        switch ( shutdownType )
-        {
-            case KILL:
-                Runtime.getRuntime().halt( returnCode );
-            case EXIT:
-                if ( stopReaderOnExit )
-                {
-                    reader.stop();
-                }
-                launchLastDitchDaemonShutdownThread( returnCode );
-                System.exit( returnCode );
-            case DEFAULT:
-                // refers to shutdown=testset, but not used now, keeping reader open
-            default:
-                break;
-        }
+        kill( 1 );
     }
 
-    private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
-                                                 ProviderConfiguration providerConfiguration,
-                                                 PrintStream originalSystemOut )
-        throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
+    private void kill( int returnCode )
+    {
+        commandReader.stop();
+        Runtime.getRuntime().halt( returnCode );
+    }
+
+    private void exit( int returnCode )
+    {
+        launchLastDitchDaemonShutdownThread( returnCode );
+        System.exit( returnCode );
+    }
+
+    private void acknowledgedExit()
     {
-        final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );
+        final Semaphore barrier = new Semaphore( 0 );
+        commandReader.addByeAckListener( new CommandListener()
+                                          {
+                                              @Override
+                                              public void update( Command command )
+                                              {
+                                                  barrier.release();
+                                              }
+                                          }
+        );
+        encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n" );
+        launchLastDitchDaemonShutdownThread( 0 );
+        long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
+        acquireOnePermit( barrier, timeoutMillis );
+        cancelPingScheduler();
+        commandReader.stop();
+        System.exit( 0 );
+    }
 
-        return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
-                                                      false );
+    private RunResult runSuitesInProcess()
+        throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
+    {
+        ForkingReporterFactory factory = createForkingReporterFactory();
+        return invokeProviderInSameClassLoader( factory );
     }
 
-    private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
-                                                                 PrintStream originalSystemOut )
+    private ForkingReporterFactory createForkingReporterFactory()
     {
         final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
-        return new ForkingReporterFactory( trimStackTrace, originalSystemOut );
+        return new ForkingReporterFactory( trimStackTrace, originalOut );
     }
 
-    private static ScheduledExecutorService createJvmTerminator()
+    private synchronized ScheduledThreadPoolExecutor getJvmTerminator()
     {
-        ThreadFactory threadFactory = newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-"
-                                                            + systemExitTimeoutInSeconds
-                                                            + "s" );
-        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
-        executor.setMaximumPoolSize( 1 );
-        executor.prestartCoreThread();
-        return executor;
+        if ( jvmTerminator == null )
+        {
+            ThreadFactory threadFactory =
+                    newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-" + systemExitTimeoutInSeconds + "s" );
+            jvmTerminator = new ScheduledThreadPoolExecutor( 1, threadFactory );
+            jvmTerminator.setMaximumPoolSize( 1 );
+        }
+        return jvmTerminator;
     }
 
     @SuppressWarnings( "checkstyle:emptyblock" )
-    private static void launchLastDitchDaemonShutdownThread( final int returnCode )
+    private void launchLastDitchDaemonShutdownThread( final int returnCode )
     {
-        JVM_TERMINATOR.schedule( new Runnable()
-        {
-            public void run()
-            {
-                Runtime.getRuntime().halt( returnCode );
-            }
-        }, systemExitTimeoutInSeconds, SECONDS );
+        getJvmTerminator().schedule( new Runnable()
+                                        {
+                                            @Override
+                                            public void run()
+                                            {
+                                                kill( returnCode );
+                                            }
+                                        }, systemExitTimeoutInSeconds, SECONDS
+        );
     }
 
-    private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory,
-                                                              ProviderConfiguration providerConfig,
-                                                              boolean insideFork,
-                                                              StartupConfiguration startupConfig,
-                                                              boolean restoreStreams )
+    private RunResult invokeProviderInSameClassLoader( ForkingReporterFactory factory )
         throws TestSetFailedException, InvocationTargetException
     {
-        final PrintStream orgSystemOut = out;
-        final PrintStream orgSystemErr = err;
-        // Note that System.out/System.err are also read in the "ReporterConfiguration" instatiation
-        // in createProvider below. These are the same values as here.
-
-        try
-        {
-            return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfig, factory )
-                           .invoke( testSet );
-        }
-        finally
-        {
-            if ( restoreStreams && System.getSecurityManager() == null )
-            {
-                setOut( orgSystemOut );
-                setErr( orgSystemErr );
-            }
-        }
+        return createProviderInCurrentClassloader( factory )
+                       .invoke( testSet );
     }
 
-    private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration,
-                                                                        boolean isInsideFork,
-                                                                       ProviderConfiguration providerConfiguration,
-                                                                       Object reporterManagerFactory )
+    private SurefireProvider createProviderInCurrentClassloader( ForkingReporterFactory reporterManagerFactory )
     {
-        BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, isInsideFork );
+        BaseProviderFactory bpf = new BaseProviderFactory( reporterManagerFactory, true );
         bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
         bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
         ClassLoader classLoader = currentThread().getContextClassLoader();
@@ -330,4 +391,98 @@ private static SurefireProvider createProviderInCurrentClassloader( StartupConfi
         String providerClass = startupConfiguration.getActualClassName();
         return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
     }
+
+    /**
+     * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
+     * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
+     *
+     * @param args Commandline arguments
+     */
+    public static void main( String... args )
+    {
+        ForkedBooter booter = new ForkedBooter();
+        try
+        {
+            booter.setupBooter( args[0], args[1], args[2], args.length > 3 ? args[3] : null );
+            booter.execute();
+        }
+        catch ( Throwable t )
+        {
+            DumpErrorSingleton.getSingleton().dumpException( t );
+            t.printStackTrace();
+            booter.cancelPingScheduler();
+            booter.exit( 1 );
+        }
+    }
+
+    private static boolean canUseNewPingMechanism( PpidChecker pluginProcessChecker )
+    {
+        return pluginProcessChecker != null && pluginProcessChecker.canUse();
+    }
+
+    private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
+    {
+        try
+        {
+            return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
+        }
+        catch ( InterruptedException e )
+        {
+            return true;
+        }
+    }
+
+    private static ScheduledExecutorService createPingScheduler()
+    {
+        ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" );
+        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
+        executor.setKeepAliveTime( 3L, SECONDS );
+        executor.setMaximumPoolSize( 2 );
+        return executor;
+    }
+
+    private static InputStream createSurefirePropertiesIfFileExists( String tmpDir, String propFileName )
+            throws FileNotFoundException
+    {
+        File surefirePropertiesFile = new File( tmpDir, propFileName );
+        return surefirePropertiesFile.exists() ? new FileInputStream( surefirePropertiesFile ) : null;
+    }
+
+    private static boolean isDebugging()
+    {
+        for ( String argument : ManagementFactory.getRuntimeMXBean().getInputArguments() )
+        {
+            if ( "-Xdebug".equals( argument ) || argument.startsWith( "-agentlib:jdwp" ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static class PingScheduler
+    {
+        private final ScheduledExecutorService pingScheduler;
+        private final PpidChecker pluginProcessChecker;
+
+        PingScheduler( ScheduledExecutorService pingScheduler, PpidChecker pluginProcessChecker )
+        {
+            this.pingScheduler = pingScheduler;
+            this.pluginProcessChecker = pluginProcessChecker;
+        }
+
+        void shutdown()
+        {
+            pingScheduler.shutdown();
+            if ( pluginProcessChecker != null )
+            {
+                pluginProcessChecker.destroyActiveCommands();
+            }
+        }
+
+        boolean isShutdown()
+        {
+            return pingScheduler.isShutdown();
+        }
+    }
 }
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/IsolatedClassLoader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/IsolatedClassLoader.java
index 31db087a2..601acd564 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/IsolatedClassLoader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/IsolatedClassLoader.java
@@ -25,7 +25,7 @@
 import java.util.Set;
 
 /**
- * @noinspection CustomClassloader
+ * Loads classes from jar files added via {@link #addURL(URL)}.
  */
 public class IsolatedClassLoader
     extends URLClassLoader
@@ -53,6 +53,7 @@ public IsolatedClassLoader( ClassLoader parent, boolean childDelegation, String
      * @deprecated this method will use {@link java.io.File} instead of {@link URL} in the next
      * major version.
      */
+    @Override
     @Deprecated
     public void addURL( URL url )
     {
@@ -65,6 +66,7 @@ public void addURL( URL url )
         }
     }
 
+    @Override
     public synchronized Class loadClass( String name )
         throws ClassNotFoundException
     {
@@ -99,6 +101,7 @@ public synchronized Class loadClass( String name )
         }
     }
 
+    @Override
     public String toString()
     {
         return "IsolatedClassLoader{roleName='" + roleName + "'}";
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
index 29a59b67c..3237d077f 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
@@ -63,16 +63,19 @@
     {
         private final Iterator<String> it = getReader().getIterableClasses( originalOutStream ).iterator();
 
+        @Override
         public boolean hasNext()
         {
             return it.hasNext();
         }
 
+        @Override
         public Class<?> next()
         {
             return findClass( it.next() );
         }
 
+        @Override
         public void remove()
         {
             throw new UnsupportedOperationException();
@@ -93,6 +96,7 @@ public void remove()
      * {@inheritDoc}
      * @see org.apache.maven.surefire.util.TestsToRun#iterator()
      * */
+    @Override
     public Iterator<Class<?>> iterator()
     {
         return new BlockingIterator();
@@ -102,6 +106,7 @@ public void remove()
      * {@inheritDoc}
       * @see org.apache.maven.surefire.util.TestsToRun#toString()
       */
+    @Override
     public String toString()
     {
         return "LazyTestsToRun";
@@ -111,6 +116,7 @@ public String toString()
      * {@inheritDoc}
      * @see org.apache.maven.surefire.util.TestsToRun#allowEagerReading()
      */
+    @Override
     public boolean allowEagerReading()
     {
         return false;
@@ -153,6 +159,7 @@ protected void doRemove()
             {
             }
 
+            @Override
             public void remove()
             {
                 throw new UnsupportedOperationException( "unsupported remove" );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
new file mode 100644
index 000000000..7c2758455
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
@@ -0,0 +1,305 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Queue;
+import java.util.Scanner;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.Long.parseLong;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.regex.Pattern.compile;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.maven.surefire.booter.ProcessInfo.ERR_PROCESS_INFO;
+import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO;
+
+/**
+ * Recognizes PID of Plugin process and determines lifetime.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+final class PpidChecker
+{
+    private static final String WMIC_CREATION_DATE = "CreationDate";
+
+    private final Queue<Process> destroyableCommands = new ConcurrentLinkedQueue<Process>();
+
+    /**
+     * The etime is in the form of [[dd-]hh:]mm:ss on Unix like systems.
+     */
+    static final Pattern UNIX_CMD_OUT_PATTERN = compile( "^(((\\d+)-)?(\\d{2}):)?(\\d{2}):(\\d{2})$" );
+
+    private final long pluginPid;
+
+    private volatile ProcessInfo pluginProcessInfo;
+    private volatile boolean stopped;
+
+    PpidChecker( long pluginPid )
+    {
+        this.pluginPid = pluginPid;
+    }
+
+    boolean canUse()
+    {
+        return pluginProcessInfo == null
+                       ? IS_OS_WINDOWS || IS_OS_UNIX
+                       : pluginProcessInfo.isValid() && !pluginProcessInfo.isError();
+    }
+
+    /**
+     * This method can be called only after {@link #canUse()} has returned {@code true}.
+     *
+     * @return {@code true} if parent process is alive; {@code false} otherwise
+     * @throws IllegalStateException if {@link #canUse()} returns {@code false}
+     *                               or the object has been {@link #destroyActiveCommands() destroyed}
+     */
+    @SuppressWarnings( "unchecked" )
+    boolean isProcessAlive()
+    {
+        if ( !canUse() )
+        {
+            throw new IllegalStateException( "irrelevant to call isProcessAlive()" );
+        }
+
+        if ( IS_OS_WINDOWS )
+        {
+            ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
+            pluginProcessInfo = windows();
+            if ( isStopped() || pluginProcessInfo.isError() )
+            {
+                throw new IllegalStateException( "error to read process" );
+            }
+            // let's compare creation time, should be same unless killed or PID is reused by OS into another process
+            return pluginProcessInfo.isValid()
+                           && ( previousPluginProcessInfo == null
+                                        || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo ) );
+        }
+        else if ( IS_OS_UNIX )
+        {
+            ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
+            pluginProcessInfo = unix();
+            if ( isStopped() || pluginProcessInfo.isError() )
+            {
+                throw new IllegalStateException( "error to read process" );
+            }
+            // let's compare elapsed time, should be greater or equal if parent process is the same and still alive
+            return pluginProcessInfo.isValid()
+                           && ( previousPluginProcessInfo == null
+                                        || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo )
+                                        || pluginProcessInfo.isTimeAfter( previousPluginProcessInfo ) );
+        }
+
+        throw new IllegalStateException();
+    }
+
+    // https://www.freebsd.org/cgi/man.cgi?ps(1)
+    // etimes elapsed running time, in decimal integer seconds
+
+    // http://manpages.ubuntu.com/manpages/xenial/man1/ps.1.html
+    // etimes elapsed time since the process was started, in seconds.
+
+    // http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/test/java/lang/ProcessBuilder/Basic.java#L167
+    ProcessInfo unix()
+    {
+        ProcessInfoConsumer reader = new ProcessInfoConsumer( Charset.defaultCharset().name() )
+        {
+            @Override
+            ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
+            {
+                if ( !previousProcessInfo.isValid() )
+                {
+                    Matcher matcher = UNIX_CMD_OUT_PATTERN.matcher( line );
+                    if ( matcher.matches() )
+                    {
+                        long pidUptime = fromDays( matcher )
+                                                 + fromHours( matcher )
+                                                 + fromMinutes( matcher )
+                                                 + fromSeconds( matcher );
+                        return ProcessInfo.unixProcessInfo( pluginPid, pidUptime );
+                    }
+                }
+                return previousProcessInfo;
+            }
+        };
+
+        return reader.execute( "/bin/sh", "-c", unixPathToPS() + " -o etime= -p " + pluginPid );
+    }
+
+    ProcessInfo windows()
+    {
+        ProcessInfoConsumer reader = new ProcessInfoConsumer( "US-ASCII" )
+        {
+            private boolean hasHeader;
+
+            @Override
+            ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
+            {
+                if ( !previousProcessInfo.isValid() )
+                {
+                    StringTokenizer args = new StringTokenizer( line );
+                    if ( args.countTokens() == 1 )
+                    {
+                        if ( hasHeader )
+                        {
+                            String startTimestamp = args.nextToken();
+                            return ProcessInfo.windowsProcessInfo( pluginPid, startTimestamp );
+                        }
+                        else
+                        {
+                            hasHeader = WMIC_CREATION_DATE.equals( args.nextToken() );
+                        }
+                    }
+                }
+                return previousProcessInfo;
+            }
+        };
+        String pid = String.valueOf( pluginPid );
+        return reader.execute( "CMD", "/A", "/X", "/C",
+                                     "wmic process where (ProcessId=" + pid + ") get " + WMIC_CREATION_DATE
+        );
+    }
+
+    void destroyActiveCommands()
+    {
+        stopped = true;
+        for ( Process p = destroyableCommands.poll(); p != null; p = destroyableCommands.poll() )
+        {
+            p.destroy();
+        }
+    }
+
+    private boolean isStopped()
+    {
+        return stopped;
+    }
+
+    static String unixPathToPS()
+    {
+        return new File( "/usr/bin/ps" ).canExecute() ? "/usr/bin/ps" : "/bin/ps";
+    }
+
+    static long fromDays( Matcher matcher )
+    {
+        String s = matcher.group( 3 );
+        return s == null ? 0L : DAYS.toSeconds( parseLong( s ) );
+    }
+
+    static long fromHours( Matcher matcher )
+    {
+        String s = matcher.group( 4 );
+        return s == null ? 0L : HOURS.toSeconds( parseLong( s ) );
+    }
+
+    static long fromMinutes( Matcher matcher )
+    {
+        String s = matcher.group( 5 );
+        return s == null ? 0L : MINUTES.toSeconds( parseLong( s ) );
+    }
+
+    static long fromSeconds( Matcher matcher )
+    {
+        String s = matcher.group( 6 );
+        return s == null ? 0L : parseLong( s );
+    }
+
+    private static void checkValid( Scanner scanner )
+            throws IOException
+    {
+        IOException exception = scanner.ioException();
+        if ( exception != null )
+        {
+            throw exception;
+        }
+    }
+
+    /**
+     * Reads standard output from {@link Process}.
+     * <br>
+     * The artifact maven-shared-utils has non-daemon Threads which is an issue in Surefire to satisfy System.exit.
+     * This implementation is taylor made without using any Thread.
+     * It's easy to destroy Process from other Thread.
+     */
+    private abstract class ProcessInfoConsumer
+    {
+        private final String charset;
+
+        ProcessInfoConsumer( String charset )
+        {
+            this.charset = charset;
+        }
+
+        abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo );
+
+        ProcessInfo execute( String... command )
+        {
+            ProcessBuilder processBuilder = new ProcessBuilder( command );
+            processBuilder.redirectErrorStream( true );
+            Process process = null;
+            ProcessInfo processInfo = INVALID_PROCESS_INFO;
+            try
+            {
+                process = processBuilder.start();
+                destroyableCommands.add( process );
+                Scanner scanner = new Scanner( process.getInputStream(), charset );
+                while ( scanner.hasNextLine() )
+                {
+                    String line = scanner.nextLine().trim();
+                    processInfo = consumeLine( line, processInfo );
+                }
+                checkValid( scanner );
+                int exitCode = process.waitFor();
+                return exitCode == 0 ? processInfo : INVALID_PROCESS_INFO;
+            }
+            catch ( IOException e )
+            {
+                DumpErrorSingleton.getSingleton().dumpException( e );
+                return ERR_PROCESS_INFO;
+            }
+            catch ( InterruptedException e )
+            {
+                DumpErrorSingleton.getSingleton().dumpException( e );
+                return ERR_PROCESS_INFO;
+            }
+            finally
+            {
+                if ( process != null )
+                {
+                    destroyableCommands.remove( process );
+                    process.destroy();
+                    closeQuietly( process.getInputStream() );
+                    closeQuietly( process.getErrorStream() );
+                    closeQuietly( process.getOutputStream() );
+                }
+            }
+        }
+    }
+
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
new file mode 100644
index 000000000..212e22199
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
@@ -0,0 +1,109 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+
+/**
+ * Immutable object which encapsulates PID and elapsed time (Unix) or start time (Windows).
+ * <br>
+ * Methods
+ * ({@link #getPID()}, {@link #getTime()}, {@link #isTimeAfter(ProcessInfo)}, {@link #isTimeEqualTo(ProcessInfo)})
+ * throw {@link IllegalStateException}
+ * if {@link #isValid()} returns {@code false} or {@link #isError()} returns {@code true}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+final class ProcessInfo
+{
+    static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null );
+    static final ProcessInfo ERR_PROCESS_INFO = new ProcessInfo( null, null );
+
+    /**
+     * On Unix we do not get PID due to the command is interested only to etime of PPID:
+     * <br>
+     * <pre>/bin/ps -o etime= -p 123</pre>
+     */
+    static ProcessInfo unixProcessInfo( long pid, long etime )
+    {
+        return new ProcessInfo( pid, etime );
+    }
+
+    static ProcessInfo windowsProcessInfo( long pid, String startTimestamp )
+    {
+        return new ProcessInfo( pid, requireNonNull( startTimestamp, "startTimestamp is NULL" ) );
+    }
+
+    private final Long pid;
+    private final Comparable time;
+
+    private ProcessInfo( Long pid, Comparable time )
+    {
+        this.pid = pid;
+        this.time = time;
+    }
+
+    boolean isValid()
+    {
+        return this != INVALID_PROCESS_INFO;
+    }
+
+    boolean isError()
+    {
+        return this == ERR_PROCESS_INFO;
+    }
+
+    long getPID()
+    {
+        checkValid();
+        return pid;
+    }
+
+    Comparable getTime()
+    {
+        checkValid();
+        return time;
+    }
+
+    @SuppressWarnings( "unchecked" )
+    boolean isTimeEqualTo( ProcessInfo that )
+    {
+        checkValid();
+        that.checkValid();
+        return this.time.compareTo( that.time ) == 0;
+    }
+
+    @SuppressWarnings( "unchecked" )
+    boolean isTimeAfter( ProcessInfo that )
+    {
+        checkValid();
+        that.checkValid();
+        return this.time.compareTo( that.time ) > 0;
+    }
+
+    private void checkValid()
+    {
+        if ( !isValid() || isError() )
+        {
+            throw new IllegalStateException( "invalid process info" );
+        }
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
index 5a19e2602..d94be7136 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
@@ -70,6 +70,12 @@ public int getIntProperty( String propertyName )
         return Integer.parseInt( properties.get( propertyName ) );
     }
 
+    public Long getLongProperty( String propertyName )
+    {
+        String number = getProperty( propertyName );
+        return number == null ? null : Long.parseLong( number );
+    }
+
     public File getFileProperty( String key )
     {
         final String property = getProperty( key );
@@ -167,6 +173,7 @@ public void addList( List items, String propertyPrefix )
         }
     }
 
+    @Override
     public void copyTo( Map<Object, Object> target )
     {
         target.putAll( properties );
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
index b0825a899..c7379e4bc 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java
@@ -33,7 +33,7 @@
 /**
  * Represents the surefire configuration that passes all the way into the provider
  * classloader and the provider.
- * <p/>
+ * <br>
  *
  * @author Jason van Zyl
  * @author Emmanuel Venisse
@@ -41,11 +41,6 @@
  */
 public class ProviderConfiguration
 {
-    /**
-     * @noinspection UnusedDeclaration
-     */
-    public static final int TESTS_SUCCEEDED_EXIT_CODE = 0;
-
     private final DirectoryScannerParameters dirScannerParams;
 
     private final ReporterConfiguration reporterConfiguration;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
index 36f823b9b..e800cfe8b 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java
@@ -34,7 +34,7 @@
 
 /**
  * Creates the surefire provider.
- * <p/>
+ * <br>
  *
  * @author Kristian Rosenvold
  */
@@ -134,6 +134,7 @@ private ProviderProxy( Object providerInOtherClassLoader, ClassLoader testsClass
             this.testsClassLoader = testsClassLoader;
         }
 
+        @Override
         @SuppressWarnings( "unchecked" )
         public Iterable<Class<?>> getSuites()
         {
@@ -148,6 +149,7 @@ private ProviderProxy( Object providerInOtherClassLoader, ClassLoader testsClass
             }
         }
 
+        @Override
         public RunResult invoke( Object forkTestSet )
             throws TestSetFailedException, InvocationTargetException
         {
@@ -175,6 +177,7 @@ private ClassLoader swapClassLoader( ClassLoader newClassLoader )
             return current;
         }
 
+        @Override
         public void cancel()
         {
             Class<?> providerType = providerInOtherClassLoader.getClass();
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
index ce23d3e3c..91530b7a8 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/StartupConfiguration.java
@@ -95,8 +95,8 @@ public String getActualClassName()
 
     /**
      * <p>Strip any of a supplied String from the end of a String.</p>
-     * <p/>
-     * <p>If the strip String is <code>null</code>, whitespace is
+     * <br>
+     * <p>If the strip String is {@code null}, whitespace is
      * stripped.</p>
      *
      * @param str   the String to remove characters from
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java
index 17db48925..713d4fed8 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemPropertyManager.java
@@ -68,7 +68,6 @@ private static PropertiesWrapper loadProperties( File file )
         return loadProperties( new FileInputStream( file ) );
     }
 
-
     public static void setSystemProperties( File file )
         throws IOException
     {
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
new file mode 100644
index 000000000..3a53ddf31
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
@@ -0,0 +1,322 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.util.ReflectionUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import static java.lang.Thread.currentThread;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.StringUtils.isNumeric;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_FREE_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_NET_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_OPEN_BSD;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodChain;
+import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;
+import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+
+/**
+ * JDK 9 support.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class SystemUtils
+{
+    private static final double JIGSAW_JAVA_VERSION = 9.0d;
+
+    private static final int PROC_STATUS_PID_FIRST_CHARS = 20;
+
+    private SystemUtils()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    /**
+     * @param jvmExecPath    e.g. /jdk/bin/java, /jdk/jre/bin/java
+     * @return {@code true} if {@code jvmExecPath} is path to java binary executor
+     */
+    public static boolean endsWithJavaPath( String jvmExecPath )
+    {
+        File javaExec = new File( jvmExecPath ).getAbsoluteFile();
+        File bin = javaExec.getParentFile();
+        String exec = javaExec.getName();
+        return exec.startsWith( "java" ) && bin != null && bin.getName().equals( "bin" );
+    }
+
+    /**
+     * If {@code jvmExecutable} is <tt>/jdk/bin/java</tt> (since jdk9) or <tt>/jdk/jre/bin/java</tt> (prior to jdk9)
+     * then the absolute path to JDK home is returned <tt>/jdk</tt>.
+     * <br>
+     * Null is returned if {@code jvmExecutable} is incorrect.
+     *
+     * @param jvmExecutable    /jdk/bin/java* or /jdk/jre/bin/java*
+     * @return path to jdk directory; or <tt>null</tt> if wrong path or directory layout of JDK installation.
+     */
+    public static File toJdkHomeFromJvmExec( String jvmExecutable )
+    {
+        File bin = new File( jvmExecutable ).getAbsoluteFile().getParentFile();
+        if ( "bin".equals( bin.getName() ) )
+        {
+            File parent = bin.getParentFile();
+            if ( "jre".equals( parent.getName() ) )
+            {
+                File jdk = parent.getParentFile();
+                return new File( jdk, "bin" ).isDirectory() ? jdk : null;
+            }
+            return parent;
+        }
+        return null;
+    }
+
+    /**
+     * If system property <tt>java.home</tt> is <tt>/jdk</tt> (since jdk9) or <tt>/jdk/jre</tt> (prior to jdk9) then
+     * the absolute path to
+     * JDK home is returned <tt>/jdk</tt>.
+     *
+     * @return path to JDK
+     */
+    public static File toJdkHomeFromJre()
+    {
+        return toJdkHomeFromJre( System.getProperty( "java.home" ) );
+    }
+
+    /**
+     * If {@code jreHome} is <tt>/jdk</tt> (since jdk9) or <tt>/jdk/jre</tt> (prior to jdk9) then
+     * the absolute path to JDK home is returned <tt>/jdk</tt>.
+     * <br>
+     * JRE home directory {@code jreHome} must be taken from system property <tt>java.home</tt>.
+     *
+     * @param jreHome    path to /jdk or /jdk/jre
+     * @return path to JDK
+     */
+    static File toJdkHomeFromJre( String jreHome )
+    {
+        File pathToJreOrJdk = new File( jreHome ).getAbsoluteFile();
+        return "jre".equals( pathToJreOrJdk.getName() ) ? pathToJreOrJdk.getParentFile() : pathToJreOrJdk;
+    }
+
+    public static Double toJdkVersionFromReleaseFile( File jdkHome )
+    {
+        File release = new File( requireNonNull( jdkHome ).getAbsoluteFile(), "release" );
+        if ( !release.isFile() )
+        {
+            return null;
+        }
+        InputStream is = null;
+        try
+        {
+            Properties properties = new Properties();
+            is = new FileInputStream( release );
+            properties.load( is );
+            String javaVersion = properties.getProperty( "JAVA_VERSION" ).replace( "\"", "" );
+            StringTokenizer versions = new StringTokenizer( javaVersion, "._" );
+
+            if ( versions.countTokens() == 1 )
+            {
+                javaVersion = versions.nextToken();
+            }
+            else if ( versions.countTokens() >= 2 )
+            {
+                String majorVersion = versions.nextToken();
+                String minorVersion = versions.nextToken();
+                javaVersion = isNumeric( minorVersion ) ? majorVersion + "." + minorVersion : majorVersion;
+            }
+            else
+            {
+                return null;
+            }
+
+            return Double.valueOf( javaVersion );
+        }
+        catch ( IOException e )
+        {
+            return null;
+        }
+        finally
+        {
+            closeQuietly( is );
+        }
+    }
+
+    public static boolean isJava9AtLeast( String jvmExecutablePath )
+    {
+        File externalJavaHome = toJdkHomeFromJvmExec( jvmExecutablePath );
+        File thisJavaHome = toJdkHomeFromJre();
+        if ( thisJavaHome.equals( externalJavaHome ) )
+        {
+            return isBuiltInJava9AtLeast();
+        }
+        Double releaseFileVersion = externalJavaHome == null ? null : toJdkVersionFromReleaseFile( externalJavaHome );
+        return SystemUtils.isJava9AtLeast( releaseFileVersion );
+    }
+
+    static boolean isBuiltInJava9AtLeast()
+    {
+        return isJavaVersionAtLeast( JAVA_9 );
+    }
+
+    public static boolean isJava9AtLeast( Double version )
+    {
+        return version != null && version >= JIGSAW_JAVA_VERSION;
+    }
+
+    public static ClassLoader platformClassLoader()
+    {
+        if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+        {
+            return reflectClassLoader( ClassLoader.class, "getPlatformClassLoader" );
+        }
+        return null;
+    }
+
+    public static Long pid()
+    {
+        if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+        {
+            Long pid = pidOnJava9();
+            if ( pid != null )
+            {
+                return pid;
+            }
+        }
+
+        if ( IS_OS_LINUX )
+        {
+            try
+            {
+                return pidStatusOnLinux();
+            }
+            catch ( Exception e )
+            {
+                // examine PID via JMX
+            }
+        }
+        else if ( IS_OS_FREE_BSD || IS_OS_NET_BSD || IS_OS_OPEN_BSD )
+        {
+            try
+            {
+                return pidStatusOnBSD();
+            }
+            catch ( Exception e )
+            {
+                // examine PID via JMX
+            }
+        }
+
+        return pidOnJMX();
+    }
+
+    static Long pidOnJMX()
+    {
+        String processName = ManagementFactory.getRuntimeMXBean().getName();
+        if ( processName.contains( "@" ) )
+        {
+            String pid = processName.substring( 0, processName.indexOf( '@' ) ).trim();
+            try
+            {
+                return Long.parseLong( pid );
+            }
+            catch ( NumberFormatException e )
+            {
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    static Long pidStatusOnLinux() throws Exception
+    {
+        FileReader input = new FileReader( "/proc/self/stat" );
+        try
+        {
+            // Reading and encoding 20 characters is bit faster than whole line.
+            // size of (long) = 19, + 1 space
+            char[] buffer = new char[PROC_STATUS_PID_FIRST_CHARS];
+            String startLine = new String( buffer, 0, input.read( buffer ) );
+            return Long.parseLong( startLine.substring( 0, startLine.indexOf( ' ' ) ) );
+        }
+        finally
+        {
+            input.close();
+        }
+    }
+
+    /**
+     * The process status.  This file is read-only and returns a single
+     * line containing multiple space-separated fields.
+     * See <a href="https://www.freebsd.org/cgi/man.cgi?query=procfs&sektion=5">procfs status</a>
+     *
+     * @return current PID
+     * @throws Exception if could not read /proc/curproc/status
+     */
+    static Long pidStatusOnBSD() throws Exception
+    {
+        BufferedReader input = new BufferedReader( new FileReader( "/proc/curproc/status" ) );
+        try
+        {
+            String line = input.readLine();
+            int i1 = 1 + line.indexOf( ' ' );
+            int i2 = line.indexOf( ' ', i1 );
+            return Long.parseLong( line.substring( i1, i2 ) );
+        }
+        finally
+        {
+            input.close();
+        }
+    }
+
+    static Long pidOnJava9()
+    {
+        ClassLoader classLoader = currentThread().getContextClassLoader();
+        Class<?> processHandle = tryLoadClass( classLoader, "java.lang.ProcessHandle" );
+        Class<?>[] classesChain = { processHandle, processHandle };
+        String[] methodChain = { "current", "pid" };
+        return (Long) invokeMethodChain( classesChain, methodChain, null );
+    }
+
+    static ClassLoader reflectClassLoader( Class<?> target, String getterMethodName )
+    {
+        try
+        {
+            Method getter = ReflectionUtils.getMethod( target, getterMethodName );
+            return (ClassLoader) ReflectionUtils.invokeMethodWithArray( null, getter );
+        }
+        catch ( RuntimeException e )
+        {
+            return null;
+        }
+    }
+}
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
index bbd0f7056..00ad2e92a 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/TypeEncodedValue.java
@@ -21,9 +21,11 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
 import java.util.Properties;
 
 import static org.apache.maven.surefire.util.ReflectionUtils.loadClass;
+import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
 
 /**
  * @author Kristian Rosenvold
@@ -51,8 +53,7 @@ public Object getDecodedValue()
 
     public Object getDecodedValue( ClassLoader classLoader )
     {
-        // todo: use jdk6 switch case
-        if ( type.trim().length() == 0 )
+        if ( type.trim().isEmpty() )
         {
             return null;
         }
@@ -79,13 +80,12 @@ else if ( type.equals( Integer.class.getName() ) )
         else if ( type.equals( Properties.class.getName() ) )
         {
             Properties result = new Properties();
-            // todo: use jdk7 Closable
             try
             {
-                result.load( new ByteArrayInputStream( value.getBytes( "8859_1" ) ) );
+                result.load( new ByteArrayInputStream( value.getBytes( ISO_8859_1 ) ) );
                 return result;
             }
-            catch ( Exception e )
+            catch ( IOException e )
             {
                 throw new IllegalStateException( "bug in property conversion", e );
             }
@@ -96,6 +96,7 @@ else if ( type.equals( Properties.class.getName() ) )
         }
     }
 
+    @Override
     public boolean equals( Object o )
     {
         if ( this == o )
@@ -113,6 +114,7 @@ public boolean equals( Object o )
 
     }
 
+    @Override
     public int hashCode()
     {
         int result = type != null ? type.hashCode() : 0;
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index b731dc07c..ccc33d5fb 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -40,7 +40,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
+import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -160,6 +160,7 @@ public void stopBeforeReadInThread()
     {
         Runnable runnable = new Runnable()
         {
+            @Override
             public void run()
             {
                 Iterator<String> it = reader.getIterableClasses( nul() ).iterator();
@@ -187,6 +188,7 @@ public void readTwoClassesInThread()
         final CountDownLatch counter = new CountDownLatch( 1 );
         Runnable runnable = new Runnable()
         {
+            @Override
             public void run()
             {
                 Iterator<String> it = reader.getIterableClasses( nul() ).iterator();
@@ -240,7 +242,7 @@ public int read()
     private void addTestToPipeline( String cls )
         throws UnsupportedEncodingException
     {
-        byte[] clazz = cls.getBytes( FORK_STREAM_CHARSET_NAME );
+        byte[] clazz = cls.getBytes( ISO_8859_1 );
         ByteBuffer buffer = ByteBuffer.allocate( 8 + clazz.length )
             .putInt( MasterProcessCommand.RUN_CLASS.getId() )
             .putInt( clazz.length )
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
index 99b856494..ccb01e357 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
@@ -52,6 +52,7 @@
 
     boolean called = false;
 
+    @Override
     public void setDirectoryScannerParameters( DirectoryScannerParameters directoryScanner )
     {
         this.directoryScannerParameters = directoryScanner;
@@ -60,26 +61,28 @@ public void setDirectoryScannerParameters( DirectoryScannerParameters directoryS
 
 
     /**
-     * @return true if it has been callsed
-     * @noinspection UnusedDeclaration
+     * @return true if it has been called
      */
     public Boolean isCalled()
     {
         return called;
     }
 
+    @Override
     public void setProviderProperties( Map<String, String> providerProperties )
     {
         this.providerProperties = providerProperties;
         this.called = true;
     }
 
+    @Override
     public void setReporterConfiguration( ReporterConfiguration reporterConfiguration )
     {
         this.reporterConfiguration = reporterConfiguration;
         this.called = true;
     }
 
+    @Override
     public void setClassLoaders( ClassLoader testClassLoader )
     {
         this.testClassLoader = testClassLoader;
@@ -87,18 +90,21 @@ public void setClassLoaders( ClassLoader testClassLoader )
         this.called = true;
     }
 
+    @Override
     public void setTestRequest( TestRequest testRequest1 )
     {
         this.testRequest = testRequest1;
         this.called = true;
     }
 
+    @Override
     public void setTestArtifactInfo( TestArtifactInfo testArtifactInfo )
     {
         this.testArtifactInfo = testArtifactInfo;
         this.called = true;
     }
 
+    @Override
     public void setRunOrderParameters( RunOrderParameters runOrderParameters )
     {
         this.runOrderParameters = runOrderParameters;
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index 2bdcf21b3..f073a8b00 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -34,7 +34,9 @@
     ClasspathTest.class,
     CommandReaderTest.class,
     PropertiesWrapperTest.class,
-    SurefireReflectorTest.class
+    SurefireReflectorTest.class,
+    PpidCheckerTest.class,
+    SystemUtilsTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
index 4342ec7e9..4cf9424a5 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
@@ -34,10 +34,19 @@
 import org.junit.runners.model.Statement;
 import org.junit.runners.model.TestClass;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 
+import static java.io.File.pathSeparator;
+import static org.apache.commons.io.FileUtils.readFileToString;
+
 /**
  * JUnit runner testing methods in a separate class loader.
  *
@@ -185,7 +194,63 @@ protected Object createTest()
     {
         public TestClassLoader()
         {
-            super( ( (URLClassLoader) Thread.currentThread().getContextClassLoader() ).getURLs(), null );
+            super( toClassPath(), null );
+        }
+
+        /**
+         * Compliant with Java 9 or prior version of JRE.
+         *
+         * @return classpath
+         */
+        private static URL[] toClassPath()
+        {
+            try
+            {
+                Collection<URL> cp = toPathList(); // if Maven run
+                if ( cp.isEmpty() )
+                {
+                    // if IDE
+                    cp = toPathList( System.getProperty( "java.class.path" ) );
+                }
+                return cp.toArray( new URL[cp.size()] );
+            }
+            catch ( IOException e )
+            {
+                return new URL[0];
+            }
+        }
+
+        private static Collection<URL> toPathList( String path ) throws MalformedURLException
+        {
+            Collection<URL> classPath = new HashSet<URL>();
+            for ( String file : path.split( pathSeparator ) )
+            {
+                classPath.add( new File( file ).toURL() );
+            }
+            return classPath;
+        }
+
+        private static Collection<URL> toPathList()
+        {
+            Collection<URL> classPath = new HashSet<URL>();
+            try
+            {
+                String[] files = readFileToString( new File( "target/test-classpath/cp.txt" ) ).split( pathSeparator );
+                for ( String file : files )
+                {
+                    File f = new File( file );
+                    File dir = f.getParentFile();
+                    classPath.add( ( dir.getName().equals( "target" ) ? new File( dir, "classes" ) : f ).toURL() );
+                }
+                classPath.add( new File( "target/classes" ).toURL() );
+                classPath.add( new File( "target/test-classes" ).toURL() );
+            }
+            catch ( IOException e )
+            {
+                // turn to java.class.path
+                classPath.clear();
+            }
+            return classPath;
         }
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
new file mode 100644
index 000000000..b0153ac5a
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
@@ -0,0 +1,144 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+import java.util.regex.Matcher;
+
+import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Testing {@link PpidChecker} on a platform.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class PpidCheckerTest
+{
+    @Test
+    public void shouldHavePpidAsWindows()
+    {
+        assumeTrue( IS_OS_WINDOWS );
+
+        long expectedPid = Long.parseLong( ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() );
+
+        PpidChecker checker = new PpidChecker( expectedPid );
+        ProcessInfo processInfo = checker.windows();
+
+        assertThat( processInfo )
+                .isNotNull();
+
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        assertThat( checker.isProcessAlive() )
+                .isTrue();
+
+        assertThat( processInfo.getPID() )
+                .isEqualTo( expectedPid );
+
+        assertThat( processInfo.getTime() )
+                .isNotNull();
+    }
+
+    @Test
+    public void shouldHavePpidAsUnix()
+    {
+        assumeTrue( IS_OS_UNIX );
+
+        long expectedPid = Long.parseLong( ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() );
+
+        PpidChecker checker = new PpidChecker( expectedPid );
+        ProcessInfo processInfo = checker.unix();
+
+        assertThat( processInfo )
+                .isNotNull();
+
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        assertThat( checker.isProcessAlive() )
+                .isTrue();
+
+        assertThat( processInfo.getPID() )
+                .isEqualTo( expectedPid );
+
+        assertThat( processInfo.getTime() )
+                .isNotNull();
+    }
+
+    @Test
+    public void shouldNotFindSuchPID()
+    {
+        PpidChecker checker = new PpidChecker( 1000000L );
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        boolean isAlive = checker.isProcessAlive();
+
+        assertThat( isAlive )
+                .isFalse();
+    }
+
+    @Test
+    public void shouldParseEtime()
+    {
+        Matcher m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "38" );
+        assertThat( m.matches() )
+                .isFalse();
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "00:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "01:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "02-01:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 2 * 24 * 3600L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+    }
+}
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
new file mode 100644
index 000000000..1917cbb99
--- /dev/null
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
@@ -0,0 +1,333 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import static java.io.File.separator;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_FREE_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_NET_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_OPEN_BSD;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.verifyStatic;
+
+/**
+ * Test of {@link SystemUtils}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+@RunWith( Enclosed.class )
+public class SystemUtilsTest
+{
+    public static class PlainUnitTests
+    {
+
+        @Test
+        public void shouldParseProprietaryReleaseFile() throws IOException
+        {
+            String classes = new File( "." ).getCanonicalPath() + separator + "target" + separator + "test-classes";
+
+            File path = new File( classes, "jdk8-IBM" + separator + "bin" + separator + "java" );
+            assertThat( SystemUtils.isJava9AtLeast( path.getAbsolutePath() ) ).isFalse();
+
+            path = new File( classes, "jdk8-oracle" + separator + "bin" + separator + "java" );
+            assertThat( SystemUtils.isJava9AtLeast( path.getAbsolutePath() ) ).isFalse();
+
+            path = new File( classes, "jdk9-oracle" + separator + "bin" + separator + "java" );
+            assertThat( SystemUtils.isJava9AtLeast( path.getAbsolutePath() ) ).isTrue();
+        }
+
+        @Test
+        public void incorrectJdkPath() throws IOException
+        {
+            File jre = new File( System.getProperty( "java.home" ) );
+            File jdk = jre.getParentFile();
+            File incorrect = jdk.getParentFile();
+            assertThat( SystemUtils.isJava9AtLeast( incorrect.getAbsolutePath() ) ).isFalse();
+        }
+
+        @Test
+        public void shouldHaveJavaPath()
+        {
+            String javaPath = System.getProperty( "java.home" ) + separator + "bin" + separator + "java";
+            assertThat( SystemUtils.endsWithJavaPath( javaPath ) ).isTrue();
+        }
+
+        @Test
+        public void shouldNotHaveJavaPath()
+        {
+            assertThat( SystemUtils.endsWithJavaPath( "/jdk" ) ).isFalse();
+        }
+
+        @Test
+        public void shouldNotExtractJdkHomeFromJavaExec()
+        {
+            File pathToJdk = SystemUtils.toJdkHomeFromJvmExec( "/jdk/binx/java" );
+            assertThat( pathToJdk ).isNull();
+        }
+
+        @Test
+        public void shouldExtractJdkHomeFromJavaExec()
+        {
+            File pathToJdk = SystemUtils.toJdkHomeFromJvmExec( "/jdk/bin/java" );
+            assertThat( pathToJdk ).isEqualTo( new File( "/jdk" ).getAbsoluteFile() );
+        }
+
+        @Test
+        public void shouldNotExtractJdkHomeFromJreExec() throws IOException
+        {
+            String classes = new File( "." ).getCanonicalPath() + separator + "target" + separator + "test-classes";
+            File jdk = new File( classes, "jdk" );
+            String pathToJreExec = jdk.getAbsolutePath() + separator + "jre" + separator + "binx" + separator + "java";
+            File pathToJdk = SystemUtils.toJdkHomeFromJvmExec( pathToJreExec );
+            assertThat( pathToJdk ).isNull();
+        }
+
+        @Test
+        public void shouldExtractJdkHomeFromJreExec() throws IOException
+        {
+            String classes = new File( "." ).getCanonicalPath() + separator + "target" + separator + "test-classes";
+            File jdk = new File( classes, "jdk" );
+            String pathToJreExec = jdk.getAbsolutePath() + separator + "jre" + separator + "bin" + separator + "java";
+            File pathToJdk = SystemUtils.toJdkHomeFromJvmExec( pathToJreExec );
+            assertThat( pathToJdk ).isEqualTo( jdk );
+        }
+
+        @Test
+        public void shouldExtractJdkHomeFromJre()
+        {
+            File pathToJdk = SystemUtils.toJdkHomeFromJre( "/jdk/jre" );
+            assertThat( pathToJdk ).isEqualTo( new File( "/jdk" ).getAbsoluteFile() );
+        }
+
+        @Test
+        public void shouldExtractJdkHomeFromJdk()
+        {
+            File pathToJdk = SystemUtils.toJdkHomeFromJre( "/jdk/" );
+            assertThat( pathToJdk ).isEqualTo( new File( "/jdk" ).getAbsoluteFile() );
+        }
+
+        @Test
+        public void shouldExtractJdkHomeFromRealPath()
+        {
+            File pathToJdk = SystemUtils.toJdkHomeFromJre();
+
+            if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+            {
+                File realJdkHome = new File( System.getProperty( "java.home" ) ).getAbsoluteFile();
+                assertThat( realJdkHome ).isDirectory();
+                assertThat( realJdkHome.getName() ).isNotEqualTo( "jre" );
+                assertThat( pathToJdk ).isEqualTo( realJdkHome );
+            }
+            else
+            {
+                File realJreHome = new File( System.getProperty( "java.home" ) ).getAbsoluteFile();
+                assertThat( realJreHome ).isDirectory();
+                assertThat( realJreHome.getName() ).isEqualTo( "jre" );
+                File realJdkHome = realJreHome.getParentFile();
+                assertThat( pathToJdk ).isEqualTo( realJdkHome );
+            }
+        }
+
+        @Test
+        public void shouldBeJavaVersion()
+        {
+            assertThat( SystemUtils.isJava9AtLeast( (Double) null ) ).isFalse();
+            assertThat( SystemUtils.isJava9AtLeast( 1.8d ) ).isFalse();
+            assertThat( SystemUtils.isJava9AtLeast( 9.0d ) ).isTrue();
+        }
+
+        @Test
+        public void shouldBePlatformClassLoader()
+        {
+            ClassLoader cl = SystemUtils.platformClassLoader();
+            if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+            {
+                assertThat( cl ).isNotNull();
+            }
+            else
+            {
+                assertThat( cl ).isNull();
+            }
+        }
+
+        @Test
+        public void shouldNotFindClassLoader()
+        {
+            ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "_getPlatformClassLoader_" );
+            assertThat( cl ).isNull();
+        }
+
+        @Test
+        public void shouldFindClassLoader()
+        {
+            ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "getPlatformClassLoader" );
+            assertThat( cl ).isSameAs( ClassLoader.getSystemClassLoader() );
+        }
+
+        @Test
+        public void shouldBePidOnJigsaw()
+        {
+            assumeTrue( JAVA_RECENT.atLeast( JAVA_9 ) );
+
+            Long actualPid = SystemUtils.pidOnJava9();
+            String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+            assertThat( actualPid + "" )
+                    .isEqualTo( expectedPid );
+        }
+
+        @Test
+        public void shouldBePidStatusOnLinux() throws Exception
+        {
+            assumeTrue( IS_OS_LINUX );
+
+            Long actualPid = SystemUtils.pidStatusOnLinux();
+            String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+            assertThat( actualPid + "" )
+                    .isEqualTo( expectedPid );
+        }
+
+        @Test
+        public void shouldBePidStatusOnBSD() throws Exception
+        {
+            assumeTrue( IS_OS_FREE_BSD || IS_OS_NET_BSD || IS_OS_OPEN_BSD );
+
+            Long actualPid = SystemUtils.pidStatusOnBSD();
+            String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+            assertThat( actualPid + "" )
+                    .isEqualTo( expectedPid );
+        }
+
+        @Test
+        public void shouldBePidOnJMX()
+        {
+            Long actualPid = SystemUtils.pidOnJMX();
+            String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+            assertThat( actualPid + "" )
+                    .isEqualTo( expectedPid );
+        }
+
+        @Test
+        public void shouldBePid()
+        {
+            Long actualPid = SystemUtils.pid();
+            String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+            assertThat( actualPid + "" )
+                    .isEqualTo( expectedPid );
+        }
+
+        public static ClassLoader getPlatformClassLoader()
+        {
+            return ClassLoader.getSystemClassLoader();
+        }
+
+    }
+
+    @RunWith( PowerMockRunner.class )
+    @PrepareForTest( SystemUtils.class )
+    // todo check PowerMock is compliant with Java 9
+    @Ignore( value = "use this test after issue is fixed https://github.com/powermock/powermock/issues/783")
+    public static class MockTest
+    {
+
+        @Test
+        public void shouldBeDifferentJdk9() throws IOException
+        {
+            testIsJava9AtLeast( new File( System.getProperty( "java.home" ) ) );
+        }
+
+        @Test
+        public void shouldBeSameJdk9() throws IOException
+        {
+            assumeFalse( JAVA_RECENT.atLeast( JAVA_9 ) );
+            testIsJava9AtLeast( new File( System.getProperty( "java.home" ) ).getParentFile() );
+        }
+
+        private static void testIsJava9AtLeast( File pathInJdk ) throws IOException
+        {
+            File path = new File( pathInJdk, "bin" + separator + "java" );
+
+            mockStatic( SystemUtils.class );
+
+            when( SystemUtils.isJava9AtLeast( anyString() ) )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.toJdkHomeFromJvmExec( anyString() ) )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.toJdkHomeFromJre() )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.toJdkHomeFromJre( anyString() ) )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.isBuiltInJava9AtLeast() )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.toJdkVersionFromReleaseFile( any( File.class ) ) )
+                    .thenCallRealMethod();
+
+            when( SystemUtils.isJava9AtLeast( anyDouble() ) )
+                    .thenCallRealMethod();
+
+            if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+            {
+                assertThat( SystemUtils.isJava9AtLeast( path.getAbsolutePath() ) ).isTrue();
+            }
+            else
+            {
+                assertThat( SystemUtils.isJava9AtLeast( path.getAbsolutePath() ) ).isFalse();
+            }
+
+            verifyStatic( Mockito.times( 0 ) );
+            SystemUtils.toJdkVersionFromReleaseFile( any( File.class ) );
+
+            verifyStatic( Mockito.times( 1 ) );
+            SystemUtils.isBuiltInJava9AtLeast();
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/surefire-booter/src/test/resources/jdk/bin/java b/surefire-booter/src/test/resources/jdk/bin/java
new file mode 100644
index 000000000..e69de29bb
diff --git a/surefire-booter/src/test/resources/jdk/jre/bin/java b/surefire-booter/src/test/resources/jdk/jre/bin/java
new file mode 100644
index 000000000..e69de29bb
diff --git a/surefire-booter/src/test/resources/jdk8-IBM/release b/surefire-booter/src/test/resources/jdk8-IBM/release
new file mode 100644
index 000000000..f8baa302a
--- /dev/null
+++ b/surefire-booter/src/test/resources/jdk8-IBM/release
@@ -0,0 +1 @@
+JAVA_VERSION="1.8.0"
diff --git a/surefire-booter/src/test/resources/jdk8-oracle/release b/surefire-booter/src/test/resources/jdk8-oracle/release
new file mode 100644
index 000000000..567277b3a
--- /dev/null
+++ b/surefire-booter/src/test/resources/jdk8-oracle/release
@@ -0,0 +1 @@
+JAVA_VERSION="1.8.0_141"
diff --git a/surefire-booter/src/test/resources/jdk9-oracle/release b/surefire-booter/src/test/resources/jdk9-oracle/release
new file mode 100644
index 000000000..afcc74706
--- /dev/null
+++ b/surefire-booter/src/test/resources/jdk9-oracle/release
@@ -0,0 +1 @@
+JAVA_VERSION="9"
diff --git a/surefire-grouper/pom.xml b/surefire-grouper/pom.xml
index bcd1b3f79..80f19ff34 100644
--- a/surefire-grouper/pom.xml
+++ b/surefire-grouper/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>surefire-grouper</artifactId>
diff --git a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/AndGroupMatcher.java b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/AndGroupMatcher.java
index 1ffc89483..e9adb22b5 100644
--- a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/AndGroupMatcher.java
+++ b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/AndGroupMatcher.java
@@ -45,6 +45,7 @@ public AndGroupMatcher( Collection<GroupMatcher> matchers )
         }
     }
 
+    @Override
     public boolean enabled( Class<?>... cats )
     {
         for ( GroupMatcher matcher : getMatchers() )
@@ -59,6 +60,7 @@ public boolean enabled( Class<?>... cats )
         return true;
     }
 
+    @Override
     public boolean enabled( String... cats )
     {
         for ( GroupMatcher matcher : getMatchers() )
diff --git a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/InverseGroupMatcher.java b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/InverseGroupMatcher.java
index 0ab1d4335..2028b1bca 100644
--- a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/InverseGroupMatcher.java
+++ b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/InverseGroupMatcher.java
@@ -35,11 +35,13 @@ public InverseGroupMatcher( GroupMatcher matcher )
         this.matcher = matcher;
     }
 
+    @Override
     public boolean enabled( Class<?>... cats )
     {
         return cats == null || !matcher.enabled( cats );
     }
 
+    @Override
     public boolean enabled( String... cats )
     {
         return cats == null || !matcher.enabled( cats );
@@ -87,6 +89,7 @@ else if ( !matcher.equals( other.matcher ) )
         return true;
     }
 
+    @Override
     public void loadGroupClasses( ClassLoader cloader )
     {
         matcher.loadGroupClasses( cloader );
diff --git a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/JoinGroupMatcher.java b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/JoinGroupMatcher.java
index ec8ac7640..f130ee48a 100644
--- a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/JoinGroupMatcher.java
+++ b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/JoinGroupMatcher.java
@@ -42,6 +42,7 @@ public final boolean addMatcher( GroupMatcher matcher )
         return matchers;
     }
 
+    @Override
     public void loadGroupClasses( ClassLoader cloader )
     {
         for ( GroupMatcher matcher : matchers )
diff --git a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/OrGroupMatcher.java b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/OrGroupMatcher.java
index 65122c7a2..631086e91 100644
--- a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/OrGroupMatcher.java
+++ b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/OrGroupMatcher.java
@@ -45,6 +45,7 @@ public OrGroupMatcher( Collection<GroupMatcher> matchers )
         }
     }
 
+    @Override
     public boolean enabled( Class<?>... cats )
     {
         for ( GroupMatcher matcher : getMatchers() )
@@ -59,6 +60,7 @@ public boolean enabled( Class<?>... cats )
         return false;
     }
 
+    @Override
     public boolean enabled( String... cats )
     {
         for ( GroupMatcher matcher : getMatchers() )
diff --git a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/SingleGroupMatcher.java b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/SingleGroupMatcher.java
index 99dde9910..65181faeb 100644
--- a/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/SingleGroupMatcher.java
+++ b/surefire-grouper/src/main/java/org/apache/maven/surefire/group/match/SingleGroupMatcher.java
@@ -83,6 +83,7 @@ public String toString()
         return "*" + enabled;
     }
 
+    @Override
     public boolean enabled( Class<?>... cats )
     {
         if ( cats != null )
@@ -105,11 +106,12 @@ public boolean enabled( Class<?>... cats )
         return false;
     }
 
+    @Override
     public boolean enabled( String... cats )
     {
         for ( String cat : cats )
         {
-            if ( cat == null || cat.trim().length() < 1 )
+            if ( cat == null || cat.trim().isEmpty() )
             {
                 continue;
             }
@@ -128,6 +130,7 @@ public boolean enabled( String... cats )
         return false;
     }
 
+    @Override
     public void loadGroupClasses( ClassLoader classLoader )
     {
         try
diff --git a/surefire-integration-tests/pom.xml b/surefire-integration-tests/pom.xml
index beab2be96..4187d5f9b 100644
--- a/surefire-integration-tests/pom.xml
+++ b/surefire-integration-tests/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>org.apache.maven.surefire</groupId>
     <artifactId>surefire</artifactId>
-    <version>2.19.2-SNAPSHOT</version>
+    <version>2.21.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>surefire-integration-tests</artifactId>
@@ -35,9 +35,10 @@
   <properties>
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <it.settings.showPasswords>false</it.settings.showPasswords>
-    <testng.version>5.7</testng.version>
     <surefire.threadcount>5</surefire.threadcount>
     <useInterpolatedSettings>false</useInterpolatedSettings>
+    <maven.compiler.source>1.7</maven.compiler.source>
+    <maven.compiler.target>1.7</maven.compiler.target>
   </properties>
 
   <dependencies>
@@ -54,7 +55,6 @@
     <dependency>
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-settings</artifactId>
-      <version>2.0.6</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -105,16 +105,18 @@
           <!-- can forward to all integration test projects. SUREFIRE-513 -->
           <systemPropertyVariables>
             <surefire.version>${project.version}</surefire.version>
-            <testng.version>${testng.version}</testng.version>
             <maven.home>${maven.home}</maven.home>
             <maven.settings.file>${project.basedir}/../surefire-setup-integration-tests/target/private/it-settings.xml
             </maven.settings.file>
+            <maven.toolchains.file>${project.basedir}/../surefire-setup-integration-tests/target/private/toolchains.xml
+            </maven.toolchains.file>
             <maven.repo.local>${project.basedir}/../surefire-setup-integration-tests/target/it-repo</maven.repo.local>
             <maven.test.tmpdir>${project.build.directory}</maven.test.tmpdir>
             <user.localRepository>${settings.localRepository}</user.localRepository>
             <useInterpolatedSettings>${useInterpolatedSettings}</useInterpolatedSettings>
             <testBuildDirectory>${project.build.testOutputDirectory}</testBuildDirectory>
             <verifier.forkMode>${verifier.forkMode}</verifier.forkMode>
+            <jdk.home>${jdk.home}</jdk.home>
           </systemPropertyVariables>
           <redirectTestOutputToFile>false</redirectTestOutputToFile>
         </configuration>
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
index 2dfd1ccf7..9a49d1a78 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractFailFastIT.java
@@ -32,7 +32,7 @@
 import static org.junit.runners.Parameterized.Parameter;
 
 /**
- * Base test class for SUREFIRE-580, configuration parameter <em>skipAfterFailureCount</em>.
+ * Base test class for SUREFIRE-580, configuration parameter {@code skipAfterFailureCount}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractJigsawIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractJigsawIT.java
new file mode 100644
index 000000000..c2d0173f6
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/AbstractJigsawIT.java
@@ -0,0 +1,113 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Abstract test class for Jigsaw tests.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public abstract class AbstractJigsawIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    protected static final String JDK_HOME_KEY = "jdk.home";
+    protected static final String JDK_HOME = System.getProperty( JDK_HOME_KEY );
+    private static final double JIGSAW_JAVA_VERSION = 9.0d;
+
+    protected abstract String getProjectDirectoryName();
+
+    protected SurefireLauncher assumeJigsaw() throws IOException
+    {
+        assumeTrue( "There's no JDK 9 provided.",
+                          JAVA_RECENT.atLeast( JAVA_9 ) || JDK_HOME != null && isExtJava9AtLeast() );
+        // fail( JDK_HOME_KEY + " was provided with value " + JDK_HOME + " but it is not Jigsaw Java 9." );
+
+        SurefireLauncher launcher = unpack();
+
+        if ( JDK_HOME != null )
+        {
+            launcher.setLauncherJavaHome( JDK_HOME );
+        }
+
+        return launcher;
+    }
+
+    protected SurefireLauncher assumeJava9Property() throws IOException
+    {
+        assumeTrue( "There's no JDK 9 provided.", JDK_HOME != null && isExtJava9AtLeast() );
+        return unpack();
+    }
+
+    private SurefireLauncher unpack()
+    {
+        return unpack( getProjectDirectoryName() );
+    }
+
+    private static boolean isExtJava9AtLeast() throws IOException
+    {
+        File release = new File( JDK_HOME, "release" );
+
+        if ( !release.isFile() )
+        {
+            fail( JDK_HOME_KEY + " was provided with value " + JDK_HOME + " but file does not exist "
+                          + JDK_HOME + File.separator + "release"
+            );
+        }
+
+        Properties properties = new Properties();
+        try ( InputStream is = new FileInputStream( release ) )
+        {
+            properties.load( is );
+        }
+        String javaVersion = properties.getProperty( "JAVA_VERSION" ).replace( "\"", "" );
+        StringTokenizer versions = new StringTokenizer( javaVersion, "._" );
+
+        if ( versions.countTokens() == 1 )
+        {
+            javaVersion = versions.nextToken();
+        }
+        else if ( versions.countTokens() >= 2 )
+        {
+            javaVersion = versions.nextToken() + "." + versions.nextToken();
+        }
+        else
+        {
+            fail( "unexpected java version format" );
+        }
+
+        return Double.valueOf( javaVersion ) >= JIGSAW_JAVA_VERSION;
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodFailureIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodFailureIT.java
index 9e9d68869..440955cc7 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodFailureIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodFailureIT.java
@@ -36,6 +36,12 @@
     public void TestNgBeforeMethodFailure()
         throws Exception
     {
-        unpack( "/testng-beforeMethodFailure" ).maven().withFailure().executeTest().assertTestSuiteResults( 2, 0, 1, 1 );
+        unpack( "/testng-beforeMethodFailure" )
+                .maven()
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .withFailure()
+                .executeTest()
+                .assertTestSuiteResults( 2, 0, 1, 1 );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodIT.java
index ab252795b..d96fefc6f 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgBeforeMethodIT.java
@@ -35,6 +35,10 @@
     public void TestNgBeforeMethod()
         throws Exception
     {
-        unpack( "/testng-beforeMethod" ).executeTest().verifyErrorFree( 1 );
+        unpack( "/testng-beforeMethod" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .executeTest()
+                .verifyErrorFree( 1 );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgExecuteErrorIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgExecuteErrorIT.java
index ce90b3bc6..24ee3e638 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgExecuteErrorIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgExecuteErrorIT.java
@@ -23,6 +23,11 @@
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.junit.Test;
 
+import java.io.File;
+import java.io.FilenameFilter;
+
+import static org.fest.assertions.Assertions.assertThat;
+
 
 /**
  * Test for checking that the output from a forked suite is properly captured even if the suite encounters a severe error.
@@ -37,7 +42,28 @@
     public void executionError()
         throws Exception
     {
-        OutputValidator outputValidator = unpack( "/testng-execute-error" ).maven().withFailure().executeTest();
-        outputValidator.verifyTextInLog( "at org.apache.maven.surefire.testng.TestNGExecutor.run" );
+        OutputValidator outputValidator = unpack( "/testng-execute-error" )
+                                                  .maven()
+                                                  .sysProp( "testNgVersion", "5.7" )
+                                                  .sysProp( "testNgClassifier", "jdk15" )
+                                                  .showErrorStackTraces()
+                                                  .withFailure()
+                                                  .executeTest();
+
+        File reportDir = outputValidator.getSurefireReportsDirectory();
+        String[] dumpFiles = reportDir.list( new FilenameFilter()
+                                             {
+                                                 @Override
+                                                 public boolean accept( File dir, String name )
+                                                 {
+                                                     return name.endsWith( ".dump" );
+                                                 }
+                                             });
+        assertThat( dumpFiles ).isNotEmpty();
+        for ( String dump : dumpFiles )
+        {
+            outputValidator.getSurefireReportsFile( dump )
+                    .assertContainsText( "at org.apache.maven.surefire.testng.TestNGExecutor.run" );
+        }
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgGroupThreadParallelIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgGroupThreadParallelIT.java
index 36ef57ea8..f56e2e4d4 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgGroupThreadParallelIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgGroupThreadParallelIT.java
@@ -34,6 +34,10 @@
     @Test
     public void TestNgGroupThreadParallel()
     {
-        unpack( "testng-group-thread-parallel" ).executeTest().verifyErrorFree( 3 );
+        unpack( "testng-group-thread-parallel" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .executeTest()
+                .verifyErrorFree( 3 );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgListenerReporterIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgListenerReporterIT.java
index e028215be..9c223ea98 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgListenerReporterIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgListenerReporterIT.java
@@ -21,6 +21,7 @@
 
 import org.apache.commons.lang3.JavaVersion;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,32 +49,32 @@
     public static Collection<Object[]> data()
     {
         return Arrays.asList(new Object[][] {
-            { "5.6", JAVA_1_5 }, // First TestNG version with reporter support
-            { "5.7", JAVA_1_5 }, // default version from pom of the test case
-            { "5.10", JAVA_1_5 },
-            { "5.13", JAVA_1_5 }, // "reporterslist" param becomes String instead of List<ReporterConfig>
+            { "5.6", "jdk15", JAVA_1_5 }, // First TestNG version with reporter support
+            { "5.7", "jdk15", JAVA_1_5 }, // default version from pom of the test case
+            { "5.10", "jdk15", JAVA_1_5 },
+            { "5.13", null, JAVA_1_5 }, // "reporterslist" param becomes String instead of List<ReporterConfig>
                         // "listener" param becomes String instead of List<Class>
 
                 // configure(Map) in 5.14.1 and 5.14.2 is transforming List<Class> into a String with a space as separator.
                 // Then configure(CommandLineArgs) splits this String into a List<String> with , or ; as separator => fail.
                 // If we used configure(CommandLineArgs), we would not have the problem with white spaces.
-            //{ "5.14.1", "1.5" }, // "listener" param becomes List instead of String
+            //{ "5.14.1", null, "1.5" }, // "listener" param becomes List instead of String
                             // Fails: Issue with 5.14.1 and 5.14.2 => join with <space>, split with ","
                             // TODO will work with "configure(CommandLineArgs)"
-            //{ "5.14.2", "1.5" }, // ReporterConfig is not available
+            //{ "5.14.2", null, "1.5" }, // ReporterConfig is not available
 
-            //{ "5.14.3", "1.5" }, // TestNG uses "reporter" instead of "reporterslist"
+            //{ "5.14.3", null, "1.5" }, // TestNG uses "reporter" instead of "reporterslist"
                           // Both String or List are possible for "listener"
                           // Fails: not able to test due to system dependency org.testng:guice missed the path and use to break CI
                           // ClassNotFoundException: com.beust.jcommander.ParameterException
 
-            //{ "5.14.4", "1.5" }, { "5.14.5", "1.5" }, // Fails: not able to test due to system dependency org.testng:guice missed the path and use to break CI
+            //{ "5.14.4", null, "1.5" }, { "5.14.5", null, "1.5" }, // Fails: not able to test due to system dependency org.testng:guice missed the path and use to break CI
                                         // ClassNotFoundException: com.beust.jcommander.ParameterException
 
-            { "5.14.6", JAVA_1_5 }, // Usage of org.testng:guice removed
-            { "5.14.9", JAVA_1_5 }, // Latest 5.14.x TestNG version
-            { "6.0", JAVA_1_5 },
-            { "6.9.9", JAVA_1_7 } // Currently latest TestNG version
+            { "5.14.6", null, JAVA_1_5 }, // Usage of org.testng:guice removed
+            { "5.14.9", null, JAVA_1_5 }, // Latest 5.14.x TestNG version
+            { "6.0", null, JAVA_1_5 },
+            { "6.9.9", null, JAVA_1_7 } // Currently latest TestNG version
         });
     }
 
@@ -81,16 +82,24 @@
     public String version;
 
     @Parameter(1)
+    public String classifier;
+
+    @Parameter(2)
     public JavaVersion javaVersion;
 
     @Test
     public void testNgListenerReporter()
     {
-
         assumeJavaVersion( javaVersion );
-        unpack( "testng-listener-reporter", "_" + version )
-                .resetInitialGoals( version )
-                .executeTest()
+        final SurefireLauncher launcher = unpack( "testng-listener-reporter", "_" + version )
+                                                  .sysProp( "testNgVersion", version );
+
+        if ( classifier != null )
+        {
+            launcher.sysProp( "testNgClassifier", "jdk15" );
+        }
+
+        launcher.executeTest()
                 .verifyErrorFree( 1 )
                 .getTargetFile( "resultlistener-output.txt" ).assertFileExists()
                 .getTargetFile( "suitelistener-output.txt" ).assertFileExists()
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgPathWithSpacesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgPathWithSpacesIT.java
index bf713d35c..aa77d1b21 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgPathWithSpacesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgPathWithSpacesIT.java
@@ -33,6 +33,10 @@
     @Test
     public void TestWithSpaces()
     {
-        unpack( "testng-path with spaces" ).executeTest().verifyErrorFree( 1 );
+        unpack( "testng-path with spaces" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .executeTest()
+                .verifyErrorFree( 1 );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgReportTestIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgReportTestIT.java
index 1bad84b25..a358172f7 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgReportTestIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgReportTestIT.java
@@ -19,7 +19,6 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.junit.Test;
 
@@ -38,9 +37,14 @@
     public void testNgReport()
         throws Exception
     {
-        final OutputValidator outputValidator =
-            unpack( "/testng-simple" ).addSurefireReportGoal().executeCurrentGoals().verifyErrorFree( 3 );
-        outputValidator.getSiteFile( "surefire-report.html" ).assertFileExists();
+        unpack( "/testng-simple" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .addSurefireReportGoal()
+                .executeCurrentGoals()
+                .verifyErrorFree( 3 )
+                .getSiteFile( "surefire-report.html" )
+                .assertFileExists();
     }
 
     @Test
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlIT.java
index e8cef5f8e..2e56dc93d 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlIT.java
@@ -53,6 +53,8 @@ public void suiteXmlForkCountTwoReuse()
 
     private SurefireLauncher unpack()
     {
-        return unpack( "testng-suite-xml" );
+        return unpack( "testng-suite-xml" )
+                       .sysProp( "testNgVersion", "5.7" )
+                       .sysProp( "testNgClassifier", "jdk15" );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlSingleIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlSingleIT.java
index 903c14a0a..e2b63b63b 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlSingleIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgSuiteXmlSingleIT.java
@@ -33,7 +33,12 @@
     @Test
     public void TestNgSuite()
     {
-        unpack( "/testng-twoTestCaseSuite" ).setTestToRun( "TestNGTestTwo" ).executeTest().verifyErrorFree( 1 );
+        unpack( "/testng-twoTestCaseSuite" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .setTestToRun( "TestNGTestTwo" )
+                .executeTest()
+                .verifyErrorFree( 1 );
     }
 
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgVersionsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgVersionsIT.java
index 49c0be7fd..6d2bbfa21 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgVersionsIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CheckTestNgVersionsIT.java
@@ -25,6 +25,7 @@
 import org.apache.maven.surefire.its.fixture.HelperAssertions;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -46,73 +47,73 @@
     @Test public void test47()
         throws Exception
     {
-        runTestNgTest( "4.7" );
+        runTestNgTest( "4.7", "jdk15" );
     }
 
     @Test @Ignore( "5.0 and 5.0.1 jars on central are malformed SUREFIRE-375 + MAVENUPLOAD-1024" ) public void XXXtest50()
         throws Exception
     {
-        runTestNgTest( "5.0" );
+        runTestNgTest( "5.0", "jdk15" );
     }
 
     @Test @Ignore( "5.0 and 5.0.1 jars on central are malformed SUREFIRE-375 + MAVENUPLOAD-1024" ) public void XXXtest501()
         throws Exception
     {
-        runTestNgTest( "5.0.1" );
+        runTestNgTest( "5.0.1", "jdk15" );
     }
 
     @Test public void test502()
         throws Exception
     {
-        runTestNgTest( "5.0.2" );
+        runTestNgTest( "5.0.2", "jdk15" );
     }
 
     @Test public void test51()
         throws Exception
     {
-        runTestNgTest( "5.1" );
+        runTestNgTest( "5.1", "jdk15" );
     }
 
     @Test public void test55()
         throws Exception
     {
-        runTestNgTest( "5.5" );
+        runTestNgTest( "5.5", "jdk15" );
     }
 
     @Test public void test56()
         throws Exception
     {
-        runTestNgTest( "5.6" );
+        runTestNgTest( "5.6", "jdk15" );
     }
 
     @Test public void test57()
         throws Exception
     {
-        runTestNgTest( "5.7" );
+        runTestNgTest( "5.7", "jdk15" );
     }
 
     @Test public void test58()
         throws Exception
     {
-        runTestNgTest( "5.8" );
+        runTestNgTest( "5.8", "jdk15" );
     }
 
     @Test public void test59()
         throws Exception
     {
-        runTestNgTest( "5.9" );
+        runTestNgTest( "5.9", "jdk15" );
     }
 
     @Test public void test510()
         throws Exception
     {
-        runTestNgTest( "5.10" );
+        runTestNgTest( "5.10", "jdk15" );
     }
 
     @Test public void test511()
         throws Exception
     {
-        runTestNgTest( "5.11" );
+        runTestNgTest( "5.11", "jdk15" );
     }
 
     @Test public void test512()
@@ -154,7 +155,7 @@
     @Test public void test60()
         throws Exception
     {
-        runTestNgTest( "6.0", false );
+        runTestNgTest( "6.0" );
     }
 
     @Test public void test685()
@@ -166,20 +167,40 @@
     private void runTestNgTestWithRunOrder( String version )
         throws Exception
     {
-        runTestNgTest( version, true );
+        runTestNgTest( version, null, true );
     }
 
     private void runTestNgTest( String version )
-        throws Exception
+            throws Exception
     {
-        runTestNgTest( version, false );
+        runTestNgTest( version, null, false );
     }
 
     private void runTestNgTest( String version, boolean validateRunOrder )
+            throws Exception
+    {
+        runTestNgTest( version, null, validateRunOrder );
+    }
+
+    private void runTestNgTest( String version, String classifier )
+        throws Exception
+    {
+        runTestNgTest( version, classifier, false );
+    }
+
+    private void runTestNgTest( String version, String classifier, boolean validateRunOrder )
         throws Exception
     {
+        final SurefireLauncher launcher = unpack( "testng-simple" )
+                                            .sysProp( "testNgVersion", version );
+
+        if ( classifier != null )
+        {
+            launcher.sysProp( "testNgClassifier", classifier );
+        }
+
+        final OutputValidator outputValidator = launcher.executeTest();
 
-        final OutputValidator outputValidator = unpack( "testng-simple" ).resetInitialGoals( version ).executeTest();
         outputValidator.verifyErrorFreeLog().assertTestSuiteResults( 3, 0, 0, 0 );
 
         if ( validateRunOrder )
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
index 90c46306e..b5489d1c7 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
@@ -37,12 +37,25 @@ public void crashInFork()
     @Test
     public void crashInReusableFork()
     {
-        unpack( "crash-detection" ).forkOncePerThread().threadCount( 1 ).maven().withFailure().executeTest();
+        unpack( "crash-detection" )
+                .forkPerThread()
+                .reuseForks( true )
+                .threadCount( 1 )
+                .maven()
+                .withFailure()
+                .executeTest();
     }
 
     @Test
     public void hardCrashInReusableFork()
     {
-        unpack( "crash-detection" ).forkOncePerThread().threadCount( 1 ).addGoal( "-DkillHard=true" ).maven().withFailure().executeTest();
+        unpack( "crash-detection" )
+                .forkPerThread()
+                .reuseForks( true )
+                .threadCount( 1 )
+                .addGoal( "-DkillHard=true" )
+                .maven()
+                .withFailure()
+                .executeTest();
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
index 86df7fa4e..beb1e328c 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastJUnitIT.java
@@ -24,7 +24,7 @@
 import static org.junit.runners.Parameterized.Parameters;
 
 /**
- * Test class for SUREFIRE-580, configuration parameter <em>skipAfterFailureCount</em>.
+ * Test class for SUREFIRE-580, configuration parameter {@code skipAfterFailureCount}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
index 40ebc9fdc..f4aa2b7f7 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/FailFastTestNgIT.java
@@ -24,7 +24,7 @@
 import static org.junit.runners.Parameterized.Parameters;
 
 /**
- * Test class for SUREFIRE-580, configuration parameter <em>skipAfterFailureCount</em>.
+ * Test class for SUREFIRE-580, configuration parameter {@code skipAfterFailureCount}.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputIT.java
index 7ec0039c5..6aac33068 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputIT.java
@@ -19,7 +19,6 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
@@ -37,30 +36,47 @@
     @Test
     public void printSummaryTrueWithRedirect()
     {
-        final OutputValidator outputValidator = unpack().redirectToFile( true ).printSummary( true ).executeTest();
-        outputValidator.getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" ).assertFileExists();
+        unpack().setForkJvm()
+                .redirectToFile( true )
+                .printSummary( true )
+                .executeTest()
+                .getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" )
+                .assertFileExists();
     }
 
     @Test
     public void printSummaryTrueWithoutRedirect()
     {
-        final OutputValidator outputValidator = unpack().redirectToFile( false ).printSummary( true ).executeTest();
-        outputValidator.getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" ).assertFileNotExists();
+        unpack().setForkJvm()
+                .redirectToFile( false )
+                .printSummary( true )
+                .executeTest()
+                .getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" )
+                .assertFileNotExists();
     }
 
     @Test
     public void printSummaryFalseWithRedirect()
     {
-        final OutputValidator outputValidator =
-            unpack().redirectToFile( true ).printSummary( false ).debugLogging().showErrorStackTraces().executeTest();
-        outputValidator.getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" ).assertFileExists();
+        unpack().setForkJvm()
+                .redirectToFile( true )
+                .printSummary( false )
+                .debugLogging()
+                .showErrorStackTraces()
+                .executeTest()
+                .getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" )
+                .assertFileExists();
     }
 
     @Test
     public void printSummaryFalseWithoutRedirect()
     {
-        final OutputValidator outputValidator = unpack().redirectToFile( false ).printSummary( false ).executeTest();
-        outputValidator.getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" ).assertFileNotExists();
+        unpack().setForkJvm()
+                .redirectToFile( false )
+                .printSummary( false )
+                .executeTest()
+                .getSurefireReportsFile( "forkConsoleOutput.Test1-output.txt" )
+                .assertFileNotExists();
     }
 
 
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputWithErrorsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputWithErrorsIT.java
index af4a6e67e..e5feb7fe9 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputWithErrorsIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkConsoleOutputWithErrorsIT.java
@@ -19,9 +19,7 @@
  * under the License.
  */
 
-import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
-import org.apache.maven.surefire.its.fixture.TestFile;
 import org.junit.Test;
 
 /**
@@ -32,16 +30,18 @@
  * @author Kristian Rosenvold
  */
 public class ForkConsoleOutputWithErrorsIT
-    extends SurefireJUnit4IntegrationTestCase
+        extends SurefireJUnit4IntegrationTestCase
 {
     @Test
     public void xmlFileContainsConsoleOutput()
     {
-        final OutputValidator outputValidator = unpack( "/fork-consoleOutputWithErrors" ).
-            failNever().redirectToFile( true ).executeTest();
-        final TestFile surefireReportsFile =
-            outputValidator.getSurefireReportsXmlFile( "TEST-forkConsoleOutput.Test2.xml" );
-        surefireReportsFile.assertContainsText( "sout: Will Fail soon" );
-        surefireReportsFile.assertContainsText( "serr: Will Fail now" );
+        unpack( "/fork-consoleOutputWithErrors" )
+                .setForkJvm()
+                .failNever()
+                .redirectToFile( true )
+                .executeTest()
+                .getSurefireReportsXmlFile( "TEST-forkConsoleOutput.Test2.xml" )
+                .assertContainsText( "sout: Will Fail soon" )
+                .assertContainsText( "serr: Will Fail now" );
     }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
index 061c5a602..574d14494 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
@@ -91,7 +91,11 @@ public void testForkModeNone()
     @Test
     public void testForkModeOncePerThreadSingleThread()
     {
-        String[] pids = doTest( unpack( getProject() ).setForkJvm().forkOncePerThread().threadCount( 1 ) );
+        String[] pids = doTest( unpack( getProject() )
+                .setForkJvm()
+                .forkPerThread()
+                .reuseForks( true )
+                .threadCount( 1 ) );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3 );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMainPID() ) );
@@ -100,8 +104,11 @@ public void testForkModeOncePerThreadSingleThread()
     @Test
     public void testForkModeOncePerThreadTwoThreads()
     {
-        String[] pids =
-            doTest( unpack( getProject() ).forkOncePerThread().threadCount( 2 ).addGoal( "-DsleepLength=1200" ) );
+        String[] pids = doTest( unpack( getProject() )
+                .forkPerThread()
+                .reuseForks( true )
+                .threadCount( 2 )
+                .addGoal( "-DsleepLength=1200" ) );
         assertDifferentPids( pids, 2 );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMainPID() ) );
     }
@@ -137,7 +144,7 @@ public void testForkCountOneReuse()
     public void testForkCountTwoNoReuse()
     {
         String[] pids =
-            doTest( unpack( getProject() ).forkCount( 2 ).reuseForks( false ).addGoal( "-DsleepLength=1200" ) );
+            doTest( unpack( getProject() ).setForkJvm().forkCount( 2 ).reuseForks( false ).addGoal( "-DsleepLength=1200" ) );
         assertDifferentPids( pids );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMainPID() ) );
     }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeTestNGIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeTestNGIT.java
index 05ba2f92f..5ba0577ea 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeTestNGIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeTestNGIT.java
@@ -27,6 +27,7 @@
 public class ForkModeTestNGIT
     extends ForkModeIT
 {
+    @Override
     protected String getProject()
     {
         return "fork-mode-testng";
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/IncludesExcludesFromFileIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/IncludesExcludesFromFileIT.java
index 58d270a24..a6c539cba 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/IncludesExcludesFromFileIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/IncludesExcludesFromFileIT.java
@@ -26,7 +26,7 @@
 
 /**
  * Test include/exclude from files.
- * <p/>
+ * <br>
  * Based on {@link IncludesExcludesIT}.
  */
 public class IncludesExcludesFromFileIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit47ParallelNotThreadSafeIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit47ParallelNotThreadSafeIT.java
index d6856b3b5..bf393caa7 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit47ParallelNotThreadSafeIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit47ParallelNotThreadSafeIT.java
@@ -24,7 +24,7 @@
 import org.junit.Test;
 
 /**
- * Testing <code>@net.jcip.annotations.NotThreadSafe</code> with ParallelComputerBuilder.
+ * Testing {@code @net.jcip.annotations.NotThreadSafe} with ParallelComputerBuilder.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.19
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
index 47fb71a05..7d6f5609d 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
@@ -42,22 +42,22 @@ public void testRerunFailingErrorTestsWithOneRetry()
         throws Exception
     {
         OutputValidator outputValidator =
-            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
                 "-Dsurefire.rerunFailingTestsCount=1" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0,
                                                                                                             0 );
         verifyFailuresOneRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal(
             "-DforkCount=2" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
         verifyFailuresOneRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=methods" ).addGoal(
             "-DuseUnlimitedThreads=true" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
         verifyFailuresOneRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=classes" ).addGoal(
             "-DuseUnlimitedThreads=true" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
         verifyFailuresOneRetryAllClasses( outputValidator );
@@ -69,24 +69,24 @@ public void testRerunFailingErrorTestsTwoRetry()
     {
         // Four flakes, both tests have been re-run twice
         OutputValidator outputValidator =
-            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
                 "-Dsurefire.rerunFailingTestsCount=2" ).executeTest().assertTestSuiteResults( 5, 0, 0, 0, 4 );
 
         verifyFailuresTwoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=2" ).addGoal( "-DforkCount=3" ).executeTest()
             .assertTestSuiteResults( 5, 0, 0, 0, 4 );
 
         verifyFailuresTwoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=2" ).addGoal( "-Dparallel=methods" ).addGoal(
             "-DuseUnlimitedThreads=true" ).executeTest().assertTestSuiteResults( 5, 0, 0, 0, 4 );
 
         verifyFailuresTwoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=2" ).addGoal( "-Dparallel=classes" ).addGoal(
             "-DuseUnlimitedThreads=true" ).executeTest().assertTestSuiteResults( 5, 0, 0, 0, 4 );
 
@@ -98,22 +98,22 @@ public void testRerunFailingErrorTestsFalse()
         throws Exception
     {
         OutputValidator outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion(
-            "4.7" ).maven().withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
+            "4.12" ).maven().withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
 
         verifyFailuresNoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-DforkCount=3" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
 
         verifyFailuresNoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dparallel=methods" ).addGoal(
             "-DuseUnlimitedThreads=true" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
 
         verifyFailuresNoRetryAllClasses( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dparallel=classes" ).addGoal(
             "-DuseUnlimitedThreads=true" ).withFailure().executeTest().assertTestSuiteResults( 5, 1, 1, 0, 0 );
 
@@ -125,26 +125,26 @@ public void testRerunOneTestClass()
         throws Exception
     {
         OutputValidator outputValidator =
-            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
                 "-Dsurefire.rerunFailingTestsCount=1" ).addGoal(
                 "-Dtest=FlakyFirstTimeTest" ).withFailure().executeTest().assertTestSuiteResults( 3, 1, 1, 0, 0 );
 
         verifyFailuresOneRetryOneClass( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-DforkCount=3" ).addGoal(
             "-Dtest=FlakyFirstTimeTest" ).withFailure().executeTest().assertTestSuiteResults( 3, 1, 1, 0, 0 );
 
         verifyFailuresOneRetryOneClass( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=methods" ).addGoal(
             "-DuseUnlimitedThreads=true" ).addGoal(
             "-Dtest=FlakyFirstTimeTest" ).withFailure().executeTest().assertTestSuiteResults( 3, 1, 1, 0, 0 );
 
         verifyFailuresOneRetryOneClass( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=classes" ).addGoal(
             "-DuseUnlimitedThreads=true" ).addGoal(
             "-Dtest=FlakyFirstTimeTest" ).withFailure().executeTest().assertTestSuiteResults( 3, 1, 1, 0, 0 );
@@ -157,21 +157,21 @@ public void testRerunOneTestMethod()
         throws Exception
     {
         OutputValidator outputValidator =
-            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+            unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
                 "-Dsurefire.rerunFailingTestsCount=1" ).addGoal(
                 "-Dtest=FlakyFirstTimeTest#testFailing*" ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1,
                                                                                                                0, 0 );
 
         verifyFailuresOneRetryOneMethod( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-DforkCount=3" ).addGoal(
             "-Dtest=FlakyFirstTimeTest#testFailing*" ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1, 0,
                                                                                                            0 );
 
         verifyFailuresOneRetryOneMethod( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=methods" ).addGoal(
             "-DuseUnlimitedThreads=true" ).addGoal(
             "-Dtest=FlakyFirstTimeTest#testFailing*" ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1, 0,
@@ -179,7 +179,7 @@ public void testRerunOneTestMethod()
 
         verifyFailuresOneRetryOneMethod( outputValidator );
 
-        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.7" ).maven().addGoal(
+        outputValidator = unpack().addGoal( "-Dprovider=surefire-junit4" ).setJUnitVersion( "4.12" ).maven().addGoal(
             "-Dsurefire.rerunFailingTestsCount=1" ).addGoal( "-Dparallel=classes" ).addGoal(
             "-DuseUnlimitedThreads=true" ).addGoal(
             "-Dtest=FlakyFirstTimeTest#testFailing*" ).withFailure().executeTest().assertTestSuiteResults( 1, 0, 1, 0,
@@ -216,11 +216,11 @@ private void verifyFailuresOneRetryOneMethod( OutputValidator outputValidator )
     private void verifyFailuresOneRetry( OutputValidator outputValidator, int run, int failures, int errors,
                                          int flakes )
     {
-        outputValidator.verifyTextInLog( "Failed tests" );
+        outputValidator.verifyTextInLog( "Failures:" );
         outputValidator.verifyTextInLog( "Run 1: FlakyFirstTimeTest.testFailingTestOne" );
         outputValidator.verifyTextInLog( "Run 2: FlakyFirstTimeTest.testFailingTestOne" );
 
-        outputValidator.verifyTextInLog( "Tests in error" );
+        outputValidator.verifyTextInLog( "Errors:" );
         outputValidator.verifyTextInLog( "Run 1: FlakyFirstTimeTest.testErrorTestOne" );
         outputValidator.verifyTextInLog( "Run 2: FlakyFirstTimeTest.testErrorTestOne" );
 
@@ -230,7 +230,7 @@ private void verifyFailuresOneRetry( OutputValidator outputValidator, int run, i
     private void verifyOnlyFailuresOneRetry( OutputValidator outputValidator, int run, int failures, int errors,
                                              int flakes )
     {
-        outputValidator.verifyTextInLog( "Failed tests" );
+        outputValidator.verifyTextInLog( "Failures:" );
         outputValidator.verifyTextInLog( "Run 1: FlakyFirstTimeTest.testFailingTestOne" );
         outputValidator.verifyTextInLog( "Run 2: FlakyFirstTimeTest.testFailingTestOne" );
 
@@ -240,7 +240,7 @@ private void verifyOnlyFailuresOneRetry( OutputValidator outputValidator, int ru
     private void verifyFailuresTwoRetry( OutputValidator outputValidator, int run, int failures, int errors,
                                          int flakes )
     {
-        outputValidator.verifyTextInLog( "Flaked tests" );
+        outputValidator.verifyTextInLog( "Flakes:" );
         outputValidator.verifyTextInLog( "Run 1: FlakyFirstTimeTest.testFailingTestOne" );
         outputValidator.verifyTextInLog( "Run 2: FlakyFirstTimeTest.testFailingTestOne" );
         outputValidator.verifyTextInLog( "Run 3: PASS" );
@@ -253,7 +253,7 @@ private void verifyFailuresTwoRetry( OutputValidator outputValidator, int run, i
 
     private void verifyFailuresNoRetry( OutputValidator outputValidator, int run, int failures, int errors, int flakes )
     {
-        outputValidator.verifyTextInLog( "Failed tests" );
+        outputValidator.verifyTextInLog( "Failures:" );
         outputValidator.verifyTextInLog( "testFailingTestOne(junit4.FlakyFirstTimeTest)" );
         outputValidator.verifyTextInLog( "ERROR" );
         outputValidator.verifyTextInLog( "testErrorTestOne(junit4.FlakyFirstTimeTest)" );
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Java9FullApiIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Java9FullApiIT.java
new file mode 100644
index 000000000..b1bea128a
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Java9FullApiIT.java
@@ -0,0 +1,95 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Running Surefire on the top of JDK 9 and should be able to load
+ * classes of multiple different Jigsaw modules without error.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class Java9FullApiIT
+        extends AbstractJigsawIT
+{
+
+    @Test
+    public void shouldLoadMultipleJavaModules_JavaHome() throws IOException
+    {
+        OutputValidator validator = assumeJigsaw()
+                                            .setForkJvm()
+                                            .debugLogging()
+                                            .execute( "verify" )
+                                            .verifyErrorFree( 2 );
+
+        validator.verifyTextInLog( "loaded class java.sql.SQLException" )
+                .verifyTextInLog( "loaded class javax.xml.ws.Holder" )
+                .verifyTextInLog( "loaded class javax.xml.bind.JAXBException" )
+                .verifyTextInLog( "loaded class org.omg.CORBA.BAD_INV_ORDER" )
+                .verifyTextInLog( "java.specification.version=9" );
+    }
+
+    @Test
+    public void shouldLoadMultipleJavaModules_JvmParameter() throws IOException
+    {
+        OutputValidator validator = assumeJava9Property()
+                                            .setForkJvm()
+                                            .debugLogging()
+                                            .sysProp( JDK_HOME_KEY, new File( JDK_HOME ).getCanonicalPath() )
+                                            .execute( "verify" )
+                                            .verifyErrorFree( 2 );
+
+        validator.verifyTextInLog( "loaded class java.sql.SQLException" )
+                .verifyTextInLog( "loaded class javax.xml.ws.Holder" )
+                .verifyTextInLog( "loaded class javax.xml.bind.JAXBException" )
+                .verifyTextInLog( "loaded class org.omg.CORBA.BAD_INV_ORDER" )
+                .verifyTextInLog( "java.specification.version=9" );
+    }
+
+    @Test
+    public void shouldLoadMultipleJavaModules_ToolchainsXML() throws IOException
+    {
+        OutputValidator validator = assumeJava9Property()
+                                            .setForkJvm()
+                                            .activateProfile( "use-toolchains" )
+                                            .addGoal( "--toolchains" )
+                                            .addGoal( System.getProperty( "maven.toolchains.file" ) )
+                                            .execute( "verify" )
+                                            .verifyErrorFree( 2 );
+
+        validator.verifyTextInLog( "loaded class java.sql.SQLException" )
+                .verifyTextInLog( "loaded class javax.xml.ws.Holder" )
+                .verifyTextInLog( "loaded class javax.xml.bind.JAXBException" )
+                .verifyTextInLog( "loaded class org.omg.CORBA.BAD_INV_ORDER" )
+                .verifyTextInLog( "java.specification.version=9" );
+    }
+
+    @Override
+    protected String getProjectDirectoryName()
+    {
+        return "java9-full-api";
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/LongWindowsPathIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/LongWindowsPathIT.java
new file mode 100644
index 000000000..62808358a
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/LongWindowsPathIT.java
@@ -0,0 +1,88 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Testing long path of base.dir where Windows CLI crashes.
+ * <br>
+ * Integration test for <a href="https://issues.apache.org/jira/browse/SUREFIRE-1400">SUREFIRE-1400</a>.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class LongWindowsPathIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    private static final String PROJECT_DIR = "long-windows-path";
+    private static final String LONG_PATH = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+    // the IT engine crashes using long path
+    private static final String LONG_DIR = LONG_PATH + LONG_PATH + LONG_PATH;
+
+    @Test
+    public void shouldRunInSystemTmp() throws Exception
+    {
+        assumeTrue( IS_OS_WINDOWS );
+
+        OutputValidator validator = unpack().setForkJvm()
+                                            .showErrorStackTraces()
+                                            .executeTest()
+                                            .verifyErrorFreeLog();
+
+        validator.assertThatLogLine( containsString( "SUREFIRE-1400 user.dir=" ), is( 1 ) )
+                .assertThatLogLine( containsString( "SUREFIRE-1400 surefire.real.class.path=" ), is( 1 ) );
+
+        for ( String line : validator.loadLogLines() )
+        {
+            if ( line.contains( "SUREFIRE-1400 user.dir=" ) )
+            {
+                File buildDir = new File( System.getProperty( "user.dir" ), "target" );
+                File itBaseDir = new File( buildDir, "LongWindowsPathIT_shouldRunInSystemTmp" );
+
+                assertThat( line )
+                        .contains( itBaseDir.getAbsolutePath() );
+            }
+            else if ( line.contains( "SUREFIRE-1400 surefire.real.class.path=" ) )
+            {
+                assertThat( line )
+                        .contains( System.getProperty( "java.io.tmpdir" ) );
+            }
+        }
+    }
+
+    private SurefireLauncher unpack() throws IOException
+    {
+        return unpack( PROJECT_DIR/*, "_" + LONG_DIR*/ );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
index b057a34e7..447d8f133 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestMethodPatternIT.java
@@ -24,6 +24,11 @@
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
 /**
  * Test project using -Dtest=mtClass#myMethod
  *
@@ -34,9 +39,13 @@
 {
     private static final String RUNNING_WITH_PROVIDER47 = "parallel='none', perCoreThreadCount=true, threadCount=0";
 
-    public OutputValidator runMethodPattern( String projectName, String... goals )
+    public OutputValidator runMethodPattern( String projectName, Map<String, String> props, String... goals )
     {
         SurefireLauncher launcher = unpack( projectName );
+        for ( Entry<String, String> entry : props.entrySet() )
+        {
+            launcher.sysProp( entry.getKey(), entry.getValue() );
+        }
         for ( String goal : goals )
         {
             launcher.addGoal( goal );
@@ -49,19 +58,19 @@ public OutputValidator runMethodPattern( String projectName, String... goals )
     @Test
     public void testJUnit44()
     {
-        runMethodPattern( "junit44-method-pattern" );
+        runMethodPattern( "junit44-method-pattern", Collections.<String, String>emptyMap() );
     }
 
     @Test
     public void testJUnit48Provider4()
     {
-        runMethodPattern( "junit48-method-pattern", "-P surefire-junit4" );
+        runMethodPattern( "junit48-method-pattern", Collections.<String, String>emptyMap(), "-P surefire-junit4" );
     }
 
     @Test
     public void testJUnit48Provider47()
     {
-        runMethodPattern( "junit48-method-pattern", "-P surefire-junit47" )
+        runMethodPattern( "junit48-method-pattern", Collections.<String, String>emptyMap(), "-P surefire-junit47" )
             .verifyTextInLog( RUNNING_WITH_PROVIDER47 );
     }
 
@@ -71,28 +80,36 @@ public void testJUnit48WithCategoryFilter()
         unpack( "junit48-method-pattern" )
             .addGoal( "-Dgroups=junit4.SampleCategory" )
             .executeTest()
-            .assertTestSuiteResults( 1, 0, 0, 0 );;
+            .assertTestSuiteResults( 1, 0, 0, 0 );
     }
 
     @Test
     public void testTestNgMethodBefore()
     {
-        runMethodPattern( "testng-method-pattern-before" );
+        Map<String, String> props = new HashMap<String, String>();
+        props.put( "testNgVersion", "5.7" );
+        props.put( "testNgClassifier", "jdk15" );
+        runMethodPattern( "testng-method-pattern-before", props );
     }
 
     @Test
     public void testTestNGMethodPattern()
     {
-        runMethodPattern( "/testng-method-pattern" );
+        Map<String, String> props = new HashMap<String, String>();
+        props.put( "testNgVersion", "5.7" );
+        props.put( "testNgClassifier", "jdk15" );
+        runMethodPattern( "/testng-method-pattern", props );
     }
 
     @Test
     public void testMethodPatternAfter()
     {
         unpack( "testng-method-pattern-after" )
-            .executeTest()
-            .verifyErrorFree( 2 )
-            .verifyTextInLog( "Called tearDown" );
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .executeTest()
+                .verifyErrorFree( 2 )
+                .verifyTextInLog( "Called tearDown" );
     }
 
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestNgSuccessPercentageIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestNgSuccessPercentageIT.java
index a9f26e75d..3b12e0356 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestNgSuccessPercentageIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestNgSuccessPercentageIT.java
@@ -34,7 +34,11 @@
     @Test
     public void testPassesWhenFailuresLessThanSuccessPercentage()
     {
-        OutputValidator validator = unpack("/testng-succes-percentage").mavenTestFailureIgnore( true ).executeTest();
+        OutputValidator validator = unpack("/testng-succes-percentage")
+                                            .sysProp( "testNgVersion", "5.7" )
+                                            .sysProp( "testNgClassifier", "jdk15" )
+                                            .mavenTestFailureIgnore( true )
+                                            .executeTest();
         validator.assertTestSuiteResults(8, 0, 1, 0);
     }
 
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
index 9cc14fbf8..53eddce63 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
@@ -24,6 +24,10 @@
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Test project using -Dtest=mtClass#myMethod
  *
@@ -34,10 +38,15 @@
 {
     private static final String RUNNING_WITH_PROVIDER47 = "parallel='none', perCoreThreadCount=true, threadCount=0";
 
-    public OutputValidator singleMethod( String projectName, String testToRun, String... goals )
+    public OutputValidator singleMethod( String projectName, Map<String, String> props, String testToRun,
+                                         String... goals )
         throws Exception
     {
         SurefireLauncher launcher = unpack( projectName );
+        for ( Map.Entry<String, String> entry : props.entrySet() )
+        {
+            launcher.sysProp( entry.getKey(), entry.getValue() );
+        }
         for ( String goal : goals )
         {
             launcher.addGoal( goal );
@@ -56,21 +65,21 @@ public OutputValidator singleMethod( String projectName, String testToRun, Strin
     public void testJunit44()
         throws Exception
     {
-        singleMethod( "junit44-single-method", null );
+        singleMethod( "junit44-single-method", Collections.<String, String>emptyMap(), null );
     }
 
     @Test
     public void testJunit48Provider4()
         throws Exception
     {
-        singleMethod( "junit48-single-method", null, "-P surefire-junit4" );
+        singleMethod( "junit48-single-method", Collections.<String, String>emptyMap(), null, "-P surefire-junit4" );
     }
 
     @Test
     public void testJunit48Provider47()
         throws Exception
     {
-        singleMethod( "junit48-single-method", null, "-P surefire-junit47" )
+        singleMethod( "junit48-single-method", Collections.<String, String>emptyMap(), null, "-P surefire-junit47" )
             .verifyTextInLog( RUNNING_WITH_PROVIDER47 );
     }
 
@@ -90,35 +99,43 @@ public void testJunit48parallel()
     public void testTestNg()
         throws Exception
     {
-        singleMethod( "testng-single-method", null );
+        Map<String, String> props = new HashMap<String, String>();
+        props.put( "testNgVersion", "5.7" );
+        props.put( "testNgClassifier", "jdk15" );
+        singleMethod( "testng-single-method", props, null );
     }
 
     @Test
     public void testTestNg5149()
         throws Exception
     {
-        singleMethod( "/testng-single-method-5-14-9", null );
+        singleMethod( "/testng-single-method-5-14-9", Collections.<String, String>emptyMap(), null );
     }
 
     @Test
     public void fullyQualifiedJunit48Provider4()
             throws Exception
     {
-        singleMethod( "junit48-single-method", "junit4.BasicTest#testSuccessOne", "-P surefire-junit4" );
+        singleMethod( "junit48-single-method", Collections.<String, String>emptyMap(),
+                            "junit4.BasicTest#testSuccessOne", "-P surefire-junit4" );
     }
 
     @Test
     public void fullyQualifiedJunit48Provider47()
             throws Exception
     {
-        singleMethod("junit48-single-method", "junit4.BasicTest#testSuccessOne", "-P surefire-junit47");
+        singleMethod("junit48-single-method", Collections.<String, String>emptyMap(),
+                            "junit4.BasicTest#testSuccessOne", "-P surefire-junit47");
     }
 
     @Test
     public void fullyQualifiedTestNg()
             throws Exception
     {
-        singleMethod( "testng-single-method", "testng.BasicTest#testSuccessOne" );
+        Map<String, String> props = new HashMap<String, String>();
+        props.put( "testNgVersion", "5.7" );
+        props.put( "testNgClassifier", "jdk15" );
+        singleMethod( "testng-single-method", props, "testng.BasicTest#testSuccessOne" );
     }
 
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TwoTestCasesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TwoTestCasesIT.java
index eca2fb051..c21876930 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TwoTestCasesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TwoTestCasesIT.java
@@ -41,7 +41,12 @@
     public void testTwoTestCases()
         throws Exception
     {
-        unpack( "junit-twoTestCases" ).executeTest().verifyErrorFreeLog().assertTestSuiteResults( 2, 0, 0, 0 );
+        unpack( "junit-twoTestCases" )
+                .sysProp( "testNgVersion", "5.7" )
+                .sysProp( "testNgClassifier", "jdk15" )
+                .executeTest()
+                .verifyErrorFreeLog()
+                .assertTestSuiteResults( 2, 0, 0, 0 );
     }
 
     /**
@@ -51,7 +56,10 @@ public void testTwoTestCases()
     public void testTwoTestCaseSuite()
         throws Exception
     {
-        final OutputValidator outputValidator = unpack( "junit-twoTestCaseSuite" ).executeTest();
+        final OutputValidator outputValidator = unpack( "junit-twoTestCaseSuite" )
+                                                        .sysProp( "testNgVersion", "5.7" )
+                                                        .sysProp( "testNgClassifier", "jdk15" )
+                                                        .executeTest();
         outputValidator.verifyErrorFreeLog().assertTestSuiteResults( 2, 0, 0, 0 );
         List<ReportTestSuite> reports = HelperAssertions.extractReports( outputValidator.getBaseDir() );
         Set<String> classNames = extractClassNames( reports );
@@ -85,7 +93,10 @@ private void assertContains( Set<String> set, String expected )
     public void testJunit4Suite()
         throws Exception
     {
-        final OutputValidator outputValidator = unpack( "junit4-twoTestCaseSuite" ).executeTest();
+        final OutputValidator outputValidator = unpack( "junit4-twoTestCaseSuite" )
+                                                        .sysProp( "testNgVersion", "5.7" )
+                                                        .sysProp( "testNgClassifier", "jdk15" )
+                                                        .executeTest();
         outputValidator.verifyErrorFreeLog().assertTestSuiteResults( 2, 0, 0, 0 );
 
         List<ReportTestSuite> reports =
@@ -102,7 +113,10 @@ public void testJunit4Suite()
     public void testTestNGSuite()
         throws Exception
     {
-        final OutputValidator outputValidator = unpack( "testng-twoTestCaseSuite" ).executeTest();
+        final OutputValidator outputValidator = unpack( "testng-twoTestCaseSuite" )
+                                                        .sysProp( "testNgVersion", "5.7" )
+                                                        .sysProp( "testNgClassifier", "jdk15" )
+                                                        .executeTest();
         outputValidator.verifyErrorFreeLog().assertTestSuiteResults( 2, 0, 0, 0 );
         List<ReportTestSuite> reports = HelperAssertions.extractReports( outputValidator.getBaseDir() );
         Set<String> classNames = extractClassNames( reports );
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/UnicodeTestNamesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/UnicodeTestNamesIT.java
index a99c14237..5470a3f3c 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/UnicodeTestNamesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/UnicodeTestNamesIT.java
@@ -31,7 +31,7 @@
 
 /**
  * Verifies unicode filenames pass through correctly.
- * <p/>
+ * <br>
  * If the underlying file system turns out not to support unicode, we just fail an assumption.s
  */
 public class UnicodeTestNamesIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/FailsafeOutputValidator.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/FailsafeOutputValidator.java
index fcfcc9f61..b96cf9bd6 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/FailsafeOutputValidator.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/FailsafeOutputValidator.java
@@ -29,6 +29,7 @@ public FailsafeOutputValidator( OutputValidator source )
         super( source.verifier );
     }
 
+    @Override
     public OutputValidator verifyErrorFree( int total )
     {
         try
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/HelperAssertions.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/HelperAssertions.java
index 98edcfb39..17e52a664 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/HelperAssertions.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/HelperAssertions.java
@@ -18,11 +18,6 @@
  * under the License.
  */
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
 import org.apache.commons.lang3.JavaVersion;
 import org.apache.commons.lang3.SystemUtils;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
@@ -30,12 +25,14 @@
 import org.apache.maven.plugins.surefire.report.ReportTestSuite;
 import org.apache.maven.plugins.surefire.report.SurefireReportParser;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 import static org.apache.commons.lang3.SystemUtils.JAVA_SPECIFICATION_VERSION;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 @SuppressWarnings( { "JavaDoc" } )
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java
index 1198fcb6b..bd12dda5c 100755
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java
@@ -19,22 +19,26 @@
  * under the License.
  */
 
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.apache.maven.it.util.ResourceExtractor;
+import org.apache.maven.shared.utils.io.FileUtils;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
-import org.apache.commons.lang.text.StrSubstitutor;
-import org.apache.maven.it.VerificationException;
-import org.apache.maven.it.Verifier;
-import org.apache.maven.it.util.ResourceExtractor;
-import org.apache.maven.shared.utils.io.FileUtils;
+
+import static java.util.Collections.unmodifiableList;
 
 /**
  * Encapsulate all needed features to start a maven run
- * <p/>
+ * <br>
  *
  * @author Kristian Rosenvold
  */
@@ -203,13 +207,13 @@ public MavenLauncher offline()
 
     public MavenLauncher skipClean()
     {
-        goals.add( "-Dclean.skip=true" );
+        writeGoal( "-Dclean.skip=true" );
         return this;
     }
 
     public MavenLauncher addGoal( String goal )
     {
-        goals.add( goal );
+        writeGoal( goal );
         return this;
     }
 
@@ -223,6 +227,35 @@ public OutputValidator executeTest()
         return conditionalExec( "test" );
     }
 
+    List<String> getGoals()
+    {
+        return unmodifiableList( goals );
+    }
+
+    private void writeGoal( String newGoal )
+    {
+        if ( newGoal != null && newGoal.startsWith( "-D" ) )
+        {
+            final String sysPropKey =
+                    newGoal.contains( "=" ) ? newGoal.substring( 0, newGoal.indexOf( '=' ) ) : newGoal;
+
+            final String sysPropStarter = sysPropKey + "=";
+
+            for ( ListIterator<String> it = goals.listIterator(); it.hasNext(); )
+            {
+                String goal = it.next();
+                if ( goal.equals( sysPropKey ) || goal.startsWith( sysPropStarter ) )
+                {
+                    System.out.printf( "[WARNING] System property already exists '%s'. Overriding to '%s'.\n",
+                                             goal, newGoal );
+                    it.set( newGoal );
+                    return;
+                }
+            }
+        }
+        goals.add( newGoal );
+    }
+
     private OutputValidator conditionalExec(String goal)
     {
         OutputValidator verify;
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncherTest.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncherTest.java
new file mode 100644
index 000000000..42657c058
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncherTest.java
@@ -0,0 +1,47 @@
+package org.apache.maven.surefire.its.fixture;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.hasItems;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20
+ */
+public class MavenLauncherTest
+{
+    @Test
+    public void shouldNotDuplicateSystemProperties()
+    {
+        MavenLauncher launcher = new MavenLauncher( getClass(), "", "" )
+                                         .addGoal( "-DskipTests" )
+                                         .addGoal( "-Dx=a" )
+                                         .addGoal( "-DskipTests" )
+                                         .addGoal( "-Dx=b" );
+
+        assertThat( launcher.getGoals(), hasItems( "-Dx=b", "-DskipTests" ) );
+
+        assertThat( launcher.getGoals().size(), is( 2 ) );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
index 8184abef4..a76f86ffd 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
@@ -184,14 +184,19 @@ public TestFile getTargetFile( String fileName )
 
     public TestFile getSurefireReportsFile( String fileName )
     {
-        File targetDir = getSubFile( "target/surefire-reports" );
+        File targetDir = getSurefireReportsDirectory();
         return new TestFile( new File( targetDir, fileName ), this );
     }
 
     public TestFile getSurefireReportsXmlFile( String fileName )
     {
-        File targetDir = getSubFile( "target/surefire-reports" );
-        return new TestFile( new File( targetDir, fileName ), Charset.forName("UTF-8"), this );
+        File targetDir = getSurefireReportsDirectory();
+        return new TestFile( new File( targetDir, fileName ), Charset.forName( "UTF-8" ), this );
+    }
+
+    public File getSurefireReportsDirectory()
+    {
+        return getSubFile( "target/surefire-reports" );
     }
 
     public TestFile getSiteFile( String fileName )
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireJUnit4IntegrationTestCase.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireJUnit4IntegrationTestCase.java
index c300abf96..ee04dbcb6 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireJUnit4IntegrationTestCase.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireJUnit4IntegrationTestCase.java
@@ -22,7 +22,7 @@
 /**
  * Contains commonly used features for most tests, encapsulating
  * common use cases.
- * <p/>
+ * <br>
  * Also includes thread-safe access to the extracted resource
  * files, which AbstractSurefireIntegrationTestClass does not.
  * Thread safe only for running in "classes" mode.
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
index cf0d90d84..163aaac85 100755
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
@@ -19,31 +19,26 @@
  * under the License.
  */
 
+import org.apache.maven.it.VerificationException;
+
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
-import org.apache.maven.artifact.versioning.ArtifactVersion;
-import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
-import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
-import org.apache.maven.artifact.versioning.VersionRange;
-import org.apache.maven.it.VerificationException;
+
+import static java.util.Collections.singletonList;
 
 /**
  * Encapsulate all needed features to start a surefire run
- * <p/>
+ * <br>
  * Also includes thread-safe access to the extracted resource
  * files
  *
  * @author Kristian Rosenvold                                 -
  */
-public class SurefireLauncher
+public final class SurefireLauncher
 {
-
     private final MavenLauncher mavenLauncher;
 
-    private final String testNgVersion = System.getProperty( "testng.version" );
-
     private final String surefireVersion = System.getProperty( "surefire.version" );
 
     public SurefireLauncher( MavenLauncher mavenLauncher )
@@ -65,7 +60,7 @@ String getTestMethodName()
     public void reset()
     {
         mavenLauncher.reset();
-        for ( String s : getInitialGoals( testNgVersion ) )
+        for ( String s : getInitialGoals() )
         {
             mavenLauncher.addGoal( s );
         }
@@ -75,7 +70,7 @@ public void reset()
     public SurefireLauncher setInProcessJavaHome()
     {
         String javaHome = System.getenv( "JAVA_HOME" );
-        if ( javaHome != null && javaHome.length() > 0 )
+        if ( javaHome != null && !javaHome.isEmpty() )
         {
             try
             {
@@ -122,40 +117,9 @@ public SurefireLauncher setMavenOpts(String opts){
         return this;
     }
 
-    private List<String> getInitialGoals( String testNgVersion )
-    {
-        List<String> goals1 = new ArrayList<String>();
-        goals1.add( "-Dsurefire.version=" + surefireVersion );
-
-        if ( this.testNgVersion != null )
-        {
-            goals1.add( "-DtestNgVersion=" + testNgVersion );
-
-            ArtifactVersion v = new DefaultArtifactVersion( testNgVersion );
-            try
-            {
-                if ( VersionRange.createFromVersionSpec( "(,5.12.1)" ).containsVersion( v ) )
-                {
-                    goals1.add( "-DtestNgClassifier=jdk15" );
-                }
-            }
-            catch ( InvalidVersionSpecificationException e )
-            {
-                throw new RuntimeException( e.getMessage(), e );
-            }
-        }
-
-        return goals1;
-    }
-
-    public SurefireLauncher resetInitialGoals( String testNgVersion )
+    private List<String> getInitialGoals()
     {
-        mavenLauncher.resetGoals();
-        for ( String s : getInitialGoals( testNgVersion ) )
-        {
-            mavenLauncher.addGoal( s );
-        }
-        return this;
+        return singletonList( "-Dsurefire.version=" + surefireVersion );
     }
 
     public SurefireLauncher showErrorStackTraces()
@@ -264,12 +228,7 @@ public SurefireLauncher forkPerTest()
 
     public SurefireLauncher forkPerThread()
     {
-        return forkMode( "perthread" ).reuseForks( false );
-    }
-
-    public SurefireLauncher forkOncePerThread()
-    {
-        return forkPerThread().reuseForks( true );
+        return forkMode( "perthread" );
     }
 
     public SurefireLauncher threadCount( int threadCount )
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/TestFile.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/TestFile.java
index 61736df46..cf6ad8461 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/TestFile.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/TestFile.java
@@ -39,7 +39,6 @@
  */
 public class TestFile
 {
-
     private final File file;
 
     private final Charset encoding;
@@ -148,4 +147,9 @@ public URI toURI()
     {
         return file.toURI();
     }
+
+    public File getFile()
+    {
+        return file;
+    }
 }
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1024VerifyFailsafeIfTestedIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1024VerifyFailsafeIfTestedIT.java
index 9866dd587..886cd0740 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1024VerifyFailsafeIfTestedIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1024VerifyFailsafeIfTestedIT.java
@@ -29,7 +29,7 @@
  * Found in Surefire 2.16.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1024}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1024">SUREFIRE-1024</a>
  * @since 2.19
  */
 public class Surefire1024VerifyFailsafeIfTestedIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1028UnableToRunSingleIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1028UnableToRunSingleIT.java
index 74cb28193..ec63a8a05 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1028UnableToRunSingleIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1028UnableToRunSingleIT.java
@@ -25,20 +25,20 @@
 
 /**
  * Plugin Configuration: parallel=classes
- * <p/>
+ * <br>
  * With Surefire 2.15
  * {@code $ mvn test -Dtest=MyTest#testFoo}
- * Results :
+ * Results:
  * Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
- * <p/>
+ * <br>
  * With Surefire 2.16
  * {@code $ mvn test -Dtest=MyTest#testFoo}
- * <p/>
- * Results :
+ * <br>
+ * Results:
  * Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1028}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1028">SUREFIRE-1028</a>
  * @since 2.18
  */
 public class Surefire1028UnableToRunSingleIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1036NonFilterableJUnitRunnerWithCategoriesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1036NonFilterableJUnitRunnerWithCategoriesIT.java
index 1d5d35329..0d6d8b6f2 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1036NonFilterableJUnitRunnerWithCategoriesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1036NonFilterableJUnitRunnerWithCategoriesIT.java
@@ -34,7 +34,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1036}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1036">SUREFIRE-1036</a>
  * @since 2.18
  */
 public class Surefire1036NonFilterableJUnitRunnerWithCategoriesIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1053SystemPropertiesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1053SystemPropertiesIT.java
index 6494b7964..b3ed7444a 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1053SystemPropertiesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1053SystemPropertiesIT.java
@@ -28,7 +28,7 @@
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1053}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1053">SUREFIRE-1053</a>
  * @since 2.18
  */
 public class Surefire1053SystemPropertiesIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1080ParallelForkDoubleTestIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1080ParallelForkDoubleTestIT.java
index 27d8cfd2e..d6a38fa7c 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1080ParallelForkDoubleTestIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1080ParallelForkDoubleTestIT.java
@@ -24,8 +24,8 @@
 import org.junit.Test;
 
 /**
- * Description of SUREFIRE-1080: <p/>
- * <p/>
+ * Description of SUREFIRE-1080: <br>
+ * <br>
  * There are 9 tests in total in the attached project, and mvn test will show 9 tests run.
  * When I use the command " mvn test -Dparallel=classes -DforkCount=2 -DuseUnlimitedThreads=true", it shows 13 tests
  * run (and sometimes 16), and some tests are run more than once.
@@ -35,7 +35,7 @@
  * JUnit 4.11
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1080}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1080">SUREFIRE-1080</a>
  * @since 2.18
  */
 public class Surefire1080ParallelForkDoubleTestIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1082ParallelJUnitParameterizedIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1082ParallelJUnitParameterizedIT.java
index c50d4e40b..2669b2f21 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1082ParallelJUnitParameterizedIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1082ParallelJUnitParameterizedIT.java
@@ -23,38 +23,54 @@
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.apache.maven.surefire.its.fixture.TestFile;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 
+import java.nio.charset.Charset;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.TreeSet;
 
 import static org.hamcrest.core.AnyOf.anyOf;
 import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertThat;
 
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1082}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1082">SUREFIRE-1082</a>
  * @since 2.18
  */
 public class Surefire1082ParallelJUnitParameterizedIT
     extends SurefireJUnit4IntegrationTestCase
 {
+    private static Set<String> printOnlyTestLinesFromConsole( OutputValidator validator )
+            throws VerificationException
+    {
+        return printOnlyTestLines( validator.loadLogLines() );
+    }
+
+    private static Set<String> printOnlyTestLinesFromOutFile( OutputValidator validator )
+            throws VerificationException
+    {
+        TestFile report = validator.getSurefireReportsFile( "jiras.surefire1082.Jira1082Test-output.txt" );
+        report.assertFileExists();
+        return printOnlyTestLines( validator.loadFile( report.getFile(), Charset.forName( "UTF-8" ) ) );
+    }
 
-    private static Set<String> printOnlyTestLines( OutputValidator validator )
+    private static Set<String> printOnlyTestLines( Collection<String> logs )
         throws VerificationException
     {
-        Set<String> log = new TreeSet<String>( validator.loadLogLines() );
-        for ( Iterator<String> it = log.iterator(); it.hasNext(); )
+        Set<String> log = new TreeSet<String>();
+        for ( String line : logs )
         {
-            String line = it.next();
-            if ( !line.startsWith( "class jiras.surefire1082." ) )
+            if ( line.startsWith( "class jiras.surefire1082." ) )
             {
-                it.remove();
+                log.add( line );
             }
         }
         return log;
@@ -65,14 +81,8 @@
         return new IsRegex( r );
     }
 
-    @Test
-    public void test()
-        throws VerificationException
+    private static void assertParallelRun( Set<String> log )
     {
-        OutputValidator validator = unpack().setTestToRun(
-            "Jira1082Test" ).parallelClasses().useUnlimitedThreads().executeTest().verifyErrorFree( 4 );
-
-        Set<String> log = printOnlyTestLines( validator );
         assertThat( log.size(), is( 4 ) );
 
         Set<String> expectedLogs1 = new TreeSet<String>();
@@ -90,6 +100,71 @@ public void test()
         assertThat( log, anyOf( regex( expectedLogs1 ), regex( expectedLogs2 ) ) );
     }
 
+    @Test
+    public void checkClassesRunParallel()
+        throws VerificationException
+    {
+        OutputValidator validator = unpack().setTestToRun( "Jira1082Test" )
+                                            .parallelClasses()
+                                            .useUnlimitedThreads()
+                                            .executeTest()
+                                            .verifyErrorFree( 4 );
+
+        validator.getSurefireReportsXmlFile( "TEST-jiras.surefire1082.Jira1082Test.xml" )
+                .assertFileExists();
+
+        validator.assertThatLogLine( containsString( "Running jiras.surefire1082.Jira1082Test" ), is( 1 ) );
+
+        Set<String> log = printOnlyTestLinesFromConsole( validator );
+        assertParallelRun( log );
+    }
+
+    @Test
+    public void checkOutFileClassesRunParallel()
+            throws VerificationException
+    {
+        OutputValidator validator = unpack().redirectToFile( true )
+                                            .setTestToRun( "Jira1082Test" )
+                                            .parallelClasses()
+                                            .useUnlimitedThreads()
+                                            .executeTest()
+                                            .verifyErrorFree( 4 );
+
+        validator.getSurefireReportsXmlFile( "TEST-jiras.surefire1082.Jira1082Test.xml" )
+                .assertFileExists();
+
+        validator.assertThatLogLine( containsString( "Running jiras.surefire1082.Jira1082Test" ), is( 1 ) );
+
+        Set<String> log = printOnlyTestLinesFromOutFile( validator );
+        assertParallelRun( log );
+    }
+
+    @Test
+    public void shouldRunTwo() throws VerificationException
+    {
+        OutputValidator validator = unpack().redirectToFile( true )
+                                            .parallelClasses()
+                                            .useUnlimitedThreads()
+                                            .executeTest()
+                                            .verifyErrorFree( 8 );
+
+        validator.getSurefireReportsXmlFile( "TEST-jiras.surefire1082.Jira1082Test.xml" )
+                .assertFileExists();
+
+        validator.getSurefireReportsXmlFile( "TEST-jiras.surefire1082.Jira1264Test.xml" )
+                .assertFileExists();
+
+        validator.getSurefireReportsFile( "jiras.surefire1082.Jira1082Test-output.txt" )
+                .assertFileExists();
+
+        validator.getSurefireReportsFile( "jiras.surefire1082.Jira1264Test-output.txt" )
+                .assertFileExists();
+
+        validator.assertThatLogLine( containsString( "Running jiras.surefire1082.Jira1082Test" ), is( 1 ) );
+
+        validator.assertThatLogLine( containsString( "Running jiras.surefire1082.Jira1264Test" ), is( 1 ) );
+    }
+
     private SurefireLauncher unpack()
     {
         return unpack( "surefire-1082-parallel-junit-parameterized" );
@@ -105,6 +180,7 @@ private SurefireLauncher unpack()
             this.expectedRegex = expectedRegex;
         }
 
+        @Override
         public boolean matches( Object o )
         {
             if ( o != null && o instanceof Set )
@@ -127,6 +203,7 @@ public boolean matches( Object o )
             }
         }
 
+        @Override
         public void describeTo( Description description )
         {
             description.appendValue( expectedRegex );
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1095NpeInRunListener.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1095NpeInRunListener.java
index 509f3d0ab..f894265a2 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1095NpeInRunListener.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1095NpeInRunListener.java
@@ -23,6 +23,7 @@
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
 
+@SuppressWarnings( { "javadoc", "checkstyle:javadoctype" } )
 /**
  *
  * In the surefire plugin, it is possible to specify one or more RunListener when running tests with JUnit.
@@ -41,7 +42,7 @@
  * Note: other methods in the RunListener I tested seems fine (i.e., they get a valid Description object as input)
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1095}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1095}"/>
  * @since 2.18
  */
 public final class Surefire1095NpeInRunListener
@@ -50,7 +51,7 @@
 
     /**
      * Method Request.classes( String, Class[] ); exists in JUnit 4.0 - 4.4
-     * @see JUnit4Reflector
+     * See JUnit4Reflector.
      */
     @Test
     public void testRunStartedWithJUnit40()
@@ -64,7 +65,7 @@ public void testRunStartedWithJUnit40()
 
     /**
      * Method Request.classes( Class[] ); Since of JUnit 4.5
-     * @see JUnit4Reflector
+     * See JUnit4Reflector.
      */
     @Test
     public void testRunStartedWithJUnit45()
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1098BalancedRunOrderIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1098BalancedRunOrderIT.java
index fc559ece4..e81f0663a 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1098BalancedRunOrderIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1098BalancedRunOrderIT.java
@@ -35,17 +35,17 @@
 import static org.hamcrest.core.AnyOf.anyOf;
 
 /**
- * The purpose of this IT is to assert that the run order of test classes is according to the settings:<p/>
+ * The purpose of this IT is to assert that the run order of test classes is according to the settings:<br>
  *
- * runOrder=balanced<p/>
- * parallel=classes<p/>
- * threadCount=2<p/>
- * perCoreThreadCount=false<p/>
- * <p/>
+ * runOrder=balanced<br>
+ * parallel=classes<br>
+ * threadCount=2<br>
+ * perCoreThreadCount=false<br>
+ * <br>
  * The list of tests should be reordered to (DTest, CTest, BTest, ATest) in the second run.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1098}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1098">SUREFIRE-1098</a>
  * @since 2.18
  */
 public class Surefire1098BalancedRunOrderIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1122ParallelAndFlakyTestsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1122ParallelAndFlakyTestsIT.java
index 03e76392f..2f9dca81a 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1122ParallelAndFlakyTestsIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1122ParallelAndFlakyTestsIT.java
@@ -24,7 +24,7 @@
 
 /**
  * @author agudian
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1122}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1122">SUREFIRE-1122</a>
  */
 public class Surefire1122ParallelAndFlakyTestsIT
     extends SurefireJUnit4IntegrationTestCase
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1135ImproveIgnoreMessageForTestNGIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1135ImproveIgnoreMessageForTestNGIT.java
index 131244fe9..f7324c910 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1135ImproveIgnoreMessageForTestNGIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1135ImproveIgnoreMessageForTestNGIT.java
@@ -1 +1,152 @@
-package org.apache.maven.surefire.its.jiras;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import static org.apache.maven.shared.utils.xml.Xpp3DomBuilder.build;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;

import java.io.FileNotFoundException;

import org.apache.maven.shared.utils.xml.Xpp3Dom;
import org.apache.maven.surefire.its.fixture.OutputValidator;
import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
import org.junit.Test;

/**
 * Test surefire-report on TestNG test
 *
 * @author <a href="mailto:michal.bocek@gmail.com">Michal Bocek</a>
 */
public class Surefire1135ImproveIgnoreMessageForTestNGIT
        extends SurefireJUnit4IntegrationTestCase
{

    private enum ResultType
    {
        SKIPPED( "skipped" ), FAILURE( "failure" );

        private final String type;

        ResultType(String type)
        {
            this.type = type;
        }

        public String getType() {
            return type;
        }
    }

    @Test
    public void testNgReport688() throws Exception {
        testNgReport( "6.8.8", ResultType.SKIPPED,
                            "Skip test",
                                   /*"org.testng.SkipException"*/ null,
                                   /*"SkipExceptionReportTest.java:30"*/ null );
    }

    @Test
    public void testNgReport57() throws Exception {
        testNgReport( "5.7", ResultType.SKIPPED,
                            "Skip test",
                                   /*"org.testng.SkipException"*/ null,
                                   /*"SkipExceptionReportTest.java:30"*/ null );
    }

    private void testNgReport( String version, ResultType resultType, String message, String type, String stackTrace )
            throws Exception
    {
        OutputValidator outputValidator =
                runTest( version, resultType, "/surefire-1135-improve-ignore-message-for-testng" );

        Xpp3Dom[] children = readTests( outputValidator, "testng.SkipExceptionReportTest" );
        assertThat( "Report should contains only one test case", children.length, is( 1 ) );

        Xpp3Dom test = children[0];
        assertThat( "Not expected classname", test.getAttribute( "classname" ),
                          is( "testng.SkipExceptionReportTest" ) );

        assertThat( "Not expected test name", test.getAttribute( "name" ), is( "testSkipException" ) );

        children = test.getChildren( resultType.getType() );
        assertThat( "Test should contains only one " + resultType.getType() + " element", children,
                          is( arrayWithSize( 1 ) ) );

        Xpp3Dom result = children[0];
        if ( message == null )
        {
            assertThat( "Subelement message attribute must be null", result.getAttribute( "message" ),
                              is( nullValue() ) );
        }
        else
        {
            assertThat( "Subelement should contains message attribute", result.getAttribute( "message" ),
                              is( message ) );
        }

        if ( type == null )
        {
            assertThat( "Subelement type attribute must be null", result.getAttribute( "type" ), is( nullValue() ) );
        }
        else
        {
            assertThat( "Subelement should contains type attribute", result.getAttribute( "type" ), is( type ) );
        }

        if ( stackTrace == null )
        {
            assertThat( "Element body must be null", result.getValue() , isEmptyOrNullString() );
        }
        else
        {
            assertThat( "Element body must contains", result.getValue(), containsString( stackTrace ) );
        }
    }

    private OutputValidator runTest( String version, ResultType resultType, String resource )
    {
        int skipped = ResultType.SKIPPED.equals( resultType ) ? 1 : 0;
        int failure = ResultType.FAILURE.equals( resultType ) ? 1 : 0;

        return unpack( resource )
                       .resetInitialGoals( version )
                       .addSurefireReportGoal()
                       .executeCurrentGoals()
                       .assertTestSuiteResults( 1, 0, failure, skipped );
    }

    private static Xpp3Dom[] readTests( OutputValidator validator, String className )
            throws FileNotFoundException
    {
        Xpp3Dom testResult =
                build( validator.getSurefireReportsXmlFile( "TEST-" + className + ".xml" ).getFileInputStream(),
                             "UTF-8"
                );
        return testResult.getChildren( "testcase" );
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.apache.maven.shared.utils.xml.Xpp3DomBuilder.build;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+import java.io.FileNotFoundException;
+
+import org.apache.maven.shared.utils.xml.Xpp3Dom;
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+/**
+ * Test surefire-report on TestNG test
+ *
+ * @author <a href="mailto:michal.bocek@gmail.com">Michal Bocek</a>
+ */
+public class Surefire1135ImproveIgnoreMessageForTestNGIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+
+    private enum ResultType
+    {
+        SKIPPED( "skipped" ), FAILURE( "failure" );
+
+        private final String type;
+
+        ResultType(String type)
+        {
+            this.type = type;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+
+    @Test
+    public void testNgReport688() throws Exception {
+        testNgReport( "6.8.8", null, ResultType.SKIPPED,
+                            "Skip test",
+                                   /*"org.testng.SkipException"*/ null,
+                                   /*"SkipExceptionReportTest.java:30"*/ null );
+    }
+
+    @Test
+    public void testNgReport57() throws Exception {
+        testNgReport( "5.7", "jdk15", ResultType.SKIPPED,
+                            "Skip test",
+                                   /*"org.testng.SkipException"*/ null,
+                                   /*"SkipExceptionReportTest.java:30"*/ null );
+    }
+
+    private void testNgReport( String version, String classifier, ResultType resultType, String message, String type,
+                               String stackTrace )
+            throws Exception
+    {
+        OutputValidator outputValidator =
+                runTest( version, classifier, resultType, "/surefire-1135-improve-ignore-message-for-testng" );
+
+        Xpp3Dom[] children = readTests( outputValidator, "testng.SkipExceptionReportTest" );
+        assertThat( "Report should contains only one test case", children.length, is( 1 ) );
+
+        Xpp3Dom test = children[0];
+        assertThat( "Not expected classname", test.getAttribute( "classname" ),
+                          is( "testng.SkipExceptionReportTest" ) );
+
+        assertThat( "Not expected test name", test.getAttribute( "name" ), is( "testSkipException" ) );
+
+        children = test.getChildren( resultType.getType() );
+        assertThat( "Test should contains only one " + resultType.getType() + " element", children,
+                          is( arrayWithSize( 1 ) ) );
+
+        Xpp3Dom result = children[0];
+        if ( message == null )
+        {
+            assertThat( "Subelement message attribute must be null", result.getAttribute( "message" ),
+                              is( nullValue() ) );
+        }
+        else
+        {
+            assertThat( "Subelement should contains message attribute", result.getAttribute( "message" ),
+                              is( message ) );
+        }
+
+        if ( type == null )
+        {
+            assertThat( "Subelement type attribute must be null", result.getAttribute( "type" ), is( nullValue() ) );
+        }
+        else
+        {
+            assertThat( "Subelement should contains type attribute", result.getAttribute( "type" ), is( type ) );
+        }
+
+        if ( stackTrace == null )
+        {
+            assertThat( "Element body must be null", result.getValue() , isEmptyOrNullString() );
+        }
+        else
+        {
+            assertThat( "Element body must contains", result.getValue(), containsString( stackTrace ) );
+        }
+    }
+
+    private OutputValidator runTest( String version, String classifier, ResultType resultType, String resource )
+    {
+        int skipped = ResultType.SKIPPED.equals( resultType ) ? 1 : 0;
+        int failure = ResultType.FAILURE.equals( resultType ) ? 1 : 0;
+
+        SurefireLauncher launcher = unpack( resource ).sysProp( "testNgVersion", version );
+
+        if ( classifier != null )
+        {
+            launcher.sysProp( "testNgClassifier", classifier );
+        }
+
+        return launcher.addSurefireReportGoal()
+                .executeCurrentGoals()
+                .assertTestSuiteResults( 1, 0, failure, skipped );
+    }
+
+    private static Xpp3Dom[] readTests( OutputValidator validator, String className )
+            throws FileNotFoundException
+    {
+        Xpp3Dom testResult =
+                build( validator.getSurefireReportsXmlFile( "TEST-" + className + ".xml" ).getFileInputStream(),
+                             "UTF-8"
+                );
+        return testResult.getChildren( "testcase" );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1146RerunFailedAndParameterized.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1146RerunFailedAndParameterized.java
index 24d39bc30..f78c16ab2 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1146RerunFailedAndParameterized.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1146RerunFailedAndParameterized.java
@@ -24,7 +24,7 @@
 import org.junit.Test;
 
 /**
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1146}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1146">SUREFIRE-1146</a>
  */
 public class Surefire1146RerunFailedAndParameterized
     extends SurefireJUnit4IntegrationTestCase
@@ -39,7 +39,7 @@ public void testsAreRerun()
 
     private void verify( OutputValidator outputValidator, int run, int failures, int errors, int skipped, int flakes )
     {
-        outputValidator.verifyTextInLog( "Flaked tests:" );
+        outputValidator.verifyTextInLog( "Flakes:" );
         outputValidator.verifyTextInLog( "jiras.surefire1146.CustomDescriptionParameterizedTest.flakyTest[0: (Test11); Test12; Test13;](jiras.surefire1146.CustomDescriptionParameterizedTest)" );
         outputValidator.verifyTextInLog( "Run 1: CustomDescriptionParameterizedTest.flakyTest:" );
         outputValidator.verifyTextInLog( "Run 2: CustomDescriptionParameterizedTest.flakyTest:" );
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1158RemoveInfoLinesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1158RemoveInfoLinesIT.java
index 17064c0c7..706f6b5f4 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1158RemoveInfoLinesIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1158RemoveInfoLinesIT.java
@@ -33,7 +33,7 @@
 /**
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1158}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1158">SUREFIRE-1158</a>
  * @since 2.19
  */
 @RunWith( Parameterized.class )
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Surefire1179IT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1179IT.java
similarity index 96%
rename from surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Surefire1179IT.java
rename to surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1179IT.java
index d29ccf933..e4e8e31bb 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Surefire1179IT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1179IT.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.its;
+package org.apache.maven.surefire.its.jiras;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1185DoNotSpawnTestsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1185DoNotSpawnTestsIT.java
index 16e27cfc2..74a4e469b 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1185DoNotSpawnTestsIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1185DoNotSpawnTestsIT.java
@@ -37,7 +37,7 @@
  * Running pkg.RunningTest
  * Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec - in pkg.RunningTest
  *
- * Results :
+ * Results:
  *
  * Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
  */
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1202RerunAndSkipIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1202RerunAndSkipIT.java
index 7d601388d..f88644603 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1202RerunAndSkipIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1202RerunAndSkipIT.java
@@ -28,7 +28,7 @@
  * Allow rerunFailingTestsCount, skipAfterFailureCount together
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1202}
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1202">SUREFIRE-1202</a>
  * @since 2.19.1
  */
 public class Surefire1202RerunAndSkipIT
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
index a6cb73d52..6ee87fbb0 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
@@ -1 +1,75 @@
-package org.apache.maven.surefire.its.jiras;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.it.VerificationException;
import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
import org.apache.maven.surefire.its.fixture.SurefireLauncher;
import org.junit.Test;

/**
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1209}
 * @since 2.19
 */
public class Surefire1209RerunAndForkCountIT
        extends SurefireJUnit4IntegrationTestCase
{
    @Test
    public void reusableForksJUnit47()
            throws VerificationException
    {
        unpack().executeTest()
                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
    }

    @Test
    public void notReusableForksJUnit47()
            throws VerificationException
    {
        unpack().reuseForks( false )
                .executeTest()
                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
    }

    @Test
    public void reusableForksJUnit4()
            throws VerificationException
    {
        unpack().addGoal( "-Pjunit4" )
                .executeTest()
                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
    }

    @Test
    public void notReusableForksJUnit4()
            throws VerificationException
    {
        unpack().addGoal( "-Pjunit4" )
                .reuseForks( false )
                .executeTest()
                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
    }

    private SurefireLauncher unpack()
    {
        return unpack( "surefire-1209-rerun-and-forkcount" );
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1209">SUREFIRE-1209</a>
+ * @since 2.19
+ */
+public class Surefire1209RerunAndForkCountIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    @Test
+    public void reusableForksJUnit47()
+            throws VerificationException
+    {
+        unpack().executeTest()
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+    }
+
+    @Test
+    public void notReusableForksJUnit47()
+            throws VerificationException
+    {
+        unpack().reuseForks( false )
+                .executeTest()
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+    }
+
+    @Test
+    public void reusableForksJUnit4()
+            throws VerificationException
+    {
+        unpack().addGoal( "-Pjunit4" )
+                .executeTest()
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+    }
+
+    @Test
+    public void notReusableForksJUnit4()
+            throws VerificationException
+    {
+        unpack().addGoal( "-Pjunit4" )
+                .reuseForks( false )
+                .executeTest()
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+    }
+
+    private SurefireLauncher unpack()
+    {
+        return unpack( "surefire-1209-rerun-and-forkcount" );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1211JUnitTestNgIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1211JUnitTestNgIT.java
index da69e9da3..27b4da8b6 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1211JUnitTestNgIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1211JUnitTestNgIT.java
@@ -1 +1,63 @@
-package org.apache.maven.surefire.its.jiras;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
import org.apache.maven.surefire.its.fixture.SurefireLauncher;
import org.junit.Test;

import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
import static org.apache.maven.surefire.its.fixture.HelperAssertions.assumeJavaVersion;

/**
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-1211}
 * @since 2.19.1
 */
public class Surefire1211JUnitTestNgIT
        extends SurefireJUnit4IntegrationTestCase
{

    @Test
    public void withJUnit()
    {
        assumeJavaVersion( JAVA_1_7 );

        unpack().threadCount( 1 )
                .executeTest()
                .verifyErrorFree( 2 );
    }

    @Test
    public void withoutJUnit()
    {
        assumeJavaVersion( JAVA_1_7 );

        unpack().threadCount( 1 )
                .sysProp( "junit", "false" )
                .executeTest()
                .verifyErrorFree( 1 );
    }

    private SurefireLauncher unpack()
    {
        return unpack( "surefire-1211" );
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
+import static org.apache.maven.surefire.its.fixture.HelperAssertions.assumeJavaVersion;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1211">SUREFIRE-1211</a>
+ * @since 2.19.1
+ */
+public class Surefire1211JUnitTestNgIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+
+    @Test
+    public void withJUnit()
+    {
+        assumeJavaVersion( JAVA_1_7 );
+
+        unpack().threadCount( 1 )
+                .executeTest()
+                .verifyErrorFree( 2 );
+    }
+
+    @Test
+    public void withoutJUnit()
+    {
+        assumeJavaVersion( JAVA_1_7 );
+
+        unpack().threadCount( 1 )
+                .sysProp( "junit", "false" )
+                .executeTest()
+                .verifyErrorFree( 1 );
+    }
+
+    private SurefireLauncher unpack()
+    {
+        return unpack( "surefire-1211" );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1260NewTestsPattern.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1260NewTestsPattern.java
index 63b1e243c..c4031c25e 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1260NewTestsPattern.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1260NewTestsPattern.java
@@ -1 +1,49 @@
-package org.apache.maven.surefire.its.jiras;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.surefire.its.fixture.*;
import org.junit.Test;

/**
 * Added included pattern Tests.java.
 * <p>
 * Found in Surefire 2.19.1.
 *
 * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
 * @see {@linkplain https://jira.codehaus.org/browse/SUREFIRE-12-60}
 * @since 2.19.2
 */
public class Surefire1260NewTestsPattern
        extends SurefireJUnit4IntegrationTestCase
{
    @Test
    public void defaultConfig()
    {
        unpack()
                .executeTest()
                .verifyErrorFree( 5 );
    }

    private SurefireLauncher unpack()
    {
        return unpack( "/surefire-1260-new-tests-pattern" );
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.*;
+import org.junit.Test;
+
+/**
+ * Added included pattern Tests.java.
+ * <p>
+ * Found in Surefire 2.19.1.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1260">SUREFIRE-1260</a>
+ * @since 2.20
+ */
+public class Surefire1260NewTestsPattern
+        extends SurefireJUnit4IntegrationTestCase
+{
+    @Test
+    public void defaultConfig()
+    {
+        unpack()
+                .executeTest()
+                .verifyErrorFree( 5 );
+    }
+
+    private SurefireLauncher unpack()
+    {
+        return unpack( "/surefire-1260-new-tests-pattern" );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1264IT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1264IT.java
new file mode 100644
index 000000000..bd9af176d
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1264IT.java
@@ -0,0 +1,58 @@
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1264">SUREFIRE-1264</a>
+ * @since 2.20.1
+ */
+public class Surefire1264IT
+        extends SurefireJUnit4IntegrationTestCase
+{
+
+    @Test
+    public void positiveTests()
+    {
+        unpack( "surefire-1264" )
+                .setForkJvm()
+                .parallelAll()
+                .useUnlimitedThreads()
+                .sysProp( "canFail", "false" )
+                .executeTest()
+                .assertTestSuiteResults( 16, 0, 0, 0 );
+    }
+
+    @Test
+    public void negativeTests()
+    {
+        unpack( "surefire-1264" )
+                .setForkJvm()
+                .parallelAll()
+                .useUnlimitedThreads()
+                .sysProp( "canFail", "true" )
+                .mavenTestFailureIgnore( true )
+                .executeTest()
+                .assertTestSuiteResults( 16, 0, 16, 0 );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1265Java9IT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1265Java9IT.java
new file mode 100644
index 000000000..2e92805e4
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1265Java9IT.java
@@ -0,0 +1,57 @@
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.AbstractJigsawIT;
+import org.junit.Test;
+
+import java.io.IOException;
+
+@SuppressWarnings( { "javadoc", "checkstyle:javadoctype" } )
+/**
+ * IsolatedClassLoader should take platform ClassLoader as a parent ClassLoader if running on the top of JDK9.
+ * The IsolatedClassLoader should not fail like this:
+ *
+ * [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test (default-test) on project
+ * maven-surefire-plugin-example: Execution default-test of goal
+ * org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test failed:
+ * java.lang.NoClassDefFoundError: java/sql/SQLException: java.sql.SQLException -> [Help 1]
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1265">SUREFIRE-1265</a>
+ * @since 2.20.1
+ */
+public class Surefire1265Java9IT
+        extends AbstractJigsawIT
+{
+    @Test
+    public void shouldRunInPluginJava9() throws IOException
+    {
+        assumeJigsaw()
+                .executeTest()
+                .verifyErrorFree( 2 );
+    }
+
+    @Override
+    protected String getProjectDirectoryName()
+    {
+        return "/surefire-1265";
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
new file mode 100644
index 000000000..f051c1cf3
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
@@ -0,0 +1,120 @@
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.apache.commons.lang.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang.SystemUtils.IS_OS_MAC_OSX;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * https://issues.apache.org/jira/browse/SUREFIRE-1295
+ * https://github.com/apache/maven-surefire/pull/136
+ *
+ * @author michaeltandy
+ * @since 2.20
+ */
+public class Surefire1295AttributeJvmCrashesToTestsIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    @Before
+    public void skipWindows()
+    {
+        assumeTrue( IS_OS_LINUX || IS_OS_MAC_OSX );
+    }
+
+    @Test
+    public void crashInFork() throws VerificationException
+    {
+        SurefireLauncher launcher = unpack( "crash-during-test" );
+
+        checkCrashTypes( launcher );
+    }
+
+    @Test
+    public void crashInSingleUseFork() throws VerificationException
+    {
+        SurefireLauncher launcher = unpack( "crash-during-test" )
+                                            .forkCount( 1 )
+                                            .reuseForks( false );
+
+        checkCrashTypes( launcher );
+    }
+
+    @Test
+    public void crashInReusableFork() throws VerificationException
+    {
+        SurefireLauncher launcher = unpack( "crash-during-test" )
+                                            .forkPerThread()
+                                            .reuseForks( true )
+                                            .threadCount( 1 );
+
+        checkCrashTypes( launcher );
+    }
+
+    private static void checkCrashTypes( SurefireLauncher launcher )
+            throws VerificationException
+    {
+        checkCrash( launcher.addGoal( "-DcrashType=exit" ) );
+        checkCrash( launcher.addGoal( "-DcrashType=abort" ) );
+        checkCrash( launcher.addGoal( "-DcrashType=segfault" ) );
+    }
+
+    private static void checkCrash( SurefireLauncher launcher ) throws VerificationException
+    {
+        OutputValidator validator = launcher.maven()
+                                            .withFailure()
+                                            .executeTest()
+                                            .verifyTextInLog( "The forked VM terminated without properly saying "
+                                                                      + "goodbye. VM crash or System.exit called?"
+                                            )
+                                            .verifyTextInLog( "Crashed tests:" );
+
+        for ( Iterator<String> it = validator.loadLogLines().iterator(); it.hasNext(); )
+        {
+            String line = it.next();
+            if ( line.contains( "Crashed tests:" ) )
+            {
+                line = it.next();
+                if ( it.hasNext() )
+                {
+                    assertThat( line ).contains( "junit44.environment.BasicTest" );
+                }
+                else
+                {
+                    fail( "Could not find any line after 'Crashed tests:'." );
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1364SystemPropertiesIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1364SystemPropertiesIT.java
new file mode 100644
index 000000000..d13b0f24a
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1364SystemPropertiesIT.java
@@ -0,0 +1,203 @@
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+/**
+ * Report XML should contain system properties of forked JVM.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class Surefire1364SystemPropertiesIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    @Test
+    public void junit3Forked()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit3" )
+                                            .forkMode( "once" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit3InProcess()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit3" )
+                                            .forkMode( "never" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit4Forked()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .forkMode( "once" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit4InProcess()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .forkMode( "never" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit47Forked()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit47" )
+                                            .forkMode( "once" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit47InProcess()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit47" )
+                                            .forkMode( "never" )
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit47ForkedParallel()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit47" )
+                                            .forkMode( "once" )
+                                            .parallelClasses()
+                                            .threadCount( 2 )
+                                            .disablePerCoreThreadCount()
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void junit47InProcessParallel()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "junit47" )
+                                            .forkMode( "never" )
+                                            .parallelClasses()
+                                            .threadCount( 2 )
+                                            .disablePerCoreThreadCount()
+                                            .executeTest()
+                                            .verifyErrorFree( 2 );
+
+        validator.getSurefireReportsXmlFile( "TEST-FirstTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+
+        validator.getSurefireReportsXmlFile( "TEST-SecondTest.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void testNg()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "testng" )
+                                            .forkMode( "once" )
+                                            .executeTest()
+                                            .verifyErrorFree( 3 );
+
+        validator.getSurefireReportsXmlFile( "TEST-TestSuite.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+
+    @Test
+    public void testNgInProcess()
+    {
+        SurefireLauncher launcher = unpack( "surefire-1364" );
+        OutputValidator validator = launcher.setForkJvm()
+                                            .activateProfile( "testng" )
+                                            .forkMode( "never" )
+                                            .executeTest()
+                                            .verifyErrorFree( 3 );
+
+        validator.getSurefireReportsXmlFile( "TEST-TestSuite.xml" )
+                .assertContainsText( "<property name=\"forkedProp\" value=\"forkedValue1\"/>" );
+    }
+}
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1367AssumptionLogsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1367AssumptionLogsIT.java
new file mode 100644
index 000000000..48564fb2a
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1367AssumptionLogsIT.java
@@ -0,0 +1,157 @@
+package org.apache.maven.surefire.its.jiras;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1367">SUREFIRE-1367</a>
+ * @since 2.20.1
+ */
+public class Surefire1367AssumptionLogsIT
+        extends SurefireJUnit4IntegrationTestCase
+{
+    private static final String NL = System.getProperty( "line.separator" );
+
+    @Test
+    public void shouldSeeLogsParallelForked()
+    {
+        OutputValidator outputValidator = unpack().setForkJvm()
+                                                  .forkMode( "once" )
+                                                  .parallelClassesAndMethods()
+                                                  .disablePerCoreThreadCount()
+                                                  .threadCountClasses( 2 )
+                                                  .threadCountMethods( 2 )
+                                                  .executeTest()
+                                                  .assertTestSuiteResults( 2, 0, 0, 2 );
+
+        verifyReportA( outputValidator );
+        verifyReportB( outputValidator );
+    }
+
+    @Test
+    public void shouldSeeLogsParallelInPlugin()
+    {
+        OutputValidator outputValidator = unpack().setForkJvm()
+                                                  .forkMode( "never" )
+                                                  .parallelClassesAndMethods()
+                                                  .disablePerCoreThreadCount()
+                                                  .threadCountClasses( 2 )
+                                                  .threadCountMethods( 2 )
+                                                  .executeTest()
+                                                  .assertTestSuiteResults( 2, 0, 0, 2 );
+
+        verifyReportA( outputValidator );
+        verifyReportB( outputValidator );
+    }
+
+    @Test
+    public void shouldSeeLogsForked()
+    {
+        OutputValidator outputValidator = unpack().setForkJvm()
+                                                  .forkMode( "once" )
+                                                  .executeTest()
+                                                  .assertTestSuiteResults( 2, 0, 0, 2 );
+
+        verifyReportA( outputValidator );
+        verifyReportB( outputValidator );
+    }
+
+    @Test
+    public void shouldSeeLogsInPlugin()
+    {
+        OutputValidator outputValidator = unpack().setForkJvm()
+                                                  .forkMode( "never" )
+                                                  .executeTest()
+                                                  .assertTestSuiteResults( 2, 0, 0, 2 );
+
+        verifyReportA( outputValidator );
+        verifyReportB( outputValidator );
+    }
+
+
+    private SurefireLauncher unpack()
+    {
+        return unpack( "/surefire-1367" );
+    }
+
+    private void verifyReportA( OutputValidator outputValidator )
+    {
+        String xmlReport = outputValidator.getSurefireReportsXmlFile( "TEST-ATest.xml" )
+                                   .readFileToString();
+
+        String outputCData = "<system-out><![CDATA[Hi" + NL +
+                                     NL +
+                                     "There!" + NL +
+                                     "]]></system-out>" + NL +
+                                     "    <system-err><![CDATA[Hello" + NL +
+                                     NL +
+                                     "What's up!" + NL +
+                                     "]]></system-err>";
+
+        assertThat( xmlReport )
+                .contains( outputCData );
+
+
+        String output = outputValidator.getSurefireReportsFile( "ATest-output.txt" )
+                                .readFileToString();
+
+        String outputExpected = "Hi" + NL +
+                                        NL +
+                                        "There!" + NL +
+                                        "Hello" + NL +
+                                        NL +
+                                        "What's up!" + NL;
+
+        assertThat( output )
+                .isEqualTo( outputExpected );
+    }
+
+    private void verifyReportB( OutputValidator outputValidator )
+    {
+        String xmlReport = outputValidator.getSurefireReportsXmlFile( "TEST-BTest.xml" )
+                                   .readFileToString();
+
+        String outputCData = "<system-out><![CDATA[Hey" + NL +
+                                     NL +
+                                     "you!" + NL +
+                                     "]]></system-out>";
+
+        assertThat( xmlReport )
+                .contains( outputCData );
+
+
+        String output = outputValidator.getSurefireReportsFile( "BTest-output.txt" )
+                                .readFileToString();
+
+        String outputExpected = "Hey" + NL +
+                                        NL +
+                                        "you!" + NL;
+
+        assertThat( output )
+                .isEqualTo( outputExpected );
+    }

  (This diff was longer than 20,000 lines, and has been truncated...)


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services