You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2019/05/23 17:16:43 UTC

[maven-surefire] 02/02: [SUREFIRE-1546] JUnit 5 runner does not honor JUnit 5 display names

This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch 1546-1222-v2
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit d624dadcaaac18e8a26517400e16143fa6cc2f11
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Apr 2 10:54:39 2019 +0200

    [SUREFIRE-1546] JUnit 5 runner does not honor JUnit 5 display names
---
 Jenkinsfile                                        |   3 -
 maven-failsafe-plugin/pom.xml                      |  22 ++
 .../plugin/failsafe/IntegrationTestMojoTest.java   |   2 +-
 maven-surefire-common/pom.xml                      |  59 ++++-
 .../plugin/surefire/AbstractSurefireMojo.java      |  48 +++-
 .../maven/plugin/surefire/CommonReflector.java     |  32 ++-
 .../surefire/StartupReportConfiguration.java       | 102 ++++++---
 .../surefire/booterclient/output/ForkClient.java   |   3 +-
 .../booterclient/output/ForkedChannelDecoder.java  |  17 +-
 .../DefaultStatelessReportMojoConfiguration.java   |  58 +++++
 .../extensions/SurefireConsoleOutputReporter.java  |  86 ++++++++
 .../extensions/SurefireStatelessReporter.java      |  98 +++++++++
 .../SurefireStatelessTestsetInfoReporter.java      |  87 ++++++++
 .../junit5/JUnit5ConsoleOutputReporter.java        |  92 ++++++++
 .../junit5/JUnit5StatelessTestsetInfoReporter.java | 148 +++++++++++++
 .../junit5/JUnit5Xml30StatelessReporter.java       | 161 ++++++++++++++
 .../surefire/report/ConsoleOutputFileReporter.java |  21 +-
 .../plugin/surefire/report/ConsoleReporter.java    |  43 ++--
 .../surefire/report/DefaultReporterFactory.java    |  33 +--
 .../surefire/report/DirectConsoleOutput.java       |  18 +-
 .../maven/plugin/surefire/report/FileReporter.java |  33 ++-
 ...eporter.java => NullConsoleOutputReceiver.java} |  35 +--
 .../surefire/report/NullConsoleReporter.java       |   6 +-
 .../plugin/surefire/report/NullFileReporter.java   |   2 +-
 .../surefire/report/NullStatelessXmlReporter.java  |   2 +-
 .../surefire/report/StatelessXmlReporter.java      |  39 +++-
 .../plugin/surefire/report/TestSetRunListener.java |  23 +-
 .../maven/plugin/surefire/report/TestSetStats.java |  35 +--
 .../report/TestcycleConsoleOutputReceiver.java     |   9 +-
 .../plugin/surefire/report/WrappedReportEntry.java |  42 +++-
 .../AbstractSurefireMojoJava7PlusTest.java         |  12 +-
 .../plugin/surefire/AbstractSurefireMojoTest.java  |  25 ++-
 .../maven/plugin/surefire/CommonReflectorTest.java |  96 ++++++++
 .../maven/plugin/surefire/MojoMocklessTest.java    | 117 ++++++++++
 .../maven/plugin/surefire/SurefireHelperTest.java  |  47 ++++
 .../booterclient/DefaultForkConfigurationTest.java |   2 +
 .../booterclient/ForkingRunListenerTest.java       |   4 +-
 .../JarManifestForkConfigurationTest.java          |   2 +
 .../booterclient/TestSetMockReporterFactory.java   |   8 +-
 .../booterclient/output/ForkClientTest.java        | 100 ++++++++-
 .../output/ForkedChannelDecoderTest.java           |  55 +++--
 .../report/DefaultReporterFactoryTest.java         | 140 +++++++++++-
 .../surefire/report/StatelessXmlReporterTest.java  |  33 +--
 .../plugin/surefire/report/TestSetStatsTest.java   |  71 ++++++
 .../surefire/report/WrappedReportEntryTest.java    |  72 +++++-
 .../runorder/RunEntryStatisticsMapTest.java        |  20 +-
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |  13 +-
 .../extensions/ConsoleOutputReporterTest.java      | 173 +++++++++++++++
 .../surefire/extensions/StatelessReporterTest.java | 244 +++++++++++++++++++++
 .../StatelessTestsetInfoReporterTest.java          | 229 +++++++++++++++++++
 .../report/ConsoleOutputFileReporterTest.java      |  32 +--
 .../maven/surefire/report/FileReporterTest.java    |   8 +-
 maven-surefire-plugin/pom.xml                      |  18 ++
 .../src/site/apt/examples/junit-platform.apt.vm    |  53 +++++
 .../src/site/apt/featurematrix.apt.vm              |  13 +-
 .../maven/plugin/surefire/SurefirePluginTest.java  | 117 ++++------
 maven-surefire-report-plugin/pom.xml               |  19 ++
 .../plugins/surefire/report/JUnit4SuiteTest.java   |  22 +-
 pom.xml                                            | 146 ++++++------
 surefire-api/pom.xml                               |  16 ++
 .../surefire/booter/ForkedChannelEncoder.java      |   4 +
 .../surefire/report/CategorizedReportEntry.java    |  29 ++-
 .../apache/maven/surefire/report/ReportEntry.java  |  21 ++
 .../maven/surefire/report/SimpleReportEntry.java   | 135 ++++++++----
 .../maven/surefire/util/ReflectionUtils.java       |   6 +
 .../java/org/apache/maven/JUnit4SuiteTest.java     |   2 +
 .../maven/surefire/booter/CommandReaderTest.java   |   4 +-
 .../java/org/apache/maven/surefire/booter/Foo.java |   0
 .../surefire/booter/ForkedChannelEncoderTest.java  | 108 ++++++---
 .../surefire/booter/NewClassLoaderRunner.java      |   7 +-
 .../surefire/booter/SurefireReflectorTest.java     | 151 ++++++++++++-
 .../maven/surefire/util/ReflectionUtilsTest.java   |   8 +
 surefire-booter/pom.xml                            |  28 ++-
 .../maven/surefire/booter/JUnit4SuiteTest.java     |  22 +-
 .../surefire/booter/SurefireReflectorTest.java     | 154 -------------
 .../maven/surefire/booter/SystemUtilsTest.java     |   5 +-
 surefire-extensions-api/pom.xml                    |  97 ++++++++
 .../ConsoleOutputReportEventListener.java          |  21 +-
 .../surefire/extensions/ConsoleOutputReporter.java |  72 ++++++
 .../extensions/StatelessReportEventListener.java   |  43 ++++
 .../StatelessReportMojoConfiguration.java          |  75 +++++++
 .../surefire/extensions/StatelessReporter.java     |  87 ++++++++
 ...elessTestsetInfoConsoleReportEventListener.java |  53 +++++
 ...tatelessTestsetInfoFileReportEventListener.java |  67 ++++++
 .../extensions/StatelessTestsetInfoReporter.java   |  58 +++++
 surefire-grouper/pom.xml                           |  18 ++
 surefire-its/pom.xml                               |   7 -
 .../maven/surefire/its/JUnitPlatformEnginesIT.java |   5 +-
 .../apache/maven/surefire/its/JUnitPlatformIT.java |  41 +++-
 .../junit-platform-engine-jupiter/pom.xml          |  31 +++
 ...{DisplayNameTest.javax => DisplayNameTest.java} |  16 +-
 surefire-logger-api/pom.xml                        |  44 ++++
 .../surefire/log/api/ConsoleLoggerDecorator.java   | 106 +++------
 .../surefire/log/api/ConsoleLoggerUtilsTest.java   |  64 ++++++
 .../plugin/surefire/log/api}/JUnit4SuiteTest.java  |  14 +-
 .../maven/plugin/surefire/log/api/LevelTest.java   |  73 ++++++
 .../maven/plugin/surefire/log/api/LoggersTest.java | 139 ++++++++++++
 surefire-providers/common-java5/pom.xml            |  26 ++-
 surefire-providers/common-junit3/pom.xml           |  33 +++
 surefire-providers/common-junit4/pom.xml           |  33 +++
 .../surefire/common/junit4/JUnit4RunListener.java  |  10 +-
 surefire-providers/common-junit48/pom.xml          |  23 ++
 .../surefire/common/junit48/JUnit4SuiteTest.java   |  18 +-
 surefire-providers/surefire-junit-platform/pom.xml |  21 ++
 .../surefire/junitplatform/RunListenerAdapter.java |  80 +++----
 .../surefire/junitplatform/JUnit47SuiteTest.java   |  21 +-
 .../junitplatform/RunListenerAdapterTest.java      | 182 ++++++++++-----
 surefire-providers/surefire-junit3/pom.xml         |  33 +++
 .../maven/surefire/junit/JUnit3Provider.java       |   4 +-
 .../apache/maven/surefire/junit/PojoTestSet.java   |  12 +-
 .../junit/TestListenerInvocationHandler.java       |   4 +-
 surefire-providers/surefire-junit4/pom.xml         |  36 +++
 .../maven/surefire/junit4/JUnit4Provider.java      |   4 +-
 .../maven/surefire/junit4/JUnit4ProviderTest.java  |   6 +-
 .../maven/surefire/junit4}/JUnit4SuiteTest.java    |  18 +-
 surefire-providers/surefire-junit47/pom.xml        |  23 ++
 .../junitcore/NonConcurrentRunListener.java        |   4 +-
 .../apache/maven/surefire/junitcore/TestSet.java   |   2 +-
 .../maven/surefire/junitcore/JUnit47SuiteTest.java |  40 ++--
 .../maven/surefire/junitcore/JUnitCoreTester.java  |   8 +-
 .../maven/surefire/junitcore/TestMethodTest.java   |   2 +-
 surefire-providers/surefire-testng-utils/pom.xml   |  18 ++
 surefire-providers/surefire-testng/pom.xml         |  18 ++
 .../maven/surefire/testng/TestNGReporter.java      |   9 +-
 .../apache/maven/surefire/testng/TestSuite.java    |   4 +-
 surefire-report-parser/pom.xml                     |  16 ++
 .../plugins/surefire/report/JUnit4SuiteTest.java   |  20 +-
 127 files changed, 4797 insertions(+), 1012 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 3f96cad..b2d8023 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -68,9 +68,6 @@ oses.eachWithIndex { osMapping, indexOfOs ->
                         boolean first = indexOfOs == 0 && indexOfMaven == 0 && indexOfJdk == 0
                         def failsafeItPort = 8000 + 100 * indexOfMaven + 10 * indexOfJdk
                         def allOptions = options + ["-Dfailsafe-integration-test-port=${failsafeItPort}", "-Dfailsafe-integration-test-stop-port=${1 + failsafeItPort}"]
-                        if (jdk > 7) {
-                            allOptions += ['-DpowermockVersion=2.0.0', '-Denforcer.skip=true']
-                        }
                         ws(dir: "${os == 'windows' ? "${TEMP}\\${BUILD_TAG}" : pwd()}") {
                             buildProcess(stageKey, jdkName, jdkTestName, mvnName, first ? goalsDepl : goals, allOptions, mavenOpts, first)
                         }
diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml
index 31dcfbf..bf61284 100644
--- a/maven-failsafe-plugin/pom.xml
+++ b/maven-failsafe-plugin/pom.xml
@@ -86,6 +86,12 @@
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jacoco</groupId>
+            <artifactId>org.jacoco.agent</artifactId>
+            <classifier>runtime</classifier>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -113,6 +119,21 @@
                 </executions>
             </plugin>
             <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <dependencies>
                     <dependency>
@@ -122,6 +143,7 @@
                     </dependency>
                 </dependencies>
                 <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
                     <includes>
                         <include>**/JUnit4SuiteTest.java</include>
                     </includes>
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 8414d6b..5e008aa 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.Invali
 dVersionSpecificationException;
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 = mo
 ck( 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();
        assertTha
 t( 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.Invali
 dVersionSpecificationException;
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.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 = new IntegrationTestMojo();
        MavenProject project = mock( MavenProject.class );
        when( projec
 t.getArtifact() ).thenReturn( artifact );
        mojo.setProject( 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-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 770642e..896c8be 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -44,6 +44,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-extensions-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
             <artifactId>surefire-booter</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -115,17 +120,52 @@
             <artifactId>powermock-api-mockito2</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jacoco</groupId>
+            <artifactId>org.jacoco.agent</artifactId>
+            <classifier>runtime</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>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-instrumentation</id>
+                        <goals>
+                            <goal>instrument</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>restore-classes</id>
+                        <goals>
+                            <goal>restore-instrumented-classes</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <includes>
-                        <include>**/JUnit4SuiteTest.java</include>
-                    </includes>
-                </configuration>
                 <dependencies>
                     <dependency>
                         <groupId>org.apache.maven.surefire</groupId>
@@ -133,6 +173,15 @@
                         <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
                     </dependency>
                 </dependencies>
+                <configuration>
+                    <argLine>${jvm.args.tests}</argLine>
+                    <includes>
+                        <include>**/JUnit4SuiteTest.java</include>
+                    </includes>
+                    <systemPropertyVariables>
+                        <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
+                    </systemPropertyVariables>
+                </configuration>
             </plugin>
             <plugin>
                 <artifactId>maven-shade-plugin</artifactId>
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 6eeeb5e..2f179e8 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
@@ -25,6 +25,9 @@ import org.apache.maven.artifact.DefaultArtifact;
 import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.repository.RepositorySystem;
@@ -157,6 +160,18 @@ public abstract class AbstractSurefireMojo
     private final ProviderDetector providerDetector = new ProviderDetector();
 
     /**
+     * Note: use the legacy system property <em>disableXmlReport</em> set to {@code true} to disable the report.
+     */
+    @Parameter
+    private SurefireStatelessReporter statelessTestsetReporter;
+
+    @Parameter
+    private SurefireConsoleOutputReporter consoleOutputReporter;
+
+    @Parameter
+    private SurefireStatelessTestsetInfoReporter statelessTestsetInfoReporter;
+
+    /**
      * Information about this plugin, mainly used to lookup this plugin's configuration from the currently executing
      * project.
      *
@@ -670,9 +685,11 @@ public abstract class AbstractSurefireMojo
 
     /**
      * Flag to disable the generation of report files in xml format.
-     *
+     * Deprecated since 3.0.0-M4.
+     * Instead use <em>disable</em> within {@code statelessTestsetReporter} since of 3.0.0-M6.
      * @since 2.2
      */
+    @Deprecated // todo make readonly to handle system property
     @Parameter( property = "disableXmlReport", defaultValue = "false" )
     private boolean disableXmlReport;
 
@@ -1781,7 +1798,8 @@ public abstract class AbstractSurefireMojo
         getConsoleLogger().debug( testClasspath.getCompactLogMessage( "test(compact) classpath:" ) );
         getConsoleLogger().debug( providerClasspath.getCompactLogMessage( "provider(compact) classpath:" ) );
 
-        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts =
+                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
         getConsoleLogger().debug( inProcClasspath.getLogMessage( "in-process classpath:" ) );
@@ -1881,7 +1899,8 @@ public abstract class AbstractSurefireMojo
         ModularClasspath modularClasspath = new ModularClasspath( moduleDescriptor, testModulepath.getClassPath(),
                 packages, getTestClassesDirectory() );
 
-        Artifact[] additionalInProcArtifacts = { getCommonArtifact(), getApiArtifact(), getLoggerApiArtifact() };
+        Artifact[] additionalInProcArtifacts =
+                { getCommonArtifact(), getExtensionsArtifact(), getApiArtifact(), getLoggerApiArtifact() };
         Set<Artifact> inProcArtifacts = retainInProcArtifactsUnique( providerArtifacts, additionalInProcArtifacts );
         Classpath inProcClasspath = createInProcClasspath( providerClasspath, inProcArtifacts );
 
@@ -1906,6 +1925,11 @@ public abstract class AbstractSurefireMojo
         return getPluginArtifactMap().get( "org.apache.maven.surefire:maven-surefire-common" );
     }
 
+    private Artifact getExtensionsArtifact()
+    {
+        return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-extensions-api" );
+    }
+
     private Artifact getApiArtifact()
     {
         return getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-api" );
@@ -1928,12 +1952,26 @@ public abstract class AbstractSurefireMojo
 
     private StartupReportConfiguration getStartupReportConfiguration( String configChecksum, boolean isForkMode )
     {
+        SurefireStatelessReporter xmlReporter =
+                statelessTestsetReporter == null
+                        ? new SurefireStatelessReporter( /*todo call def. constr.*/ isDisableXmlReport(), "3.0" )
+                        : statelessTestsetReporter;
+
+        xmlReporter.setDisable( isDisableXmlReport() ); // todo change to Boolean in the version 3.0.0-M6
+
+        SurefireConsoleOutputReporter outReporter =
+                consoleOutputReporter == null ? new SurefireConsoleOutputReporter() : consoleOutputReporter;
+
+        SurefireStatelessTestsetInfoReporter testsetReporter =
+                statelessTestsetInfoReporter == null
+                        ? new SurefireStatelessTestsetInfoReporter() : statelessTestsetInfoReporter;
+
         return new StartupReportConfiguration( isUseFile(), isPrintSummary(), getReportFormat(),
-                                               isRedirectTestOutputToFile(), isDisableXmlReport(),
+                                               isRedirectTestOutputToFile(),
                                                getReportsDirectory(), isTrimStackTrace(), getReportNameSuffix(),
                                                getStatisticsFile( configChecksum ), requiresRunHistory(),
                                                getRerunFailingTestsCount(), getReportSchemaLocation(), getEncoding(),
-                                               isForkMode );
+                                               isForkMode, xmlReporter, outReporter, testsetReporter );
     }
 
     private boolean isSpecificTestSpecified()
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
index 81830ca..835fb89 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
@@ -19,15 +19,17 @@ package org.apache.maven.plugin.surefire;
  * under the License.
  */
 
-import java.io.File;
-import java.lang.reflect.Constructor;
-
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.booter.SurefireReflector;
 import org.apache.maven.surefire.util.SurefireReflectionException;
 
 import javax.annotation.Nonnull;
+import java.io.File;
+import java.lang.reflect.Constructor;
 
 import static org.apache.maven.surefire.util.ReflectionUtils.getConstructor;
 import static org.apache.maven.surefire.util.ReflectionUtils.instantiateObject;
@@ -40,6 +42,9 @@ public class CommonReflector
 {
     private final Class<?> startupReportConfiguration;
     private final Class<?> consoleLogger;
+    private final Class<?> statelessTestsetReporter;
+    private final Class<?> consoleOutputReporter;
+    private final Class<?> statelessTestsetInfoReporter;
     private final ClassLoader surefireClassLoader;
 
     public CommonReflector( @Nonnull ClassLoader surefireClassLoader )
@@ -50,6 +55,10 @@ public class CommonReflector
         {
             startupReportConfiguration = surefireClassLoader.loadClass( StartupReportConfiguration.class.getName() );
             consoleLogger = surefireClassLoader.loadClass( ConsoleLogger.class.getName() );
+            statelessTestsetReporter = surefireClassLoader.loadClass( SurefireStatelessReporter.class.getName() );
+            consoleOutputReporter = surefireClassLoader.loadClass( SurefireConsoleOutputReporter.class.getName() );
+            statelessTestsetInfoReporter =
+                    surefireClassLoader.loadClass( SurefireStatelessTestsetInfoReporter.class.getName() );
         }
         catch ( ClassNotFoundException e )
         {
@@ -70,18 +79,23 @@ public class CommonReflector
     private Object createStartupReportConfiguration( @Nonnull StartupReportConfiguration reporterConfiguration )
     {
         Constructor<?> constructor = getConstructor( startupReportConfiguration, boolean.class, boolean.class,
-                                                           String.class, boolean.class, boolean.class, File.class,
-                                                           boolean.class, String.class, File.class, boolean.class,
-                                                           int.class, String.class, String.class, boolean.class );
+                                                     String.class, boolean.class, File.class,
+                                                     boolean.class, String.class, File.class, boolean.class,
+                                                     int.class, String.class, String.class, boolean.class,
+                                                     statelessTestsetReporter, consoleOutputReporter,
+                                                     statelessTestsetInfoReporter );
         //noinspection BooleanConstructorCall
         Object[] params = { reporterConfiguration.isUseFile(), reporterConfiguration.isPrintSummary(),
             reporterConfiguration.getReportFormat(), reporterConfiguration.isRedirectTestOutputToFile(),
-            reporterConfiguration.isDisableXmlReport(), reporterConfiguration.getReportsDirectory(),
+            reporterConfiguration.getReportsDirectory(),
             reporterConfiguration.isTrimStackTrace(), reporterConfiguration.getReportNameSuffix(),
             reporterConfiguration.getStatisticsFile(), reporterConfiguration.isRequiresRunHistory(),
             reporterConfiguration.getRerunFailingTestsCount(), reporterConfiguration.getXsdSchemaLocation(),
-            reporterConfiguration.getEncoding().name(), reporterConfiguration.isForkMode() };
+            reporterConfiguration.getEncoding().name(), reporterConfiguration.isForkMode(),
+            reporterConfiguration.getXmlReporter().clone( surefireClassLoader ),
+            reporterConfiguration.getConsoleOutputReporter().clone( surefireClassLoader ),
+            reporterConfiguration.getTestsetReporter().clone( surefireClassLoader )
+        };
         return newInstance( constructor, params );
     }
-
 }
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 f76465e..02ec44b 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,18 @@ package org.apache.maven.plugin.surefire;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
-import org.apache.maven.plugin.surefire.report.DirectConsoleOutput;
-import org.apache.maven.plugin.surefire.report.FileReporter;
-import org.apache.maven.plugin.surefire.report.StatelessXmlReporter;
-import org.apache.maven.plugin.surefire.report.TestcycleConsoleOutputReceiver;
+import org.apache.maven.plugin.surefire.extensions.DefaultStatelessReportMojoConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
 import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
 
 import javax.annotation.Nonnull;
 import java.io.File;
@@ -67,8 +72,6 @@ public final class StartupReportConfiguration
 
     private final boolean redirectTestOutputToFile;
 
-    private final boolean disableXmlReport;
-
     private final File reportsDirectory;
 
     private final boolean trimStackTrace;
@@ -81,22 +84,29 @@ public final class StartupReportConfiguration
 
     private final Charset encoding;
 
-    private boolean isForkMode;
+    private final boolean isForkMode;
+
+    private final SurefireStatelessReporter xmlReporter;
+
+    private final SurefireConsoleOutputReporter consoleOutputReporter;
+
+    private final SurefireStatelessTestsetInfoReporter testsetReporter;
 
     private StatisticsReporter statisticsReporter;
 
     @SuppressWarnings( "checkstyle:parameternumber" )
     public StartupReportConfiguration( boolean useFile, boolean printSummary, String reportFormat,
-                                       boolean redirectTestOutputToFile, boolean disableXmlReport,
-                                       @Nonnull File reportsDirectory, boolean trimStackTrace, String reportNameSuffix,
-                                       File statisticsFile, boolean requiresRunHistory, int rerunFailingTestsCount,
-                                       String xsdSchemaLocation, String encoding, boolean isForkMode )
+               boolean redirectTestOutputToFile,
+               @Nonnull File reportsDirectory, boolean trimStackTrace, String reportNameSuffix,
+               File statisticsFile, boolean requiresRunHistory, int rerunFailingTestsCount,
+               String xsdSchemaLocation, String encoding, boolean isForkMode,
+               SurefireStatelessReporter xmlReporter, SurefireConsoleOutputReporter consoleOutputReporter,
+               SurefireStatelessTestsetInfoReporter testsetReporter )
     {
         this.useFile = useFile;
         this.printSummary = printSummary;
         this.reportFormat = reportFormat;
         this.redirectTestOutputToFile = redirectTestOutputToFile;
-        this.disableXmlReport = disableXmlReport;
         this.reportsDirectory = reportsDirectory;
         this.trimStackTrace = trimStackTrace;
         this.reportNameSuffix = reportNameSuffix;
@@ -109,6 +119,9 @@ public final class StartupReportConfiguration
         String charset = trimToNull( encoding );
         this.encoding = charset == null ? UTF_8 : Charset.forName( charset );
         this.isForkMode = isForkMode;
+        this.xmlReporter = xmlReporter;
+        this.consoleOutputReporter = consoleOutputReporter;
+        this.testsetReporter = testsetReporter;
     }
 
     public boolean isUseFile()
@@ -136,11 +149,6 @@ public final class StartupReportConfiguration
         return redirectTestOutputToFile;
     }
 
-    public boolean isDisableXmlReport()
-    {
-        return disableXmlReport;
-    }
-
     public File getReportsDirectory()
     {
         return reportsDirectory;
@@ -151,10 +159,10 @@ public final class StartupReportConfiguration
         return rerunFailingTestsCount;
     }
 
-    @Deprecated // rename to stateful
-    public StatelessXmlReporter instantiateStatelessXmlReporter( Integer forkNumber )
+    public StatelessReportEventListener<WrappedReportEntry, TestSetStats> instantiateStatelessXmlReporter(
+            Integer forkNumber )
     {
-        assert forkNumber == null || isForkMode;
+        assert ( forkNumber == null ) == !isForkMode;
 
         // If forking TestNG the suites have same name 'TestSuite' and tend to override report statistics in stateful
         // reporter, see Surefire1535TestNGParallelSuitesIT. The testClassMethodRunHistory should be isolated.
@@ -165,30 +173,40 @@ public final class StartupReportConfiguration
                 ? new ConcurrentHashMap<String, Deque<WrappedReportEntry>>()
                 : this.testClassMethodRunHistory;
 
-        return isDisableXmlReport()
-            ? null
-            : new StatelessXmlReporter( resolveReportsDirectory( forkNumber ), reportNameSuffix, trimStackTrace,
-                rerunFailingTestsCount, testClassMethodRunHistory, xsdSchemaLocation );
+        DefaultStatelessReportMojoConfiguration xmlReporterConfig =
+                new DefaultStatelessReportMojoConfiguration( resolveReportsDirectory( forkNumber ), reportNameSuffix,
+                        trimStackTrace, rerunFailingTestsCount, xsdSchemaLocation, testClassMethodRunHistory );
+
+        return xmlReporter.isDisable() ? null : xmlReporter.createListener( xmlReporterConfig );
     }
 
-    public FileReporter instantiateFileReporter( Integer forkNumber )
+    public StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> instantiateFileReporter(
+            Integer forkNumber )
     {
-        return isUseFile() && isBriefOrPlainFormat()
-            ? new FileReporter( resolveReportsDirectory( forkNumber ), reportNameSuffix, encoding )
+        return !testsetReporter.isDisable() && isUseFile() && isBriefOrPlainFormat()
+            ? testsetReporter.createListener( resolveReportsDirectory( forkNumber ), reportNameSuffix, encoding )
             : null;
     }
 
+    public StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> instantiateConsoleReporter(
+            ConsoleLogger consoleLogger )
+    {
+        return !testsetReporter.isDisable() && shouldReportToConsole()
+                ? testsetReporter.createListener( consoleLogger ) : null;
+    }
+
     public boolean isBriefOrPlainFormat()
     {
         String fmt = getReportFormat();
         return BRIEF.equals( fmt ) || PLAIN.equals( fmt );
     }
 
-    public TestcycleConsoleOutputReceiver instantiateConsoleOutputFileReporter( Integer forkNumber )
+    public ConsoleOutputReportEventListener instantiateConsoleOutputFileReporter( Integer forkNum )
     {
-        return isRedirectTestOutputToFile()
-            ? new ConsoleOutputFileReporter( resolveReportsDirectory( forkNumber ), reportNameSuffix, forkNumber )
-            : new DirectConsoleOutput( originalSystemOut, originalSystemErr );
+        ConsoleOutputReportEventListener outputReport = isRedirectTestOutputToFile()
+                ? consoleOutputReporter.createListener( resolveReportsDirectory( forkNum ), reportNameSuffix, forkNum )
+                : consoleOutputReporter.createListener( originalSystemOut, originalSystemErr );
+        return consoleOutputReporter.isDisable() ? null : outputReport;
     }
 
     public synchronized StatisticsReporter getStatisticsReporter()
@@ -234,4 +252,24 @@ public final class StartupReportConfiguration
     {
         return forkNumber == null ? reportsDirectory : replaceForkThreadsInPath( reportsDirectory, forkNumber );
     }
+
+    public SurefireStatelessReporter getXmlReporter()
+    {
+        return xmlReporter;
+    }
+
+    public SurefireConsoleOutputReporter getConsoleOutputReporter()
+    {
+        return consoleOutputReporter;
+    }
+
+    public SurefireStatelessTestsetInfoReporter getTestsetReporter()
+    {
+        return testsetReporter;
+    }
+
+    private boolean shouldReportToConsole()
+    {
+        return isUseFile() ? isPrintSummary() : isRedirectTestOutputToFile() || isBriefOrPlainFormat();
+    }
 }
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 7e7ab78..f2a934f 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
@@ -159,7 +159,8 @@ public class ForkClient
         public void handle( RunMode runMode, TestSetReportEntry reportEntry )
         {
             testsInProgress.clear();
-            TestSetReportEntry entry = reportEntry( reportEntry.getSourceName(), reportEntry.getName(),
+            TestSetReportEntry entry = reportEntry( reportEntry.getSourceName(), reportEntry.getSourceText(),
+                    reportEntry.getName(), reportEntry.getNameText(),
                     reportEntry.getGroup(), reportEntry.getStackTraceWriter(), reportEntry.getElapsed(),
                     reportEntry.getMessage(), getTestVmSystemProperties() );
             getTestSetReporter().testSetCompleted( entry );
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
index 60ed138..b7950d0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoder.java
@@ -261,16 +261,18 @@ public final class ForkedChannelDecoder
                 if ( listener != null && encoding != null && mode != null )
                 {
                     String sourceName = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                    String sourceText = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String name = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+                    String nameText = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String group = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String message = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String elapsed = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String traceMessage = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String smartTrimmedStackTrace = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
                     String stackTrace = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
-
-                    listener.handle( mode, toReportEntry( encoding, sourceName, name, group, message, elapsed,
-                            traceMessage, smartTrimmedStackTrace, stackTrace ) );
+                    ReportEntry reportEntry = toReportEntry( encoding, sourceName, sourceText, name, nameText,
+                            group, message, elapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
+                    listener.handle( mode, reportEntry );
                 }
             }
             else if ( event.isJvmExitError() )
@@ -294,7 +296,8 @@ public final class ForkedChannelDecoder
 
     static ReportEntry toReportEntry( Charset encoding,
                    // ReportEntry:
-                   String encSource, String encName, String encGroup, String encMessage, String encTimeElapsed,
+                   String encSource, String encSourceText, String encName, String encNameText,
+                                      String encGroup, String encMessage, String encTimeElapsed,
                    // StackTraceWriter:
                    String encTraceMessage, String encSmartTrimmedStackTrace, String encStackTrace )
             throws NumberFormatException
@@ -306,14 +309,16 @@ public final class ForkedChannelDecoder
         }
 
         String source = decode( encSource, encoding );
+        String sourceText = decode( encSourceText, encoding );
         String name = decode( encName, encoding );
+        String nameText = decode( encNameText, encoding );
         String group = decode( encGroup, encoding );
         StackTraceWriter stackTraceWriter =
                 decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
         Integer elapsed = decodeToInteger( encTimeElapsed );
         String message = decode( encMessage, encoding );
-        return reportEntry( source, name, group, stackTraceWriter, elapsed, message,
-                Collections.<String, String>emptyMap() );
+        return reportEntry( source, sourceText, name, nameText,
+                group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
     }
 
     static String decode( String line, Charset encoding )
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
new file mode 100644
index 0000000..0eb6b52
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
@@ -0,0 +1,58 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.WrappedReportEntry;
+import org.apache.maven.surefire.extensions.StatelessReportMojoConfiguration;
+
+import java.io.File;
+import java.util.Deque;
+import java.util.Map;
+
+/**
+ * Why Deprecated: The field {@code testClassMethodRunHistory} makes
+ * {@link org.apache.maven.plugin.surefire.report.StatelessXmlReporter} stateful and overloads reporter by permanently
+ * overriding XML using re-run feature. To fix this issue, the providers should use more events for re-run feature and
+ * events bounding provider's execution. After provider's execution is finished, this reporter should be announced
+ * only once per test class. All test report entries should be cached in
+ * {@link org.apache.maven.plugin.surefire.report.TestSetRunListener} keeping it already stateful.
+ */
+@Deprecated
+public class DefaultStatelessReportMojoConfiguration
+        extends StatelessReportMojoConfiguration
+{
+    private final Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory;
+
+    public DefaultStatelessReportMojoConfiguration( File reportsDirectory,
+                                                    String reportNameSuffix,
+                                                    boolean trimStackTrace,
+                                                    int rerunFailingTestsCount,
+                                                    String xsdSchemaLocation,
+                                                    Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory )
+    {
+        super( reportsDirectory, reportNameSuffix, trimStackTrace, rerunFailingTestsCount, xsdSchemaLocation );
+        this.testClassMethodRunHistory = testClassMethodRunHistory;
+    }
+
+    public Map<String, Deque<WrappedReportEntry>> getTestClassMethodRunHistory()
+    {
+        return testClassMethodRunHistory;
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireConsoleOutputReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireConsoleOutputReporter.java
new file mode 100644
index 0000000..e4ac7a6
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireConsoleOutputReporter.java
@@ -0,0 +1,86 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.ConsoleOutputFileReporter;
+import org.apache.maven.plugin.surefire.report.DirectConsoleOutput;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+import org.apache.maven.surefire.extensions.ConsoleOutputReporter;
+import org.apache.maven.surefire.util.ReflectionUtils;
+
+import java.io.File;
+import java.io.PrintStream;
+
+/**
+ * Default implementation for extension of console logger.
+ * Signatures can be changed between major, minor versions or milestones.
+ * <br>
+ * Builds {@link ConsoleOutputReportEventListener listeners}.
+ * The listeners handle test set events.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class SurefireConsoleOutputReporter
+        extends ConsoleOutputReporter
+{
+    @Override
+    public ConsoleOutputReportEventListener createListener( File reportsDirectory, String reportNameSuffix,
+                                                            Integer forkNumber )
+    {
+        return new ConsoleOutputFileReporter( reportsDirectory, reportNameSuffix, false, forkNumber, getEncoding() );
+    }
+
+    @Override
+    public ConsoleOutputReportEventListener createListener( PrintStream out, PrintStream err )
+    {
+        return new DirectConsoleOutput( out, err );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Class<?> cls = ReflectionUtils.reloadClass( target, this );
+            Object clone = cls.newInstance();
+
+            cls.getMethod( "setDisable", boolean.class )
+                    .invoke( clone, isDisable() );
+            cls.getMethod( "setEncoding", String.class )
+                    .invoke( clone, getEncoding() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "SurefireConsoleOutputReporter{"
+                + "disable=" + isDisable()
+                + ", encoding=" + getEncoding()
+                + '}';
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
new file mode 100644
index 0000000..e907bfd
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
@@ -0,0 +1,98 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.StatelessXmlReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessReporter;
+import org.apache.maven.surefire.util.ReflectionUtils;
+
+/**
+ * Default implementation for extension of {@link StatelessXmlReporter} in plugin.
+ * Signatures can be changed between major, minor versions or milestones.
+ * <br>
+ * This is a builder of {@link StatelessReportEventListener listener}.
+ * The listener handles <em>testSetCompleted</em> event.
+ *
+ * author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class SurefireStatelessReporter
+        extends StatelessReporter<WrappedReportEntry, TestSetStats, DefaultStatelessReportMojoConfiguration>
+{
+    /**
+     * Activated in the injection point of MOJO.
+     */
+    public SurefireStatelessReporter()
+    {
+        this( false, "3.0" );
+    }
+
+    /**
+     * Activated if null injection point in MOJO.
+     * @param disable             {@code true} to disable performing the report
+     * @param version             (xsd 3.0) version of the schema
+     */
+    public SurefireStatelessReporter( boolean disable, String version )
+    {
+        setDisable( disable );
+        setVersion( version );
+    }
+
+    @Override
+    public StatelessReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            DefaultStatelessReportMojoConfiguration configuration )
+    {
+        return new StatelessXmlReporter( configuration.getReportsDirectory(),
+                configuration.getReportNameSuffix(),
+                configuration.isTrimStackTrace(),
+                configuration.getRerunFailingTestsCount(),
+                configuration.getTestClassMethodRunHistory(),
+                configuration.getXsdSchemaLocation(),
+                getVersion(),
+                false,
+                false,
+                false,
+                false );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Class<?> cls = ReflectionUtils.reloadClass( target, this );
+            Object clone = cls.newInstance();
+
+            cls.getMethod( "setDisable", boolean.class )
+                    .invoke( clone, isDisable() );
+            cls.getMethod( "setVersion", String.class )
+                    .invoke( clone, getVersion() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessTestsetInfoReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessTestsetInfoReporter.java
new file mode 100644
index 0000000..73a099c
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessTestsetInfoReporter.java
@@ -0,0 +1,87 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.report.ConsoleReporter;
+import org.apache.maven.plugin.surefire.report.FileReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporter;
+import org.apache.maven.surefire.util.ReflectionUtils;
+
+import java.io.File;
+import java.nio.charset.Charset;
+
+/**
+ * Default implementation for extension of
+ * {@link StatelessTestsetInfoFileReportEventListener test-set event listener for stateless file and console reporter}
+ * in plugin. Signatures can be changed between major, minor versions or milestones.
+ * <br>
+ * Builds {@link StatelessTestsetInfoFileReportEventListener listeners}.
+ * The listener handles <em>testSetCompleted</em> event.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class SurefireStatelessTestsetInfoReporter
+        extends StatelessTestsetInfoReporter<WrappedReportEntry, TestSetStats>
+{
+    @Override
+    public StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            ConsoleLogger logger )
+    {
+        return new ConsoleReporter( logger, false, false );
+    }
+
+    @Override
+    public StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            File reportsDirectory, String reportNameSuffix, Charset encoding )
+    {
+        return new FileReporter( reportsDirectory, reportNameSuffix, encoding, false, false, false );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Class<?> cls = ReflectionUtils.reloadClass( target, this );
+            Object clone = cls.newInstance();
+
+            cls.getMethod( "setDisable", boolean.class )
+                    .invoke( clone, isDisable() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "SurefireStatelessTestsetInfoReporter{disable=" + isDisable() + "}";
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5ConsoleOutputReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5ConsoleOutputReporter.java
new file mode 100644
index 0000000..f5802b7
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5ConsoleOutputReporter.java
@@ -0,0 +1,92 @@
+package org.apache.maven.plugin.surefire.extensions.junit5;
+
+/*
+ * 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.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
+import org.apache.maven.plugin.surefire.report.StatelessXmlReporter;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+
+import java.io.File;
+
+/**
+ * The extension of {@link StatelessXmlReporter logger} for JUnit5.
+ * Selectively enables report files upon JUnit5 annotation <em>DisplayName</em>.
+ *
+ * author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class JUnit5ConsoleOutputReporter
+        extends SurefireConsoleOutputReporter
+{
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedFileName;
+
+    public boolean isUsePhrasedFileName()
+    {
+        return usePhrasedFileName;
+    }
+
+    public void setUsePhrasedFileName( boolean usePhrasedFileName )
+    {
+        this.usePhrasedFileName = usePhrasedFileName;
+    }
+
+    @Override
+    public ConsoleOutputReportEventListener createListener( File reportsDirectory, String reportNameSuffix,
+                                                            Integer forkNumber )
+    {
+        return new ConsoleOutputFileReporter( reportsDirectory, reportNameSuffix, isUsePhrasedFileName(), forkNumber,
+                getEncoding() );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Object clone = super.clone( target );
+
+            Class<?> cls = clone.getClass();
+            cls.getMethod( "setUsePhrasedFileName", boolean.class )
+                    .invoke( clone, isUsePhrasedFileName() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "JUnit5ConsoleOutputReporter{"
+                + "disable=" + isDisable()
+                + ", encoding=" + getEncoding()
+                + ", usePhrasedFileName=" + isUsePhrasedFileName()
+                + '}';
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5StatelessTestsetInfoReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5StatelessTestsetInfoReporter.java
new file mode 100644
index 0000000..3c1b76b
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5StatelessTestsetInfoReporter.java
@@ -0,0 +1,148 @@
+package org.apache.maven.plugin.surefire.extensions.junit5;
+
+/*
+ * 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.extensions.SurefireStatelessTestsetInfoReporter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.report.ConsoleReporter;
+import org.apache.maven.plugin.surefire.report.FileReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
+
+import java.io.File;
+import java.nio.charset.Charset;
+
+/**
+ * Extension of {@link StatelessTestsetInfoConsoleReportEventListener file and console reporter of test-set} for JUnit5.
+ * Signatures can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class JUnit5StatelessTestsetInfoReporter
+        extends SurefireStatelessTestsetInfoReporter
+{
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedFileName;
+
+    /**
+     * Phrased class name of test case in the console log (see xxx)
+     * <em>Running xxx</em> or file report log <em>Test set: xxx</em>.
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedClassNameInRunning;
+
+    /**
+     * Phrased class name of test case in the log (see xxx)
+     * <em>Tests run: ., Failures: ., Errors: ., Skipped: ., Time elapsed: . s, - in xxx</em>.
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedClassNameInTestCaseSummary;
+
+    public boolean isUsePhrasedFileName()
+    {
+        return usePhrasedFileName;
+    }
+
+    public void setUsePhrasedFileName( boolean usePhrasedFileName )
+    {
+        this.usePhrasedFileName = usePhrasedFileName;
+    }
+
+    public boolean isUsePhrasedClassNameInRunning()
+    {
+        return usePhrasedClassNameInRunning;
+    }
+
+    public void setUsePhrasedClassNameInRunning( boolean usePhrasedClassNameInRunning )
+    {
+        this.usePhrasedClassNameInRunning = usePhrasedClassNameInRunning;
+    }
+
+    public boolean isUsePhrasedClassNameInTestCaseSummary()
+    {
+        return usePhrasedClassNameInTestCaseSummary;
+    }
+
+    public void setUsePhrasedClassNameInTestCaseSummary( boolean usePhrasedClassNameInTestCaseSummary )
+    {
+        this.usePhrasedClassNameInTestCaseSummary = usePhrasedClassNameInTestCaseSummary;
+    }
+
+    @Override
+    public StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            ConsoleLogger logger )
+    {
+        return new ConsoleReporter( logger, isUsePhrasedClassNameInRunning(),
+                                    isUsePhrasedClassNameInTestCaseSummary() );
+    }
+
+    @Override
+    public StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            File reportsDirectory, String reportNameSuffix, Charset encoding )
+    {
+        return new FileReporter( reportsDirectory, reportNameSuffix, encoding, isUsePhrasedFileName(),
+                                 isUsePhrasedClassNameInRunning(), isUsePhrasedClassNameInTestCaseSummary() );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Object clone = super.clone( target );
+
+            Class<?> cls = clone.getClass();
+            cls.getMethod( "setUsePhrasedFileName", boolean.class )
+                    .invoke( clone, isUsePhrasedFileName() );
+            cls.getMethod( "setUsePhrasedClassNameInTestCaseSummary", boolean.class )
+                    .invoke( clone, isUsePhrasedFileName() );
+            cls.getMethod( "setUsePhrasedClassNameInRunning", boolean.class )
+                    .invoke( clone, isUsePhrasedFileName() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "JUnit5StatelessTestsetInfoReporter{"
+                + "disable=" + isDisable()
+                + ", usePhrasedFileName=" + isUsePhrasedFileName()
+                + ", usePhrasedClassNameInRunning=" + isUsePhrasedClassNameInRunning()
+                + ", usePhrasedClassNameInTestCaseSummary=" + isUsePhrasedClassNameInTestCaseSummary()
+                + "}";
+    }
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
new file mode 100644
index 0000000..83dca7d
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
@@ -0,0 +1,161 @@
+package org.apache.maven.plugin.surefire.extensions.junit5;
+
+/*
+ * 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.extensions.DefaultStatelessReportMojoConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.report.StatelessXmlReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
+
+/**
+ * The extension of {@link StatelessXmlReporter xml reporter} based on XSD version 3.0 for JUnit5.
+ * Selectively enables phrased classes, methods and report files upon JUnit5 annotation <em>DisplayName</em>.
+ *
+ * author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public class JUnit5Xml30StatelessReporter
+        extends SurefireStatelessReporter
+{
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedFileName;
+
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedTestSuiteClassName;
+
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test class annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedTestCaseClassName;
+
+    /**
+     * {@code false} by default.
+     * <br>
+     * This action takes effect only in JUnit5 provider together with a test method annotated <em>DisplayName</em>.
+     */
+    private boolean usePhrasedTestCaseMethodName;
+
+    public boolean getUsePhrasedFileName()
+    {
+        return usePhrasedFileName;
+    }
+
+    public void setUsePhrasedFileName( boolean usePhrasedFileName )
+    {
+        this.usePhrasedFileName = usePhrasedFileName;
+    }
+
+    public boolean getUsePhrasedTestSuiteClassName()
+    {
+        return usePhrasedTestSuiteClassName;
+    }
+
+    public void setUsePhrasedTestSuiteClassName( boolean usePhrasedTestSuiteClassName )
+    {
+        this.usePhrasedTestSuiteClassName = usePhrasedTestSuiteClassName;
+    }
+
+    public boolean getUsePhrasedTestCaseClassName()
+    {
+        return usePhrasedTestCaseClassName;
+    }
+
+    public void setUsePhrasedTestCaseClassName( boolean usePhrasedTestCaseClassName )
+    {
+        this.usePhrasedTestCaseClassName = usePhrasedTestCaseClassName;
+    }
+
+    public boolean getUsePhrasedTestCaseMethodName()
+    {
+        return usePhrasedTestCaseMethodName;
+    }
+
+    public void setUsePhrasedTestCaseMethodName( boolean usePhrasedTestCaseMethodName )
+    {
+        this.usePhrasedTestCaseMethodName = usePhrasedTestCaseMethodName;
+    }
+
+    @Override
+    public StatelessReportEventListener<WrappedReportEntry, TestSetStats> createListener(
+            DefaultStatelessReportMojoConfiguration configuration )
+    {
+        return new StatelessXmlReporter( configuration.getReportsDirectory(),
+                configuration.getReportNameSuffix(),
+                configuration.isTrimStackTrace(),
+                configuration.getRerunFailingTestsCount(),
+                configuration.getTestClassMethodRunHistory(),
+                configuration.getXsdSchemaLocation(),
+                getVersion(),
+                getUsePhrasedFileName(),
+                getUsePhrasedTestSuiteClassName(),
+                getUsePhrasedTestCaseClassName(),
+                getUsePhrasedTestCaseMethodName() );
+    }
+
+    @Override
+    public Object clone( ClassLoader target )
+    {
+        try
+        {
+            Object clone = super.clone( target );
+
+            Class<?> cls = clone.getClass();
+            cls.getMethod( "setUsePhrasedFileName", boolean.class )
+                    .invoke( clone, getUsePhrasedFileName() );
+            cls.getMethod( "setUsePhrasedTestSuiteClassName", boolean.class )
+                    .invoke( clone, getUsePhrasedTestSuiteClassName() );
+            cls.getMethod( "setUsePhrasedTestCaseClassName", boolean.class )
+                    .invoke( clone, getUsePhrasedTestCaseClassName() );
+            cls.getMethod( "setUsePhrasedTestCaseMethodName", boolean.class )
+                    .invoke( clone, getUsePhrasedTestCaseMethodName() );
+
+            return clone;
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new IllegalStateException( e.getLocalizedMessage() );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "JUnit5Xml30StatelessReporter{"
+                + "version=" + getVersion()
+                + ", disable=" + isDisable()
+                + ", usePhrasedFileName=" + getUsePhrasedFileName()
+                + ", usePhrasedTestSuiteClassName=" + getUsePhrasedTestSuiteClassName()
+                + ", usePhrasedTestCaseClassName=" + getUsePhrasedTestCaseClassName()
+                + ", usePhrasedTestCaseMethodName=" + getUsePhrasedTestCaseMethodName()
+                + '}';
+    }
+}
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 1456c81..3bc3f2c 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
@@ -21,16 +21,17 @@ package org.apache.maven.plugin.surefire.report;
 
 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
 import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.concurrent.atomic.AtomicStampedReference;
 import java.util.concurrent.locks.ReentrantLock;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile;
 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
 
@@ -50,7 +51,9 @@ public class ConsoleOutputFileReporter
 
     private final File reportsDirectory;
     private final String reportNameSuffix;
+    private final boolean usePhrasedFileName;
     private final Integer forkNumber;
+    private final String encoding;
 
     private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
             new AtomicStampedReference<>( null, OPEN );
@@ -59,15 +62,18 @@ public class ConsoleOutputFileReporter
 
     private volatile String reportEntryName;
 
-    public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix, Integer forkNumber )
+    public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix, boolean usePhrasedFileName,
+                                      Integer forkNumber, String encoding )
     {
         this.reportsDirectory = reportsDirectory;
         this.reportNameSuffix = reportNameSuffix;
+        this.usePhrasedFileName = usePhrasedFileName;
         this.forkNumber = forkNumber;
+        this.encoding = encoding;
     }
 
     @Override
-    public void testSetStarting( ReportEntry reportEntry )
+    public void testSetStarting( TestSetReportEntry reportEntry )
     {
         lock.lock();
         try
@@ -81,7 +87,7 @@ public class ConsoleOutputFileReporter
     }
 
     @Override
-    public void testSetCompleted( ReportEntry report )
+    public void testSetCompleted( TestSetReportEntry report )
     {
     }
 
@@ -128,10 +134,11 @@ public class ConsoleOutputFileReporter
                 {
                     output = "null";
                 }
-                os.write( output.getBytes( UTF_8 ) );
+                Charset charset = Charset.forName( encoding );
+                os.write( output.getBytes( charset ) );
                 if ( newLine )
                 {
-                    os.write( NL.getBytes( UTF_8 ) );
+                    os.write( NL.getBytes( charset ) );
                 }
             }
         }
@@ -162,7 +169,7 @@ public class ConsoleOutputFileReporter
         finally
         {
             // prepare <class>-output.txt report file
-            reportEntryName = reportEntry.getSourceName();
+            reportEntryName = usePhrasedFileName ? reportEntry.getSourceText() : reportEntry.getSourceName();
         }
     }
 
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleReporter.java
index 36f311b..0bee687 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleReporter.java
@@ -23,8 +23,9 @@ import java.util.List;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.shared.utils.logging.MessageBuilder;
-import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.plugin.surefire.log.api.Level;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 import static org.apache.maven.plugin.surefire.log.api.Level.resolveLevel;
 import static org.apache.maven.plugin.surefire.report.TestSetStats.concatenateWithTestGroup;
@@ -37,31 +38,33 @@ import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
  * @author Kristian Rosenvold
  */
 public class ConsoleReporter
+        extends StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats>
 {
     public static final String BRIEF = "brief";
-
     public static final String PLAIN = "plain";
-
     private static final String TEST_SET_STARTING_PREFIX = "Running ";
 
-    private final ConsoleLogger logger;
-
-    public ConsoleReporter( ConsoleLogger logger )
-    {
-        this.logger = logger;
-    }
+    private final boolean usePhrasedClassNameInRunning;
+    private final boolean usePhrasedClassNameInTestCaseSummary;
 
-    public ConsoleLogger getConsoleLogger()
+    public ConsoleReporter( ConsoleLogger logger,
+                            boolean usePhrasedClassNameInRunning, boolean usePhrasedClassNameInTestCaseSummary )
     {
-        return logger;
+        super( logger );
+        this.usePhrasedClassNameInRunning = usePhrasedClassNameInRunning;
+        this.usePhrasedClassNameInTestCaseSummary = usePhrasedClassNameInTestCaseSummary;
     }
 
-    public void testSetStarting( ReportEntry report )
+    @Override
+    public void testSetStarting( TestSetReportEntry report )
     {
-        MessageBuilder builder = buffer();
-        logger.info( concatenateWithTestGroup( builder.a( TEST_SET_STARTING_PREFIX ), report ) );
+        MessageBuilder builder = buffer().a( TEST_SET_STARTING_PREFIX );
+        String runningTestCase = concatenateWithTestGroup( builder, report, usePhrasedClassNameInRunning );
+        getConsoleLogger()
+                .info( runningTestCase );
     }
 
+    @Override
     public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetStats, List<String> testResults )
     {
         boolean success = testSetStats.getCompletedCount() > 0;
@@ -71,13 +74,14 @@ public class ConsoleReporter
         boolean flakes = testSetStats.getSkipped() > 0;
         Level level = resolveLevel( success, failures, errors, skipped, flakes );
 
-        println( testSetStats.getColoredTestSetSummary( report ), level );
+        println( testSetStats.getColoredTestSetSummary( report, usePhrasedClassNameInTestCaseSummary ), level );
         for ( String testResult : testResults )
         {
             println( testResult, level );
         }
     }
 
+    @Override
     public void reset()
     {
     }
@@ -87,13 +91,16 @@ public class ConsoleReporter
         switch ( level )
         {
             case FAILURE:
-                logger.error( message );
+                getConsoleLogger()
+                        .error( message );
                 break;
             case UNSTABLE:
-                logger.warning( message );
+                getConsoleLogger()
+                        .warning( message );
                 break;
             default:
-                logger.info( message );
+                getConsoleLogger()
+                        .info( message );
         }
     }
 }
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 0d7ca78..ed93a65 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
@@ -24,6 +24,10 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.Level;
 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
 import org.apache.maven.shared.utils.logging.MessageBuilder;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.RunStatistics;
@@ -113,26 +117,32 @@ public class DefaultReporterFactory
         return reportConfiguration.getReportsDirectory();
     }
 
-    private ConsoleReporter createConsoleReporter()
+    private StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> createConsoleReporter()
     {
-        return shouldReportToConsole() ? new ConsoleReporter( consoleLogger ) : NullConsoleReporter.INSTANCE;
+        StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> consoleReporter =
+                reportConfiguration.instantiateConsoleReporter( consoleLogger );
+        return useNonNull( consoleReporter, NullConsoleReporter.INSTANCE );
     }
 
-    private FileReporter createFileReporter()
+    private StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> createFileReporter()
     {
-        FileReporter fileReporter = reportConfiguration.instantiateFileReporter( forkNumber );
+        StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> fileReporter =
+                reportConfiguration.instantiateFileReporter( forkNumber );
         return useNonNull( fileReporter, NullFileReporter.INSTANCE );
     }
 
-    private StatelessXmlReporter createSimpleXMLReporter()
+    private StatelessReportEventListener<WrappedReportEntry, TestSetStats> createSimpleXMLReporter()
     {
-        StatelessXmlReporter xmlReporter = reportConfiguration.instantiateStatelessXmlReporter( forkNumber );
+        StatelessReportEventListener<WrappedReportEntry, TestSetStats> xmlReporter =
+                reportConfiguration.instantiateStatelessXmlReporter( forkNumber );
         return useNonNull( xmlReporter, NullStatelessXmlReporter.INSTANCE );
     }
 
-    private TestcycleConsoleOutputReceiver createConsoleOutputReceiver()
+    private ConsoleOutputReportEventListener createConsoleOutputReceiver()
     {
-        return reportConfiguration.instantiateConsoleOutputFileReporter( forkNumber );
+        ConsoleOutputReportEventListener outputReporter =
+                reportConfiguration.instantiateConsoleOutputFileReporter( forkNumber );
+        return useNonNull( outputReporter, NullConsoleOutputReceiver.INSTANCE );
     }
 
     private StatisticsReporter createStatisticsReporter()
@@ -141,13 +151,6 @@ public class DefaultReporterFactory
         return useNonNull( statisticsReporter, NullStatisticsReporter.INSTANCE );
     }
 
-    private boolean shouldReportToConsole()
-    {
-        return reportConfiguration.isUseFile()
-                       ? reportConfiguration.isPrintSummary()
-                       : reportConfiguration.isRedirectTestOutputToFile() || reportConfiguration.isBriefOrPlainFormat();
-    }
-
     public void mergeFromOtherFactories( Collection<DefaultReporterFactory> factories )
     {
         for ( DefaultReporterFactory factory : factories )
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 6a94562..7ef2d82 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
@@ -19,7 +19,7 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 import java.io.PrintStream;
 
@@ -36,20 +36,20 @@ import static java.util.Objects.requireNonNull;
 public class DirectConsoleOutput
     implements TestcycleConsoleOutputReceiver
 {
-    private final PrintStream sout;
+    private final PrintStream out;
 
-    private final PrintStream serr;
+    private final PrintStream err;
 
-    public DirectConsoleOutput( PrintStream sout, PrintStream serr )
+    public DirectConsoleOutput( PrintStream out, PrintStream err )
     {
-        this.sout = requireNonNull( sout );
-        this.serr = requireNonNull( serr );
+        this.out = requireNonNull( out );
+        this.err = requireNonNull( err );
     }
 
     @Override
     public void writeTestOutput( String output, boolean newLine, boolean stdout )
     {
-        PrintStream stream = stdout ? sout : serr;
+        PrintStream stream = stdout ? out : err;
         if ( newLine )
         {
             stream.println( output );
@@ -61,12 +61,12 @@ public class DirectConsoleOutput
     }
 
     @Override
-    public void testSetStarting( ReportEntry reportEntry )
+    public void testSetStarting( TestSetReportEntry reportEntry )
     {
     }
 
     @Override
-    public void testSetCompleted( ReportEntry report )
+    public void testSetCompleted( TestSetReportEntry report )
     {
     }
 
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 941b88a..2301e34 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
@@ -19,6 +19,7 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
 import org.apache.maven.surefire.report.ReporterException;
 
 import java.io.BufferedWriter;
@@ -40,47 +41,55 @@ import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
  * @author Kristian Rosenvold
  */
 public class FileReporter
+        extends StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats>
 {
-    private final File reportsDirectory;
-    private final String reportNameSuffix;
-    private final Charset encoding;
+    private final boolean usePhrasedFileName;
+    private final boolean usePhrasedClassNameInRunning;
+    private final boolean usePhrasedClassNameInTestCaseSummary;
 
-    public FileReporter( File reportsDirectory, String reportNameSuffix, Charset encoding )
+    public FileReporter( File reportsDirectory, String reportNameSuffix, Charset encoding, boolean usePhrasedFileName,
+                         boolean usePhrasedClassNameInRunning, boolean usePhrasedClassNameInTestCaseSummary )
     {
-        this.reportsDirectory = reportsDirectory;
-        this.reportNameSuffix = reportNameSuffix;
-        this.encoding = encoding;
+        super( reportsDirectory, reportNameSuffix, encoding );
+        this.usePhrasedFileName = usePhrasedFileName;
+        this.usePhrasedClassNameInRunning = usePhrasedClassNameInRunning;
+        this.usePhrasedClassNameInTestCaseSummary = usePhrasedClassNameInTestCaseSummary;
     }
 
     static File getReportFile( File reportsDirectory, String reportEntryName, String reportNameSuffix,
-                                      String fileExtension )
+                               String fileExtension )
     {
         String fileName =
                 reportEntryName + ( isNotBlank( reportNameSuffix ) ? "-" + reportNameSuffix : "" ) + fileExtension;
         return new File( reportsDirectory, stripIllegalFilenameChars( fileName ) );
     }
 
+    @Override
     public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetStats, List<String> testResults )
     {
-        File reportFile = getReportFile( reportsDirectory, report.getSourceName(), reportNameSuffix, ".txt" );
+        File reportFile = getReportFile( getReportsDirectory(),
+                                         usePhrasedFileName ? report.getReportSourceName() : report.getSourceName(),
+                                         getReportNameSuffix(),
+                                         ".txt" );
 
         File reportDir = reportFile.getParentFile();
 
         // noinspection ResultOfMethodCallIgnored
         reportDir.mkdirs();
 
-        try ( BufferedWriter writer = createFileReporterWriter( reportFile, encoding ) )
+        try ( BufferedWriter writer = createFileReporterWriter( reportFile, getEncoding() ) )
         {
             writer.write( "-------------------------------------------------------------------------------" );
             writer.newLine();
 
-            writer.write( "Test set: " + report.getSourceName() );
+            String tesSet = usePhrasedClassNameInRunning ? report.getReportSourceName() : report.getSourceName();
+            writer.write( "Test set: " + tesSet );
             writer.newLine();
 
             writer.write( "-------------------------------------------------------------------------------" );
             writer.newLine();
 
-            writer.write( testSetStats.getTestSetSummary( report ) );
+            writer.write( testSetStats.getTestSetSummary( report, usePhrasedClassNameInTestCaseSummary ) );
             writer.newLine();
             for ( String testResult : testResults )
             {
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/NullConsoleOutputReceiver.java
similarity index 57%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleReporter.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullConsoleOutputReceiver.java
index af68d1e..a826b67 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/NullConsoleOutputReceiver.java
@@ -19,40 +19,45 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
-import java.util.List;
-
-import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 /**
- * ConsoleReporter doing nothing rather than using null.
+ * TestcycleConsoleOutputReceiver doing nothing rather than using null.
  *
- * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
- * @since 2.20
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
  */
-class NullConsoleReporter
-    extends ConsoleReporter
+public class NullConsoleOutputReceiver
+    implements TestcycleConsoleOutputReceiver
 {
 
-    static final NullConsoleReporter INSTANCE = new NullConsoleReporter();
+    static final NullConsoleOutputReceiver INSTANCE = new NullConsoleOutputReceiver();
 
-    private NullConsoleReporter()
+    private NullConsoleOutputReceiver()
     {
-        super( new NullConsoleLogger() );
     }
 
     @Override
-    public void testSetStarting( ReportEntry report )
+    public void testSetStarting( TestSetReportEntry reportEntry )
     {
+
     }
 
     @Override
-    public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetStats, List<String> testResults )
+    public void testSetCompleted( TestSetReportEntry report )
     {
+
     }
 
     @Override
-    public void reset()
+    public void close()
     {
+
+    }
+
+    @Override
+    public void writeTestOutput( String output, boolean newLine, 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 af68d1e..f33fca1 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
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.report;
 import java.util.List;
 
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 /**
  * ConsoleReporter doing nothing rather than using null.
@@ -38,11 +38,11 @@ class NullConsoleReporter
 
     private NullConsoleReporter()
     {
-        super( new NullConsoleLogger() );
+        super( new NullConsoleLogger(), false, false );
     }
 
     @Override
-    public void testSetStarting( ReportEntry report )
+    public void testSetStarting( TestSetReportEntry report )
     {
     }
 
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 df1bf9d..be217eb 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
@@ -35,7 +35,7 @@ class NullFileReporter
 
     private NullFileReporter()
     {
-        super( null, null, null );
+        super( null, null, null, false, false, false );
     }
 
     @Override
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 7f5d202..d43369d 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
@@ -33,7 +33,7 @@ class NullStatelessXmlReporter
 
     private NullStatelessXmlReporter()
     {
-        super( null, null, false, 0, null, null );
+        super( null, null, false, 0, null, null, null, false, false, false, false );
     }
 
     @Override
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 b9d387e..d87eae8 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
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.report;
 import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
 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.extensions.StatelessReportEventListener;
 import org.apache.maven.surefire.report.ReporterException;
 import org.apache.maven.surefire.report.SafeThrowable;
 
@@ -82,8 +82,9 @@ import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
  * @see <a href="http://wiki.apache.org/ant/Proposals/EnhancedTestReports">Ant's format enhancement proposal</a>
  *      (not yet implemented by Ant 1.8.2)
  */
-@Deprecated // this is no more stateless due to existence of testClassMethodRunHistoryMap since of 2.19. Rename to StatefulXmlReporter in 3.0.
+//todo this is no more stateless due to existence of testClassMethodRunHistoryMap since of 2.19.
 public class StatelessXmlReporter
+        implements StatelessReportEventListener<WrappedReportEntry, TestSetStats>
 {
     private final File reportsDirectory;
 
@@ -95,14 +96,25 @@ public class StatelessXmlReporter
 
     private final String xsdSchemaLocation;
 
+    private final String xsdVersion;
+
     // Map between test class name and a map between test method name
     // and the list of runs for each test method
     private final Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistoryMap;
 
+    private final boolean phrasedFileName;
+
+    private final boolean phrasedSuiteName;
+
+    private final boolean phrasedClassName;
+
+    private final boolean phrasedMethodName;
+
     public StatelessXmlReporter( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace,
                                  int rerunFailingTestsCount,
                                  Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistoryMap,
-                                 String xsdSchemaLocation )
+                                 String xsdSchemaLocation, String xsdVersion, boolean phrasedFileName,
+                                 boolean phrasedSuiteName, boolean phrasedClassName, boolean phrasedMethodName )
     {
         this.reportsDirectory = reportsDirectory;
         this.reportNameSuffix = reportNameSuffix;
@@ -110,8 +122,14 @@ public class StatelessXmlReporter
         this.rerunFailingTestsCount = rerunFailingTestsCount;
         this.testClassMethodRunHistoryMap = testClassMethodRunHistoryMap;
         this.xsdSchemaLocation = xsdSchemaLocation;
+        this.xsdVersion = xsdVersion;
+        this.phrasedFileName = phrasedFileName;
+        this.phrasedSuiteName = phrasedSuiteName;
+        this.phrasedClassName = phrasedClassName;
+        this.phrasedMethodName = phrasedMethodName;
     }
 
+    @Override
     public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
     {
         Map<String, Map<String, List<WrappedReportEntry>>> classMethodStatistics =
@@ -344,9 +362,9 @@ public class StatelessXmlReporter
         return new OutputStreamWriter( fos, UTF_8 );
     }
 
-    private File getReportFile( ReportEntry report )
+    private File getReportFile( WrappedReportEntry report )
     {
-        String reportName = "TEST-" + report.getSourceName();
+        String reportName = "TEST-" + ( phrasedFileName ? report.getReportSourceName() : report.getSourceName() );
         String customizedReportName = isBlank( reportNameSuffix ) ? reportName : reportName + "-" + reportNameSuffix;
         return new File( reportsDirectory, stripIllegalFilenameChars( customizedReportName + ".xml" ) );
     }
@@ -354,14 +372,16 @@ public class StatelessXmlReporter
     private void startTestElement( XMLWriter ppw, WrappedReportEntry report )
     {
         ppw.startElement( "testcase" );
-        ppw.addAttribute( "name", report.getName() == null ? "" : extraEscape( report.getName(), true ) );
+        String name = phrasedMethodName ? report.getReportName() : report.getName();
+        ppw.addAttribute( "name", name == null ? "" : extraEscape( name, true ) );
 
         if ( report.getGroup() != null )
         {
             ppw.addAttribute( "group", report.getGroup() );
         }
 
-        String className = report.getReportName( reportNameSuffix );
+        String className = phrasedClassName ? report.getReportSourceName( reportNameSuffix )
+                : report.getSourceName( reportNameSuffix );
         if ( className != null )
         {
             ppw.addAttribute( "classname", extraEscape( className, true ) );
@@ -376,9 +396,10 @@ public class StatelessXmlReporter
 
         ppw.addAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
         ppw.addAttribute( "xsi:noNamespaceSchemaLocation", xsdSchemaLocation );
-        ppw.addAttribute( "version", "3.0" );
+        ppw.addAttribute( "version", xsdVersion );
 
-        String reportName = report.getReportName( reportNameSuffix );
+        String reportName = phrasedSuiteName ? report.getReportSourceName( reportNameSuffix )
+                : report.getSourceName( reportNameSuffix );
         ppw.addAttribute( "name", reportName == null ? "" : extraEscape( reportName, true ) );
 
         if ( report.getGroup() != null )
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 decf63c..2adbac4 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
@@ -27,6 +27,10 @@ import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.runorder.StatisticsReporter;
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoConsoleReportEventListener;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoFileReportEventListener;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
@@ -53,15 +57,15 @@ public class TestSetRunListener
 
     private final TestSetStats detailsForThis;
 
-    private final TestcycleConsoleOutputReceiver consoleOutputReceiver;
+    private final ConsoleOutputReportEventListener consoleOutputReceiver;
 
     private final boolean briefOrPlainFormat;
 
-    private final StatelessXmlReporter simpleXMLReporter;
+    private final StatelessReportEventListener<WrappedReportEntry, TestSetStats> simpleXMLReporter;
 
-    private final ConsoleReporter consoleReporter;
+    private final StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> consoleReporter;
 
-    private final FileReporter fileReporter;
+    private final StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> fileReporter;
 
     private final StatisticsReporter statisticsReporter;
 
@@ -72,9 +76,12 @@ public class TestSetRunListener
     private volatile RunMode runMode = NORMAL_RUN;
 
     @SuppressWarnings( "checkstyle:parameternumber" )
-    public TestSetRunListener( ConsoleReporter consoleReporter, FileReporter fileReporter,
-                               StatelessXmlReporter simpleXMLReporter,
-                               TestcycleConsoleOutputReceiver consoleOutputReceiver,
+    public TestSetRunListener( StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats>
+                                           consoleReporter,
+                               StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats>
+                                       fileReporter,
+                               StatelessReportEventListener<WrappedReportEntry, TestSetStats> simpleXMLReporter,
+                               ConsoleOutputReportEventListener consoleOutputReceiver,
                                StatisticsReporter statisticsReporter, boolean trimStackTrace,
                                boolean isPlainFormat, boolean briefOrPlainFormat )
     {
@@ -138,7 +145,7 @@ public class TestSetRunListener
     @Override
     public void error( String message, Throwable t )
     {
-        consoleReporter.getConsoleLogger().error( message, t );
+        consoleReporter.getConsoleLogger().error( trimTrailingNewLine( message ), t );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
index 2ea290d..3e360c1 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
@@ -170,7 +170,7 @@ public class TestSetStats
         completedCount += 1;
     }
 
-    public String getTestSetSummary( WrappedReportEntry reportEntry )
+    public String getTestSetSummary( WrappedReportEntry reportEntry, boolean phrasedClassName )
     {
         String summary = TESTS_RUN + completedCount
                                  + COMMA
@@ -187,12 +187,13 @@ public class TestSetStats
             summary += FAILURE_MARKER;
         }
 
-        summary += IN_MARKER + reportEntry.getNameWithGroup();
+        summary += IN_MARKER;
+        summary += phrasedClassName ? reportEntry.getReportNameWithGroup() : reportEntry.getNameWithGroup();
 
         return summary;
     }
 
-    public String getColoredTestSetSummary( WrappedReportEntry reportEntry )
+    public String getColoredTestSetSummary( WrappedReportEntry reportEntry, boolean phrasedClassName )
     {
         final boolean isSuccessful = failures == 0 && errors == 0 && skipped == 0;
         final boolean isFailure = failures > 0;
@@ -256,7 +257,7 @@ public class TestSetStats
             builder.failure( FAILURE_MARKER );
         }
         builder.a( IN_MARKER );
-        return concatenateWithTestGroup( builder, reportEntry );
+        return concatenateWithTestGroup( builder, reportEntry, phrasedClassName );
     }
 
     public List<String> getTestResults()
@@ -294,15 +295,23 @@ public class TestSetStats
      * @param report     report whose test set is starting
      * @return the message
      */
-    static String concatenateWithTestGroup( MessageBuilder builder, ReportEntry report )
+    static String concatenateWithTestGroup( MessageBuilder builder, ReportEntry report, boolean phrasedClassName )
     {
-        String testClass = report.getNameWithGroup();
-        int indexOfGroup = testClass.indexOf( GROUP_PREFIX );
-        int delimiter = testClass.lastIndexOf( '.', indexOfGroup == -1 ? testClass.length() : indexOfGroup );
-        String pkg = testClass.substring( 0, 1 + delimiter );
-        String cls = testClass.substring( 1 + delimiter );
-        return builder.a( pkg )
-                       .strong( cls )
-                       .toString();
+        if ( phrasedClassName )
+        {
+            return builder.strong( report.getReportNameWithGroup() )
+                    .toString();
+        }
+        else
+        {
+            String testClass = report.getNameWithGroup();
+            int indexOfGroup = testClass.indexOf( GROUP_PREFIX );
+            int delimiter = testClass.lastIndexOf( '.', indexOfGroup == -1 ? testClass.length() : indexOfGroup );
+            String pkg = testClass.substring( 0, 1 + delimiter );
+            String cls = testClass.substring( 1 + delimiter );
+            return builder.a( pkg )
+                    .strong( cls )
+                    .toString();
+        }
     }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java
index f1b5b5a..cdd4b3a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java
@@ -19,18 +19,19 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
+import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 /**
  * @author Kristian Rosenvold
  */
 public interface TestcycleConsoleOutputReceiver
-    extends ConsoleOutputReceiver
+    extends ConsoleOutputReceiver, ConsoleOutputReportEventListener
 {
-    void testSetStarting( ReportEntry reportEntry );
+    void testSetStarting( TestSetReportEntry reportEntry );
 
-    void testSetCompleted( ReportEntry report );
+    void testSetCompleted( TestSetReportEntry report );
 
     void close();
 }
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 bb4c1b1..102eea4 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
@@ -103,14 +103,26 @@ public class WrappedReportEntry
     }
 
     @Override
+    public String getSourceText()
+    {
+        return original.getSourceText();
+    }
+
+    @Override
     public String getName()
     {
         return original.getName();
     }
 
+    @Override
+    public String getNameText()
+    {
+        return original.getNameText();
+    }
+
     public String getClassMethodName()
     {
-        return getSourceName() + "." + getName();
+        return original.getSourceName() + "." + original.getName();
     }
 
     @Override
@@ -142,14 +154,28 @@ public class WrappedReportEntry
         return formatElapsedTime( getElapsed() );
     }
 
-    public String getReportName()
+    String getReportSourceName()
+    {
+        String sourceName = getSourceName();
+        String sourceText = getSourceText();
+        return isBlank( sourceText ) ? sourceName : sourceText;
+    }
+
+    String getReportSourceName( String suffix )
+    {
+        return isBlank( suffix ) ? getReportSourceName() : getReportSourceName() + "(" + suffix + ")";
+    }
+
+    String getSourceName( String suffix )
     {
-        return getSourceName();
+        return isBlank( suffix ) ? getSourceName() : getSourceName() + "(" + suffix + ")";
     }
 
-    public String getReportName( String suffix )
+    String getReportName()
     {
-        return isBlank( suffix ) ? getReportName() : getReportName() + "(" + suffix + ")";
+        String name = getName();
+        String nameText = getNameText();
+        return isBlank( nameText ) ? name : nameText;
     }
 
     public String getOutput( boolean trimStackTrace )
@@ -193,6 +219,12 @@ public class WrappedReportEntry
     }
 
     @Override
+    public String getReportNameWithGroup()
+    {
+        return original.getReportNameWithGroup();
+    }
+
+    @Override
     public Map<String, String> getSystemProperties()
     {
         return systemProperties;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
index 2d268bc..b178d64 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
@@ -39,6 +39,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -60,7 +61,6 @@ import static org.powermock.api.mockito.PowerMockito.doNothing;
 import static org.powermock.api.mockito.PowerMockito.mock;
 import static org.powermock.api.mockito.PowerMockito.mockStatic;
 import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 import static org.powermock.api.mockito.PowerMockito.verifyStatic;
 import static org.powermock.reflect.Whitebox.invokeMethod;
 
@@ -69,6 +69,7 @@ import static org.powermock.reflect.Whitebox.invokeMethod;
  */
 @RunWith( PowerMockRunner.class )
 @PrepareForTest( { AbstractSurefireMojo.class, ResolvePathsRequest.class } )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class AbstractSurefireMojoJava7PlusTest
 {
     @Mock
@@ -155,6 +156,10 @@ public class AbstractSurefireMojoJava7PlusTest
                 "jar", "", handler );
         common.setFile( mockFile( "maven-surefire-common.jar" ) );
 
+        Artifact ext = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-api", v5, "runtime",
+                "jar", "", handler );
+        ext.setFile( mockFile( "surefire-extensions-api.jar" ) );
+
         Artifact api = new DefaultArtifact( "org.apache.maven.surefire", "surefire-api", v5, "runtime",
                 "jar", "", handler );
         api.setFile( mockFile( "surefire-api.jar" ) );
@@ -165,6 +170,7 @@ public class AbstractSurefireMojoJava7PlusTest
 
         Map<String, Artifact> artifacts = new HashMap<>();
         artifacts.put( "org.apache.maven.surefire:maven-surefire-common", common );
+        artifacts.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         artifacts.put( "org.apache.maven.surefire:surefire-api", api );
         artifacts.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
         when( mojo.getPluginArtifactMap() ).thenReturn( artifacts );
@@ -200,8 +206,8 @@ public class AbstractSurefireMojoJava7PlusTest
                         "test(compact) classpath:  non-modular.jar  junit.jar  hamcrest.jar",
                         "test(compact) modulepath:  modular.jar  classes",
                         "provider(compact) classpath:  surefire-provider.jar",
-                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-api.jar  surefire-logger-api.jar",
-                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-api.jar  surefire-logger-api.jar"
+                        "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
+                        "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
                 );
 
         assertThat( conf ).isNotNull();
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
index 080f2b5..cec6760 100644
--- 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
@@ -50,6 +50,7 @@ import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -90,6 +91,7 @@ import static org.powermock.reflect.Whitebox.invokeMethod;
  */
 @RunWith( PowerMockRunner.class )
 @PrepareForTest( AbstractSurefireMojo.class )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class AbstractSurefireMojoTest
 {
     @Mock
@@ -236,6 +238,10 @@ public class AbstractSurefireMojoTest
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         common.setFile( mockFile( "maven-surefire-common.jar" ) );
 
+        Artifact ext = new DefaultArtifact( "org.apache.maven.surefire", "surefire-extensions-api",
+                createFromVersion( "1" ), "runtime", "jar", "", handler );
+        ext.setFile( mockFile( "surefire-extensions-api.jar" ) );
+
         Artifact api = new DefaultArtifact( "org.apache.maven.surefire", "surefire-api",
                 createFromVersion( "1" ), "runtime", "jar", "", handler );
         api.setFile( mockFile( "surefire-api.jar" ) );
@@ -246,6 +252,7 @@ public class AbstractSurefireMojoTest
 
         Map<String, Artifact> providerArtifactsMap = new HashMap<>();
         providerArtifactsMap.put( "org.apache.maven.surefire:maven-surefire-common", common );
+        providerArtifactsMap.put( "org.apache.maven.surefire:surefire-extensions-api", ext );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-api", api );
         providerArtifactsMap.put( "org.apache.maven.surefire:surefire-logger-api", loggerApi );
 
@@ -297,8 +304,8 @@ public class AbstractSurefireMojoTest
                 "provider classpath:  surefire-provider.jar",
                 "test(compact) classpath:  test-classes  classes  junit.jar  hamcrest.jar",
                 "provider(compact) classpath:  surefire-provider.jar",
-                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-api.jar  surefire-logger-api.jar",
-                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-api.jar  surefire-logger-api.jar"
+                "in-process classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar",
+                "in-process(compact) classpath:  surefire-provider.jar  maven-surefire-common.jar  surefire-extensions-api.jar  surefire-api.jar  surefire-logger-api.jar"
                 );
 
         assertThat( conf.getClassLoaderConfiguration() )
@@ -1571,7 +1578,7 @@ public class AbstractSurefireMojoTest
     }
 
     public static class Mojo
-            extends AbstractSurefireMojo
+            extends AbstractSurefireMojo implements SurefireReportParameters
     {
         private JUnitPlatformProviderInfo createJUnitPlatformProviderInfo( Artifact providerArtifact,
                                                                            TestClassPath testClasspathWrapper )
@@ -1634,6 +1641,18 @@ public class AbstractSurefireMojoTest
         }
 
         @Override
+        public boolean isTestFailureIgnore()
+        {
+            return false;
+        }
+
+        @Override
+        public void setTestFailureIgnore( boolean testFailureIgnore )
+        {
+
+        }
+
+        @Override
         public File getBasedir()
         {
             return null;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
new file mode 100644
index 0000000..b00d131
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
@@ -0,0 +1,96 @@
+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.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.powermock.reflect.Whitebox.getInternalState;
+
+public class CommonReflectorTest
+{
+    private StartupReportConfiguration startupReportConfiguration;
+    private ConsoleLogger consoleLogger;
+    private File reportsDirectory;
+    private File statistics;
+    private SurefireStatelessReporter xmlReporter;
+    private SurefireConsoleOutputReporter consoleOutputReporter = new SurefireConsoleOutputReporter();
+    private SurefireStatelessTestsetInfoReporter infoReporter = new SurefireStatelessTestsetInfoReporter();
+
+    @Before
+    public void setup()
+    {
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        reportsDirectory = new File( target, "tmp6" );
+        statistics = new File( reportsDirectory, "TESTHASH" );
+        xmlReporter = new SurefireStatelessReporter();
+        infoReporter = new SurefireStatelessTestsetInfoReporter();
+
+        startupReportConfiguration = new StartupReportConfiguration( true, true, "PLAIN", false, reportsDirectory,
+                false, null, statistics, false, 1, null, null, false,
+                xmlReporter, consoleOutputReporter, infoReporter);
+
+        consoleLogger = mock( ConsoleLogger.class );
+    }
+
+    @Test
+    public void createReportingReporterFactory()
+    {
+        CommonReflector reflector = new CommonReflector( Thread.currentThread().getContextClassLoader() );
+        DefaultReporterFactory factory = (DefaultReporterFactory) reflector.createReportingReporterFactory(
+                startupReportConfiguration, consoleLogger );
+
+        assertThat( factory )
+                .isNotNull();
+
+        StartupReportConfiguration reportConfiguration = getInternalState( factory, "reportConfiguration" );
+        assertThat( reportConfiguration )
+                .isNotSameAs( startupReportConfiguration );
+        assertThat( reportConfiguration.isUseFile() ).isTrue();
+        assertThat( reportConfiguration.isPrintSummary() ).isTrue();
+        assertThat( reportConfiguration.getReportFormat() ).isEqualTo( "PLAIN" );
+        assertThat( reportConfiguration.isRedirectTestOutputToFile() ).isFalse();
+        assertThat( reportConfiguration.getReportsDirectory() ).isSameAs( reportsDirectory );
+        assertThat( reportConfiguration.isTrimStackTrace() ).isFalse();
+        assertThat( reportConfiguration.getReportNameSuffix() ).isNull();
+        assertThat( reportConfiguration.getStatisticsFile() ).isSameAs( statistics );
+        assertThat( reportConfiguration.isRequiresRunHistory() ).isFalse();
+        assertThat( reportConfiguration.getRerunFailingTestsCount() ).isEqualTo( 1 );
+        assertThat( reportConfiguration.getXsdSchemaLocation() ).isNull();
+        assertThat( reportConfiguration.getEncoding() ).isEqualTo( UTF_8 );
+        assertThat( reportConfiguration.isForkMode() ).isFalse();
+        assertThat( reportConfiguration.getXmlReporter().toString() )
+                .isEqualTo( xmlReporter.toString() );
+        assertThat( reportConfiguration.getTestsetReporter().toString() )
+                .isEqualTo( infoReporter.toString() );
+        assertThat( reportConfiguration.getConsoleOutputReporter().toString() )
+                .isEqualTo( consoleOutputReporter.toString() );
+    }
+}
\ No newline at end of file
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
index f8ba15f..4371638 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
@@ -25,8 +25,12 @@ import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.handler.DefaultArtifactHandler;
 import org.apache.maven.artifact.versioning.VersionRange;
 import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
+import org.apache.maven.toolchain.Toolchain;
 import org.junit.Test;
 
 import java.io.File;
@@ -38,10 +42,123 @@ import java.util.zip.ZipOutputStream;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+import static org.powermock.reflect.Whitebox.setInternalState;
 
 public class MojoMocklessTest
 {
     @Test
+    public void testGetStartupReportConfiguration() throws Exception
+    {
+        AbstractSurefireMojo surefirePlugin = new Mojo( null, null );
+        StartupReportConfiguration config = invokeMethod( surefirePlugin, "getStartupReportConfiguration", "", false );
+
+        assertThat( config.getXmlReporter() )
+                .isNotNull()
+                .isInstanceOf( SurefireStatelessReporter.class );
+
+        assertThat( config.getConsoleOutputReporter() )
+                .isNotNull()
+                .isInstanceOf( SurefireConsoleOutputReporter.class );
+
+        assertThat( config.getTestsetReporter() )
+                .isNotNull()
+                .isInstanceOf( SurefireStatelessTestsetInfoReporter.class );
+    }
+
+    @Test
+    public void testGetStartupReportConfiguration2() throws Exception
+    {
+        AbstractSurefireMojo surefirePlugin = new Mojo( null, null );
+        SurefireStatelessReporter xmlReporter = new SurefireStatelessReporter( false, "3.0" );
+        SurefireConsoleOutputReporter consoleReporter = new SurefireConsoleOutputReporter();
+        SurefireStatelessTestsetInfoReporter testsetInfoReporter = new SurefireStatelessTestsetInfoReporter();
+        setInternalState( surefirePlugin, "statelessTestsetReporter", xmlReporter );
+        setInternalState( surefirePlugin, "consoleOutputReporter", consoleReporter );
+        setInternalState( surefirePlugin, "statelessTestsetInfoReporter", testsetInfoReporter );
+
+        StartupReportConfiguration config = invokeMethod( surefirePlugin, "getStartupReportConfiguration", "", false );
+
+        assertThat( config.getXmlReporter() )
+                .isNotNull()
+                .isSameAs( xmlReporter );
+
+        assertThat( config.getConsoleOutputReporter() )
+                .isNotNull()
+                .isSameAs( consoleReporter );
+
+        assertThat( config.getTestsetReporter() )
+                .isNotNull()
+                .isSameAs( testsetInfoReporter );
+    }
+
+    @Test
+    public void testForkMode()
+    {
+        AbstractSurefireMojo surefirePlugin = new Mojo( null, null );
+        setInternalState( surefirePlugin, "toolchain", new MyToolChain() );
+        setInternalState( surefirePlugin, "forkMode", "never" );
+        assertThat( surefirePlugin.getEffectiveForkMode() )
+                .isEqualTo( "once" );
+    }
+
+    @Test
+    public void testForkCountComputation()
+    {
+        AbstractSurefireMojo surefirePlugin = new Mojo( null, null );
+        assertConversionFails( surefirePlugin, "nothing" );
+
+        assertConversionFails( surefirePlugin, "5,0" );
+        assertConversionFails( surefirePlugin, "5.0" );
+        assertConversionFails( surefirePlugin, "5,0C" );
+        assertConversionFails( surefirePlugin, "5.0CC" );
+
+        assertForkCount( surefirePlugin, 5, "5" );
+
+        int availableProcessors = Runtime.getRuntime().availableProcessors();
+        assertForkCount( surefirePlugin, 3 * availableProcessors, "3C" );
+        assertForkCount( surefirePlugin, (int) ( 2.5 * availableProcessors ), "2.5C" );
+        assertForkCount( surefirePlugin, availableProcessors, "1.0001 C" );
+        assertForkCount( surefirePlugin, 1, 1d / ( (double) availableProcessors + 1 ) + "C" );
+        assertForkCount( surefirePlugin, 0, "0 C" );
+    }
+
+    private static void assertForkCount( AbstractSurefireMojo surefirePlugin, int expected, String value )
+    {
+        assertThat( surefirePlugin.convertWithCoreCount( value ) )
+                .isEqualTo( expected );
+    }
+
+    private static void assertConversionFails( AbstractSurefireMojo surefirePlugin, String value )
+    {
+        try
+        {
+            surefirePlugin.convertWithCoreCount( value );
+        }
+        catch ( NumberFormatException e )
+        {
+            return;
+        }
+        fail( "Expected NumberFormatException when converting " + value );
+    }
+
+    private static class MyToolChain implements Toolchain
+    {
+        @Override
+        public String getType()
+        {
+            return null;
+        }
+
+        @Override
+        public String findTool( String s )
+        {
+            return null;
+        }
+    }
+
+    @Test
     public void scanDependenciesShouldReturnNull()
             throws MojoFailureException
     {
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
index e5f8eb8..9541d3a 100644
--- 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
@@ -19,6 +19,9 @@ package org.apache.maven.plugin.surefire;
  * under the License.
  */
 
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest.Mojo;
+import org.apache.maven.surefire.suite.RunResult;
 import org.junit.Test;
 
 import java.io.File;
@@ -29,7 +32,9 @@ 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.apache.maven.plugin.surefire.SurefireHelper.reportExecution;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 /**
@@ -108,4 +113,46 @@ public class SurefireHelperTest
         escaped = escapeToPlatformPath( path );
         assertThat( escaped ).isEqualTo( root + "\\" + pathToJar );
     }
+
+    @Test
+    public void shouldHandleFailIfNoTests() throws Exception
+    {
+        RunResult summary = new RunResult( 0, 0, 0, 0 );
+        try
+        {
+            Mojo plugin = new Mojo();
+            plugin.setFailIfNoTests( true );
+            reportExecution( plugin, summary, null, null );
+        }
+        catch ( MojoFailureException e )
+        {
+            assertThat( e.getLocalizedMessage() )
+                    .isEqualTo( "No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)" );
+            return;
+        }
+        fail( "Expected MojoFailureException with message "
+                + "'No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)'" );
+    }
+
+    @Test
+    public void shouldHandleTestFailure() throws Exception
+    {
+        RunResult summary = new RunResult( 1, 0, 1, 0 );
+        try
+        {
+            reportExecution( new Mojo(), summary, null, null );
+        }
+        catch ( MojoFailureException e )
+        {
+            assertThat( e.getLocalizedMessage() )
+                    .isEqualTo( "There are test failures.\n\nPlease refer to null "
+                            + "for the individual test results.\nPlease refer to dump files (if any exist) "
+                            + "[date].dump, [date]-jvmRun[N].dump and [date].dumpstream." );
+            return;
+        }
+        fail( "Expected MojoFailureException with message "
+                + "'There are test failures.\n\nPlease refer to null "
+                + "for the individual test results.\nPlease refer to dump files (if any exist) "
+                + "[date].dump, [date]-jvmRun[N].dump and [date].dumpstream.'");
+    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
index 6bedab9..a8f6c69 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
@@ -31,6 +31,7 @@ import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -58,6 +59,7 @@ import static org.powermock.reflect.Whitebox.invokeMethod;
  */
 @RunWith( PowerMockRunner.class )
 @PrepareForTest( { DefaultForkConfiguration.class, Relocator.class } )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class DefaultForkConfigurationTest
 {
     private Classpath booterClasspath;
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 6b693bd..ab84690 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
@@ -268,7 +268,7 @@ public class ForkingRunListenerTest
 
     private SimpleReportEntry createDefaultReportEntry( Map<String, String> sysProps )
     {
-        return new SimpleReportEntry( "com.abc.TestClass", "testMethod", null, 22, sysProps );
+        return new SimpleReportEntry( "com.abc.TestClass", null, "testMethod", null, null, 22, sysProps );
     }
 
     private SimpleReportEntry createDefaultReportEntry()
@@ -278,7 +278,7 @@ public class ForkingRunListenerTest
 
     private SimpleReportEntry createAnotherDefaultReportEntry()
     {
-        return new SimpleReportEntry( "com.abc.AnotherTestClass", "testAnotherMethod", 42 );
+        return new SimpleReportEntry( "com.abc.AnotherTestClass", null, "testAnotherMethod", null, 42 );
     }
 
     private SimpleReportEntry createReportEntryWithStackTrace()
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java
index 08b3030..8f8cd8f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java
@@ -53,6 +53,7 @@ import static org.powermock.api.mockito.PowerMockito.when;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -61,6 +62,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
  */
 @RunWith( PowerMockRunner.class )
 @PrepareForTest( { JarManifestForkConfiguration.class, InPluginProcessDumpSingleton.class } )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
 public class JarManifestForkConfigurationTest
 {
     private static final File TMP = newTemporaryFolder();
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 0ab24b9..3770fa0 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
@@ -20,6 +20,9 @@ package org.apache.maven.plugin.surefire.booterclient;
  */
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.surefire.report.RunListener;
@@ -55,7 +58,8 @@ public class TestSetMockReporterFactory
     {
         File target = new File( "./target" );
         File statisticsFile = new File( target, "TESTHASH" );
-        return new StartupReportConfiguration( true, true, "PLAIN", false, false, target, false, null, statisticsFile,
-                false, 0, null, null, true );
+        return new StartupReportConfiguration( true, true, "PLAIN", false, target, false, null, statisticsFile,
+                false, 0, null, null, true, new SurefireStatelessReporter(), new SurefireConsoleOutputReporter(),
+                new SurefireStatelessTestsetInfoReporter() );
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index e4107da..a2405c5 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -907,8 +907,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -939,8 +943,12 @@ public class ForkClientTest
                 .hasSize( 1 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getMessage() )
@@ -1017,12 +1025,16 @@ public class ForkClientTest
         when( reportEntry.getGroup() ).thenReturn( "this group" );
         when( reportEntry.getMessage() ).thenReturn( "some test" );
         when( reportEntry.getName() ).thenReturn( "my test" );
+        when( reportEntry.getNameText() ).thenReturn( "dn2" );
         when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+        when( reportEntry.getSourceText() ).thenReturn( "dn1" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
+        String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
         String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
+        String encodedNameText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
@@ -1030,8 +1042,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + encodedSourceText
+                + ":"
                 + encodedName
                 + ":"
+                + encodedNameText
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1060,8 +1076,12 @@ public class ForkClientTest
                 .hasSize( 1 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isEqualTo( "dn1" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isEqualTo( "dn2" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getMessage() )
@@ -1153,8 +1173,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:testset-completed:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1181,8 +1205,12 @@ public class ForkClientTest
                 .hasSize( 1 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getMessage() )
@@ -1274,8 +1302,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1307,8 +1339,12 @@ public class ForkClientTest
                 .hasSize( 1 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getMessage() )
@@ -1396,7 +1432,7 @@ public class ForkClientTest
 
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
-                + ":-:-:-:-:-:-:-" );
+                + ":-:-:-:-:-:-:-:-:-" );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
@@ -1405,8 +1441,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-succeeded:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1433,8 +1473,12 @@ public class ForkClientTest
                 .hasSize( 2 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isNull();
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getName() )
@@ -1530,7 +1574,7 @@ public class ForkClientTest
 
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
-                + ":-:-:-:-:-:-:-" );
+                + ":-:-:-:-:-:-:-:-:-" );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
@@ -1539,8 +1583,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-failed:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1567,12 +1615,20 @@ public class ForkClientTest
                 .hasSize( 2 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isNull();
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getMessage() )
@@ -1664,7 +1720,7 @@ public class ForkClientTest
 
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
-                + ":-:-:-:-:-:-:-" );
+                + ":-:-:-:-:-:-:-:-:-" );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
@@ -1673,8 +1729,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-skipped:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1701,12 +1761,20 @@ public class ForkClientTest
                 .hasSize( 2 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isNull();
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getNameText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getMessage() )
@@ -1787,9 +1855,11 @@ public class ForkClientTest
         when( reportEntry.getName() ).thenReturn( "my test" );
         when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+        when( reportEntry.getSourceText() ).thenReturn( "display name" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
+        String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
         String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
@@ -1798,7 +1868,9 @@ public class ForkClientTest
 
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
-                + ":-:-:-:-:-:-:-" );
+                + ":"
+                + encodedSourceText
+                + ":-:':-:-:-:-:-:-" );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
@@ -1807,8 +1879,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-error:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + encodedSourceText
+                + ":"
                 + encodedName
                 + ":"
+                + "-"
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1835,10 +1911,14 @@ public class ForkClientTest
                 .hasSize( 2 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isEqualTo( "display name" );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceText() )
+                .isEqualTo( "display name" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getName() )
                 .isEqualTo( "my test" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getElapsed() )
@@ -1919,12 +1999,14 @@ public class ForkClientTest
         when( reportEntry.getGroup() ).thenReturn( "this group" );
         when( reportEntry.getMessage() ).thenReturn( "some test" );
         when( reportEntry.getName() ).thenReturn( "my test" );
+        when( reportEntry.getNameText() ).thenReturn("display name");
         when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
         when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
         when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
         String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
         String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
+        String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
         String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
@@ -1932,7 +2014,7 @@ public class ForkClientTest
 
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                 + encodedSourceName
-                + ":-:-:-:-:-:-:-" );
+                + ":-:-:-:-:-:-:-:-:-" );
 
         assertThat( client.testsInProgress() )
                 .hasSize( 1 )
@@ -1941,8 +2023,12 @@ public class ForkClientTest
         client.consumeMultiLineContent( ":maven:surefire:std:out:test-assumption-failure:normal-run:UTF-8:"
                 + encodedSourceName
                 + ":"
+                + "-"
+                + ":"
                 + encodedName
                 + ":"
+                + encodedText
+                + ":"
                 + encodedGroup
                 + ":"
                 + encodedMessage
@@ -1969,12 +2055,16 @@ public class ForkClientTest
                 .hasSize( 2 );
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getSourceText() )
+                .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 0 ) ).getName() )
                 .isNull();
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getSourceName() )
                 .isEqualTo( "pkg.MyTest" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getName() )
                 .isEqualTo( "my test" );
+        assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getNameText() )
+                .isEqualTo( "display name" );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getElapsed() )
                 .isEqualTo( 102 );
         assertThat( ( (ReportEntry) receiver.getData().get( 1 ) ).getMessage() )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
index 894ec2d..53c3562 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkedChannelDecoderTest.java
@@ -107,22 +107,24 @@ public class ForkedChannelDecoderTest
         @Test
         public void shouldRecognizeEmptyStream4ReportEntry()
         {
-            ReportEntry reportEntry = toReportEntry( null, null, "", null, null, "",
+            ReportEntry reportEntry = toReportEntry( null, null, null, "", "", null, null, "",
                     "", "", null );
             assertThat( reportEntry ).isNull();
 
-            reportEntry = toReportEntry( UTF_8, "", "", "", "", "-", "", "", "" );
+            reportEntry = toReportEntry( UTF_8, "", "", "", "", "", "", "-", "", "", "" );
             assertThat( reportEntry ).isNotNull();
             assertThat( reportEntry.getStackTraceWriter() ).isNull();
             assertThat( reportEntry.getSourceName() ).isEmpty();
+            assertThat( reportEntry.getSourceText() ).isEmpty();
             assertThat( reportEntry.getName() ).isEmpty();
+            assertThat( reportEntry.getNameText() ).isEmpty();
             assertThat( reportEntry.getGroup() ).isEmpty();
             assertThat( reportEntry.getNameWithGroup() ).isEmpty();
             assertThat( reportEntry.getMessage() ).isEmpty();
             assertThat( reportEntry.getElapsed() ).isNull();
 
             rule.expect( NumberFormatException.class );
-            toReportEntry( UTF_8, "", "", "", "", "", "", "", "" );
+            toReportEntry( UTF_8, "", "", "", "", "", "", "", "", "", "" );
             fail();
         }
 
@@ -153,58 +155,66 @@ public class ForkedChannelDecoderTest
             when( reportEntry.getGroup() ).thenReturn( "this group" );
             when( reportEntry.getMessage() ).thenReturn( "skipped test" );
             when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getNameText() ).thenReturn( "my display name" );
             when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
             when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getSourceText() ).thenReturn( "test class display name" );
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             String encodedSourceName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceName() ) ) );
+            String encodedSourceText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getSourceText() ) ) );
             String encodedName = encodeBase64String( toArray( UTF_8.encode( reportEntry.getName() ) ) );
+            String encodedText = encodeBase64String( toArray( UTF_8.encode( reportEntry.getNameText() ) ) );
             String encodedGroup = encodeBase64String( toArray( UTF_8.encode( reportEntry.getGroup() ) ) );
             String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
-            ReportEntry decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedName, encodedGroup,
-                                                                  encodedMessage, "-", null, null, null
-            );
+            ReportEntry decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText,
+                    encodedName, encodedText, encodedGroup, encodedMessage, "-", null, null, null );
 
             assertThat( decodedReportEntry ).isNotNull();
             assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
             assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo(reportEntry.getNameText());
             assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
 
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
-                    "-", encodedExceptionMsg, encodedSmartStackTrace, null
-            );
+            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
+                    encodedGroup, encodedMessage, "-", encodedExceptionMsg, encodedSmartStackTrace, null );
 
             assertThat( decodedReportEntry ).isNotNull();
             assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
             assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo(reportEntry.getNameText());
             assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getElapsed() ).isNull();
             assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
 
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
-                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace, null
-            );
+            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
+                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace, null );
 
             assertThat( decodedReportEntry ).isNotNull();
             assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
             assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo(reportEntry.getNameText());
             assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
             assertThat( decodedReportEntry.getStackTraceWriter() ).isNull();
 
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
-                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace,
-                                                      encodedStackTrace
-            );
+            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
+                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace,
+                    encodedStackTrace );
 
             assertThat( decodedReportEntry ).isNotNull();
             assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
             assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo(reportEntry.getNameText());
             assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
@@ -217,14 +227,15 @@ public class ForkedChannelDecoderTest
             assertThat( decodedReportEntry.getStackTraceWriter().writeTraceToString() ).isEqualTo( stackTrace );
             assertThat( decodedReportEntry.getStackTraceWriter().writeTrimmedTraceToString() ).isEqualTo( stackTrace );
 
-            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedName, encodedGroup, encodedMessage,
-                                                      "1003", encodedExceptionMsg, encodedSmartStackTrace,
-                                                      encodedTrimmedStackTrace
-            );
+            decodedReportEntry = toReportEntry( UTF_8, encodedSourceName, encodedSourceText, encodedName, encodedText,
+                    encodedGroup, encodedMessage, "1003", encodedExceptionMsg, encodedSmartStackTrace,
+                    encodedTrimmedStackTrace );
 
             assertThat( decodedReportEntry ).isNotNull();
             assertThat( decodedReportEntry.getSourceName() ).isEqualTo( reportEntry.getSourceName() );
+            assertThat( decodedReportEntry.getSourceText() ).isEqualTo( reportEntry.getSourceText() );
             assertThat( decodedReportEntry.getName() ).isEqualTo( reportEntry.getName() );
+            assertThat( decodedReportEntry.getNameText() ).isEqualTo(reportEntry.getNameText());
             assertThat( decodedReportEntry.getGroup() ).isEqualTo( reportEntry.getGroup() );
             assertThat( decodedReportEntry.getMessage() ).isEqualTo( reportEntry.getMessage() );
             assertThat( decodedReportEntry.getElapsed() ).isEqualTo( 1003 );
@@ -643,8 +654,10 @@ public class ForkedChannelDecoderTest
             when( reportEntry.getGroup() ).thenReturn( "this group" );
             when( reportEntry.getMessage() ).thenReturn( reportedMessage );
             when( reportEntry.getName() ).thenReturn( "my test" );
+            when( reportEntry.getName() ).thenReturn( "display name of test" );
             when( reportEntry.getNameWithGroup() ).thenReturn( "name with group" );
             when( reportEntry.getSourceName() ).thenReturn( "pkg.MyTest" );
+            when( reportEntry.getSourceText() ).thenReturn("test class display name");
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             Stream out = Stream.newStream();
@@ -777,7 +790,9 @@ public class ForkedChannelDecoderTest
         public void handle( RunMode runMode, ReportEntry reportEntry )
         {
             assertThat( reportEntry.getSourceName() ).isEqualTo( this.reportEntry.getSourceName() );
+            assertThat( reportEntry.getSourceText() ).isEqualTo( this.reportEntry.getSourceText() );
             assertThat( reportEntry.getName() ).isEqualTo( this.reportEntry.getName() );
+            assertThat( reportEntry.getNameText() ).isEqualTo( this.reportEntry.getNameText() );
             assertThat( reportEntry.getGroup() ).isEqualTo( this.reportEntry.getGroup() );
             assertThat( reportEntry.getMessage() ).isEqualTo( this.reportEntry.getMessage() );
             assertThat( reportEntry.getElapsed() ).isEqualTo( this.reportEntry.getElapsed() );
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 4bdfe8b..a9c5bee 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
@@ -22,18 +22,24 @@ package org.apache.maven.plugin.surefire.report;
 import java.io.File;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Queue;
 
 import junit.framework.TestCase;
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 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;
+import org.apache.maven.surefire.suite.RunResult;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType.error;
@@ -45,6 +51,7 @@ import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.Tes
 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.getTestResultType;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.powermock.reflect.Whitebox.getInternalState;
 import static org.powermock.reflect.Whitebox.invokeMethod;
 
 public class DefaultReporterFactoryTest
@@ -68,10 +75,13 @@ public class DefaultReporterFactoryTest
             throws Exception
     {
         MessageUtils.setColorEnabled( false );
-        File reportsDirectory = new File("target");
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "tmp5" );
         StartupReportConfiguration reportConfig =
-                new StartupReportConfiguration( true, true, "PLAIN", false, false, reportsDirectory, false, null,
-                        new File( reportsDirectory, "TESTHASH" ), false, 1, null, null, false );
+                new StartupReportConfiguration( true, true, "PLAIN", false, reportsDirectory, false, null,
+                        new File( reportsDirectory, "TESTHASH" ), false, 1, null, null, false,
+                        new SurefireStatelessReporter(), new SurefireConsoleOutputReporter(),
+                        new SurefireStatelessTestsetInfoReporter() );
 
         DummyTestReporter reporter = new DummyTestReporter();
 
@@ -198,12 +208,13 @@ public class DefaultReporterFactoryTest
         @Override
         public void error( String message, Throwable t )
         {
-            messages.add( message );
+            messages.add( message + " " + t.getLocalizedMessage() );
         }
 
         @Override
         public void error( Throwable t )
         {
+            messages.add( t.getLocalizedMessage() );
         }
 
         List<String> getMessages()
@@ -257,6 +268,127 @@ public class DefaultReporterFactoryTest
         assertEquals( skipped, getTestResultType( skippedList, 1 ) );
     }
 
+    public void testLogger()
+    {
+        MessageUtils.setColorEnabled( false );
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "tmp6" );
+        StartupReportConfiguration reportConfig =
+                new StartupReportConfiguration( true, true, "PLAIN", false, reportsDirectory, false, null,
+                        new File( reportsDirectory, "TESTHASH" ), false, 1, null, null, false,
+                        new SurefireStatelessReporter(), new SurefireConsoleOutputReporter(),
+                        new SurefireStatelessTestsetInfoReporter() );
+
+        DummyTestReporter reporter = new DummyTestReporter();
+
+        DefaultReporterFactory factory = new DefaultReporterFactory( reportConfig, reporter );
+
+        TestSetRunListener runListener = (TestSetRunListener) factory.createReporter();
+
+        assertTrue( runListener.isDebugEnabled() );
+        assertTrue( runListener.isInfoEnabled() );
+        assertTrue( runListener.isWarnEnabled() );
+        assertTrue( runListener.isErrorEnabled() );
+
+        runListener.debug( "msg" );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "msg", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+
+        runListener.info( "msg\n" );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "msg", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+
+        runListener.warning( "msg\r\n" );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "msg", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+
+        runListener.error( "msg" );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "msg", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+
+        runListener.error( "msg\n", new Exception( "e" ) );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "msg e", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+
+        runListener.error( new Exception( "e" ) );
+        assertEquals( 1, reporter.getMessages().size() );
+        assertEquals( "e", reporter.getMessages().get( 0 ) );
+        reporter.reset();
+    }
+
+    public void testCreateReporterWithZeroStatistics()
+    {
+        MessageUtils.setColorEnabled( false );
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "tmp7" );
+        StartupReportConfiguration reportConfig =
+                new StartupReportConfiguration( true, true, "PLAIN", false, reportsDirectory, false, null,
+                        new File( reportsDirectory, "TESTHASH" ), false, 0, null, null, false,
+                        new SurefireStatelessReporter(), new SurefireConsoleOutputReporter(),
+                        new SurefireStatelessTestsetInfoReporter() );
+
+        assertTrue( reportConfig.isUseFile() );
+        assertTrue( reportConfig.isPrintSummary() );
+        assertEquals( "PLAIN", reportConfig.getReportFormat() );
+        assertFalse( reportConfig.isRedirectTestOutputToFile() );
+        assertEquals( reportsDirectory, reportConfig.getReportsDirectory() );
+        assertFalse( reportConfig.isTrimStackTrace() );
+        assertNull( reportConfig.getReportNameSuffix() );
+        assertEquals( new File( reportsDirectory, "TESTHASH" ), reportConfig.getStatisticsFile() );
+        assertFalse( reportConfig.isRequiresRunHistory() );
+        assertEquals( 0, reportConfig.getRerunFailingTestsCount() );
+        assertNull( reportConfig.getXsdSchemaLocation() );
+        assertEquals( UTF_8, reportConfig.getEncoding() );
+        assertFalse( reportConfig.isForkMode() );
+        assertNotNull( reportConfig.getXmlReporter() );
+        assertNotNull( reportConfig.getConsoleOutputReporter() );
+        assertNotNull( reportConfig.getTestsetReporter() );
+        assertNull( reportConfig.getStatisticsReporter() );
+
+        DummyTestReporter reporter = new DummyTestReporter();
+
+        DefaultReporterFactory factory = new DefaultReporterFactory( reportConfig, reporter );
+        assertEquals( reportsDirectory, factory.getReportsDirectory() );
+
+        TestSetRunListener runListener = (TestSetRunListener) factory.createReporter();
+        Collection listeners = getInternalState( factory, "listeners" );
+        assertEquals( 1, listeners.size() );
+        assertTrue( listeners.contains( runListener ) );
+
+        assertNotNull( runListener.getTestMethodStats() );
+
+        factory.runStarting();
+
+        factory.close();
+
+        RunStatistics statistics = factory.getGlobalRunStatistics();
+        assertEquals( 0, statistics.getCompletedCount() );
+        assertEquals( new RunResult( 0, 0, 0, 0 ), statistics.getRunResult() );
+        assertEquals( 0, statistics.getFailures() );
+        assertEquals( 0, statistics.getErrors() );
+        assertEquals( 0, statistics.getSkipped() );
+        assertEquals( 0, statistics.getFlakes() );
+        assertEquals( "Tests run: 0, Failures: 0, Errors: 0, Skipped: 0", statistics.getSummary() );
+        assertEquals( 0, statistics.getCompletedCount() );
+
+        List<String> messages = reporter.getMessages();
+        assertEquals( "", messages.get( 0 ) );
+        assertEquals( "-------------------------------------------------------", messages.get( 1 ) );
+        assertEquals( " T E S T S", messages.get( 2 ) );
+        assertEquals( "-------------------------------------------------------", messages.get( 3 ) );
+        assertEquals( "", messages.get( 4 ) );
+        assertEquals( "Results:", messages.get( 5 ) );
+        assertEquals( "", messages.get( 6 ) );
+        assertEquals( "Tests run: 0, Failures: 0, Errors: 0, Skipped: 0", messages.get( 7 ) );
+        assertEquals( "", messages.get( 8 ) );
+        assertEquals( 9, messages.size() );
+    }
+
     static class DummyStackTraceWriter
         implements StackTraceWriter
     {
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 bebaed3..5faf8e3 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
@@ -84,10 +84,11 @@ public class StatelessXmlReporterTest
     {
         StatelessXmlReporter reporter =
                 new StatelessXmlReporter( reportDir, null, false, 0,
-                        new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD );
+                        new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD, "3.0",
+                        false, false, false, false );
         reporter.cleanTestHistoryMap();
 
-        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), getClass().getName(), 12 );
+        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), null, getClass().getName(), null, 12 );
         WrappedReportEntry testSetReportEntry = new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS,
                 12, null, null, systemProps() );
         stats.testSucceeded( testSetReportEntry );
@@ -102,7 +103,7 @@ public class StatelessXmlReporterTest
     public void testAllFieldsSerialized()
             throws IOException
     {
-        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 );
+        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), null, TEST_ONE, null, 12 );
         WrappedReportEntry testSetReportEntry =
                 new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
         expectedReportFile = new File( reportDir, "TEST-" + getClass().getName() + ".xml" );
@@ -129,12 +130,12 @@ public class StatelessXmlReporterTest
 
         stdErr.write( stdErrPrefix + "?&-&amp;&#163;\u0020\u0000\u001F", false );
         WrappedReportEntry t2 =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriter, 13 ),
-                        ReportEntryType.ERROR, 13, stdOut, stdErr );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_TWO, null,
+                        stackTraceWriter, 13 ), ReportEntryType.ERROR, 13, stdOut, stdErr );
 
         stats.testSucceeded( t2 );
         StatelessXmlReporter reporter = new StatelessXmlReporter( reportDir, null, false, 0,
-                new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD );
+                new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD, "3.0", false, false, false, false );
         reporter.testSetCompleted( testSetReportEntry, stats );
 
         FileInputStream fileInputStream = new FileInputStream( expectedReportFile );
@@ -171,7 +172,7 @@ public class StatelessXmlReporterTest
             throws IOException
     {
         WrappedReportEntry testSetReportEntry =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 ),
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_ONE, null, 12 ),
                         ReportEntryType.SUCCESS, 12, null, null, systemProps() );
         expectedReportFile = new File( reportDir, "TEST-" + getClass().getName() + ".xml" );
 
@@ -187,23 +188,23 @@ public class StatelessXmlReporterTest
         String secondRunErr = "second run err";
 
         WrappedReportEntry testTwoFirstError =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriterOne, 5 ),
-                        ReportEntryType.ERROR, 5, createStdOutput( firstRunOut ),
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_TWO, null,
+                        stackTraceWriterOne, 5 ), ReportEntryType.ERROR, 5, createStdOutput( firstRunOut ),
                         createStdOutput( firstRunErr ) );
 
         WrappedReportEntry testTwoSecondError =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriterTwo, 13 ),
-                        ReportEntryType.ERROR, 13, createStdOutput( secondRunOut ),
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_TWO, null,
+                        stackTraceWriterTwo, 13 ), ReportEntryType.ERROR, 13, createStdOutput( secondRunOut ),
                         createStdOutput( secondRunErr ) );
 
         WrappedReportEntry testThreeFirstRun =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_THREE, stackTraceWriterOne, 13 ),
-                        ReportEntryType.FAILURE, 13, createStdOutput( firstRunOut ),
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_THREE, null,
+                        stackTraceWriterOne, 13 ), ReportEntryType.FAILURE, 13, createStdOutput( firstRunOut ),
                         createStdOutput( firstRunErr ) );
 
         WrappedReportEntry testThreeSecondRun =
-                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_THREE, stackTraceWriterTwo, 2 ),
-                        ReportEntryType.SUCCESS, 2, createStdOutput( secondRunOut ),
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), null, TEST_THREE, null,
+                        stackTraceWriterTwo, 2 ), ReportEntryType.SUCCESS, 2, createStdOutput( secondRunOut ),
                         createStdOutput( secondRunErr ) );
 
         stats.testSucceeded( testTwoFirstError );
@@ -213,7 +214,7 @@ public class StatelessXmlReporterTest
 
         StatelessXmlReporter reporter =
                 new StatelessXmlReporter( reportDir, null, false, 1,
-                        new HashMap<String, Deque<WrappedReportEntry>>(), XSD );
+                        new HashMap<String, Deque<WrappedReportEntry>>(), XSD, "3.0", false, false, false, false );
 
         reporter.testSetCompleted( testSetReportEntry, stats );
         reporter.testSetCompleted( testSetReportEntry, rerunStats );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/TestSetStatsTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/TestSetStatsTest.java
new file mode 100644
index 0000000..7aac381
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/TestSetStatsTest.java
@@ -0,0 +1,71 @@
+package org.apache.maven.plugin.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 org.apache.maven.surefire.report.ReportEntry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * tests for {@link TestSetStats}.
+ */
+@RunWith( PowerMockRunner.class )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
+public class TestSetStatsTest
+{
+    @Mock
+    private ReportEntry reportEntry;
+
+    @Test
+    public void shouldConcatenateWithTestGroup()
+    {
+        when( reportEntry.getNameWithGroup() )
+                .thenReturn( "pkg.MyTest (my group)" );
+        String actual = TestSetStats.concatenateWithTestGroup( buffer(), reportEntry, false );
+        verify( reportEntry, times( 1 ) ).getNameWithGroup();
+        verifyNoMoreInteractions( reportEntry );
+        String expected = buffer().a( "pkg." ).strong( "MyTest (my group)" ).toString();
+        assertThat( actual )
+                .isEqualTo( expected );
+    }
+
+    @Test
+    public void shouldConcatenateWithJUnit5TestGroup()
+    {
+        when( reportEntry.getReportNameWithGroup() )
+                .thenReturn( "pkg.MyTest (my group)" );
+        String actual = TestSetStats.concatenateWithTestGroup( buffer(), reportEntry, true );
+        verify( reportEntry, times( 1 ) ).getReportNameWithGroup();
+        verifyNoMoreInteractions( reportEntry );
+        String expected = buffer().strong( "pkg.MyTest (my group)" ).toString();
+        assertThat( actual )
+                .isEqualTo( expected );
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
index 61080a1..abbc093 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
@@ -23,6 +23,8 @@ import org.apache.maven.surefire.report.SimpleReportEntry;
 
 import junit.framework.TestCase;
 
+import static org.apache.maven.plugin.surefire.report.ReportEntryType.*;
+
 /**
  * @author Kristian Rosenvold
  */
@@ -33,32 +35,84 @@ public class WrappedReportEntryTest
     {
         String className = "surefire.testcase.JunitParamsTest";
         WrappedReportEntry wr =
-            new WrappedReportEntry( new SimpleReportEntry( className, null ), null, 12, null, null );
-        final String reportName = wr.getReportName();
+            new WrappedReportEntry( new SimpleReportEntry( className, null, null, null ), SUCCESS, 12, null, null );
+        final String reportName = wr.getReportSourceName();
+        assertEquals( "surefire.testcase.JunitParamsTest.null", wr.getClassMethodName() );
         assertEquals( "surefire.testcase.JunitParamsTest", reportName );
+        assertTrue( wr.isSucceeded() );
+        assertFalse( wr.isErrorOrFailure() );
+        assertFalse( wr.isSkipped() );
     }
 
     public void testRegular()
     {
-        ReportEntry reportEntry = new SimpleReportEntry( "surefire.testcase.JunitParamsTest", "testSum" );
+        ReportEntry reportEntry = new SimpleReportEntry( "surefire.testcase.JunitParamsTest", null, "testSum", null );
         WrappedReportEntry wr = new WrappedReportEntry( reportEntry, null, 12, null, null );
-        final String reportName = wr.getReportName();
-        assertEquals( "surefire.testcase.JunitParamsTest", reportName );
+        assertEquals( "surefire.testcase.JunitParamsTest.testSum", wr.getClassMethodName() );
+        assertEquals( "surefire.testcase.JunitParamsTest", wr.getReportSourceName() );
+        assertEquals( "surefire.testcase.JunitParamsTest", wr.getReportSourceName( "" ) );
+        assertEquals( "surefire.testcase.JunitParamsTest(BDD)", wr.getReportSourceName( "BDD" ) );
+        assertEquals( "surefire.testcase.JunitParamsTest", wr.getSourceName( "" ) );
+        assertEquals( "surefire.testcase.JunitParamsTest(BDD)", wr.getSourceName( "BDD" ) );
+        assertEquals( "testSum", wr.getReportName() );
+        assertFalse(wr.isSucceeded());
+        assertFalse( wr.isErrorOrFailure() );
+        assertFalse( wr.isSkipped() );
+        assertTrue( wr.getSystemProperties().isEmpty() );
+        assertNull( wr.getGroup() );
+        assertEquals( "surefire.testcase.JunitParamsTest", wr.getNameWithGroup() );
+    }
+
+    public void testDisplayNames()
+    {
+        ReportEntry reportEntry =
+                new SimpleReportEntry( "surefire.testcase.JunitParamsTest", "dn1", "testSum", "dn2", "exception" );
+        WrappedReportEntry wr = new WrappedReportEntry( reportEntry, ERROR, 12, null, null );
+        assertEquals( "surefire.testcase.JunitParamsTest.testSum", wr.getClassMethodName() );
+        assertEquals( "dn1", wr.getReportSourceName() );
+        assertEquals( "dn1(BDD)", wr.getReportSourceName( "BDD" ) );
+        assertEquals( "surefire.testcase.JunitParamsTest(BDD)", wr.getSourceName( "BDD" ) );
+        assertEquals( "dn2", wr.getReportName() );
+        assertFalse(wr.isSucceeded());
+        assertTrue(wr.isErrorOrFailure());
+        assertFalse( wr.isSkipped() );
+        assertNull( wr.getStackTraceWriter() );
+        assertEquals( "surefire.testcase.JunitParamsTest.testSum  Time elapsed: 0.012 s",
+                wr.getElapsedTimeSummary() );
+        assertEquals( "surefire.testcase.JunitParamsTest.testSum  Time elapsed: 0.012 s  <<< ERROR!",
+                wr.getOutput( false ) );
+        assertEquals( "exception", wr.getMessage() );
+    }
+
+    public void testEqualDisplayNames()
+    {
+        ReportEntry reportEntry = new SimpleReportEntry( "surefire.testcase.JunitParamsTest",
+                "surefire.testcase.JunitParamsTest", "testSum", "testSum" );
+        WrappedReportEntry wr = new WrappedReportEntry( reportEntry, FAILURE, 12, null, null );
+        assertEquals( "surefire.testcase.JunitParamsTest", wr.getReportSourceName() );
+        assertEquals( "surefire.testcase.JunitParamsTest(BDD)", wr.getReportSourceName( "BDD" ) );
+        assertEquals( "testSum", wr.getReportName() );
+        assertFalse(wr.isSucceeded());
+        assertTrue( wr.isErrorOrFailure() );
+        assertFalse( wr.isSkipped() );
     }
 
     public void testGetReportNameWithParams()
     {
         String className = "[0] 1\u002C 2\u002C 3 (testSum)";
-        ReportEntry reportEntry = new SimpleReportEntry( className, null );
-        WrappedReportEntry wr = new WrappedReportEntry( reportEntry, null, 12, null, null );
-        final String reportName = wr.getReportName();
+        ReportEntry reportEntry = new SimpleReportEntry( className, null, null, null );
+        WrappedReportEntry wr = new WrappedReportEntry( reportEntry, SKIPPED, 12, null, null );
+        final String reportName = wr.getReportSourceName();
         assertEquals( "[0] 1, 2, 3 (testSum)", reportName );
+        assertFalse( wr.isSucceeded() );
+        assertFalse (wr.isErrorOrFailure() );
+        assertTrue( wr.isSkipped() );
     }
 
     public void testElapsed()
     {
         String className = "[0] 1\u002C 2\u002C 3 (testSum)";
-        ReportEntry reportEntry = new SimpleReportEntry( className, null );
+        ReportEntry reportEntry = new SimpleReportEntry( className, null, null, null );
         WrappedReportEntry wr = new WrappedReportEntry( reportEntry, null, 12, null, null );
         String elapsedTimeSummary = wr.getElapsedTimeSummary();
         assertEquals( "[0] 1, 2, 3 (testSum)  Time elapsed: 0.012 s",
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java
index 2970356..b38d036 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMapTest.java
@@ -81,9 +81,9 @@ public class RunEntryStatisticsMapTest
         RunEntryStatisticsMap existingEntries = RunEntryStatisticsMap.fromFile( data );
         RunEntryStatisticsMap newResults = new RunEntryStatisticsMap();
 
-        ReportEntry reportEntry1 = new SimpleReportEntry( "abc", "method1", 42 );
-        ReportEntry reportEntry2 = new SimpleReportEntry( "abc", "willFail", 17 );
-        ReportEntry reportEntry3 = new SimpleReportEntry( "abc", "method3", 100 );
+        ReportEntry reportEntry1 = new SimpleReportEntry( "abc", null, "method1", null, 42 );
+        ReportEntry reportEntry2 = new SimpleReportEntry( "abc", null, "willFail", null, 17 );
+        ReportEntry reportEntry3 = new SimpleReportEntry( "abc", null, "method3", null, 100 );
 
         newResults.add( existingEntries.createNextGeneration( reportEntry1 ) );
         newResults.add( existingEntries.createNextGeneration( reportEntry2 ) );
@@ -104,9 +104,9 @@ public class RunEntryStatisticsMapTest
         RunEntryStatisticsMap nextRun = RunEntryStatisticsMap.fromFile( data );
         newResults = new RunEntryStatisticsMap();
 
-        ReportEntry newRunReportEntry1 = new SimpleReportEntry( "abc", "method1", 52 );
-        ReportEntry newRunReportEntry2 = new SimpleReportEntry( "abc", "willFail", 27 );
-        ReportEntry newRunReportEntry3 = new SimpleReportEntry( "abc", "method3", 110 );
+        ReportEntry newRunReportEntry1 = new SimpleReportEntry( "abc", null, "method1", null, 52 );
+        ReportEntry newRunReportEntry2 = new SimpleReportEntry( "abc", null, "willFail", null, 27 );
+        ReportEntry newRunReportEntry3 = new SimpleReportEntry( "abc", null, "method3", null, 110 );
 
         newResults.add( nextRun.createNextGeneration( newRunReportEntry1 ) );
         newResults.add( nextRun.createNextGenerationFailure( newRunReportEntry2 ) );
@@ -129,7 +129,7 @@ public class RunEntryStatisticsMapTest
     {
         File data = File.createTempFile( "surefire-unit", "test" );
         RunEntryStatisticsMap reportEntries = RunEntryStatisticsMap.fromFile( data );
-        ReportEntry reportEntry = new SimpleReportEntry( "abc", "line1\nline2" + NL + " line3", 42 );
+        ReportEntry reportEntry = new SimpleReportEntry( "abc", null, "line1\nline2" + NL + " line3", null, 42 );
         reportEntries.add( reportEntries.createNextGeneration( reportEntry ) );
 
         reportEntries.serialize( data );
@@ -163,8 +163,10 @@ public class RunEntryStatisticsMapTest
     {
         File data = File.createTempFile( "surefire-unit", "test" );
         RunEntryStatisticsMap reportEntries = RunEntryStatisticsMap.fromFile( data );
-        reportEntries.add( reportEntries.createNextGeneration( new SimpleReportEntry( "abc", "line1\nline2", 42 ) ) );
-        reportEntries.add( reportEntries.createNextGeneration( new SimpleReportEntry( "abc", "test", 10 ) ) );
+        reportEntries.add(
+                reportEntries.createNextGeneration( new SimpleReportEntry( "abc", null, "line1\nline2", null, 42 ) ) );
+        reportEntries.add(
+                reportEntries.createNextGeneration( new SimpleReportEntry( "abc", null, "test", null, 10 ) ) );
 
         reportEntries.serialize( data );
         try ( InputStream io = new FileInputStream( data ) )
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 c6177cd..d28a89c 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
@@ -25,6 +25,7 @@ import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.apache.maven.plugin.surefire.AbstractSurefireMojoJava7PlusTest;
 import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest;
+import org.apache.maven.plugin.surefire.CommonReflectorTest;
 import org.apache.maven.plugin.surefire.MojoMocklessTest;
 import org.apache.maven.plugin.surefire.SurefireHelperTest;
 import org.apache.maven.plugin.surefire.SurefireReflectorTest;
@@ -42,13 +43,16 @@ import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkedChannelDecoderTest;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest;
 import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest;
+import org.apache.maven.plugin.surefire.report.TestSetStatsTest;
 import org.apache.maven.plugin.surefire.report.WrappedReportEntryTest;
 import org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest;
 import org.apache.maven.plugin.surefire.util.DependenciesScannerTest;
 import org.apache.maven.plugin.surefire.util.DirectoryScannerTest;
 import org.apache.maven.plugin.surefire.util.ScannerUtilTest;
 import org.apache.maven.plugin.surefire.util.SpecificFileFilterTest;
-import org.apache.maven.surefire.report.ConsoleOutputFileReporterTest;
+import org.apache.maven.surefire.extensions.ConsoleOutputReporterTest;
+import org.apache.maven.surefire.extensions.StatelessReporterTest;
+import org.apache.maven.surefire.extensions.StatelessTestsetInfoReporterTest;
 import org.apache.maven.surefire.report.FileReporterTest;
 import org.apache.maven.surefire.report.RunStatisticsTest;
 import org.apache.maven.surefire.spi.SPITest;
@@ -68,7 +72,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTestSuite( RelocatorTest.class );
         suite.addTestSuite( RunStatisticsTest.class );
         suite.addTestSuite( FileReporterTest.class );
-        suite.addTestSuite( ConsoleOutputFileReporterTest.class );
+        suite.addTestSuite( org.apache.maven.surefire.report.ConsoleOutputFileReporterTest.class );
         suite.addTestSuite( SurefirePropertiesTest.class );
         suite.addTestSuite( SpecificFileFilterTest.class );
         suite.addTest( new JUnit4TestAdapter( DirectoryScannerTest.class ) );
@@ -95,6 +99,11 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( MojoMocklessTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkClientTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkedChannelDecoderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ConsoleOutputReporterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( StatelessReporterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( TestSetStatsTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( StatelessTestsetInfoReporterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( CommonReflectorTest.class ) );
         return suite;
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java
new file mode 100644
index 0000000..546e554
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ConsoleOutputReporterTest.java
@@ -0,0 +1,173 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
+import org.apache.maven.plugin.surefire.report.DirectConsoleOutput;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.reflect.Whitebox.getInternalState;
+
+/**
+ * tests for {@link SurefireConsoleOutputReporter} and {@link JUnit5ConsoleOutputReporter}.
+ */
+public class ConsoleOutputReporterTest
+{
+    @Test
+    public void shouldCloneConsoleReporter()
+    {
+        SurefireConsoleOutputReporter extension = new SurefireConsoleOutputReporter();
+        extension.setDisable( true );
+        extension.setEncoding( "ISO-8859-1" );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( SurefireConsoleOutputReporter.class );
+        assertThat( clone.toString() )
+                .isEqualTo( "SurefireConsoleOutputReporter{disable=true, encoding=ISO-8859-1}" );
+        assertThat( ( (SurefireConsoleOutputReporter) clone ).isDisable() )
+                .isTrue();
+        assertThat( ( (SurefireConsoleOutputReporter) clone ).getEncoding() )
+                .isEqualTo( "ISO-8859-1" );
+    }
+
+    @Test
+    public void shouldAssertToStringConsoleReporter()
+    {
+        SurefireConsoleOutputReporter extension = new SurefireConsoleOutputReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "SurefireConsoleOutputReporter{disable=false, encoding=UTF-8}" );
+    }
+
+    @Test
+    public void shouldCreateConsoleListener()
+    {
+        ConsoleOutputReporter extension = new SurefireConsoleOutputReporter();
+
+        ConsoleOutputReportEventListener listener1 = extension.createListener( System.out, System.err );
+        assertThat( listener1 )
+                .isInstanceOf( DirectConsoleOutput.class );
+        assertThat( getInternalState( listener1, "out" ) )
+                .isSameAs( System.out );
+        assertThat( getInternalState( listener1, "err" ) )
+                .isSameAs( System.err );
+
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "surefire-reports" );
+        String reportNameSuffix = "suffix";
+        boolean usePhrasedFileName = false;
+        Integer forkNumber = 1;
+        String encoding = "ISO-8859-2";
+        extension.setEncoding( encoding );
+        ConsoleOutputReportEventListener listener2 =
+                extension.createListener( reportsDirectory, reportNameSuffix, forkNumber );
+        assertThat( listener2 )
+                .isInstanceOf( ConsoleOutputFileReporter.class );
+        assertThat( getInternalState( listener2, "reportsDirectory" ) )
+                .isSameAs( reportsDirectory );
+        assertThat( getInternalState( listener2, "reportNameSuffix" ) )
+                .isSameAs( reportNameSuffix );
+        assertThat( getInternalState( listener2, "usePhrasedFileName" ) )
+                .isEqualTo( usePhrasedFileName );
+        assertThat( getInternalState( listener2, "forkNumber" ) )
+                .isSameAs( forkNumber );
+        assertThat( getInternalState( listener2, "encoding" ) )
+                .isSameAs( encoding );
+        assertThat( getInternalState( listener2, "reportEntryName" ) )
+                .isNull();
+    }
+
+    @Test
+    public void shouldCloneJUnit5ConsoleReporter()
+    {
+        JUnit5ConsoleOutputReporter extension = new JUnit5ConsoleOutputReporter();
+        extension.setDisable( true );
+        extension.setEncoding( "ISO-8859-1" );
+        extension.setUsePhrasedFileName( true );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( JUnit5ConsoleOutputReporter.class );
+        assertThat( clone.toString() ).isEqualTo(
+                "JUnit5ConsoleOutputReporter{disable=true, encoding=ISO-8859-1, usePhrasedFileName=true}" );
+        assertThat( ( (JUnit5ConsoleOutputReporter) clone ).isDisable() )
+                .isTrue();
+        assertThat( ( (JUnit5ConsoleOutputReporter) clone ).getEncoding() )
+                .isEqualTo( "ISO-8859-1" );
+        assertThat( ( (JUnit5ConsoleOutputReporter) clone ).isUsePhrasedFileName() )
+                .isTrue();
+    }
+
+    @Test
+    public void shouldAssertToStringJUnit5ConsoleReporter()
+    {
+        JUnit5ConsoleOutputReporter extension = new JUnit5ConsoleOutputReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "JUnit5ConsoleOutputReporter{disable=false, encoding=UTF-8, usePhrasedFileName=false}" );
+    }
+
+    @Test
+    public void shouldCreateJUnit5ConsoleListener()
+    {
+        JUnit5ConsoleOutputReporter extension = new JUnit5ConsoleOutputReporter();
+
+        ConsoleOutputReportEventListener listener1 = extension.createListener( System.out, System.err );
+        assertThat( listener1 )
+                .isInstanceOf( DirectConsoleOutput.class );
+        assertThat( getInternalState( listener1, "out" ) )
+                .isSameAs( System.out );
+        assertThat( getInternalState( listener1, "err" ) )
+                .isSameAs( System.err );
+
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "surefire-reports" );
+        String reportNameSuffix = "suffix";
+        boolean usePhrasedFileName = true;
+        Integer forkNumber = 1;
+        String encoding = "ISO-8859-1";
+        extension.setEncoding( encoding );
+        extension.setUsePhrasedFileName( usePhrasedFileName );
+        ConsoleOutputReportEventListener listener2 =
+                extension.createListener( reportsDirectory, reportNameSuffix, forkNumber );
+        assertThat( listener2 )
+                .isInstanceOf( ConsoleOutputFileReporter.class );
+        assertThat( getInternalState( listener2, "reportsDirectory" ) )
+                .isSameAs( reportsDirectory );
+        assertThat( getInternalState( listener2, "reportNameSuffix" ) )
+                .isSameAs( reportNameSuffix );
+        assertThat( getInternalState( listener2, "usePhrasedFileName" ) )
+                .isEqualTo( usePhrasedFileName );
+        assertThat( getInternalState( listener2, "forkNumber" ) )
+                .isSameAs( forkNumber );
+        assertThat( getInternalState( listener2, "encoding" ) )
+                .isSameAs( encoding );
+        assertThat( getInternalState( listener2, "reportEntryName" ) )
+                .isNull();
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java
new file mode 100644
index 0000000..bb06eff
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessReporterTest.java
@@ -0,0 +1,244 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.extensions.DefaultStatelessReportMojoConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter;
+import org.apache.maven.plugin.surefire.report.StatelessXmlReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.reflect.Whitebox.getInternalState;
+
+/**
+ * tests for {@link SurefireStatelessReporter} and {@link JUnit5Xml30StatelessReporter}.
+ */
+public class StatelessReporterTest
+{
+    @Test
+    public void shouldCloneXmlReporter()
+    {
+        SurefireStatelessReporter extension = new SurefireStatelessReporter();
+        extension.setDisable( true );
+        extension.setVersion( "V1" );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( SurefireStatelessReporter.class );
+        assertThat( clone.toString() )
+                .isEqualTo( "SurefireStatelessReporter{version=V1, disable=true}" );
+        assertThat( ( (SurefireStatelessReporter) clone ).isDisable() )
+                .isTrue();
+        assertThat( ( (SurefireStatelessReporter) clone ).getVersion() )
+                .isEqualTo( "V1" );
+    }
+
+    @Test
+    public void shouldAssertToStringXmlReporter()
+    {
+        SurefireStatelessReporter extension = new SurefireStatelessReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "SurefireStatelessReporter{version=3.0, disable=false}" );
+    }
+
+    @Test
+    public void shouldCreateConsoleListener()
+    {
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "surefire-reports" );
+        String reportNameSuffix = "suffix";
+        String schema = "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd";
+        Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory = new HashMap<>();
+        DefaultStatelessReportMojoConfiguration config =
+                new DefaultStatelessReportMojoConfiguration( reportsDirectory, reportNameSuffix, true, 5, schema,
+                        testClassMethodRunHistory );
+        SurefireStatelessReporter extension = new SurefireStatelessReporter();
+
+        assertThat( extension.getVersion() )
+                .isEqualTo( "3.0" );
+        extension.setVersion( "V3" );
+        assertThat( extension.getVersion() )
+                .isEqualTo( "V3" );
+
+        assertThat( extension.isDisable() )
+                .isFalse();
+        extension.setDisable( true );
+        assertThat( extension.isDisable() )
+                .isTrue();
+
+        StatelessReportEventListener<WrappedReportEntry, TestSetStats> listener = extension.createListener( config );
+        assertThat( listener )
+                .isInstanceOf( StatelessXmlReporter.class );
+        assertThat( getInternalState( listener, "reportsDirectory" ) )
+                .isSameAs( reportsDirectory );
+        assertThat( getInternalState( listener, "reportNameSuffix" ) )
+                .isSameAs( reportNameSuffix );
+        assertThat( getInternalState( listener, "trimStackTrace" ) )
+                .isEqualTo( true );
+        assertThat( getInternalState( listener, "rerunFailingTestsCount" ) )
+                .isEqualTo( 5 );
+        assertThat( getInternalState( listener, "xsdSchemaLocation" ) )
+                .isSameAs( schema );
+        assertThat( getInternalState( listener, "xsdVersion" ) )
+                .isEqualTo( "V3" );
+        assertThat( getInternalState( listener, "testClassMethodRunHistoryMap" ) )
+                .isSameAs( testClassMethodRunHistory );
+        assertThat( getInternalState( listener, "phrasedFileName" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "phrasedSuiteName" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "phrasedClassName" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "phrasedMethodName" ) )
+                .isEqualTo( false );
+    }
+
+    @Test
+    public void shouldCloneJUnit5XmlReporter()
+    {
+        JUnit5Xml30StatelessReporter extension = new JUnit5Xml30StatelessReporter();
+        extension.setDisable( true );
+        extension.setVersion( "V1" );
+        extension.setUsePhrasedFileName( true );
+        extension.setUsePhrasedTestSuiteClassName( true );
+        extension.setUsePhrasedTestCaseClassName( true );
+        extension.setUsePhrasedTestCaseMethodName( true );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( JUnit5Xml30StatelessReporter.class );
+        assertThat( clone.toString() )
+                .isEqualTo( "JUnit5Xml30StatelessReporter{version=V1, disable=true, usePhrasedFileName=true, "
+                        + "usePhrasedTestSuiteClassName=true, usePhrasedTestCaseClassName=true, "
+                        + "usePhrasedTestCaseMethodName=true}" );
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).isDisable() )
+                .isTrue();
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).getVersion() )
+                .isEqualTo( "V1" );
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).getUsePhrasedFileName() )
+                .isTrue();
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).getUsePhrasedTestCaseClassName() )
+                .isTrue();
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).getUsePhrasedTestCaseMethodName() )
+                .isTrue();
+        assertThat( ( (JUnit5Xml30StatelessReporter) clone ).getUsePhrasedTestSuiteClassName() )
+                .isTrue();
+    }
+
+    @Test
+    public void shouldAssertToStringJUnit5ConsoleReporter()
+    {
+        JUnit5Xml30StatelessReporter extension = new JUnit5Xml30StatelessReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "JUnit5Xml30StatelessReporter{version=3.0, disable=false, "
+                        + "usePhrasedFileName=false, usePhrasedTestSuiteClassName=false, "
+                        + "usePhrasedTestCaseClassName=false, usePhrasedTestCaseMethodName=false}" );
+    }
+
+    @Test
+    public void shouldCreateJUnit5ConsoleListener()
+    {
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "surefire-reports" );
+        String reportNameSuffix = "suffix";
+        String schema = "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd";
+        Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory = new HashMap<>();
+        DefaultStatelessReportMojoConfiguration config =
+                new DefaultStatelessReportMojoConfiguration( reportsDirectory, reportNameSuffix, true, 5, schema,
+                        testClassMethodRunHistory );
+        JUnit5Xml30StatelessReporter extension = new JUnit5Xml30StatelessReporter();
+
+        assertThat( extension.getVersion() )
+                .isEqualTo( "3.0" );
+        extension.setVersion( "V3" );
+        assertThat( extension.getVersion() )
+                .isEqualTo( "V3" );
+
+        assertThat( extension.isDisable() )
+                .isFalse();
+        extension.setDisable( true );
+        assertThat( extension.isDisable() )
+                .isTrue();
+
+        assertThat( extension.getUsePhrasedFileName() )
+                .isFalse();
+        extension.setUsePhrasedFileName( true );
+        assertThat( extension.getUsePhrasedFileName() )
+                .isTrue();
+
+        assertThat( extension.getUsePhrasedTestSuiteClassName() )
+                .isFalse();
+        extension.setUsePhrasedTestSuiteClassName( true );
+        assertThat( extension.getUsePhrasedTestSuiteClassName() )
+                .isTrue();
+
+        assertThat( extension.getUsePhrasedTestCaseClassName() )
+                .isFalse();
+        extension.setUsePhrasedTestCaseClassName( true );
+        assertThat( extension.getUsePhrasedTestSuiteClassName() )
+                .isTrue();
+
+        assertThat( extension.getUsePhrasedTestCaseMethodName() )
+                .isFalse();
+        extension.setUsePhrasedTestCaseMethodName( true );
+        assertThat( extension.getUsePhrasedTestCaseMethodName() )
+                .isTrue();
+
+        StatelessReportEventListener<WrappedReportEntry, TestSetStats> listener = extension.createListener( config );
+        assertThat( listener )
+                .isInstanceOf( StatelessXmlReporter.class );
+        assertThat( getInternalState( listener, "reportsDirectory" ) )
+                .isSameAs( reportsDirectory );
+        assertThat( getInternalState( listener, "reportNameSuffix" ) )
+                .isSameAs( reportNameSuffix );
+        assertThat( getInternalState( listener, "trimStackTrace" ) )
+                .isEqualTo( true );
+        assertThat( getInternalState( listener, "rerunFailingTestsCount" ) )
+                .isEqualTo( 5 );
+        assertThat( getInternalState( listener, "xsdSchemaLocation" ) )
+                .isSameAs( schema );
+        assertThat( getInternalState( listener, "xsdVersion" ) )
+                .isEqualTo( "V3" );
+        assertThat( getInternalState( listener, "testClassMethodRunHistoryMap" ) )
+                .isSameAs( testClassMethodRunHistory );
+        assertThat( getInternalState( listener, "phrasedFileName" ) )
+                .isEqualTo( true );
+        assertThat( getInternalState( listener, "phrasedSuiteName" ) )
+                .isEqualTo( true );
+        assertThat( getInternalState( listener, "phrasedClassName" ) )
+                .isEqualTo( true );
+        assertThat( getInternalState( listener, "phrasedMethodName" ) )
+                .isEqualTo( true );
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java
new file mode 100644
index 0000000..aa10111
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporterTest.java
@@ -0,0 +1,229 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.extensions.SurefireStatelessTestsetInfoReporter;
+import org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoReporter;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.plugin.surefire.report.ConsoleReporter;
+import org.apache.maven.plugin.surefire.report.FileReporter;
+import org.apache.maven.plugin.surefire.report.TestSetStats;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntry;
+import org.apache.maven.shared.utils.logging.MessageUtils;
+import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.powermock.reflect.Whitebox.getInternalState;
+
+/**
+ * tests for {@link SurefireStatelessTestsetInfoReporter} and {@link JUnit5StatelessTestsetInfoReporter}.
+ */
+@RunWith( PowerMockRunner.class )
+@PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
+public class StatelessTestsetInfoReporterTest
+{
+    @Mock
+    private ConsoleLogger consoleLogger;
+
+    @Mock
+    private TestSetReportEntry eventTestsetStarting;
+
+    @Mock
+    private WrappedReportEntry eventTestsetFinished;
+
+    @Test
+    public void shouldCloneReporter()
+    {
+        SurefireStatelessTestsetInfoReporter extension = new SurefireStatelessTestsetInfoReporter();
+        extension.setDisable( true );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( SurefireStatelessTestsetInfoReporter.class );
+        assertThat( clone.toString() )
+                .isEqualTo( "SurefireStatelessTestsetInfoReporter{disable=true}" );
+        assertThat( ( (SurefireStatelessTestsetInfoReporter) clone ).isDisable() )
+                .isTrue();
+    }
+
+    @Test
+    public void shouldAssertToStringReporter()
+    {
+        SurefireStatelessTestsetInfoReporter extension = new SurefireStatelessTestsetInfoReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "SurefireStatelessTestsetInfoReporter{disable=false}" );
+    }
+
+    @Test
+    public void shouldCreateFileReporterListener()
+    {
+        File target = new File( System.getProperty( "user.dir" ), "target" );
+        File reportsDirectory = new File( target, "surefire-reports" );
+        String reportNameSuffix = "suffix";
+        Charset encoding = StandardCharsets.UTF_8;
+        SurefireStatelessTestsetInfoReporter extension = new SurefireStatelessTestsetInfoReporter();
+
+        assertThat( extension.isDisable() )
+                .isFalse();
+        extension.setDisable( true );
+        assertThat( extension.isDisable() )
+                .isTrue();
+
+        StatelessTestsetInfoFileReportEventListener<WrappedReportEntry, TestSetStats> listener =
+                extension.createListener( reportsDirectory, reportNameSuffix, encoding );
+        assertThat( listener )
+                .isInstanceOf( FileReporter.class );
+        assertThat( listener.getReportsDirectory() )
+                .isSameAs( reportsDirectory );
+        assertThat( listener.getReportNameSuffix() )
+                .isSameAs( reportNameSuffix );
+        assertThat( listener.getEncoding() )
+                .isSameAs( encoding );
+        assertThat( getInternalState( listener, "usePhrasedFileName" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "usePhrasedClassNameInRunning" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "usePhrasedClassNameInTestCaseSummary" ) )
+                .isEqualTo( false );
+    }
+
+    @Test
+    public void shouldCreateConsoleReporterListener()
+    {
+        SurefireStatelessTestsetInfoReporter extension = new SurefireStatelessTestsetInfoReporter();
+
+        assertThat( extension.isDisable() )
+                .isFalse();
+        extension.setDisable( true );
+        assertThat( extension.isDisable() )
+                .isTrue();
+
+        StatelessTestsetInfoConsoleReportEventListener<WrappedReportEntry, TestSetStats> listener =
+                extension.createListener( consoleLogger );
+        assertThat( listener )
+                .isInstanceOf( ConsoleReporter.class );
+        assertThat( listener.getConsoleLogger() )
+                .isSameAs( consoleLogger );
+        assertThat( getInternalState( listener, "usePhrasedClassNameInRunning" ) )
+                .isEqualTo( false );
+        assertThat( getInternalState( listener, "usePhrasedClassNameInTestCaseSummary" ) )
+                .isEqualTo( false );
+    }
+
+    @Test
+    public void shouldCloneJUnit5Reporter()
+    {
+        JUnit5StatelessTestsetInfoReporter extension = new JUnit5StatelessTestsetInfoReporter();
+        extension.setDisable( true );
+        extension.setUsePhrasedFileName( true );
+        extension.setUsePhrasedClassNameInTestCaseSummary( true );
+        extension.setUsePhrasedClassNameInRunning( true );
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        Object clone = extension.clone( classLoader );
+        assertThat( clone )
+                .isNotSameAs( extension );
+        assertThat( clone )
+                .isInstanceOf( JUnit5StatelessTestsetInfoReporter.class );
+        assertThat( clone.toString() )
+                .isEqualTo( "JUnit5StatelessTestsetInfoReporter{disable=true, usePhrasedFileName=true, "
+                        + "usePhrasedClassNameInRunning=true, usePhrasedClassNameInTestCaseSummary=true}" );
+        assertThat( ( (JUnit5StatelessTestsetInfoReporter) clone ).isDisable() )
+                .isTrue();
+        assertThat( ( (JUnit5StatelessTestsetInfoReporter) clone ).isUsePhrasedFileName() )
+                .isTrue();
+        assertThat( ( (JUnit5StatelessTestsetInfoReporter) clone ).isUsePhrasedClassNameInTestCaseSummary() )
+                .isTrue();
+        assertThat( ( (JUnit5StatelessTestsetInfoReporter) clone ).isUsePhrasedClassNameInRunning() )
+                .isTrue();
+    }
+
+    @Test
+    public void shouldAssertToStringJUnit5Reporter()
+    {
+        JUnit5StatelessTestsetInfoReporter extension = new JUnit5StatelessTestsetInfoReporter();
+        assertThat( extension.toString() )
+                .isEqualTo( "JUnit5StatelessTestsetInfoReporter{disable=false, usePhrasedFileName=false, "
+                                + "usePhrasedClassNameInRunning=false, usePhrasedClassNameInTestCaseSummary=false}" );
+    }
+
+    @Test
+    public void shouldReportTestsetLifecycle()
+    {
+        ConsoleReporter consoleReporter = new ConsoleReporter( consoleLogger, false, false );
+        MessageUtils.setColorEnabled( false );
+
+        when( eventTestsetStarting.getNameWithGroup() ).thenReturn( "pkg.MyTest" );
+        when( eventTestsetFinished.getNameWithGroup() ).thenReturn( "pkg.MyTest" );
+        when( eventTestsetFinished.getElapsedTimeVerbose() ).thenReturn( "Time elapsed: 1.03 s" );
+
+        consoleReporter.testSetStarting( eventTestsetStarting );
+        ArgumentCaptor<String> logs = ArgumentCaptor.forClass( String.class );
+        verify( consoleLogger, times( 1 ) ).info( logs.capture() );
+        verifyNoMoreInteractions( consoleLogger );
+        assertThat( logs.getAllValues() )
+                .hasSize( 1 )
+                .contains( "Running pkg.MyTest" );
+
+        TestSetStats testSetStats = new TestSetStats( false, true );
+        testSetStats.testStart();
+        testSetStats.testFailure( eventTestsetFinished );
+        assertThat( testSetStats.getCompletedCount() ).isEqualTo( 1 );
+        assertThat( testSetStats.getFailures() ).isEqualTo( 1 );
+        assertThat( testSetStats.getErrors() ).isEqualTo( 0 );
+        assertThat( testSetStats.getSkipped() ).isEqualTo( 0 );
+        reset( consoleLogger );
+        consoleReporter.testSetCompleted( eventTestsetFinished, testSetStats, singletonList( "pkg.MyTest failed" ) );
+        consoleReporter.reset();
+        verify( consoleLogger, never() ).info( anyString() );
+        verify( consoleLogger, never() ).warning( anyString() );
+        logs = ArgumentCaptor.forClass( String.class );
+        verify( consoleLogger, times( 2 ) ).error( logs.capture() );
+        List<String> messages = logs.getAllValues();
+        assertThat( messages )
+                .hasSize( 2 )
+                .containsSequence(
+                        "Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.03 s "
+                        + "<<< FAILURE! - in pkg.MyTest", "pkg.MyTest failed" );
+        verifyNoMoreInteractions( consoleLogger );
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/ConsoleOutputFileReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/ConsoleOutputFileReporterTest.java
index ee086f5..c74e5b7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/ConsoleOutputFileReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/ConsoleOutputFileReporterTest.java
@@ -45,8 +45,9 @@ public class ConsoleOutputFileReporterTest
         File reportDir = new File( new File( System.getProperty( "user.dir" ), "target" ), "tmp1" );
         //noinspection ResultOfMethodCallIgnored
         reportDir.mkdirs();
-        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), getClass().getName() );
-        ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, null, null );
+        TestSetReportEntry reportEntry =
+                new SimpleReportEntry( getClass().getName(), null, getClass().getName(), null );
+        ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, null, false, null, "UTF-8" );
         reporter.testSetStarting( reportEntry );
         reporter.writeTestOutput( "some ", false, true );
         reporter.testSetCompleted( reportEntry );
@@ -70,13 +71,14 @@ public class ConsoleOutputFileReporterTest
     public void testFileNameWithSuffix() throws IOException
     {
         File reportDir = new File( new File( System.getProperty( "user.dir" ), "target" ), "tmp2" );
-        //noinspection ResultOfMethodCallIgnored
-        reportDir.mkdirs();
         String suffixText = "sampleSuffixText";
-        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), getClass().getName() );
-        ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, suffixText, null );
+        TestSetReportEntry reportEntry =
+                new SimpleReportEntry( getClass().getName(), null, getClass().getName(), null );
+        ConsoleOutputFileReporter reporter =
+                new ConsoleOutputFileReporter( reportDir, suffixText, false, null, "UTF-8" );
         reporter.testSetStarting( reportEntry );
-        reporter.writeTestOutput( "some ", false, true );
+        reporter.writeTestOutput( null, true, true );
+        reporter.writeTestOutput( "some ", true, true );
         reporter.testSetCompleted( reportEntry );
         reporter.close();
 
@@ -88,6 +90,9 @@ public class ConsoleOutputFileReporterTest
         assertThat( FileUtils.fileRead( expectedReportFile, US_ASCII.name() ) )
                 .contains( "some " );
 
+        assertThat( expectedReportFile )
+                .hasSize( 9 + 2 * System.lineSeparator().length() );
+
         //noinspection ResultOfMethodCallIgnored
         expectedReportFile.delete();
     }
@@ -95,11 +100,9 @@ public class ConsoleOutputFileReporterTest
     public void testNullReportFile() throws IOException
     {
         File reportDir = new File( new File( System.getProperty( "user.dir" ), "target" ), "tmp3" );
-        //noinspection ResultOfMethodCallIgnored
-        reportDir.mkdirs();
-        ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, null, null );
+        ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, null, false, null, "UTF-8" );
         reporter.writeTestOutput( "some text", false, true );
-        reporter.testSetCompleted( new SimpleReportEntry( getClass().getName(), getClass().getName() ) );
+        reporter.testSetCompleted( new SimpleReportEntry( getClass().getName(), null, getClass().getName(), null ) );
         reporter.close();
 
         File expectedReportFile = new File( reportDir, "null-output.txt" );
@@ -117,10 +120,9 @@ public class ConsoleOutputFileReporterTest
     public void testConcurrentAccessReportFile() throws Exception
     {
         File reportDir = new File( new File( System.getProperty( "user.dir" ), "target" ), "tmp4" );
-        //noinspection ResultOfMethodCallIgnored
-        reportDir.mkdirs();
-        final ConsoleOutputFileReporter reporter = new ConsoleOutputFileReporter( reportDir, null, null );
-        reporter.testSetStarting( new SimpleReportEntry( getClass().getName(), getClass().getName() ) );
+        final ConsoleOutputFileReporter reporter =
+                new ConsoleOutputFileReporter( reportDir, null, false, null, "UTF-8" );
+        reporter.testSetStarting( new SimpleReportEntry( getClass().getName(), null, getClass().getName(), null ) );
         ExecutorService scheduler = Executors.newFixedThreadPool( 10 );
         final ArrayList<Callable<Void>> jobs = new ArrayList<>();
         for ( int i = 0; i < 10; i++ )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
index b733e2a..0b9d84b 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
@@ -42,10 +42,10 @@ public class FileReporterTest
     public void testFileNameWithoutSuffix()
     {
         File reportDir = new File( "target" );
-        reportEntry = new SimpleReportEntry( this.getClass().getName(), testName );
+        reportEntry = new SimpleReportEntry( getClass().getName(), null, testName, null );
         WrappedReportEntry wrappedReportEntry =
             new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null );
-        reporter = new FileReporter( reportDir, null, Charset.defaultCharset() );
+        reporter = new FileReporter( reportDir, null, Charset.defaultCharset(), false, false, false );
         reporter.testSetCompleted( wrappedReportEntry, createTestSetStats(), new ArrayList<String>() );
 
         File expectedReportFile = new File( reportDir, testName + ".txt" );
@@ -64,10 +64,10 @@ public class FileReporterTest
     {
         File reportDir = new File( "target" );
         String suffixText = "sampleSuffixText";
-        reportEntry = new SimpleReportEntry( this.getClass().getName(), testName );
+        reportEntry = new SimpleReportEntry( getClass().getName(), null, testName, null );
         WrappedReportEntry wrappedReportEntry =
             new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null );
-        reporter = new FileReporter( reportDir, suffixText, Charset.defaultCharset() );
+        reporter = new FileReporter( reportDir, suffixText, Charset.defaultCharset(), false, false, false );
         reporter.testSetCompleted( wrappedReportEntry, createTestSetStats(), new ArrayList<String>() );
 
         File expectedReportFile = new File( reportDir, testName + "-" + suffixText + ".txt" );
diff --git a/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml
index ac34a2b..c1c25cf 100644
--- a/maven-surefire-plugin/pom.xml
+++ b/maven-surefire-plugin/pom.xml
@@ -86,7 +86,25 @@
                 </executions>
             </plugin>
             <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+                </configuration>
                 <dependencies>
                     <dependency>
                         <groupId>org.apache.maven.surefire</groupId>
diff --git a/maven-surefire-plugin/src/site/apt/examples/junit-platform.apt.vm b/maven-surefire-plugin/src/site/apt/examples/junit-platform.apt.vm
index 1a11987..2a86f2a 100644
--- a/maven-surefire-plugin/src/site/apt/examples/junit-platform.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/junit-platform.apt.vm
@@ -309,3 +309,56 @@ else
 </build>
 ...
 +---+
+
+
+* Surefire Extensions and Reports Configuration for @DisplayName
+
+   Since plugin version 3.0.0-M4 you can use fine grained configuration of reports and enable phrased names together
+   with <<<...@DisplayName>>> in you tests.
+   This is the complete list of attributes of particular objects. You do not have to specify e.g. <<<disable>>>,
+   <<<version>>> and <<<encoding>>>. The boolean values reach default values <<<false>>> if not specified otherwise.
+
++---+
+...
+<build>
+    <plugins>
+        ...
+        <plugin>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>${project.artifactId}</artifactId>
+            <version>${project.version}</version>
+            <configuration>
+                <statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
+                    <disable>false</disable>
+                    <version>3.0</version>
+                    <usePhrasedFileName>false</usePhrasedFileName>
+                    <usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
+                    <usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
+                    <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
+                </statelessTestsetReporter>
+                <consoleOutputReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter">
+                    <disable>false</disable>
+                    <encoding>UTF-8</encoding>
+                    <usePhrasedFileName>false</usePhrasedFileName>
+                </consoleOutputReporter>
+                <statelessTestsetInfoReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoReporter">
+                    <disable>false</disable>
+                    <usePhrasedFileName>false</usePhrasedFileName>
+                    <usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
+                    <usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
+                </statelessTestsetInfoReporter>
+            </configuration>
+        </plugin>
+    </plugins>
+</build>
+...
++---+
+
+   Default implementations of these extensions are
+   <<<org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter>>>,
+   <<<org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter>>>, and
+   <<<org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter>>>.
+
+   The aim of extensions is to let the users customizing the default behavior. We are keen on listing useful
+   extensions on Apache Maven Surefire site if you propagate your extensions on GitHub.
+
diff --git a/maven-surefire-plugin/src/site/apt/featurematrix.apt.vm b/maven-surefire-plugin/src/site/apt/featurematrix.apt.vm
index d4024a2..521895a 100644
--- a/maven-surefire-plugin/src/site/apt/featurematrix.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/featurematrix.apt.vm
@@ -40,6 +40,14 @@ Feature Matrix
 *---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
 | parallel support                            |     N      |    N     |      Y     |    Y      |  N       |  N                   |
 *---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
+| custom run-listener                         |     N      |    Y     |      Y     |    Y      |  -       |  N                   |
+*---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
+| re-run count                                |     N      |    Y     |      Y     |    N      |  N       |  N                   |
+*---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
+| skip after failure count                    |     N      |    Y     |      Y     |    Y      |  N       |  N                   |
+*---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
+| Surefire Extensions                         |     Y      |    Y     |      Y     |    Y      |  Y       |  Y(*2)               |
+*---------------------------------------------+------------+----------+------------+-----------+----------+----------------------+
 
 
     Legend: "Y" means supported, "N" means not supported. "?" means not tested.
@@ -50,4 +58,7 @@ Feature Matrix
 
    (*1) The JUnit 5 Platform supports running multiple individual test methods in a single class, but there are some
    corner cases that are not supported, yet: {{{https://github.com/junit-team/junit5/issues/1343}junit-team/junit5#1343}}
-   and {{{https://github.com/junit-team/junit5/issues/1406}junit-team/junit5#1406)}}.
\ No newline at end of file
+   and {{{https://github.com/junit-team/junit5/issues/1406}junit-team/junit5#1406)}}.
+
+   (*2) 3 extensions related to JUnit5 annotation <<<DisplayName>>>.
+
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 4cbbf00..6d7b4a3 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
@@ -18,99 +18,70 @@ package org.apache.maven.plugin.surefire;
  * under the License.
  */
 
-
-import java.lang.reflect.Field;
-import org.apache.maven.toolchain.Toolchain;
-
 import junit.framework.TestCase;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.surefire.suite.RunResult;
 
-public class SurefirePluginTest
-    extends TestCase
-{
-
-    public void testForkMode()
-        throws NoSuchFieldException, IllegalAccessException
-    {
-        SurefirePlugin surefirePlugin = new SurefirePlugin();
-        setFieldValue( surefirePlugin, "toolchain", new MyToolChain() );
-        setFieldValue( surefirePlugin, "forkMode", "never" );
-        assertEquals( "once", surefirePlugin.getEffectiveForkMode() );
-    }
+import static org.fest.assertions.Assertions.assertThat;
 
-    public void testForkCountComputation()
+public class SurefirePluginTest extends TestCase
+{
+    public void testDefaultIncludes()
     {
-        SurefirePlugin surefirePlugin = new SurefirePlugin();
-        assertConversionFails( surefirePlugin, "nothing" );
-
-        assertConversionFails( surefirePlugin, "5,0" );
-        assertConversionFails( surefirePlugin, "5.0" );
-        assertConversionFails( surefirePlugin, "5,0C" );
-        assertConversionFails( surefirePlugin, "5.0CC" );
-
-        assertForkCount( surefirePlugin, 5, "5" );
-
-        int availableProcessors = Runtime.getRuntime().availableProcessors();
-        assertForkCount( surefirePlugin, 3*availableProcessors, "3C" );
-        assertForkCount( surefirePlugin, (int) ( 2.5*availableProcessors ), "2.5C" );
-        assertForkCount( surefirePlugin, availableProcessors, "1.0001 C" );
-        assertForkCount( surefirePlugin, 1, 1d / ( (double) availableProcessors + 1 ) + "C" );
-        assertForkCount( surefirePlugin, 0, "0 C" );
+        assertThat( new SurefirePlugin().getDefaultIncludes() )
+                .containsOnly( "**/Test*.java", "**/*Test.java", "**/*Tests.java", "**/*TestCase.java" );
     }
 
-    private void assertForkCount( SurefirePlugin surefirePlugin, int expected, String value )
+    public void testReportSchemaLocation()
     {
-        assertEquals( expected, surefirePlugin.convertWithCoreCount( value ));
+        assertThat( new SurefirePlugin().getReportSchemaLocation() )
+            .isEqualTo( "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd" );
     }
 
-    private void assertConversionFails( SurefirePlugin surefirePlugin, String value )
+    public void testFailIfNoTests() throws Exception
     {
-        try {
-            surefirePlugin.convertWithCoreCount( value );
-        } catch (NumberFormatException nfe)
+        RunResult runResult = new RunResult( 0, 0, 0, 0 );
+        try
         {
-            return;
+            SurefirePlugin plugin = new SurefirePlugin();
+            plugin.setFailIfNoTests( true );
+            plugin.handleSummary( runResult, null );
         }
-        fail( "Expected NumberFormatException when converting " + value );
-    }
-
-    private void setFieldValue( SurefirePlugin plugin, String fieldName, Object value )
-        throws NoSuchFieldException, IllegalAccessException
-    {
-        Field field = findField( plugin.getClass(), fieldName );
-        field.setAccessible( true );
-        field.set( plugin, value );
-
-    }
-
-    private Field findField( Class clazz, String fieldName )
-    {
-        while ( clazz != null )
+        catch ( MojoFailureException e )
         {
-            try
-            {
-                return clazz.getDeclaredField( fieldName );
-            }
-            catch ( NoSuchFieldException e )
-            {
-                clazz = clazz.getSuperclass();
-            }
+            assertThat( e.getLocalizedMessage() )
+                    .isEqualTo( "No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)" );
+            return;
         }
-        throw new IllegalArgumentException( "Field not found" );
+        fail( "Expected MojoFailureException with message "
+                + "'No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)'" );
     }
 
-    private class MyToolChain
-        implements Toolchain
+    public void testTestFailure() throws Exception
     {
-        @Override
-        public String getType()
+        RunResult runResult = new RunResult( 1, 0, 1, 0 );
+        try
         {
-            return null;
+            SurefirePlugin plugin = new SurefirePlugin();
+            plugin.handleSummary( runResult, null );
         }
-
-        @Override
-        public String findTool( String s )
+        catch ( MojoFailureException e )
         {
-            return null;
+            assertThat( e.getLocalizedMessage() )
+                    .isEqualTo( "There are test failures.\n\nPlease refer to null "
+                            + "for the individual test results.\nPlease refer to dump files (if any exist) "
+                            + "[date].dump, [date]-jvmRun[N].dump and [date].dumpstream." );
+            return;
         }
+        fail( "Expected MojoFailureException with message "
+                + "'There are test failures.\n\nPlease refer to null "
+                + "for the individual test results.\nPlease refer to dump files (if any exist) "
+                + "[date].dump, [date]-jvmRun[N].dump and [date].dumpstream.'");
+    }
+
+    public void testPluginName()
+    {
+        assertThat( new SurefirePlugin().getPluginName() )
+                .isEqualTo( "surefire" );
     }
 }
diff --git a/maven-surefire-report-plugin/pom.xml b/maven-surefire-report-plugin/pom.xml
index cf941ce..49eeb5f 100644
--- a/maven-surefire-report-plugin/pom.xml
+++ b/maven-surefire-report-plugin/pom.xml
@@ -179,8 +179,27 @@
                 </executions>
             </plugin>
             <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+                    <includes>
+                        <include>**/JUnit4SuiteTest.java</include>
+                    </includes>
                     <classpathDependencyExcludes>
                         <classpathDependencyExclude>org.fusesource.jansi:jansi</classpathDependencyExclude>
                     </classpathDependencyExcludes>
diff --git a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
similarity index 70%
copy from surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
copy to maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
index 142ca9d..5ab3ee1 100644
--- a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
+++ b/maven-surefire-report-plugin/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
@@ -21,26 +21,24 @@ package org.apache.maven.plugins.surefire.report;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
  *
  * @author Tibor Digana (tibor17)
- * @since 2.21.0
+ * @since 3.0.0-M4
  */
-@Suite.SuiteClasses( {
-        ReportTestCaseTest.class,
-        ReportTestSuiteTest.class,
-        SurefireReportParserTest.class,
-        TestSuiteXmlParserTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTest( new JUnit4TestAdapter( Surefire597Test.class ) );
+        suite.addTest( new JUnit4TestAdapter( SurefireSchemaValidationTest.class ) );
+        suite.addTestSuite( Surefire1183Test.class );
+        suite.addTestSuite(  SurefireReportMojoTest.class);
+        return suite;
     }
 }
diff --git a/pom.xml b/pom.xml
index e30a333..8469f31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@
   <modules>
     <module>surefire-logger-api</module>
     <module>surefire-api</module>
+    <module>surefire-extensions-api</module>
     <module>surefire-booter</module>
     <module>surefire-grouper</module>
     <module>surefire-providers</module>
@@ -93,7 +94,8 @@
     <doxiaSitetoolsVersion>1.8.1</doxiaSitetoolsVersion>
     <!-- maven-shared-utils:3.2.0+ another behavior - broke Surefire performance - end of subprocess notification not arrived in ForkStarter -->
     <mavenSharedUtilsVersion>3.1.0</mavenSharedUtilsVersion>
-    <powermockVersion>2.0.0-RC.1</powermockVersion>
+    <powermockVersion>2.0.2</powermockVersion>
+    <jacocoVersion>0.8.4</jacocoVersion>
     <maven.surefire.scm.devConnection>scm:git:https://gitbox.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 -->
@@ -330,7 +332,7 @@
       <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
-        <version>2.21.0</version>
+        <version>2.27.0</version>
         <exclusions>
           <exclusion>
             <groupId>org.hamcrest</groupId>
@@ -357,6 +359,17 @@
         <artifactId>powermock-api-mockito2</artifactId>
         <version>${powermockVersion}</version>
       </dependency>
+      <dependency>
+        <groupId>org.powermock</groupId>
+        <artifactId>powermock-reflect</artifactId>
+        <version>${powermockVersion}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.javassist</groupId>
+        <artifactId>javassist</artifactId>
+        <version>3.22.0-GA</version>
+      </dependency>
       <!-- END: PowerMock@Java9 -->
       <dependency>
         <groupId>junit</groupId>
@@ -377,12 +390,19 @@
         <groupId>org.assertj</groupId>
         <artifactId>assertj-core</artifactId>
         <version>3.9.1</version>
+<!--        <version>3.12.2</version>-->
       </dependency>
       <dependency>
         <groupId>com.google.code.findbugs</groupId>
         <artifactId>jsr305</artifactId>
         <version>2.0.3</version>
       </dependency>
+      <dependency>
+        <groupId>org.jacoco</groupId>
+        <artifactId>org.jacoco.agent</artifactId>
+        <classifier>runtime</classifier>
+        <version>${jacocoVersion}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <dependencies>
@@ -464,6 +484,7 @@
             <compilerArgs>
               <arg>-Xdoclint:all</arg>
             </compilerArgs>
+            <encoding>UTF-8</encoding>
           </configuration>
         </plugin>
         <!-- NOTE: animal sniffer does not check test classes: https://jira.codehaus.org/browse/MANIMALSNIFFER-40 -->
@@ -471,21 +492,6 @@
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>animal-sniffer-maven-plugin</artifactId>
           <version>1.17</version>
-          <executions>
-            <execution>
-              <id>signature-check</id>
-              <goals>
-                <goal>check</goal>
-              </goals>
-              <configuration>
-                <signature>
-                  <groupId>org.codehaus.mojo.signature</groupId>
-                  <artifactId>java17</artifactId>
-                  <version>1.0</version>
-                </signature>
-              </configuration>
-            </execution>
-          </executions>
         </plugin>
         <plugin>
           <artifactId>maven-surefire-plugin</artifactId>
@@ -493,7 +499,7 @@
           <configuration>
             <!-- NOTE: Be sure to isolate the Surefire version under test from the version running the tests! -->
             <useSystemClassLoader>false</useSystemClassLoader>
-            <argLine>${jvm.args.tests} ${jacoco.agent} -Dnet.bytebuddy.experimental=true</argLine>
+            <argLine>${jvm.args.tests}</argLine><!-- -Dnet.bytebuddy.experimental=true ${jacoco.agent}-->
             <useFile>false</useFile>
             <redirectTestOutputToFile>false</redirectTestOutputToFile>
             <jvm>${jdk.home}/bin/java</jvm>
@@ -519,35 +525,65 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.8.3</version>
+          <version>${jacocoVersion}</version>
+          <configuration>
+            <!--<append>true</append>
+            <inclNoLocationClasses>true</inclNoLocationClasses>
+            <haltOnFailure>false</haltOnFailure>
+            <jmx>false</jmx>-->
+            <includes>
+              <include>**/failsafe/*</include>
+              <include>**/failsafe/**/*</include>
+              <include>**/surefire/*</include>
+              <include>**/surefire/**/*</include>
+            </includes>
+            <excludes>
+              <exclude>**/HelpMojo.class</exclude>
+              <exclude>**/shadefire/**/*</exclude>
+              <exclude>org/jacoco/**/*</exclude>
+              <exclude>com/vladium/emma/rt/*</exclude>
+            </excludes>
+          </configuration>
         </plugin>
       </plugins>
     </pluginManagement>
     <plugins>
       <plugin>
-        <groupId>org.jacoco</groupId>
-        <artifactId>jacoco-maven-plugin</artifactId>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
         <executions>
           <execution>
-            <id>jacoco-agent</id>
+            <id>rat-check</id>
             <goals>
-              <goal>prepare-agent</goal>
+              <goal>check</goal>
             </goals>
+            <configuration>
+              <excludes combine.children="append">
+                <exclude>Jenkinsfile</exclude>
+                <exclude>README.md</exclude>
+                <exclude>.gitignore</exclude>
+                <exclude>.git/**/*</exclude>
+                <exclude>**/.idea</exclude>
+                <exclude>**/.svn/**/*</exclude>
+                <exclude>**/*.iml</exclude>
+                <exclude>**/*.ipr</exclude>
+                <exclude>**/*.iws</exclude>
+                <exclude>**/*.versionsBackup</exclude>
+                <exclude>**/dependency-reduced-pom.xml</exclude>
+                <exclude>.repository/**</exclude> <!-- jenkins with local maven repository -->
+                <exclude>src/test/resources/**/*</exclude>
+                <exclude>src/test/resources/**/*.css</exclude>
+                <exclude>**/*.jj</exclude>
+                <exclude>src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider
+                </exclude>
+                <exclude>DEPENDENCIES</exclude>
+                <exclude>.m2/**</exclude>
+                <exclude>.m2</exclude>
+                <exclude>.travis.yml</exclude>
+              </excludes>
+            </configuration>
           </execution>
         </executions>
-        <configuration>
-          <propertyName>jacoco.agent</propertyName>
-          <append>true</append>
-          <inclNoLocationClasses>true</inclNoLocationClasses>
-          <haltOnFailure>false</haltOnFailure>
-          <jmx>false</jmx>
-          <includes>
-            <include>**/failsafe/*</include>
-            <include>**/failsafe/**/*</include>
-            <include>**/surefire/*</include>
-            <include>**/surefire/**/*</include>
-          </includes>
-        </configuration>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -606,45 +642,27 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>animal-sniffer-maven-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.rat</groupId>
-        <artifactId>apache-rat-plugin</artifactId>
         <executions>
           <execution>
-            <id>rat-check</id>
+            <id>signature-check</id>
             <goals>
               <goal>check</goal>
             </goals>
             <configuration>
-              <excludes combine.children="append">
-                <exclude>Jenkinsfile</exclude>
-                <exclude>README.md</exclude>
-                <exclude>.gitignore</exclude>
-                <exclude>.git/**/*</exclude>
-                <exclude>**/.idea</exclude>
-                <exclude>**/.svn/**/*</exclude>
-                <exclude>**/*.iml</exclude>
-                <exclude>**/*.ipr</exclude>
-                <exclude>**/*.iws</exclude>
-                <exclude>**/*.versionsBackup</exclude>
-                <exclude>**/dependency-reduced-pom.xml</exclude>
-                <exclude>.repository/**</exclude> <!-- jenkins with local maven repository -->
-                <exclude>src/test/resources/**/*</exclude>
-                <exclude>src/test/resources/**/*.css</exclude>
-                <exclude>**/*.jj</exclude>
-                <exclude>src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider
-                </exclude>
-                <exclude>DEPENDENCIES</exclude>
-                <exclude>.m2/**</exclude>
-                <exclude>.m2</exclude>
-                <exclude>.travis.yml</exclude>
-              </excludes>
+              <signature>
+                <groupId>org.codehaus.mojo.signature</groupId>
+                <artifactId>java17</artifactId>
+                <version>1.0</version>
+              </signature>
             </configuration>
           </execution>
         </executions>
       </plugin>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
         <artifactId>maven-deploy-plugin</artifactId>
         <configuration>
           <deployAtEnd>true</deployAtEnd>
diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml
index c42041b..65ca886 100644
--- a/surefire-api/pom.xml
+++ b/surefire-api/pom.xml
@@ -60,8 +60,24 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
index 9a46fca..f66e137 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkedChannelEncoder.java
@@ -328,8 +328,12 @@ public final class ForkedChannelEncoder
                 .append( ':' )
                 .append( toBase64( reportEntry.getSourceName() ) )
                 .append( ':' )
+                .append( toBase64( reportEntry.getSourceText() ) )
+                .append( ':' )
                 .append( toBase64( reportEntry.getName() ) )
                 .append( ':' )
+                .append( toBase64( reportEntry.getNameText() ) )
+                .append( ':' )
                 .append( toBase64( reportEntry.getGroup() ) )
                 .append( ':' )
                 .append( toBase64( reportEntry.getMessage() ) )
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 114bb85..c35d6d5 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
@@ -21,6 +21,7 @@ package org.apache.maven.surefire.report;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * @author Kristian Rosenvold
@@ -42,30 +43,35 @@ public class CategorizedReportEntry
     public CategorizedReportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
                                    Integer elapsed )
     {
-        super( source, name, stackTraceWriter, elapsed );
+        super( source, null, name, null, stackTraceWriter, elapsed );
         this.group = group;
     }
 
     public CategorizedReportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
                                    Integer elapsed, String message )
     {
-        this( source, name, group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+        this( source, null, name, null,
+                group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
     }
 
-    public CategorizedReportEntry( String source, String name, String group, StackTraceWriter stackTraceWriter,
+    public CategorizedReportEntry( String source, String sourceText, String name, String nameText,
+                                   String group, StackTraceWriter stackTraceWriter,
                                    Integer elapsed, String message, Map<String, String> systemProperties )
     {
-        super( source, name, stackTraceWriter, elapsed, message, systemProperties );
+        super( source, sourceText, name, nameText, stackTraceWriter, elapsed, message, systemProperties );
         this.group = group;
     }
 
-    public static TestSetReportEntry reportEntry( String source, String name, String group,
+    public static TestSetReportEntry reportEntry( String source, String sourceText, String name, String nameText,
+                                                  String group,
                                                   StackTraceWriter stackTraceWriter, Integer elapsed, String message,
                                                   Map<String, String> systemProperties )
     {
         return group != null
-            ? new CategorizedReportEntry( source, name, group, stackTraceWriter, elapsed, message, systemProperties )
-            : new SimpleReportEntry( source, name, stackTraceWriter, elapsed, message, systemProperties );
+            ? new CategorizedReportEntry( source, sourceText, name, nameText,
+                group, stackTraceWriter, elapsed, message, systemProperties )
+            : new SimpleReportEntry( source, sourceText, name, nameText,
+                stackTraceWriter, elapsed, message, systemProperties );
     }
 
     @Override
@@ -81,6 +87,12 @@ public class CategorizedReportEntry
     }
 
     @Override
+    public String getReportNameWithGroup()
+    {
+        return isNameWithGroup() ? getSourceText() + GROUP_PREFIX + getGroup() + GROUP_SUFIX : getSourceText();
+    }
+
+    @Override
     public boolean equals( Object o )
     {
         if ( this == o )
@@ -98,8 +110,7 @@ public class CategorizedReportEntry
 
         CategorizedReportEntry that = (CategorizedReportEntry) o;
 
-        return !( group != null ? !group.equals( that.group ) : that.group != null );
-
+        return Objects.equals( group, that.group );
     }
 
     @Override
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 6e4a04a..6bc9618 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
@@ -33,6 +33,13 @@ public interface ReportEntry
     String getSourceName();
 
     /**
+     * Human readable {@link #getSourceName() test class}.
+     *
+     * @return source text
+     */
+    String getSourceText();
+
+    /**
      * The name of the test case
      *
      * @return A string describing the test case
@@ -40,6 +47,13 @@ public interface ReportEntry
     String getName();
 
     /**
+     * Human readable {@link #getName() test case}.
+     *
+     * @return name text
+     */
+    String getNameText();
+
+    /**
      * The group/category of the testcase
      *
      * @return A string
@@ -84,4 +98,11 @@ public interface ReportEntry
      * @return A string with the test case name and group/category, or just the name.
      */
     String getNameWithGroup();
+
+    /**
+     * A source text of the test case together with the group or category (if any exists).
+     *
+     * @return A string with the test case text and group/category, or just the source text.
+     */
+    String getReportNameWithGroup();
 }
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 73e5f3b..df7b4c4 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
@@ -35,79 +35,88 @@ public class SimpleReportEntry
 
     private final String source;
 
+    private final String sourceText;
+
     private final String name;
 
+    private final String nameText;
+
     private final StackTraceWriter stackTraceWriter;
 
     private final Integer elapsed;
 
     private final String message;
 
-    public SimpleReportEntry()
-    {
-        this( null, null );
-    }
-
-    public SimpleReportEntry( String source, String name )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText )
     {
-        this( source, name, null, null );
+        this( source, sourceText, name, nameText, null, null );
     }
 
-    public SimpleReportEntry( String source, String name, Map<String, String> systemProperties )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText,
+                              Map<String, String> systemProperties )
     {
-        this( source, name, null, null, systemProperties );
+        this( source, sourceText, name, nameText, null, null, systemProperties );
     }
 
-    private SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter )
+    private SimpleReportEntry( String source, String sourceText, String name, String nameText,
+                               StackTraceWriter stackTraceWriter )
     {
-        this( source, name, stackTraceWriter, null );
+        this( source, sourceText, name, nameText, stackTraceWriter, null );
     }
 
-    public SimpleReportEntry( String source, String name, Integer elapsed )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText, Integer elapsed )
     {
-        this( source, name, null, elapsed );
+        this( source, sourceText, name, nameText, null, elapsed );
     }
 
-    public SimpleReportEntry( String source, String name, String message )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText, String message )
     {
-        this( source, name, null, null, message, Collections.<String, String>emptyMap() );
+        this( source, sourceText, name, nameText, null, null, message, Collections.<String, String>emptyMap() );
     }
 
-    protected SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed,
-                                 String message, Map<String, String> systemProperties )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText,
+                                 StackTraceWriter stackTraceWriter, Integer elapsed, String message,
+                                 Map<String, String> systemProperties )
     {
         this.source = source;
+        this.sourceText = sourceText;
         this.name = name;
+        this.nameText = nameText;
         this.stackTraceWriter = stackTraceWriter;
         this.message = message;
         this.elapsed = elapsed;
         this.systemProperties = new ImmutableMap<>( systemProperties );
     }
 
-    public SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText,
+                              StackTraceWriter stackTraceWriter, Integer elapsed )
     {
-        this( source, name, stackTraceWriter, elapsed, Collections.<String, String>emptyMap() );
+        this( source, sourceText, name, nameText, stackTraceWriter, elapsed, Collections.<String, String>emptyMap() );
     }
 
-    public SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed,
-                              Map<String, String> systemProperties )
+    public SimpleReportEntry( String source, String sourceText, String name, String nameText,
+                              StackTraceWriter stackTraceWriter, Integer elapsed, Map<String, String> systemProperties )
     {
-        this( source, name, stackTraceWriter, elapsed, safeGetMessage( stackTraceWriter ), systemProperties );
+        this( source, sourceText, name, nameText,
+                stackTraceWriter, elapsed, safeGetMessage( stackTraceWriter ), systemProperties );
     }
 
-    public static SimpleReportEntry assumption( String source, String name, String message )
+    public static SimpleReportEntry assumption( String source, String sourceText, String name, String nameText,
+                                                String message )
     {
-        return new SimpleReportEntry( source, name, message );
+        return new SimpleReportEntry( source, sourceText, name, nameText, message );
     }
 
-    public static SimpleReportEntry ignored( String source, String name, String message )
+    public static SimpleReportEntry ignored( String source, String sourceText, String name, String nameText,
+                                             String message )
     {
-        return new SimpleReportEntry( source, name, message );
+        return new SimpleReportEntry( source, sourceText, name, nameText, message );
     }
 
-    public static SimpleReportEntry withException( String source, String name, StackTraceWriter stackTraceWriter )
+    public static SimpleReportEntry withException( String source, String sourceText, String name, String nameText,
+                                                   StackTraceWriter stackTraceWriter )
     {
-        return new SimpleReportEntry( source, name, stackTraceWriter );
+        return new SimpleReportEntry( source, sourceText, name, nameText, stackTraceWriter );
     }
 
     private static String safeGetMessage( StackTraceWriter stackTraceWriter )
@@ -130,12 +139,24 @@ public class SimpleReportEntry
     }
 
     @Override
+    public String getSourceText()
+    {
+        return sourceText;
+    }
+
+    @Override
     public String getName()
     {
         return name;
     }
 
     @Override
+    public String getNameText()
+    {
+        return nameText;
+    }
+
+    @Override
     public String getGroup()
     {
         return null;
@@ -162,8 +183,9 @@ public class SimpleReportEntry
     @Override
     public String toString()
     {
-        return "ReportEntry{" + "source='" + source + '\'' + ", name='" + name + '\'' + ", stackTraceWriter="
-            + stackTraceWriter + ", elapsed=" + elapsed + ", message=" + message + '}';
+        return "ReportEntry{" + "source='" + source + "', sourceText='" + sourceText
+                + "', name='" + name + "', nameText='" + nameText + "', stackTraceWriter='"
+                + stackTraceWriter + "', elapsed='" + elapsed + "', message='" + message + "'}";
     }
 
     @Override
@@ -185,16 +207,25 @@ public class SimpleReportEntry
         }
 
         SimpleReportEntry that = (SimpleReportEntry) o;
-        return isElapsedTimeEqual( that ) && isNameEqual( that ) && isSourceEqual( that ) && isStackEqual( that );
+        return isSourceEqual( that ) && isSourceTextEqual( that )
+                && isNameEqual( that ) && isNameTextEqual( that )
+                && isStackEqual( that )
+                && isElapsedTimeEqual( that )
+                && isSystemPropertiesEqual( that )
+                && isMessageEqual( that );
     }
 
     @Override
     public int hashCode()
     {
-        int result = Objects.hashCode( source );
-        result = 31 * result + Objects.hashCode( name );
-        result = 31 * result + Objects.hashCode( stackTraceWriter );
-        result = 31 * result + Objects.hashCode( elapsed );
+        int result = Objects.hashCode( getSourceName() );
+        result = 31 * result + Objects.hashCode( getSourceText() );
+        result = 31 * result + Objects.hashCode( getName() );
+        result = 31 * result + Objects.hashCode( getNameText() );
+        result = 31 * result + Objects.hashCode( getStackTraceWriter() );
+        result = 31 * result + Objects.hashCode( getElapsed() );
+        result = 31 * result + Objects.hashCode( getSystemProperties() );
+        result = 31 * result + Objects.hashCode( getMessage() );
         return result;
     }
 
@@ -205,6 +236,12 @@ public class SimpleReportEntry
     }
 
     @Override
+    public String getReportNameWithGroup()
+    {
+        return getSourceText();
+    }
+
+    @Override
     public Map<String, String> getSystemProperties()
     {
         return systemProperties;
@@ -212,21 +249,41 @@ public class SimpleReportEntry
 
     private boolean isElapsedTimeEqual( SimpleReportEntry en )
     {
-        return Objects.equals( elapsed, en.elapsed );
+        return Objects.equals( getElapsed(), en.getElapsed() );
+    }
+
+    private boolean isNameTextEqual( SimpleReportEntry en )
+    {
+        return Objects.equals( getNameText(), en.getNameText() );
     }
 
     private boolean isNameEqual( SimpleReportEntry en )
     {
-        return Objects.equals( name, en.name );
+        return Objects.equals( getName(), en.getName() );
     }
 
     private boolean isSourceEqual( SimpleReportEntry en )
     {
-        return Objects.equals( source, en.source );
+        return Objects.equals( getSourceName(), en.getSourceName() );
+    }
+
+    private boolean isSourceTextEqual( SimpleReportEntry en )
+    {
+        return Objects.equals( getSourceText(), en.getSourceText() );
     }
 
     private boolean isStackEqual( SimpleReportEntry en )
     {
-        return Objects.equals( stackTraceWriter, en.stackTraceWriter );
+        return Objects.equals( getStackTraceWriter(), en.getStackTraceWriter() );
+    }
+
+    private boolean isSystemPropertiesEqual( SimpleReportEntry en )
+    {
+        return Objects.equals( getSystemProperties(), en.getSystemProperties() );
+    }
+
+    private boolean isMessageEqual( SimpleReportEntry en )
+    {
+        return Objects.equals( getMessage(), en.getMessage() );
     }
 }
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 63b30ff..57e9ea7 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
@@ -223,6 +223,12 @@ public final class ReflectionUtils
         }
     }
 
+    public static Class<?> reloadClass( ClassLoader classLoader, Object source )
+            throws ReflectiveOperationException
+    {
+        return classLoader.loadClass( source.getClass().getName() );
+    }
+
     /**
      * Invoker of public static no-argument method.
      *
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 63c4490..38f0c48 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -23,6 +23,7 @@ import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
 import org.apache.maven.plugin.surefire.runorder.ThreadedExecutionSchedulerTest;
 import org.apache.maven.surefire.SpecificTestClassFilterTest;
+import org.apache.maven.surefire.booter.CommandReaderTest;
 import org.apache.maven.surefire.booter.ForkedChannelEncoderTest;
 import org.apache.maven.surefire.booter.ForkingRunListenerTest;
 import org.apache.maven.surefire.booter.MasterProcessCommandTest;
@@ -50,6 +51,7 @@ import org.junit.runners.Suite;
  * @since 2.19
  */
 @Suite.SuiteClasses( {
+    CommandReaderTest.class,
     ThreadedExecutionSchedulerTest.class,
     ForkingRunListenerTest.class,
     MasterProcessCommandTest.class,
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
similarity index 98%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
rename to surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index 00d50b4..5168d2b 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -193,14 +193,14 @@ public class CommandReaderTest
                 Iterator<String> it = reader.getIterableClasses( new ForkedChannelEncoder( nul() ) ).iterator();
                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
                 counter.countDown();
-                assertThat( it.next(), is( PropertiesWrapperTest.class.getName() ) );
+                assertThat( it.next(), is( Foo.class.getName() ) );
             }
         };
         FutureTask<Object> futureTask = new FutureTask<>( runnable, null );
         Thread t = new Thread( futureTask );
         t.start();
         counter.await();
-        addTestToPipeline( PropertiesWrapperTest.class.getName() );
+        addTestToPipeline( Foo.class.getName() );
         try
         {
             futureTask.get();
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
similarity index 100%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/Foo.java
rename to surefire-api/src/test/java/org/apache/maven/surefire/booter/Foo.java
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
index b9708b0..4429f79 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkedChannelEncoderTest.java
@@ -179,43 +179,51 @@ public class ForkedChannelEncoderTest
         StringBuilder encode = encode( "X", "normal-run", reportEntry, false );
         assertThat( encode.toString() )
                 .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
-                                    + encodedSourceName
-                                    + ":"
-                                    + encodedName
-                                    + ":"
-                                    + encodedGroup
-                                    + ":"
-                                    + encodedMessage
-                                    + ":"
-                                    + 102
-                                    + ":"
-
-                                    + encodedExceptionMsg
-                                    + ":"
-                                    + encodedSmartStackTrace
-                                    + ":"
-                                    + encodedStackTrace
+                                + encodedSourceName
+                                + ":"
+                                + "-"
+                                + ":"
+                                + encodedName
+                                + ":"
+                                + "-"
+                                + ":"
+                                + encodedGroup
+                                + ":"
+                                + encodedMessage
+                                + ":"
+                                + 102
+                                + ":"
+
+                                + encodedExceptionMsg
+                                + ":"
+                                + encodedSmartStackTrace
+                                + ":"
+                                + encodedStackTrace
                 );
 
         encode = encode( "X", "normal-run", reportEntry, true );
         assertThat( encode.toString() )
                 .isEqualTo( ":maven:surefire:std:out:X:normal-run:UTF-8:"
-                                    + encodedSourceName
-                                    + ":"
-                                    + encodedName
-                                    + ":"
-                                    + encodedGroup
-                                    + ":"
-                                    + encodedMessage
-                                    + ":"
-                                    + 102
-                                    + ":"
-
-                                    + encodedExceptionMsg
-                                    + ":"
-                                    + encodedSmartStackTrace
-                                    + ":"
-                                    + encodedTrimmedStackTrace
+                                + encodedSourceName
+                                + ":"
+                                + "-"
+                                + ":"
+                                + encodedName
+                                + ":"
+                                + "-"
+                                + ":"
+                                + encodedGroup
+                                + ":"
+                                + encodedMessage
+                                + ":"
+                                + 102
+                                + ":"
+
+                                + encodedExceptionMsg
+                                + ":"
+                                + encodedSmartStackTrace
+                                + ":"
+                                + encodedTrimmedStackTrace
                 );
 
         Stream out = Stream.newStream();
@@ -227,8 +235,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
                                     + encodedSourceName
                                     + ":"
+                                    + "-"
+                                    + ":"
                                     + encodedName
                                     + ":"
+                                    + "-"
+                                    + ":"
                                     + encodedGroup
                                     + ":"
                                     + encodedMessage
@@ -253,8 +265,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:testset-starting:normal-run:UTF-8:"
                                     + encodedSourceName
                                     + ":"
+                                    + "-"
+                                    + ":"
                                     + encodedName
                                     + ":"
+                                    + "-"
+                                    + ":"
                                     + encodedGroup
                                     + ":"
                                     + encodedMessage
@@ -316,8 +332,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:testset-completed:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -379,8 +399,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-starting:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -442,8 +466,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-succeeded:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -505,8 +533,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-failed:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -567,8 +599,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-skipped:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -628,8 +664,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-error:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
@@ -687,8 +727,12 @@ public class ForkedChannelEncoderTest
                 .isEqualTo( ":maven:surefire:std:out:test-assumption-failure:normal-run:UTF-8:"
                         + encodedSourceName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedName
                         + ":"
+                        + "-"
+                        + ":"
                         + encodedGroup
                         + ":"
                         + encodedMessage
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
similarity index 97%
rename from surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
rename to surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
index 2e71dc7..f137598 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
+import org.apache.maven.shared.utils.io.FileUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -45,8 +46,6 @@ import java.util.HashSet;
 import java.util.List;
 
 import static java.io.File.pathSeparator;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.io.FileUtils.readFileToString;
 
 /**
  * JUnit runner testing methods in a separate class loader.
@@ -193,7 +192,7 @@ public class NewClassLoaderRunner
     public static class TestClassLoader
         extends URLClassLoader
     {
-        public TestClassLoader()
+        TestClassLoader()
         {
             super( toClassPath(), null );
         }
@@ -236,7 +235,7 @@ public class NewClassLoaderRunner
             Collection<URL> classPath = new HashSet<>();
             try
             {
-                String[] files = readFileToString( new File( "target/test-classpath/cp.txt" ), UTF_8 )
+                String[] files = FileUtils.fileRead( new File( "target/test-classpath/cp.txt" ), "UTF-8" )
                         .split( pathSeparator );
                 for ( String file : files )
                 {
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 8394e00..cbd2d16 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
@@ -19,23 +19,67 @@ package org.apache.maven.surefire.booter;
 */
 
 import junit.framework.TestCase;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.DirectoryScannerParameters;
+import org.apache.maven.surefire.testset.RunOrderParameters;
+import org.apache.maven.surefire.testset.TestArtifactInfo;
+import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
+import org.apache.maven.surefire.util.RunOrder;
+import org.mockito.ArgumentCaptor;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class SurefireReflectorTest
         extends TestCase
 {
+    public void testCreateConsoleLogger()
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        ConsoleLogger consoleLogger = mock( ConsoleLogger.class );
+        ConsoleLogger decorator = (ConsoleLogger) SurefireReflector.createConsoleLogger( consoleLogger, cl );
+        assertThat( decorator )
+        .isNotSameAs( consoleLogger );
+
+        assertThat( decorator.isDebugEnabled() ).isFalse();
+        when( consoleLogger.isDebugEnabled() ).thenReturn( true );
+        assertThat( decorator.isDebugEnabled() ).isTrue();
+        verify( consoleLogger, times( 2 ) ).isDebugEnabled();
+
+        decorator.info( "msg" );
+        ArgumentCaptor<String> argumentMsg = ArgumentCaptor.forClass( String.class );
+        verify( consoleLogger, times( 1 ) ).info( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "msg" );
+    }
+
     public void testShouldCreateFactoryWithoutException()
     {
-        ReporterFactory factory = new ReporterFactory() {
+        ReporterFactory factory = new ReporterFactory()
+        {
             @Override
-            public RunListener createReporter() {
+            public RunListener createReporter()
+            {
                 return null;
             }
 
             @Override
-            public RunResult close() {
+            public RunResult close()
+            {
                 return null;
             }
         };
@@ -46,4 +90,105 @@ public class SurefireReflectorTest
         assertNotNull( baseProviderFactory.getReporterFactory() );
         assertSame( factory, baseProviderFactory.getReporterFactory() );
     }
+
+    public void testSetDirectoryScannerParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        DirectoryScannerParameters directoryScannerParameters =
+                new DirectoryScannerParameters( new File( "ABC" ), new ArrayList<String>(), new ArrayList<String>(),
+                        new ArrayList<String>(), false, "hourly" );
+        surefireReflector.setDirectoryScannerParameters( foo, directoryScannerParameters );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testRunOrderParameters()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, new File( "." ) );
+        surefireReflector.setRunOrderParameters( foo, runOrderParameters );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testTestSuiteDefinition()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        TestRequest testSuiteDefinition =
+                new TestRequest( Arrays.asList( new File( "file1" ), new File( "file2" ) ),
+                        new File( "TestSOurce" ), new TestListResolver( "aUserRequestedTest#aMethodRequested" ) );
+        surefireReflector.setTestSuiteDefinition( foo, testSuiteDefinition );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testProviderProperties()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setProviderProperties( foo, new HashMap<String, String>() );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testReporterConfiguration()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        ReporterConfiguration reporterConfiguration = getReporterConfiguration();
+        surefireReflector.setReporterConfigurationAware( foo, reporterConfiguration );
+        assertTrue( isCalled( foo ) );
+    }
+
+    private ReporterConfiguration getReporterConfiguration()
+    {
+        return new ReporterConfiguration( new File( "CDE" ), true );
+    }
+
+    public void testTestClassLoaderAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        surefireReflector.setTestClassLoader( foo, getClass().getClassLoader() );
+        assertTrue( isCalled( foo ) );
+    }
+
+    public void testArtifactInfoAware()
+    {
+        SurefireReflector surefireReflector = getReflector();
+        Object foo = getFoo();
+
+        TestArtifactInfo testArtifactInfo = new TestArtifactInfo( "12.3", "test" );
+        surefireReflector.setTestArtifactInfo( foo, testArtifactInfo );
+        assertTrue( isCalled( foo ) );
+    }
+
+    private SurefireReflector getReflector()
+    {
+        return new SurefireReflector( this.getClass().getClassLoader() );
+    }
+
+    private Object getFoo()
+    { // Todo: Setup a different classloader so we can really test crossing
+        return new Foo();
+    }
+
+    private Boolean isCalled( Object foo )
+    {
+        final Method isCalled;
+        try
+        {
+            isCalled = foo.getClass().getMethod( "isCalled" );
+            return (Boolean) isCalled.invoke( foo );
+        }
+        catch ( ReflectiveOperationException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
 }
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
index 5440d6e..34eafe3 100644
--- 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
@@ -31,6 +31,14 @@ import static org.fest.assertions.Assertions.assertThat;
  */
 public class ReflectionUtilsTest
 {
+    @Test
+    public void shouldReloadClass() throws Exception
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        assertThat( ReflectionUtils.reloadClass( cl, new B() ) )
+                .isEqualTo( B.class );
+    }
+
     @Test(expected = RuntimeException.class)
     public void shouldNotInvokeStaticMethod()
     {
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 74cd93c..460293d 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -86,23 +86,31 @@
       <artifactId>powermock-api-mockito2</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.jacoco</groupId>
+      <artifactId>org.jacoco.agent</artifactId>
+      <classifier>runtime</classifier>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
     <plugins>
       <plugin>
-        <artifactId>maven-dependency-plugin</artifactId>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
         <executions>
           <execution>
-            <id>build-test-classpath</id>
-            <phase>generate-sources</phase>
+            <id>jacoco-instrumentation</id>
             <goals>
-              <goal>build-classpath</goal>
+              <goal>instrument</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>restore-classes</id>
+            <goals>
+              <goal>restore-instrumented-classes</goal>
             </goals>
-            <configuration>
-              <includeScope>test</includeScope>
-              <outputFile>target/test-classpath/cp.txt</outputFile>
-            </configuration>
           </execution>
         </executions>
       </plugin>
@@ -116,9 +124,13 @@
           </dependency>
         </dependencies>
         <configuration>
+          <argLine>${jvm.args.tests}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
+          <systemPropertyVariables>
+            <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
+          </systemPropertyVariables>
         </configuration>
       </plugin>
       <plugin>
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 f073a8b..ff731c4 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
@@ -21,8 +21,8 @@ package org.apache.maven.surefire.booter;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -30,19 +30,15 @@ import org.junit.runners.Suite;
  * @author Tibor Digana (tibor17)
  * @since 2.19
  */
-@Suite.SuiteClasses( {
-    ClasspathTest.class,
-    CommandReaderTest.class,
-    PropertiesWrapperTest.class,
-    SurefireReflectorTest.class,
-    PpidCheckerTest.class,
-    SystemUtilsTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTest( new JUnit4TestAdapter( PpidCheckerTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( SystemUtilsTest.class ) );
+        suite.addTestSuite( ClasspathTest.class );
+        suite.addTestSuite( PropertiesWrapperTest.class );
+        return suite;
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
deleted file mode 100644
index 0f5188c..0000000
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SurefireReflectorTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-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.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import org.apache.maven.surefire.report.ReporterConfiguration;
-import org.apache.maven.surefire.testset.DirectoryScannerParameters;
-import org.apache.maven.surefire.testset.RunOrderParameters;
-import org.apache.maven.surefire.testset.TestArtifactInfo;
-import org.apache.maven.surefire.testset.TestListResolver;
-import org.apache.maven.surefire.testset.TestRequest;
-import org.apache.maven.surefire.util.RunOrder;
-
-import junit.framework.TestCase;
-
-/**
- * @author Kristian Rosenvold
- */
-public class SurefireReflectorTest
-    extends TestCase
-{
-    public void testSetDirectoryScannerParameters()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        DirectoryScannerParameters directoryScannerParameters =
-            new DirectoryScannerParameters( new File( "ABC" ), new ArrayList<String>(), new ArrayList<String>(),
-                                            new ArrayList<String>(), false, "hourly" );
-        surefireReflector.setDirectoryScannerParameters( foo, directoryScannerParameters );
-        assertTrue( isCalled( foo ) );
-
-    }
-
-    public void testRunOrderParameters()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, new File( "." ) );
-        surefireReflector.setRunOrderParameters( foo, runOrderParameters );
-        assertTrue( isCalled( foo ) );
-
-    }
-
-    public void testTestSuiteDefinition()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        TestRequest testSuiteDefinition =
-            new TestRequest( Arrays.asList( new File( "file1" ), new File( "file2" ) ),
-                             new File( "TestSOurce" ), new TestListResolver( "aUserRequestedTest#aMethodRequested" ) );
-        surefireReflector.setTestSuiteDefinition( foo, testSuiteDefinition );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testProviderProperties()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        surefireReflector.setProviderProperties( foo, new HashMap<String, String>() );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testReporterConfiguration()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        ReporterConfiguration reporterConfiguration = getReporterConfiguration();
-        surefireReflector.setReporterConfigurationAware( foo, reporterConfiguration );
-        assertTrue( isCalled( foo ) );
-    }
-
-    private ReporterConfiguration getReporterConfiguration()
-    {
-        return new ReporterConfiguration( new File( "CDE" ), true );
-    }
-
-    public void testTestClassLoaderAware()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        surefireReflector.setTestClassLoader( foo, getClass().getClassLoader() );
-        assertTrue( isCalled( foo ) );
-    }
-
-    public void testArtifactInfoAware()
-        throws Exception
-    {
-        SurefireReflector surefireReflector = getReflector();
-        Object foo = getFoo();
-
-        TestArtifactInfo testArtifactInfo = new TestArtifactInfo( "12.3", "test" );
-        surefireReflector.setTestArtifactInfo( foo, testArtifactInfo );
-        assertTrue( isCalled( foo ) );
-    }
-
-    private SurefireReflector getReflector()
-    {
-        return new SurefireReflector( this.getClass().getClassLoader() );
-    }
-
-    public Object getFoo()
-    { // Todo: Setup a different classloader so we can really test crossing
-        return new Foo();
-    }
-
-
-    private Boolean isCalled( Object foo )
-    {
-        final Method isCalled;
-        try
-        {
-            isCalled = foo.getClass().getMethod( "isCalled" );
-            return (Boolean) isCalled.invoke( foo );
-        }
-        catch ( ReflectiveOperationException e )
-        {
-            throw new RuntimeException( e );
-        }
-    }
-}
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
index a957d06..438cb00 100644
--- 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
@@ -22,6 +22,7 @@ package org.apache.maven.surefire.booter;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
@@ -63,7 +64,8 @@ public class SystemUtilsTest
         public void shouldMatchJavaSpecVersion() throws Exception
         {
             BigDecimal actual = invokeMethod( SystemUtils.class, "getJavaSpecificationVersion" );
-            BigDecimal expected = new BigDecimal( System.getProperty( "java.specification.version" ) ).stripTrailingZeros();
+            BigDecimal expected =
+                    new BigDecimal( System.getProperty( "java.specification.version" ) ).stripTrailingZeros();
             assertThat( actual ).isEqualTo( expected );
             assertThat( SystemUtils.JAVA_SPECIFICATION_VERSION ).isEqualTo( expected );
         }
@@ -295,6 +297,7 @@ public class SystemUtilsTest
 
     @RunWith( PowerMockRunner.class )
     @PrepareForTest( SystemUtils.class )
+    @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
     public static class MockTest
     {
 
diff --git a/surefire-extensions-api/pom.xml b/surefire-extensions-api/pom.xml
new file mode 100644
index 0000000..bfce047
--- /dev/null
+++ b/surefire-extensions-api/pom.xml
@@ -0,0 +1,97 @@
+<?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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.surefire</groupId>
+        <artifactId>surefire</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>surefire-extensions-api</artifactId>
+    <name>Surefire Extensions API</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-component-annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jacoco</groupId>
+            <artifactId>org.jacoco.agent</artifactId>
+            <classifier>runtime</classifier>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-instrumentation</id>
+                        <!--
+                        phase: the order of the plugins matters and maven-plugin-plugin must be before jacoco plugin
+                        -->
+                        <goals>
+                            <goal>instrument</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>restore-classes</id>
+                        <goals>
+                            <goal>restore-instrumented-classes</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-shadefire</artifactId>
+                        <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+                    </dependency>
+                </dependencies>
+                <configuration>
+                    <argLine>${jvm.args.tests}</argLine>
+                    <systemPropertyVariables>
+                        <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReportEventListener.java
similarity index 60%
copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java
copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReportEventListener.java
index f1b5b5a..2b1e090 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestcycleConsoleOutputReceiver.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReportEventListener.java
@@ -1,4 +1,4 @@
-package org.apache.maven.plugin.surefire.report;
+package org.apache.maven.surefire.extensions;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,18 +19,19 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
-import org.apache.maven.surefire.report.ConsoleOutputReceiver;
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 
 /**
- * @author Kristian Rosenvold
+ * Extension listener for logger.
+ * The signature can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
  */
-public interface TestcycleConsoleOutputReceiver
-    extends ConsoleOutputReceiver
+public interface ConsoleOutputReportEventListener
 {
-    void testSetStarting( ReportEntry reportEntry );
-
-    void testSetCompleted( ReportEntry report );
-
+    void testSetStarting( TestSetReportEntry report );
+    void testSetCompleted( TestSetReportEntry report );
     void close();
+    void writeTestOutput( String output, boolean newLine, boolean stdout );
 }
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReporter.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReporter.java
new file mode 100644
index 0000000..382eaf3
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ConsoleOutputReporter.java
@@ -0,0 +1,72 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.PrintStream;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+
+/**
+ * Extension for logger.
+ * The signature can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ */
+public abstract class ConsoleOutputReporter
+{
+    /**
+     * {@code false} by default
+     */
+    private boolean disable;
+
+    /**
+     * The content is encoded <em>UTF-8</em> by default.
+     */
+    private String encoding;
+
+    public abstract ConsoleOutputReportEventListener createListener( File reportsDirectory, String reportNameSuffix,
+                                                                     Integer forkNumber );
+
+    public abstract ConsoleOutputReportEventListener createListener( PrintStream out, PrintStream err );
+
+    public abstract Object clone( ClassLoader target );
+
+    public boolean isDisable()
+    {
+        return disable;
+    }
+
+    public void setDisable( boolean disable )
+    {
+        this.disable = disable;
+    }
+
+    public String getEncoding()
+    {
+        return isBlank( encoding ) ? "UTF-8" : encoding;
+    }
+
+    public void setEncoding( String encoding )
+    {
+        this.encoding = encoding;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportEventListener.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportEventListener.java
new file mode 100644
index 0000000..3187802
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportEventListener.java
@@ -0,0 +1,43 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.TestSetReportEntry;
+
+/**
+ * Creates a report upon handled event "<em>testSetCompleted</em>".
+ * <br>
+ * Defaults to <em>org.apache.maven.plugin.surefire.report.StatelessXmlReporter</em>.
+ *
+ * author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ * @param <R> report entry type, see <em>WrappedReportEntry</em> from module the <em>maven-surefire-common</em>
+ * @param <S> test-set statistics, see <em>TestSetStats</em> from module the <em>maven-surefire-common</em>
+ */
+public interface StatelessReportEventListener<R extends TestSetReportEntry, S>
+{
+    /**
+     * The callback is called after the test class has been completed and the state of report is final.
+     *
+     * @param report <em>WrappedReportEntry</em>
+     * @param testSetStats <em>TestSetStats</em>
+     */
+     void testSetCompleted( R report, S testSetStats );
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
new file mode 100644
index 0000000..f176fce
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
@@ -0,0 +1,75 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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;
+
+/**
+ * Configuration passed to the constructor of default reporter
+ * <em>org.apache.maven.plugin.surefire.report.StatelessXmlReporter</em>.
+ * Signatures can be changed between major, minor versions or milestones.
+ */
+public class StatelessReportMojoConfiguration
+{
+    private final File reportsDirectory;
+
+    private final String reportNameSuffix;
+
+    private final boolean trimStackTrace;
+
+    private final int rerunFailingTestsCount;
+
+    private final String xsdSchemaLocation;
+
+    public StatelessReportMojoConfiguration( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace,
+                                             int rerunFailingTestsCount, String xsdSchemaLocation )
+    {
+        this.reportsDirectory = reportsDirectory;
+        this.reportNameSuffix = reportNameSuffix;
+        this.trimStackTrace = trimStackTrace;
+        this.rerunFailingTestsCount = rerunFailingTestsCount;
+        this.xsdSchemaLocation = xsdSchemaLocation;
+    }
+
+    public File getReportsDirectory()
+    {
+        return reportsDirectory;
+    }
+
+    public String getReportNameSuffix()
+    {
+        return reportNameSuffix;
+    }
+
+    public boolean isTrimStackTrace()
+    {
+        return trimStackTrace;
+    }
+
+    public int getRerunFailingTestsCount()
+    {
+        return rerunFailingTestsCount;
+    }
+
+    public String getXsdSchemaLocation()
+    {
+        return xsdSchemaLocation;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReporter.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReporter.java
new file mode 100644
index 0000000..60cee59
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReporter.java
@@ -0,0 +1,87 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.TestSetReportEntry;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+
+/**
+ * Extension for stateless reporter.
+ * Signatures can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ * @param <R> report entry type, see <em>WrappedReportEntry</em> from module the <em>maven-surefire-common</em>
+ * @param <S> test-set statistics, see <em>TestSetStats</em> from module the <em>maven-surefire-common</em>
+ * @param <C> mojo config, see <em>DefaultStatelessReportMojoConfiguration</em> from <em>maven-surefire-common</em>
+ */
+public abstract class StatelessReporter<R extends TestSetReportEntry, S, C extends StatelessReportMojoConfiguration>
+{
+    /**
+     * {@code false} by default
+     */
+    //todo remove isDisableXmlReport() in AbstractSurefireMojo and use this param instead
+    private boolean disable;
+
+    /**
+     * Version of reporter. It is version <em>3.0</em> used by default in XML reporter.
+     */
+    private String version;
+
+    /**
+     * Creates reporter.
+     *
+     * @return reporter object
+     */
+    public abstract StatelessReportEventListener<R, S> createListener( C configuration );
+
+    public abstract Object clone( ClassLoader target );
+
+    public boolean isDisable()
+    {
+        return disable;
+    }
+
+    public void setDisable( boolean disable )
+    {
+        this.disable = disable;
+    }
+
+    public String getVersion()
+    {
+        return isBlank( version ) ? "3.0" : version;
+    }
+
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName()
+                + "{"
+                + "version=" + getVersion()
+                + ", disable=" + isDisable()
+                + '}';
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoConsoleReportEventListener.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoConsoleReportEventListener.java
new file mode 100644
index 0000000..f01acff
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoConsoleReportEventListener.java
@@ -0,0 +1,53 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.surefire.report.TestSetReportEntry;
+
+import java.util.List;
+
+/**
+ * Extension listener for stateless console reporter of test-set.
+ * Signatures can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ * @param <R> report entry type, see <em>WrappedReportEntry</em> from module the <em>maven-surefire-common</em>
+ * @param <S> test-set statistics, see <em>TestSetStats</em> from module the <em>maven-surefire-common</em>
+ */
+public abstract class StatelessTestsetInfoConsoleReportEventListener<R extends TestSetReportEntry, S>
+{
+    private final ConsoleLogger logger;
+
+    public StatelessTestsetInfoConsoleReportEventListener( ConsoleLogger logger )
+    {
+        this.logger = logger;
+    }
+
+    public abstract void testSetStarting( TestSetReportEntry report );
+    public abstract void testSetCompleted( R report, S testSetStats, List<String> testResults );
+    public abstract void reset();
+
+    public ConsoleLogger getConsoleLogger()
+    {
+        return logger;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoFileReportEventListener.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoFileReportEventListener.java
new file mode 100644
index 0000000..91e460a
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoFileReportEventListener.java
@@ -0,0 +1,67 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.TestSetReportEntry;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * Extension listener for stateless file reporter of test-set.
+ * Signatures can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ * @param <R> report entry type, see <em>WrappedReportEntry</em> from module the <em>maven-surefire-common</em>
+ * @param <S> test-set statistics, see <em>TestSetStats</em> from module the <em>maven-surefire-common</em>
+ */
+public abstract class StatelessTestsetInfoFileReportEventListener<R extends TestSetReportEntry, S>
+{
+    private final File reportsDirectory;
+    private final String reportNameSuffix;
+    private final Charset encoding;
+
+    public StatelessTestsetInfoFileReportEventListener( File reportsDirectory, String reportNameSuffix,
+                                                        Charset encoding )
+    {
+        this.reportsDirectory = reportsDirectory;
+        this.reportNameSuffix = reportNameSuffix;
+        this.encoding = encoding;
+    }
+
+    public abstract void testSetCompleted( R report, S testSetStats, List<String> testResults );
+
+    protected File getReportsDirectory()
+    {
+        return reportsDirectory;
+    }
+
+    protected String getReportNameSuffix()
+    {
+        return reportNameSuffix;
+    }
+
+    protected Charset getEncoding()
+    {
+        return encoding;
+    }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporter.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporter.java
new file mode 100644
index 0000000..ca10944
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessTestsetInfoReporter.java
@@ -0,0 +1,58 @@
+package org.apache.maven.surefire.extensions;
+
+/*
+ * 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.surefire.report.TestSetReportEntry;
+
+import java.io.File;
+import java.nio.charset.Charset;
+
+/**
+ * Extension listener for stateless file and console reporter of test-set.
+ * Signatures can be changed between major, minor versions or milestones.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 3.0.0-M4
+ * @param <R> report entry type, see <em>WrappedReportEntry</em> from module the <em>maven-surefire-common</em>
+ * @param <S> test-set statistics, see <em>TestSetStats</em> from module the <em>maven-surefire-common</em>
+ */
+public abstract class StatelessTestsetInfoReporter<R extends TestSetReportEntry, S>
+{
+    private boolean disable;
+
+    public abstract StatelessTestsetInfoConsoleReportEventListener<R, S> createListener( ConsoleLogger logger );
+
+    public abstract StatelessTestsetInfoFileReportEventListener<R, S> createListener( File reportsDirectory,
+                                                                                      String reportNameSuffix,
+                                                                                      Charset encoding );
+
+    public abstract Object clone( ClassLoader target );
+
+    public boolean isDisable()
+    {
+        return disable;
+    }
+
+    public void setDisable( boolean disable )
+    {
+        this.disable = disable;
+    }
+}
diff --git a/surefire-grouper/pom.xml b/surefire-grouper/pom.xml
index 9aa676c..96c7bc4 100644
--- a/surefire-grouper/pom.xml
+++ b/surefire-grouper/pom.xml
@@ -52,7 +52,25 @@
         </executions>
       </plugin>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
         <dependencies>
           <dependency>
             <groupId>org.apache.maven.surefire</groupId>
diff --git a/surefire-its/pom.xml b/surefire-its/pom.xml
index 2da331f..c6523cd 100644
--- a/surefire-its/pom.xml
+++ b/surefire-its/pom.xml
@@ -203,13 +203,6 @@
                 </executions>
             </plugin>
             <plugin>
-                <artifactId>maven-jar-plugin</artifactId>
-                <!-- todo dont skip since of failsafe:2.19 internal use if having src/main/java/... -->
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
-            </plugin>
-            <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <executions>
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformEnginesIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformEnginesIT.java
index af8f366..0cfafc4 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformEnginesIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformEnginesIT.java
@@ -64,8 +64,9 @@ public class JUnitPlatformEnginesIT
         args.add( new Object[] { "1.1.1", "5.1.1", "1.0.0", "1.0.0" } );
         args.add( new Object[] { "1.2.0", "5.2.0", "1.1.0", "1.0.0" } );
         args.add( new Object[] { "1.3.2", "5.3.2", "1.1.1", "1.0.0" } );
-        args.add( new Object[] { "1.4.0-SNAPSHOT", "5.4.0-SNAPSHOT", "1.1.1", "1.0.0" } );
-        args.add( new Object[] { "1.4.0-RC2", "5.4.0-RC2", "1.1.1", "1.0.0" } );
+        args.add( new Object[] { "1.4.2", "5.4.2", "1.1.1", "1.0.0" } );
+        args.add( new Object[] { "1.5.0-M1", "5.5.0-M1", "1.1.1", "1.0.0" } );
+        args.add( new Object[] { "1.5.0-SNAPSHOT", "5.5.0-SNAPSHOT", "1.2.0-SNAPSHOT", "1.0.0" } );
         return args;
     }
 
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
index 7675843..9522ebe 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
@@ -22,14 +22,20 @@ package org.apache.maven.surefire.its;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.surefire.its.fixture.HelperAssertions.assumeJavaVersion;
+import static org.apache.maven.surefire.its.fixture.HelperAssertions.convertUnicodeToUTF8;
 
 public class JUnitPlatformIT
         extends SurefireJUnit4IntegrationTestCase
 {
+    private static final String XML_TESTSUITE_FRAGMENT =
+            "<testsuite xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation="
+                    + "\"https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd\" "
+                    + "version=\"3.0\" name=\"&lt;&lt; ✨ &gt;&gt;\"";
+
     @Before
     public void setUp()
     {
@@ -40,27 +46,42 @@ public class JUnitPlatformIT
     public void testJupiterEngine()
     {
         unpack( "/junit-platform-engine-jupiter" )
+                .setTestToRun( "Basic*Test" )
                 .executeTest()
                 .verifyErrorFree( 5 );
     }
 
     @Test
-    @Ignore( "Uncomment while developing SUREFIRE-1222. Rename 'javax' extension of DisplayNameTest.javax." )
     public void testJupiterEngineWithDisplayNames()
     {
         OutputValidator validator = unpack( "/junit-platform-engine-jupiter" )
                 .executeTest()
                 .verifyErrorFree( 7 );
 
-        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest.txt" )
-                 // .assertContainsText( "<< ✨ >>" ) // after @DisplayName is uncommented via SUREFIRE-1222
-                 .assertContainsText( "Test set: junitplatformenginejupiter.DisplayNameTest" );
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest.txt", UTF_8 )
+                 .assertContainsText( "<< ✨ >>" );
+
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest.txt", UTF_8 )
+                .assertContainsText( "Test set: << ✨ >>" );
+
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest.txt", UTF_8 )
+                .assertContainsText( " - in << ✨ >>" );
+
+
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest-output.txt", UTF_8 )
+                .assertContainsText( "<< ✨ >>" );
+
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest-output.txt", UTF_8 )
+                .assertContainsText( "73$71 ✔" );
+
+        validator.getSurefireReportsFile( "junitplatformenginejupiter.DisplayNameTest-output.txt", UTF_8 )
+                .assertContainsText( "73$72 ✔" );
+
 
-        validator.getSurefireReportsFile( "TEST-junitplatformenginejupiter.DisplayNameTest.xml" )
-                 // At the moment, the testcase with the same is reported twice: test1() and test2() use the same display name
-                 // SUREFIRE-1222 will solve this.
-                 .assertContainsText( "testcase name=\"73$71 ✔\" classname=\"junitplatformenginejupiter.DisplayNameTest\"" )
-                 .assertContainsText( "testcase name=\"73$71 ✔\" classname=\"junitplatformenginejupiter.DisplayNameTest\"" );
+        validator.getSurefireReportsFile( "TEST-junitplatformenginejupiter.DisplayNameTest.xml", UTF_8 )
+                .assertContainsText( "testcase name=\"73$71 ✔\" classname=\"&lt;&lt; ✨ &gt;&gt;\"" )
+                .assertContainsText( "testcase name=\"73$72 ✔\" classname=\"&lt;&lt; ✨ &gt;&gt;\"" )
+                .assertContainsText( XML_TESTSUITE_FRAGMENT );
     }
 
     @Test
diff --git a/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml b/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml
index 192cc8a..4947646 100644
--- a/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml
+++ b/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml
@@ -32,6 +32,7 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
         <junit.jupiter.version>5.2.0</junit.jupiter.version>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     </properties>
 
     <!--
@@ -58,9 +59,39 @@
     <build>
         <plugins>
             <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <version>${surefire.version}</version>
+                <configuration>
+                    <forkCount>1.0C</forkCount>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
+                        <disable>false</disable>
+                        <version>3.0</version>
+                        <usePhrasedFileName>false</usePhrasedFileName>
+                        <usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
+                        <usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
+                        <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
+                    </statelessTestsetReporter>
+                    <consoleOutputReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter">
+                        <disable>false</disable>
+                        <encoding>UTF-8</encoding>
+                        <usePhrasedFileName>false</usePhrasedFileName>
+                    </consoleOutputReporter>
+                    <statelessTestsetInfoReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoReporter">
+                        <disable>false</disable>
+                        <usePhrasedFileName>false</usePhrasedFileName>
+                        <usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
+                        <usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
+                    </statelessTestsetInfoReporter>
+                </configuration>
             </plugin>
         </plugins>
     </build>
diff --git a/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.javax b/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.java
similarity index 65%
rename from surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.javax
rename to surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.java
index 8089ad0..a6401e3 100644
--- a/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.javax
+++ b/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/DisplayNameTest.java
@@ -19,25 +19,27 @@ package junitplatformenginejupiter;
  * under the License.
  */
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-// TODO Uncomment after SUREFIRE-1222 is done
-// @DisplayName("<< ✨ >>")
+@DisplayName( "<< ✨ >>" )
 class DisplayNameTest
 {
     @Test
-    @DisplayName("73$71 ✔")
+    @DisplayName( "73$71 ✔" )
     void test1()
+            throws Exception
     {
+        System.out.println( getClass().getDeclaredMethod( "test1" ).getAnnotation( DisplayName.class ).value() );
+        System.out.println( getClass().getAnnotation( DisplayName.class ).value() );
     }
 
     @Test
-    @DisplayName("73$71 ✔")
+    @DisplayName( "73$72 ✔" )
     void test2()
+            throws Exception
     {
+        System.out.println( getClass().getDeclaredMethod( "test2" ).getAnnotation( DisplayName.class ).value() );
+        System.out.println( getClass().getAnnotation( DisplayName.class ).value() );
     }
 }
diff --git a/surefire-logger-api/pom.xml b/surefire-logger-api/pom.xml
index 4a56f3a..5f0ea0d 100644
--- a/surefire-logger-api/pom.xml
+++ b/surefire-logger-api/pom.xml
@@ -49,4 +49,48 @@
         </developer>
     </developers>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+                    <includes>
+                        <include>**/JUnit4SuiteTest.java</include>
+                    </includes>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-shadefire</artifactId>
+                        <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerDecorator.java b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerDecorator.java
index bff2c63..0ab20cc 100644
--- a/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerDecorator.java
+++ b/surefire-logger-api/src/main/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerDecorator.java
@@ -43,101 +43,59 @@ public final class ConsoleLoggerDecorator
     @Override
     public boolean isDebugEnabled()
     {
-        try
-        {
-            return (Boolean) logger.getClass()
-                    .getMethod( "isDebugEnabled" )
-                    .invoke( logger );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        return invokeReturnedBoolean( "isDebugEnabled" );
     }
 
     @Override
     public void debug( String message )
     {
-        try
-        {
-            logger.getClass()
-                    .getMethod( "debug", String.class )
-                    .invoke( logger, message );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        invokeWithMessage( message, "debug" );
     }
 
     @Override
     public boolean isInfoEnabled()
     {
-        try
-        {
-            return (Boolean) logger.getClass()
-                    .getMethod( "isInfoEnabled" )
-                    .invoke( logger );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        return invokeReturnedBoolean( "isInfoEnabled" );
     }
 
     @Override
     public void info( String message )
     {
-        try
-        {
-            logger.getClass()
-                    .getMethod( "info", String.class )
-                    .invoke( logger, message );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        invokeWithMessage( message, "info" );
     }
 
     @Override
     public boolean isWarnEnabled()
     {
-        try
-        {
-            return (Boolean) logger.getClass()
-                    .getMethod( "isWarnEnabled" )
-                    .invoke( logger );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        return invokeReturnedBoolean( "isWarnEnabled" );
     }
 
     @Override
     public void warning( String message )
     {
-        try
-        {
-            logger.getClass()
-                    .getMethod( "warning", String.class )
-                    .invoke( logger, message );
-        }
-        catch ( Exception e )
-        {
-            throw new IllegalStateException( e.getLocalizedMessage(), e );
-        }
+        invokeWithMessage( message, "warning" );
     }
 
     @Override
     public boolean isErrorEnabled()
     {
+        return invokeReturnedBoolean( "isErrorEnabled" );
+    }
+
+    @Override
+    public void error( String message )
+    {
+        invokeWithMessage( message, "error" );
+    }
+
+    @Override
+    public void error( String message, Throwable t )
+    {
         try
         {
-            return (Boolean) logger.getClass()
-                    .getMethod( "isErrorEnabled" )
-                    .invoke( logger );
+            logger.getClass()
+                    .getMethod( "error", String.class, Throwable.class )
+                    .invoke( logger, message, t );
         }
         catch ( Exception e )
         {
@@ -146,13 +104,13 @@ public final class ConsoleLoggerDecorator
     }
 
     @Override
-    public void error( String message )
+    public void error( Throwable t )
     {
         try
         {
             logger.getClass()
-                    .getMethod( "error", String.class )
-                    .invoke( logger, message );
+                    .getMethod( "error", Throwable.class )
+                    .invoke( logger, t );
         }
         catch ( Exception e )
         {
@@ -160,14 +118,13 @@ public final class ConsoleLoggerDecorator
         }
     }
 
-    @Override
-    public void error( String message, Throwable t )
+    private boolean invokeReturnedBoolean( String isErrorEnabled )
     {
         try
         {
-            logger.getClass()
-                    .getMethod( "error", String.class, Throwable.class )
-                    .invoke( logger, message, t );
+            return (Boolean) logger.getClass()
+                    .getMethod( isErrorEnabled )
+                    .invoke( logger );
         }
         catch ( Exception e )
         {
@@ -175,14 +132,13 @@ public final class ConsoleLoggerDecorator
         }
     }
 
-    @Override
-    public void error( Throwable t )
+    private void invokeWithMessage( String message, String error )
     {
         try
         {
             logger.getClass()
-                    .getMethod( "error", Throwable.class )
-                    .invoke( logger, t );
+                    .getMethod( error, String.class )
+                    .invoke( logger, message );
         }
         catch ( Exception e )
         {
diff --git a/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtilsTest.java b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtilsTest.java
new file mode 100644
index 0000000..902a17e
--- /dev/null
+++ b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/ConsoleLoggerUtilsTest.java
@@ -0,0 +1,64 @@
+package org.apache.maven.plugin.surefire.log.api;
+
+/*
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Tests for {@link ConsoleLoggerUtils}.
+ */
+public class ConsoleLoggerUtilsTest
+{
+    @Test
+    public void shouldPrintStacktraceAsString()
+    {
+        Exception e = new IllegalArgumentException( "wrong param" );
+        String msg = ConsoleLoggerUtils.toString( e );
+
+        StringWriter text = new StringWriter();
+        PrintWriter writer = new PrintWriter( text );
+        e.printStackTrace( writer );
+        String s = text.toString();
+
+        assertThat( msg )
+                .isEqualTo( s );
+    }
+
+    @Test
+    public void shouldPrintStacktracWithMessageAsString()
+    {
+        Exception e = new IllegalArgumentException( "wrong param" );
+        String msg = ConsoleLoggerUtils.toString( "issue", e );
+
+        StringWriter text = new StringWriter();
+        PrintWriter writer = new PrintWriter( text );
+        writer.println( "issue" );
+        e.printStackTrace( writer );
+        String s = text.toString();
+
+        assertThat( msg )
+                .isEqualTo( s );
+    }
+}
diff --git a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/JUnit4SuiteTest.java
similarity index 81%
copy from surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
copy to surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/JUnit4SuiteTest.java
index 547c381..69665a3 100644
--- a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
+++ b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/JUnit4SuiteTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.common.junit48;
+package org.apache.maven.plugin.surefire.log.api;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -21,22 +21,20 @@ package org.apache.maven.surefire.common.junit48;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
+import junit.framework.TestCase;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
  *
  * @author Tibor Digana (tibor17)
- * @since 2.19
+ * @since 3.0.0-M4
  */
-@Suite.SuiteClasses( {
-    FilterFactoryTest.class,
-    JUnit48ReflectorTest.class,
-    JUnit48TestCheckerTest.class
-} )
+@SuiteClasses( { ConsoleLoggerUtilsTest.class, LevelTest.class, LoggersTest.class } )
 @RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
diff --git a/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LevelTest.java b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LevelTest.java
new file mode 100644
index 0000000..0f48880
--- /dev/null
+++ b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LevelTest.java
@@ -0,0 +1,73 @@
+package org.apache.maven.plugin.surefire.log.api;
+
+/*
+ * 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.apache.maven.plugin.surefire.log.api.Level.resolveLevel;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Tests for {@link Level}.
+ */
+public class LevelTest
+{
+    @Test
+    public void shouldHaveSuccess()
+    {
+        Level level = resolveLevel( true, false, false, false, false );
+        assertThat( level ).isEqualTo( Level.SUCCESS );
+    }
+
+    @Test
+    public void shouldNotHaveSuccess()
+    {
+        Level level = resolveLevel( false, false, false, false, false );
+        assertThat( level ).isEqualTo( Level.NO_COLOR );
+    }
+
+    @Test
+    public void shouldBeFailure()
+    {
+        Level level = resolveLevel( false, true, false, false, false );
+        assertThat( level ).isEqualTo( Level.FAILURE );
+    }
+
+    @Test
+    public void shouldBeError()
+    {
+        Level level = resolveLevel( false, false, true, false, false );
+        assertThat( level ).isEqualTo( Level.FAILURE );
+    }
+
+    @Test
+    public void shouldBeSkipped()
+    {
+        Level level = resolveLevel( false, false, false, true, false );
+        assertThat( level ).isEqualTo( Level.UNSTABLE );
+    }
+
+    @Test
+    public void shouldBeFlake()
+    {
+        Level level = resolveLevel( false, false, false, false, true );
+        assertThat( level ).isEqualTo( Level.UNSTABLE );
+    }
+}
diff --git a/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LoggersTest.java b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LoggersTest.java
new file mode 100644
index 0000000..7de9148
--- /dev/null
+++ b/surefire-logger-api/src/test/java/org/apache/maven/plugin/surefire/log/api/LoggersTest.java
@@ -0,0 +1,139 @@
+package org.apache.maven.plugin.surefire.log.api;
+
+/*
+ * 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.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.internal.matchers.CapturesArguments;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link ConsoleLoggerDecorator}, {@link NullConsoleLogger} and {@link PrintStreamLogger}.
+ */
+public class LoggersTest
+{
+    @Test
+    public void testPrintStreamLogger()
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintStream printStream = new PrintStream( outputStream );
+        PrintStreamLogger logger = new PrintStreamLogger( printStream );
+
+
+        assertThat( logger.isErrorEnabled() ).isTrue();
+        assertThat( logger.isWarnEnabled() ).isTrue();
+        assertThat( logger.isInfoEnabled() ).isTrue();
+        assertThat( logger.isDebugEnabled() ).isTrue();
+
+        logger.error( "error" );
+        logger.debug( "debug" );
+        logger.info( "info" );
+        logger.warning( "warning" );
+
+        String line = System.lineSeparator();
+        assertThat( outputStream.toString() )
+                .isEqualTo( "error" + line + "debug" + line + "info" + line + "warning" + line );
+
+        Exception e = new Exception( "exception" );
+        outputStream.reset();
+        logger.error( e );
+        assertThat( outputStream.toString() )
+                .contains( "java.lang.Exception: exception" )
+                .contains( "at " + getClass().getName() + ".testPrintStreamLogger(LoggersTest.java:63)" );
+    }
+
+    @Test( expected = NullPointerException.class )
+    public void shouldThrowNPE()
+    {
+        new ConsoleLoggerDecorator( null );
+    }
+
+    @Test
+    public void testDecorator()
+    {
+        ConsoleLogger logger = mock( ConsoleLogger.class );
+        ConsoleLoggerDecorator decorator = new ConsoleLoggerDecorator( logger );
+
+        assertThat( decorator.isDebugEnabled() ).isFalse();
+        when( logger.isDebugEnabled() ).thenReturn( true );
+        assertThat( decorator.isDebugEnabled() ).isTrue();
+
+        assertThat( decorator.isInfoEnabled() ).isFalse();
+        when( logger.isInfoEnabled() ).thenReturn( true );
+        assertThat( decorator.isInfoEnabled() ).isTrue();
+
+        assertThat( decorator.isWarnEnabled() ).isFalse();
+        when( logger.isWarnEnabled() ).thenReturn( true );
+        assertThat( decorator.isWarnEnabled() ).isTrue();
+
+        assertThat( decorator.isErrorEnabled() ).isFalse();
+        when( logger.isErrorEnabled() ).thenReturn( true );
+        assertThat( decorator.isErrorEnabled() ).isTrue();
+
+        ArgumentCaptor<String> argumentMsg = ArgumentCaptor.forClass( String.class );
+        decorator.debug( "debug" );
+        verify( logger, times( 1 ) ).debug( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "debug" );
+
+        argumentMsg = ArgumentCaptor.forClass( String.class );
+        decorator.info( "info" );
+        verify( logger, times( 1 ) ).info( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "info" );
+
+        argumentMsg = ArgumentCaptor.forClass( String.class );
+        decorator.warning( "warning" );
+        verify( logger, times( 1 ) ).warning( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "warning" );
+
+        argumentMsg = ArgumentCaptor.forClass( String.class );
+        decorator.error( "error" );
+        verify( logger, times( 1 ) ).error( argumentMsg.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "error" );
+
+        ArgumentCaptor<Throwable> argumentThrowable = ArgumentCaptor.forClass( Throwable.class );
+        argumentMsg = ArgumentCaptor.forClass( String.class );
+        Exception e = new Exception();
+        decorator.error( "error", e );
+        verify( logger, times( 1 ) ).error( argumentMsg.capture(), argumentThrowable.capture() );
+        assertThat( argumentMsg.getAllValues() ).hasSize( 1 );
+        assertThat( argumentMsg.getAllValues().get( 0 ) ).isEqualTo( "error" );
+        assertThat( argumentThrowable.getAllValues() ).hasSize( 1 );
+        assertThat( argumentThrowable.getAllValues().get( 0 ) ).isSameAs( e );
+
+        argumentThrowable = ArgumentCaptor.forClass( Throwable.class );
+        decorator.error( e );
+        verify( logger, times( 1 ) ).error( argumentThrowable.capture() );
+        assertThat( argumentThrowable.getAllValues() ).hasSize( 1 );
+        assertThat( argumentThrowable.getAllValues().get( 0 ) ).isSameAs( e );
+    }
+}
diff --git a/surefire-providers/common-java5/pom.xml b/surefire-providers/common-java5/pom.xml
index ba44a98..6beeff4 100644
--- a/surefire-providers/common-java5/pom.xml
+++ b/surefire-providers/common-java5/pom.xml
@@ -41,6 +41,21 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-shade-plugin</artifactId>
         <executions>
           <execution>
@@ -68,10 +83,15 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <excludes>
-            <exclude>**/fixture/**</exclude>
-          </excludes>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
         </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
       </plugin>
     </plugins>
   </build>
diff --git a/surefire-providers/common-junit3/pom.xml b/surefire-providers/common-junit3/pom.xml
index 2089eec..bef14cb 100644
--- a/surefire-providers/common-junit3/pom.xml
+++ b/surefire-providers/common-junit3/pom.xml
@@ -40,4 +40,37 @@
     </dependency>
   </dependencies>
 
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+
 </project>
diff --git a/surefire-providers/common-junit4/pom.xml b/surefire-providers/common-junit4/pom.xml
index 0af781c..09bf4cd 100644
--- a/surefire-providers/common-junit4/pom.xml
+++ b/surefire-providers/common-junit4/pom.xml
@@ -49,4 +49,37 @@
       <version>${project.version}</version>
     </dependency>
   </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4RunListener.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4RunListener.java
index 234bcf5..c4a7991 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4RunListener.java
+++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4RunListener.java
@@ -76,7 +76,7 @@ public class JUnit4RunListener
     {
         String reason = getAnnotatedIgnoreValue( description );
         ClassMethod classMethod = toClassMethod( description );
-        reporter.testSkipped( ignored( classMethod.getClazz(), classMethod.getMethod(), reason ) );
+        reporter.testSkipped( ignored( classMethod.getClazz(), null, classMethod.getMethod(), null, reason ) );
     }
 
     /**
@@ -112,7 +112,8 @@ public class JUnit4RunListener
         {
             StackTraceWriter stackTrace = createStackTraceWriter( failure );
             ClassMethod classMethod = toClassMethod( failure.getDescription() );
-            ReportEntry report = withException( classMethod.getClazz(), classMethod.getMethod(), stackTrace );
+            ReportEntry report =
+                    withException( classMethod.getClazz(), null, classMethod.getMethod(), null, stackTrace );
 
             if ( failure.getException() instanceof AssertionError )
             {
@@ -135,7 +136,8 @@ public class JUnit4RunListener
         {
             Description desc = failure.getDescription();
             ClassMethod classMethod = toClassMethod( desc );
-            ReportEntry report = assumption( classMethod.getClazz(), classMethod.getMethod(), failure.getMessage() );
+            ReportEntry report = assumption( classMethod.getClazz(), null, classMethod.getMethod(), null,
+                    failure.getMessage() );
             reporter.testAssumptionFailure( report );
         }
         finally
@@ -176,7 +178,7 @@ public class JUnit4RunListener
     protected SimpleReportEntry createReportEntry( Description description )
     {
         ClassMethod classMethod = toClassMethod( description );
-        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getMethod() );
+        return new SimpleReportEntry( classMethod.getClazz(), null, classMethod.getMethod(), null );
     }
 
     public static void rethrowAnyTestMechanismFailures( Result run )
diff --git a/surefire-providers/common-junit48/pom.xml b/surefire-providers/common-junit48/pom.xml
index ddfdf75..006b78e 100644
--- a/surefire-providers/common-junit48/pom.xml
+++ b/surefire-providers/common-junit48/pom.xml
@@ -58,12 +58,35 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
         </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
       </plugin>
     </plugins>
   </build>
diff --git a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java b/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
index 547c381..191e1e8 100644
--- a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
+++ b/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
@@ -21,8 +21,8 @@ package org.apache.maven.surefire.common.junit48;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -30,16 +30,14 @@ import org.junit.runners.Suite;
  * @author Tibor Digana (tibor17)
  * @since 2.19
  */
-@Suite.SuiteClasses( {
-    FilterFactoryTest.class,
-    JUnit48ReflectorTest.class,
-    JUnit48TestCheckerTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTestSuite( JUnit48ReflectorTest.class );
+        suite.addTest( new JUnit4TestAdapter( JUnit48TestCheckerTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( FilterFactoryTest.class ) );
+        return suite;
     }
 }
diff --git a/surefire-providers/surefire-junit-platform/pom.xml b/surefire-providers/surefire-junit-platform/pom.xml
index 8533b56..823885f 100644
--- a/surefire-providers/surefire-junit-platform/pom.xml
+++ b/surefire-providers/surefire-junit-platform/pom.xml
@@ -101,6 +101,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-reflect</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
             <scope>test</scope>
@@ -110,6 +115,21 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>animal-sniffer-maven-plugin</artifactId>
                 <executions>
@@ -138,6 +158,7 @@
                 <artifactId>maven-surefire-plugin</artifactId>
                 <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
                 <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
                     <jvm>${java.home}/bin/java</jvm>
                     <redirectTestOutputToFile>true</redirectTestOutputToFile>
                     <includes>
diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
index 0f9f8bb..29f3eeb 100644
--- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
@@ -23,6 +23,7 @@ import static java.util.Collections.emptyMap;
 import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
 
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -74,12 +75,12 @@ final class RunListenerAdapter
                         && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
         {
             testStartTime.put( testIdentifier, System.currentTimeMillis() );
-            runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) );
+            runListener.testSetStarting( createReportEntry( testIdentifier ) );
         }
         else if ( testIdentifier.isTest() )
         {
             testStartTime.put( testIdentifier, System.currentTimeMillis() );
-            runListener.testStarting( createTestSetReportEntry( testIdentifier ) );
+            runListener.testStarting( createReportEntry( testIdentifier ) );
         }
     }
 
@@ -104,15 +105,15 @@ final class RunListenerAdapter
                     }
                     else
                     {
-                        runListener.testSetCompleted( createTestSetReportEntry( testIdentifier, testExecutionResult,
-                                systemProps(), elapsed ) );
+                        runListener.testSetCompleted( createReportEntry( testIdentifier, testExecutionResult,
+                                systemProps(), null, elapsed ) );
                     }
                     break;
                 case FAILED:
                     if ( !isTest )
                     {
-                        runListener.testSetCompleted( createTestSetReportEntry( testIdentifier, testExecutionResult,
-                                systemProps(), elapsed ) );
+                        runListener.testSetCompleted( createReportEntry( testIdentifier, testExecutionResult,
+                                systemProps(), null, elapsed ) );
                     }
                     else if ( testExecutionResult.getThrowable()
                             .filter( AssertionError.class::isInstance ).isPresent() )
@@ -127,12 +128,12 @@ final class RunListenerAdapter
                 default:
                     if ( isTest )
                     {
-                        runListener.testSucceeded( createReportEntry( testIdentifier, elapsed ) );
+                        runListener.testSucceeded( createReportEntry( testIdentifier, null, elapsed ) );
                     }
                     else
                     {
                         runListener.testSetCompleted(
-                                createTestSetReportEntry( testIdentifier, null, systemProps(), elapsed ) );
+                                createReportEntry( testIdentifier, null, systemProps(), null, elapsed ) );
                     }
             }
         }
@@ -149,58 +150,43 @@ final class RunListenerAdapter
     public void executionSkipped( TestIdentifier testIdentifier, String reason )
     {
         testStartTime.remove( testIdentifier );
-        String[] classMethodName = toClassMethodName( testIdentifier );
-        String className = classMethodName[1];
-        String methodName = classMethodName[3];
-        runListener.testSkipped( new SimpleReportEntry( className, methodName, reason ) );
+        runListener.testSkipped( createReportEntry( testIdentifier, null, emptyMap(), reason, null ) );
     }
 
-    private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier,
-                                                        TestExecutionResult testExecutionResult,
-                                                        Map<String, String> systemProperties,
-                                                        Integer elapsedTime )
+    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
+                                                 TestExecutionResult testExecutionResult,
+                                                 Map<String, String> systemProperties,
+                                                 String reason,
+                                                 Integer elapsedTime )
     {
         String[] classMethodName = toClassMethodName( testIdentifier );
-        String className = classMethodName[1];
-        String methodName = classMethodName[3];
+        String className = classMethodName[0];
+        String classText = classMethodName[1];
+        if ( Objects.equals( className, classText ) )
+        {
+            classText = null;
+        }
+        String methodName = testIdentifier.isTest() ? classMethodName[2] : null;
+        String methodText = testIdentifier.isTest() ? classMethodName[3] : null;
+        if ( Objects.equals( methodName, methodText ) )
+        {
+            methodText = null;
+        }
         StackTraceWriter stw =
                 testExecutionResult == null ? null : toStackTraceWriter( className, methodName, testExecutionResult );
-        return new SimpleReportEntry( className, methodName, stw, elapsedTime, systemProperties );
+        return new SimpleReportEntry( className, classText, methodName, methodText,
+                stw, elapsedTime, reason, systemProperties );
     }
 
-    private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier )
+    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier )
     {
-        return createTestSetReportEntry( testIdentifier, emptyMap() );
-    }
-
-    private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier,
-                                                        Map<String, String> systemProperties )
-    {
-        return createTestSetReportEntry( testIdentifier, null, systemProperties, null );
-    }
-
-    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier, Integer elapsedTime )
-    {
-        return createReportEntry( testIdentifier, (StackTraceWriter) null, elapsedTime );
+        return createReportEntry( testIdentifier, null, null );
     }
 
     private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
                                                  TestExecutionResult testExecutionResult, Integer elapsedTime )
     {
-        String[] classMethodNames = toClassMethodName( testIdentifier );
-        String realClassName = classMethodNames[0];
-        String realMethodName = classMethodNames[2];
-        return createReportEntry( testIdentifier,
-                toStackTraceWriter( realClassName, realMethodName, testExecutionResult ), elapsedTime );
-    }
-
-    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier, StackTraceWriter stackTraceWriter,
-                                                 Integer elapsedTime )
-    {
-        String[] classMethodNames = toClassMethodName( testIdentifier );
-        String className = classMethodNames[1];
-        String methodName = classMethodNames[3];
-        return new SimpleReportEntry( className, methodName, stackTraceWriter, elapsedTime );
+        return createReportEntry( testIdentifier, testExecutionResult, emptyMap(), null, elapsedTime );
     }
 
     private StackTraceWriter toStackTraceWriter( String realClassName, String realMethodName,
@@ -263,7 +249,7 @@ final class RunListenerAdapter
             String className = classSource.getClassName();
             String simpleClassName = className.substring( 1 + className.lastIndexOf( '.' ) );
             String source = display.equals( simpleClassName ) ? className : display;
-            return new String[] { source, source, null, null };
+            return new String[] { className, source, null, null };
         }
         else
         {
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
index d0d4af9..7db25c9 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
@@ -21,26 +21,23 @@ package org.apache.maven.surefire.junitplatform;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.junit.runners.Suite.SuiteClasses;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
  *
  * @since 3.0.0-M4
  */
-@SuiteClasses( {
-        JUnitPlatformProviderTest.class,
-        RunListenerAdapterTest.class,
-        TestMethodFilterTest.class,
-        TestPlanScannerFilterTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit47SuiteTest
+public class JUnit47SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit47SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTest( new JUnit4TestAdapter( JUnitPlatformProviderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( RunListenerAdapterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( TestMethodFilterTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( TestPlanScannerFilterTest.class ) );
+        return suite;
     }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
index bbc4457..e9c53ba 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTest.java
@@ -33,8 +33,10 @@ import static org.junit.platform.engine.TestExecutionResult.failed;
 import static org.junit.platform.engine.TestExecutionResult.successful;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
+import static org.powermock.reflect.Whitebox.getInternalState;
 
 import java.lang.reflect.Method;
+import java.util.Map;
 import java.util.Optional;
 
 import org.apache.maven.surefire.report.PojoStackTraceWriter;
@@ -50,7 +52,6 @@ import org.junit.jupiter.engine.descriptor.ClassTestDescriptor;
 import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
 import org.junit.platform.engine.ConfigurationParameters;
 import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.TestDescriptor.Type;
 import org.junit.platform.engine.TestSource;
 import org.junit.platform.engine.UniqueId;
 import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
@@ -121,8 +122,10 @@ public class RunListenerAdapterTest
         verify( listener ).testStarting( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
-        assertEquals( MY_TEST_METHOD_NAME + "(String)", entry.getName() );
+        assertEquals( MY_TEST_METHOD_NAME, entry.getName() );
+        assertEquals( MY_TEST_METHOD_NAME + "(String)", entry.getNameText() );
         assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
+        assertNull( entry.getSourceText() );
         assertNull( entry.getStackTraceWriter() );
     }
 
@@ -137,23 +140,31 @@ public class RunListenerAdapterTest
         parent.addChild( child );
         TestPlan plan = TestPlan.from( singletonList( engine ) );
 
+        String className = MyTestClass.class.getName();
+
         adapter.testPlanExecutionStarted( plan );
         adapter.executionStarted( TestIdentifier.from( engine ) );
         adapter.executionStarted( TestIdentifier.from( parent ) );
-        verify( listener ).testSetStarting( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
+        verify( listener )
+                .testSetStarting( new SimpleReportEntry( className, null, null, null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionStarted( TestIdentifier.from( child ) );
-        verify( listener ).testStarting( new SimpleReportEntry( MyTestClass.class.getName(), MY_TEST_METHOD_NAME ) );
+        verify( listener )
+                .testStarting( new SimpleReportEntry( className, null, MY_TEST_METHOD_NAME, null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( child ), successful() );
         ArgumentCaptor<SimpleReportEntry> report = ArgumentCaptor.forClass( SimpleReportEntry.class );
         verify( listener ).testSucceeded( report.capture() );
         assertThat( report.getValue().getSourceName() )
-                .isEqualTo( MyTestClass.class.getName() );
+                .isEqualTo( className );
+        assertThat( report.getValue().getSourceText() )
+                .isNull();
         assertThat( report.getValue().getName() )
                 .isEqualTo( MY_TEST_METHOD_NAME );
+        assertThat( report.getValue().getNameText() )
+                .isNull();
         assertThat( report.getValue().getElapsed() )
                 .isNotNull();
         assertThat( report.getValue().getSystemProperties() )
@@ -164,7 +175,7 @@ public class RunListenerAdapterTest
         report = ArgumentCaptor.forClass( SimpleReportEntry.class );
         verify( listener ).testSetCompleted( report.capture() );
         assertThat( report.getValue().getSourceName() )
-                .isEqualTo( MyTestClass.class.getName() );
+                .isEqualTo( className );
         assertThat( report.getValue().getName() )
                 .isNull();
         assertThat( report.getValue().getElapsed() )
@@ -179,14 +190,22 @@ public class RunListenerAdapterTest
 
     @Test
     public void displayNamesInClassAndMethods()
+            throws Exception
     {
         EngineDescriptor engine = newEngineDescriptor();
         TestDescriptor parent = newClassDescriptor( "parent" );
         engine.addChild( parent );
-        TestDescriptor child1 = newTestDescriptor( parent.getUniqueId().append( "test", "child1" ), "child1", TEST );
+
+        UniqueId id1 = parent.getUniqueId().append( MyTestClass.class.getName(), MY_NAMED_TEST_METHOD_NAME );
+        Method m1 = MyTestClass.class.getDeclaredMethod( MY_NAMED_TEST_METHOD_NAME );
+        TestDescriptor child1 = new TestMethodTestDescriptorWithDisplayName( id1, MyTestClass.class, m1, "dn1" );
         parent.addChild( child1 );
-        TestDescriptor child2 = newTestDescriptor( parent.getUniqueId().append( "test", "child2" ), "child2", TEST );
+
+        UniqueId id2 = parent.getUniqueId().append( MyTestClass.class.getName(), MY_TEST_METHOD_NAME );
+        Method m2 = MyTestClass.class.getDeclaredMethod( MY_TEST_METHOD_NAME, String.class );
+        TestDescriptor child2 = new TestMethodTestDescriptor( id2, MyTestClass.class, m2 );
         parent.addChild( child2 );
+
         TestPlan plan = TestPlan.from( singletonList( engine ) );
 
         InOrder inOrder = inOrder( listener );
@@ -198,6 +217,8 @@ public class RunListenerAdapterTest
         ArgumentCaptor<SimpleReportEntry> report = ArgumentCaptor.forClass( SimpleReportEntry.class );
         inOrder.verify( listener ).testSetStarting( report.capture() );
         assertThat( report.getValue().getSourceName() )
+                .isEqualTo( MyTestClass.class.getName() );
+        assertThat( report.getValue().getSourceText() )
                 .isEqualTo( "parent" );
         assertThat( report.getValue().getName() )
                 .isNull();
@@ -206,53 +227,76 @@ public class RunListenerAdapterTest
         verifyZeroInteractions( listener );
 
         adapter.executionStarted( TestIdentifier.from( child1 ) );
-        inOrder.verify( listener ).testStarting( new SimpleReportEntry( "parent", "child1" ) );
-        verifyNoMoreInteractions( listener );
+        inOrder.verify( listener )
+                .testStarting( new SimpleReportEntry( MyTestClass.class.getName(), "parent",
+                        MY_NAMED_TEST_METHOD_NAME, "dn1" ) );
+        inOrder.verifyNoMoreInteractions();
 
         adapter.executionFinished( TestIdentifier.from( child1 ), successful() );
         report = ArgumentCaptor.forClass( SimpleReportEntry.class );
         inOrder.verify( listener ).testSucceeded( report.capture() );
         assertThat( report.getValue().getSourceName() )
+                .isEqualTo( MyTestClass.class.getName() );
+        assertThat( report.getValue().getSourceText() )
                 .isEqualTo( "parent" );
         assertThat( report.getValue().getName() )
-                .isEqualTo( "child1" );
+                .isEqualTo( MY_NAMED_TEST_METHOD_NAME );
+        assertThat( report.getValue().getNameText() )
+                .isEqualTo( "dn1" );
         assertThat( report.getValue().getElapsed() )
                 .isNotNull();
         assertThat( report.getValue().getSystemProperties() )
                 .isEmpty();
-        verifyNoMoreInteractions( listener );
+        inOrder.verifyNoMoreInteractions();
 
         adapter.executionStarted( TestIdentifier.from( child2 ) );
-        inOrder.verify( listener ).testStarting( new SimpleReportEntry( "parent", "child2" ) );
-        verifyNoMoreInteractions( listener );
+        inOrder.verify( listener )
+                .testStarting( new SimpleReportEntry( MyTestClass.class.getName(), "parent",
+                        MY_TEST_METHOD_NAME, MY_TEST_METHOD_NAME + "(String)" ) );
+        inOrder.verifyNoMoreInteractions();
 
-        adapter.executionFinished( TestIdentifier.from( child2 ), successful() );
+        Exception assumptionFailure = new Exception();
+        adapter.executionFinished( TestIdentifier.from( child2 ), aborted( assumptionFailure ) );
         report = ArgumentCaptor.forClass( SimpleReportEntry.class );
-        inOrder.verify( listener ).testSucceeded( report.capture() );
+        inOrder.verify( listener ).testAssumptionFailure( report.capture() );
         assertThat( report.getValue().getSourceName() )
+                .isEqualTo( MyTestClass.class.getName() );
+        assertThat( report.getValue().getSourceText() )
                 .isEqualTo( "parent" );
         assertThat( report.getValue().getName() )
-                .isEqualTo( "child2" );
+                .isEqualTo( MY_TEST_METHOD_NAME );
+        assertThat( report.getValue().getNameText() )
+                .isEqualTo( MY_TEST_METHOD_NAME + "(String)" );
         assertThat( report.getValue().getElapsed() )
                 .isNotNull();
         assertThat( report.getValue().getSystemProperties() )
                 .isEmpty();
-        verifyNoMoreInteractions( listener );
+        assertThat( report.getValue().getStackTraceWriter() )
+                .isNotNull();
+        assertThat( report.getValue().getStackTraceWriter().getThrowable().getTarget() )
+                .isSameAs(assumptionFailure);
+        inOrder.verifyNoMoreInteractions();
 
         adapter.executionFinished( TestIdentifier.from( parent ), successful() );
         inOrder.verify( listener ).testSetCompleted( report.capture() );
         assertThat( report.getValue().getSourceName() )
+                .isEqualTo( MyTestClass.class.getName() );
+        assertThat( report.getValue().getSourceText() )
                 .isEqualTo( "parent" );
         assertThat( report.getValue().getName() )
                 .isNull();
+        assertThat( report.getValue().getNameText() )
+                .isNull();
         assertThat( report.getValue().getElapsed() )
                 .isNotNull();
         assertThat( report.getValue().getSystemProperties() )
                 .isNotEmpty();
-        verifyNoMoreInteractions( listener );
+        assertThat( report.getValue().getStackTraceWriter() )
+                .isNull();
+        inOrder.verifyNoMoreInteractions();
 
         adapter.executionFinished( TestIdentifier.from( engine ), successful() );
-        verifyNoMoreInteractions( listener );
+        inOrder.verifyNoMoreInteractions();
     }
 
     @Test
@@ -269,8 +313,15 @@ public class RunListenerAdapterTest
         TestPlan plan = TestPlan.from( singletonList( engine ) );
 
         adapter.testPlanExecutionStarted( plan );
+        assertThat( (TestPlan) getInternalState( adapter, "testPlan" ) )
+                .isSameAs( plan );
+        assertThat( (Map) getInternalState( adapter, "testStartTime" ) )
+                .isEmpty();
+
+
         adapter.executionStarted( TestIdentifier.from( engine ) );
-        verify( listener ).testStarting( new SimpleReportEntry( "engine", "engine" ) );
+        verify( listener )
+                .testStarting( new SimpleReportEntry( "engine", null, "engine", null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( engine ), successful() );
@@ -278,12 +329,25 @@ public class RunListenerAdapterTest
         verify( listener ).testSucceeded( report.capture() );
         assertThat( report.getValue().getSourceName() )
                 .isEqualTo( "engine" );
+        assertThat( report.getValue().getSourceText() )
+                .isNull();
         assertThat( report.getValue().getName() )
                 .isEqualTo( "engine" );
-        assertThat( report.getValue().getElapsed() )
+        assertThat( report.getValue().getNameText() )
+                .isNull();
+        assertThat(report.getValue().getElapsed())
                 .isNotNull();
+        assertThat( report.getValue().getStackTraceWriter() )
+                .isNull();
         assertThat( report.getValue().getSystemProperties() )
                 .isEmpty();
+
+        adapter.testPlanExecutionFinished( plan );
+        assertThat( (TestPlan) getInternalState( adapter, "testPlan" ) )
+                .isNull();
+        assertThat( (Map) getInternalState( adapter, "testStartTime" ) )
+                .isEmpty();
+
         verifyNoMoreInteractions( listener );
     }
 
@@ -405,20 +469,34 @@ public class RunListenerAdapterTest
 
         adapter.executionFinished( TestIdentifier.from( classDescriptor ), successful() );
 
-        verify( listener ).testSetStarting( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
+        String className = MyTestClass.class.getName();
+
+        verify( listener )
+                .testSetStarting( new SimpleReportEntry( className, null, null, null ) );
 
         ArgumentCaptor<SimpleReportEntry> report = ArgumentCaptor.forClass( SimpleReportEntry.class );
-        verify( listener ).testSetCompleted( report.capture() );
+        verify( listener )
+                .testSetCompleted(report.capture() );
+
         assertThat( report.getValue().getSourceName() )
-                .isEqualTo( MyTestClass.class.getName() );
+                .isEqualTo( className );
+        assertThat( report.getValue().getSourceText() )
+                .isNull();
         assertThat( report.getValue().getName() )
                 .isNull();
+        assertThat( report.getValue().getNameText() )
+                .isNull();
         assertThat( report.getValue().getStackTraceWriter() )
                 .isNull();
         assertThat( report.getValue().getElapsed() )
                 .isNotNull();
+        assertThat( report.getValue().getSystemProperties() )
+                .isNotEmpty();
 
-        verify( listener, never() ).testSucceeded( any() );
+        verify( listener, never() )
+                .testSucceeded(any() );
+
+        verifyNoMoreInteractions( listener );
     }
 
     @Test
@@ -438,6 +516,9 @@ public class RunListenerAdapterTest
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
         verify( listener ).testStarting( entryCaptor.capture() );
         assertEquals( parentDisplay, entryCaptor.getValue().getSourceName() );
+        assertNull(entryCaptor.getValue().getSourceText());
+        assertNull( entryCaptor.getValue().getName() );
+        assertNull( entryCaptor.getValue().getNameText() );
     }
 
     @Test
@@ -484,22 +565,8 @@ public class RunListenerAdapterTest
     public void displayNamesIgnoredInReport()
                     throws NoSuchMethodException
     {
-        class TestMethodTestDescriptorWithDisplayName extends AbstractTestDescriptor
-        {
-            private TestMethodTestDescriptorWithDisplayName( UniqueId uniqueId, Class<?> testClass, Method testMethod )
-            {
-                super( uniqueId, "some display name", MethodSource.from( testClass, testMethod ) );
-            }
-
-            @Override
-            public Type getType()
-            {
-                return Type.TEST;
-            }
-        }
-
         TestMethodTestDescriptorWithDisplayName descriptor = new TestMethodTestDescriptorWithDisplayName( newId(),
-                MyTestClass.class, MyTestClass.class.getDeclaredMethod( "myNamedTestMethod" ) );
+                MyTestClass.class, MyTestClass.class.getDeclaredMethod( "myNamedTestMethod" ), "some display name" );
 
         TestIdentifier factoryIdentifier = TestIdentifier.from( descriptor );
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
@@ -509,7 +576,10 @@ public class RunListenerAdapterTest
 
         ReportEntry value = entryCaptor.getValue();
 
-        assertEquals( "some display name", value.getName() );
+        assertEquals( MyTestClass.class.getName(), value.getSourceName() );
+        assertNull(value.getSourceText());
+        assertEquals( "myNamedTestMethod", value.getName() );
+        assertEquals( "some display name", value.getNameText() );
     }
 
     private static TestIdentifier newMethodIdentifier()
@@ -584,18 +654,6 @@ public class RunListenerAdapterTest
         return new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" );
     }
 
-    private TestDescriptor newTestDescriptor( UniqueId uniqueId, String displayName, Type type )
-    {
-        return new AbstractTestDescriptor( uniqueId, displayName )
-        {
-            @Override
-            public Type getType()
-            {
-                return type;
-            }
-        };
-    }
-
     private static TestIdentifier identifiersAsParentOnTestPlan(
                     TestPlan plan, TestDescriptor parent, TestDescriptor child )
     {
@@ -623,6 +681,7 @@ public class RunListenerAdapterTest
     }
 
     private static final String MY_TEST_METHOD_NAME = "myTestMethod";
+    private static final String MY_NAMED_TEST_METHOD_NAME = "myNamedTestMethod";
 
     private static class MyTestClass
     {
@@ -642,4 +701,19 @@ public class RunListenerAdapterTest
         {
         }
     }
+
+    static class TestMethodTestDescriptorWithDisplayName extends AbstractTestDescriptor
+    {
+        private TestMethodTestDescriptorWithDisplayName( UniqueId uniqueId,
+                                                         Class<?> testClass, Method testMethod, String displayName )
+        {
+            super( uniqueId, displayName, MethodSource.from( testClass, testMethod ) );
+        }
+
+        @Override
+        public Type getType()
+        {
+            return Type.TEST;
+        }
+    }
 }
diff --git a/surefire-providers/surefire-junit3/pom.xml b/surefire-providers/surefire-junit3/pom.xml
index 44d608e..d38b312 100644
--- a/surefire-providers/surefire-junit3/pom.xml
+++ b/surefire-providers/surefire-junit3/pom.xml
@@ -44,4 +44,37 @@
       <version>${project.version}</version>
     </dependency>
   </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
index bfdb4eb..d44d92b 100644
--- a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
+++ b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
@@ -133,9 +133,9 @@ public class JUnit3Provider
                                  Map<String, String> systemProperties )
         throws TestSetFailedException
     {
-        reporter.testSetStarting( new SimpleReportEntry( testSet.getName(), null ) );
+        reporter.testSetStarting( new SimpleReportEntry( testSet.getName(), null, null, null ) );
         testSet.execute( reporter, classLoader );
-        reporter.testSetCompleted( new SimpleReportEntry( testSet.getName(), null, systemProperties ) );
+        reporter.testSetCompleted( new SimpleReportEntry( testSet.getName(), null, null, null, systemProperties ) );
     }
 
     private TestsToRun scanClassPath()
diff --git a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/PojoTestSet.java b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/PojoTestSet.java
index 1b23322..7ee787e 100644
--- a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/PojoTestSet.java
+++ b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/PojoTestSet.java
@@ -116,7 +116,7 @@ public class PojoTestSet
         final String userFriendlyMethodName = methodName + '(' + ( args.length == 0 ? "" : "Reporter" ) + ')';
         final String testName = getTestName( userFriendlyMethodName );
 
-        reportManager.testStarting( new SimpleReportEntry( testClassName, testName ) );
+        reportManager.testStarting( new SimpleReportEntry( testClassName, null, testName, null ) );
 
         try
         {
@@ -125,7 +125,7 @@ public class PojoTestSet
         catch ( Throwable e )
         {
             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( testClassName, methodName, e );
-            reportManager.testFailed( withException( testClassName, testName, stackTraceWriter ) );
+            reportManager.testFailed( withException( testClassName, null, testName, null, stackTraceWriter ) );
 
             // A return value of true indicates to this class's executeTestMethods
             // method that it should abort and not attempt to execute
@@ -139,20 +139,20 @@ public class PojoTestSet
         try
         {
             method.invoke( testObject, args );
-            reportManager.testSucceeded( new SimpleReportEntry( testClassName, testName ) );
+            reportManager.testSucceeded( new SimpleReportEntry( testClassName, null, testName, null ) );
         }
         catch ( InvocationTargetException e )
         {
             Throwable t = e.getTargetException();
             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( testClassName, methodName, t );
-            reportManager.testFailed( withException( testClassName, testName, stackTraceWriter ) );
+            reportManager.testFailed( withException( testClassName, null, testName, null, stackTraceWriter ) );
             // Don't return  here, because tearDownFixture should be called even
             // if the test method throws an exception.
         }
         catch ( Throwable t )
         {
             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( testClassName, methodName, t );
-            reportManager.testFailed( withException( testClassName, testName, stackTraceWriter ) );
+            reportManager.testFailed( withException( testClassName, null, testName, null, stackTraceWriter ) );
             // Don't return  here, because tearDownFixture should be called even
             // if the test method throws an exception.
         }
@@ -165,7 +165,7 @@ public class PojoTestSet
         {
             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( testClassName, methodName, t );
             // Treat any exception from tearDownFixture as a failure of the test.
-            reportManager.testFailed( withException( testClassName, testName, stackTraceWriter ) );
+            reportManager.testFailed( withException( testClassName, null, testName, null, stackTraceWriter ) );
 
             // A return value of true indicates to this class's executeTestMethods
             // method that it should abort and not attempt to execute
diff --git a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/TestListenerInvocationHandler.java b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/TestListenerInvocationHandler.java
index e5d2232..db0563a 100644
--- a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/TestListenerInvocationHandler.java
+++ b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/TestListenerInvocationHandler.java
@@ -217,12 +217,12 @@ public class TestListenerInvocationHandler
         String className = extractClassName( description );
         String methodName = extractMethodName( description );
         StackTraceWriter stackTraceWriter = toStackTraceWriter( args );
-        return withException( className, methodName, stackTraceWriter );
+        return withException( className, null, methodName, null, stackTraceWriter );
     }
 
     private static SimpleReportEntry createStartEndReportEntry( Object[] args )
     {
         String description = args[0].toString();
-        return new SimpleReportEntry( extractClassName( description ), extractMethodName( description ) );
+        return new SimpleReportEntry( extractClassName( description ), null, extractMethodName( description ), null );
     }
 }
diff --git a/surefire-providers/surefire-junit4/pom.xml b/surefire-providers/surefire-junit4/pom.xml
index d3816ad..fc4d450 100644
--- a/surefire-providers/surefire-junit4/pom.xml
+++ b/surefire-providers/surefire-junit4/pom.xml
@@ -44,4 +44,40 @@
       <version>${project.version}</version>
     </dependency>
   </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+          <includes>
+            <include>**/JUnit4SuiteTest.java</include>
+          </includes>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-shadefire</artifactId>
+            <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
index f64b93d..b964933 100644
--- a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
+++ b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
@@ -231,7 +231,7 @@ public class JUnit4Provider
 
     private void executeTestSet( Class<?> clazz, RunListener reporter, Notifier notifier )
     {
-        final SimpleReportEntry report = new SimpleReportEntry( clazz.getName(), null, systemProps() );
+        final SimpleReportEntry report = new SimpleReportEntry( clazz.getName(), null, null, null, systemProps() );
         reporter.testSetStarting( report );
         try
         {
@@ -250,7 +250,7 @@ public class JUnit4Provider
                 String reportName = report.getName();
                 String reportSourceName = report.getSourceName();
                 PojoStackTraceWriter stackWriter = new PojoStackTraceWriter( reportSourceName, reportName, e );
-                reporter.testError( withException( reportSourceName, reportName, stackWriter ) );
+                reporter.testError( withException( reportSourceName, null, reportName, null, stackWriter ) );
             }
         }
         finally
diff --git a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
index bf6cafc..2e411b9 100644
--- a/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
+++ b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4ProviderTest.java
@@ -19,9 +19,9 @@ package org.apache.maven.surefire.junit4;
  * under the License.
  */
 
-import junit.framework.TestCase;
 import org.apache.maven.surefire.booter.BaseProviderFactory;
 import org.apache.maven.surefire.testset.TestRequest;
+import org.junit.Test;
 import org.junit.runner.Description;
 
 import java.util.HashMap;
@@ -29,14 +29,15 @@ import java.util.HashMap;
 import static java.util.Arrays.asList;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.runner.Description.createSuiteDescription;
 
 /**
  * @author Kristian Rosenvold
  */
 public class JUnit4ProviderTest
-    extends TestCase
 {
+    @Test
     public void testCreateProvider()
     {
         assertNotNull( getJUnit4Provider() );
@@ -51,6 +52,7 @@ public class JUnit4ProviderTest
         return new JUnit4Provider( providerParameters );
     }
 
+    @Test
     public void testShouldCreateDescription()
     {
         class A {
diff --git a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4SuiteTest.java
similarity index 71%
copy from surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
copy to surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4SuiteTest.java
index 547c381..2d4fb72 100644
--- a/surefire-providers/common-junit48/src/test/java/org/apache/maven/surefire/common/junit48/JUnit4SuiteTest.java
+++ b/surefire-providers/surefire-junit4/src/test/java/org/apache/maven/surefire/junit4/JUnit4SuiteTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.common.junit48;
+package org.apache.maven.surefire.junit4;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -21,25 +21,15 @@ package org.apache.maven.surefire.common.junit48;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import junit.framework.TestCase;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
- *
- * @author Tibor Digana (tibor17)
- * @since 2.19
  */
-@Suite.SuiteClasses( {
-    FilterFactoryTest.class,
-    JUnit48ReflectorTest.class,
-    JUnit48TestCheckerTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+        return new JUnit4TestAdapter( JUnit4ProviderTest.class );
     }
 }
diff --git a/surefire-providers/surefire-junit47/pom.xml b/surefire-providers/surefire-junit47/pom.xml
index 2c36b5a..d08b0b4 100644
--- a/surefire-providers/surefire-junit47/pom.xml
+++ b/surefire-providers/surefire-junit47/pom.xml
@@ -59,6 +59,21 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>jacoco-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <propertyName>jacoco.agent</propertyName>
+                </configuration>
+            </plugin>
+            <plugin>
                 <artifactId>maven-dependency-plugin</artifactId>
                 <executions>
                     <execution>
@@ -155,11 +170,19 @@
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
+                    <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
                     <redirectTestOutputToFile>true</redirectTestOutputToFile>
                     <includes>
                         <include>**/JUnit47SuiteTest.java</include>
                     </includes>
                 </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-shadefire</artifactId>
+                        <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
+                    </dependency>
+                </dependencies>
             </plugin>
         </plugins>
     </build>
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/NonConcurrentRunListener.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/NonConcurrentRunListener.java
index 4aaf1c8..135ba4e 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/NonConcurrentRunListener.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/NonConcurrentRunListener.java
@@ -65,13 +65,13 @@ public class NonConcurrentRunListener
     protected SimpleReportEntry createReportEntry( Description description )
     {
         ClassMethod classMethod = toClassMethod( description );
-        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getMethod() );
+        return new SimpleReportEntry( classMethod.getClazz(), null, classMethod.getMethod(), null );
     }
 
     private TestSetReportEntry createReportEntryForTestSet( Description description, Map<String, String> systemProps )
     {
         ClassMethod classMethod = toClassMethod( description );
-        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getClazz(), systemProps );
+        return new SimpleReportEntry( classMethod.getClazz(), null, null, null, systemProps );
     }
 
     private TestSetReportEntry createTestSetReportEntryStarted( Description description )
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/TestSet.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/TestSet.java
index e710cda..0ffcbe3 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/TestSet.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/TestSet.java
@@ -120,7 +120,7 @@ public class TestSet
 
     private TestSetReportEntry createReportEntry( Integer elapsed, Map<String, String> systemProps )
     {
-        return new SimpleReportEntry( testClassName, testClassName, null, elapsed, systemProps );
+        return new SimpleReportEntry( testClassName, null, testClassName, null, null, elapsed, systemProps );
     }
 
     public void incrementTestMethodCount()
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit47SuiteTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit47SuiteTest.java
index 9954627..02534ee 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit47SuiteTest.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit47SuiteTest.java
@@ -21,12 +21,12 @@ package org.apache.maven.surefire.junitcore;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 import org.apache.maven.surefire.junitcore.pc.OptimizedParallelComputerTest;
 import org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilderTest;
 import org.apache.maven.surefire.junitcore.pc.ParallelComputerUtilTest;
 import org.apache.maven.surefire.junitcore.pc.SchedulingStrategiesTest;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
 
 /**
  * Adapt the JUnit47 tests which use only annotations to the JUnit3 test suite.
@@ -34,27 +34,25 @@ import org.junit.runners.Suite;
  * @author Tibor Digana (tibor17)
  * @since 2.16
  */
-@Suite.SuiteClasses( {
-    Surefire746Test.class,
-    Surefire813IncorrectResultTest.class,
-    ParallelComputerUtilTest.class,
-    ParallelComputerBuilderTest.class,
-    SchedulingStrategiesTest.class,
-    OptimizedParallelComputerTest.class,
-    ConcurrentRunListenerTest.class,
-    ConfigurableParallelComputerTest.class,
-    JUnit4Reflector481Test.class,
-    JUnitCoreParametersTest.class,
-    JUnitCoreRunListenerTest.class,
-    MavenSurefireJUnit47RunnerTest.class,
-    MavenSurefireJUnit48RunnerTest.class,
-    TestMethodTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit47SuiteTest
+public class JUnit47SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit47SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTestSuite( ConcurrentRunListenerTest.class );
+        suite.addTestSuite( ConfigurableParallelComputerTest.class );
+        suite.addTestSuite( JUnitCoreRunListenerTest.class );
+        suite.addTestSuite( MavenSurefireJUnit47RunnerTest.class );
+        suite.addTestSuite( MavenSurefireJUnit48RunnerTest.class );
+        suite.addTestSuite( TestMethodTest.class );
+        suite.addTest( new JUnit4TestAdapter( Surefire746Test.class ) );
+        suite.addTest( new JUnit4TestAdapter( Surefire813IncorrectResultTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ParallelComputerUtilTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ParallelComputerBuilderTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( SchedulingStrategiesTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( OptimizedParallelComputerTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( JUnit4Reflector481Test.class ) );
+        suite.addTest( new JUnit4TestAdapter( JUnitCoreParametersTest.class ) );
+        return suite;
     }
 }
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
index 915b64a..583792f 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
@@ -20,6 +20,9 @@ package org.apache.maven.surefire.junitcore;
  */
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
+import org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessReporter;
+import org.apache.maven.plugin.surefire.extensions.SurefireStatelessTestsetInfoReporter;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
@@ -104,7 +107,8 @@ public class JUnitCoreTester
     {
         File target = new File( "./target" );
         File statisticsFile = new File( target, "TESTHASHxXML" );
-        return new StartupReportConfiguration( true, true, "PLAIN", false, true, target, false, null, statisticsFile,
-                false, 0, null, null, false );
+        return new StartupReportConfiguration( true, true, "PLAIN", false, target, false, null, statisticsFile,
+                false, 0, null, null, false, new SurefireStatelessReporter(), new SurefireConsoleOutputReporter(),
+                new SurefireStatelessTestsetInfoReporter() );
     }
 }
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/TestMethodTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/TestMethodTest.java
index cb50358..d2e1395 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/TestMethodTest.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/TestMethodTest.java
@@ -32,7 +32,7 @@ public class TestMethodTest
 {
     public void testTestFailure()
     {
-        ReportEntry reportEntry = new SimpleReportEntry( "a", "b" );
+        ReportEntry reportEntry = new SimpleReportEntry( "a", null, "b", null );
         TestMethod testMethod = new TestMethod( reportEntry, new TestSet( TestMethodTest.class.getName() ) );
         testMethod.testFailure( reportEntry );
         final int elapsed = testMethod.getElapsed();
diff --git a/surefire-providers/surefire-testng-utils/pom.xml b/surefire-providers/surefire-testng-utils/pom.xml
index b75979c..368e050 100644
--- a/surefire-providers/surefire-testng-utils/pom.xml
+++ b/surefire-providers/surefire-testng-utils/pom.xml
@@ -55,7 +55,25 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
         <dependencies>
           <dependency>
             <groupId>org.apache.maven.surefire</groupId>
diff --git a/surefire-providers/surefire-testng/pom.xml b/surefire-providers/surefire-testng/pom.xml
index 8c29441..df1bf4f 100644
--- a/surefire-providers/surefire-testng/pom.xml
+++ b/surefire-providers/surefire-testng/pom.xml
@@ -60,7 +60,25 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
+        </configuration>
         <dependencies>
           <dependency>
             <groupId>org.apache.maven.surefire</groupId>
diff --git a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGReporter.java b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGReporter.java
index 8cb7641..83c9d2b 100644
--- a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGReporter.java
+++ b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGReporter.java
@@ -73,7 +73,7 @@ public class TestNGReporter
     @Override
     public void onTestSuccess( ITestResult result )
     {
-        ReportEntry report = new SimpleReportEntry( result.getTestClass().getName(), result.getName() );
+        ReportEntry report = new SimpleReportEntry( result.getTestClass().getName(), null, result.getName(), null );
         reporter.testSucceeded( report );
     }
 
@@ -81,7 +81,7 @@ public class TestNGReporter
     public void onTestFailure( ITestResult result )
     {
         IClass clazz = result.getTestClass();
-        ReportEntry report = withException( clazz.getName(), result.getName(),
+        ReportEntry report = withException( clazz.getName(), null, result.getName(), null,
                 new PojoStackTraceWriter( clazz.getRealClass().getName(),
                         result.getMethod().getMethodName(),
                         result.getThrowable() ) );
@@ -92,9 +92,10 @@ public class TestNGReporter
     @Override
     public void onTestSkipped( ITestResult result )
     {
+        //noinspection ThrowableResultOfMethodCallIgnored
         Throwable t = result.getThrowable();
         String reason = t == null ? null : t.getMessage();
-        ReportEntry report = ignored( result.getTestClass().getName(), result.getName(), reason );
+        ReportEntry report = ignored( result.getTestClass().getName(), null, result.getName(), null, reason );
         reporter.testSkipped( report );
     }
 
@@ -102,7 +103,7 @@ public class TestNGReporter
     public void onTestFailedButWithinSuccessPercentage( ITestResult result )
     {
         IClass clazz = result.getTestClass();
-        ReportEntry report = withException( clazz.getName(), result.getName(),
+        ReportEntry report = withException( clazz.getName(), null, result.getName(), null,
                 new PojoStackTraceWriter( clazz.getRealClass().getName(),
                         result.getMethod().getMethodName(),
                         result.getThrowable() ) );
diff --git a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestSuite.java b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestSuite.java
index ffd13ae..27f46be 100644
--- a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestSuite.java
+++ b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestSuite.java
@@ -43,7 +43,7 @@ abstract class TestSuite
 
     final void startTestSuite( RunListener reporterManager )
     {
-        TestSetReportEntry report = new SimpleReportEntry( getSuiteName(), null );
+        TestSetReportEntry report = new SimpleReportEntry( getSuiteName(), null, null, null );
 
         try
         {
@@ -57,7 +57,7 @@ abstract class TestSuite
 
     final void finishTestSuite( RunListener reporterManager )
     {
-        SimpleReportEntry report = new SimpleReportEntry( getSuiteName(), null, systemProps() );
+        SimpleReportEntry report = new SimpleReportEntry( getSuiteName(), null, null, null, systemProps() );
         reporterManager.testSetCompleted( report );
     }
 }
diff --git a/surefire-report-parser/pom.xml b/surefire-report-parser/pom.xml
index 8c3f767..8620896 100644
--- a/surefire-report-parser/pom.xml
+++ b/surefire-report-parser/pom.xml
@@ -54,6 +54,21 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>jacoco-agent</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <propertyName>jacoco.agent</propertyName>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <dependencies>
           <dependency>
@@ -63,6 +78,7 @@
           </dependency>
         </dependencies>
         <configuration>
+          <argLine>${jvm.args.tests} ${jacoco.agent}</argLine>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
diff --git a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java b/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
index 142ca9d..8c143dd 100644
--- a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
+++ b/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/JUnit4SuiteTest.java
@@ -21,8 +21,8 @@ package org.apache.maven.plugins.surefire.report;
 
 import junit.framework.JUnit4TestAdapter;
 import junit.framework.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
 /**
  * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
@@ -30,17 +30,15 @@ import org.junit.runners.Suite;
  * @author Tibor Digana (tibor17)
  * @since 2.21.0
  */
-@Suite.SuiteClasses( {
-        ReportTestCaseTest.class,
-        ReportTestSuiteTest.class,
-        SurefireReportParserTest.class,
-        TestSuiteXmlParserTest.class
-} )
-@RunWith( Suite.class )
-public class JUnit4SuiteTest
+public class JUnit4SuiteTest extends TestCase
 {
     public static Test suite()
     {
-        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+        TestSuite suite = new TestSuite();
+        suite.addTestSuite( ReportTestCaseTest.class );
+        suite.addTestSuite( ReportTestSuiteTest.class );
+        suite.addTestSuite( SurefireReportParserTest.class );
+        suite.addTest( new JUnit4TestAdapter( TestSuiteXmlParserTest.class ) );
+        return suite;
     }
 }