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/01/19 17:44:39 UTC

[maven-surefire] 01/01: [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 junit5-displayname
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 496f4a289672e4f93dd2d4b70a4db6a5855ad9bb
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jan 5 15:48:30 2019 +0100

    [SUREFIRE-1546] JUnit 5 runner does not honor JUnit 5 display names
---
 .../surefire/StartupReportConfiguration.java       |   8 +-
 .../surefire/report/ConsoleOutputFileReporter.java |   2 +-
 .../maven/plugin/surefire/report/FileReporter.java |   4 +-
 .../surefire/report/StatelessXmlReporter.java      | 318 +++++++++++----------
 .../plugin/surefire/report/WrappedReportEntry.java |  15 +-
 .../surefire/runorder/StatisticsReporter.java      |   5 +-
 .../surefire/report/StatelessXmlReporterTest.java  |  85 +++---
 .../surefire/report/WrappedReportEntryTest.java    |  24 +-
 .../runorder/RunEntryStatisticsMapTest.java        | 128 ++++++++-
 pom.xml                                            |   4 +-
 .../surefire/runorder/RunEntryStatistics.java      |  47 +--
 .../surefire/runorder/RunEntryStatisticsMap.java   | 172 +++++++----
 .../maven/surefire/booter/ForkingRunListener.java  |   2 +-
 .../surefire/report/CategorizedReportEntry.java    |   4 +-
 .../apache/maven/surefire/report/ReportEntry.java  |  10 +-
 .../maven/surefire/report/SimpleReportEntry.java   |  39 ++-
 .../maven/surefire/util/internal}/ClassMethod.java |  32 ++-
 .../util/internal/TestClassMethodNameUtils.java    |  32 ++-
 .../runorder/ThreadedExecutionSchedulerTest.java   |  26 +-
 surefire-its/pom.xml                               |   2 +-
 .../surefire/its/JUnit4RerunFailingTestsIT.java    |   4 +-
 .../maven/surefire/its/JUnitPlatformEnginesIT.java |   8 +-
 .../Surefire1152RerunFailingTestsInSuiteIT.java    |   8 +-
 .../its/jiras/Surefire1209RerunAndForkCountIT.java |  36 +--
 .../its/jiras/Surefire943ReportContentIT.java      |   5 +-
 ...g.apache.maven.surefire.test.SucceedingTest.xml |  68 -----
 .../org.apache.maven.surefire.test.FailingTest.txt |  84 ------
 ...g.apache.maven.surefire.test.SucceedingTest.txt |   4 -
 ...refireReport.surefireReportTest.MyClassTest.xml |   0
 ...refireReport.surefireReportTest.MyClassTest.txt |   2 +-
 ...refireReport.surefireReportTest.MyClassTest.xml |   4 +-
 ...refireReport.surefireReportTest.MyClassTest.txt |   2 +-
 .../surefire-570-multipleReportDirectories/pom.xml |   4 +-
 .../surefire/report/PojoStackTraceWriter.java      |  24 ++
 .../surefire/common/junit4/JUnit4ProviderUtil.java |  38 +--
 .../surefire/common/junit4/JUnit4Reflector.java    |   2 +-
 .../surefire/common/junit4/JUnit4RunListener.java  |  55 +---
 .../common/junit4/JUnit4StackTraceWriter.java      |  23 +-
 .../maven/surefire/common/junit4/Notifier.java     |   4 +-
 .../common/junit4/JUnit4ProviderUtilTest.java      |  15 +-
 .../common/junit48/JUnit46StackTraceWriter.java    |  60 ----
 surefire-providers/surefire-junit-platform/pom.xml |   4 +
 .../surefire/junitplatform/RunListenerAdapter.java | 288 +++++++++----------
 .../surefire/junitplatform/JUnit47SuiteTest.java}  |  45 ++-
 .../junitplatform/JUnitPlatformProviderTest.java   |  35 +--
 .../junitplatform/RunListenerAdapterTest.java      | 135 +++++----
 .../maven/surefire/junit/JUnit3Provider.java       |   2 +-
 .../junit/TestListenerInvocationHandler.java       |  41 ++-
 .../maven/surefire/junit/JUnitTestSetTest.java     |  12 +-
 .../maven/surefire/junit4/JUnit4Provider.java      |   2 +-
 .../maven/surefire/junit4/TestResolverFilter.java  |   2 +-
 .../surefire/junitcore/JUnitCoreRunListener.java   |  20 +-
 .../junitcore/NonConcurrentRunListener.java        |  21 +-
 .../junitcore/JUnitCoreRunListenerTest.java        |   2 -
 .../maven/surefire/testng/TestNGReporter.java      |  32 +--
 .../apache/maven/surefire/testng/TestSuite.java    |   4 +-
 .../surefire/report/SurefireReportParser.java      |  29 +-
 .../surefire/report/SurefireReportParserTest.java  |  42 +--
 .../test-reports/com.shape.CircleTest.txt          |  16 +-
 .../test-reports/com.shapeclone.CircleTest.txt     |  16 +-
 60 files changed, 1017 insertions(+), 1140 deletions(-)

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 5089973..932eecd 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
@@ -31,7 +31,7 @@ import javax.annotation.Nonnull;
 import java.io.File;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
-import java.util.List;
+import java.util.Deque;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -76,7 +76,7 @@ public final class StartupReportConfiguration
 
     private final String xsdSchemaLocation;
 
-    private final Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistory
+    private final Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory
         = new ConcurrentHashMap<>();
 
     private final Charset encoding;
@@ -160,9 +160,9 @@ public final class StartupReportConfiguration
         // reporter, see Surefire1535TestNGParallelSuitesIT. The testClassMethodRunHistory should be isolated.
         // In the in-plugin execution of parallel JUnit4.7 with rerun the map must be shared because reports and
         // listeners are in ThreadLocal, see Surefire1122ParallelAndFlakyTestsIT.
-        Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistory
+        Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory
                 = isForkMode
-                ? new ConcurrentHashMap<String, Map<String, List<WrappedReportEntry>>>()
+                ? new ConcurrentHashMap<String, Deque<WrappedReportEntry>>()
                 : this.testClassMethodRunHistory;
 
         return isDisableXmlReport()
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 0ea0a78..f6645b1 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
@@ -150,7 +150,7 @@ public class ConsoleOutputFileReporter
         finally
         {
             // prepare <class>-output.txt report file
-            reportEntryName = reportEntry.getName();
+            reportEntryName = reportEntry.getSourceName();
         }
     }
 
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 5d25e6a..941b88a 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
@@ -62,7 +62,7 @@ public class FileReporter
 
     public void testSetCompleted( WrappedReportEntry report, TestSetStats testSetStats, List<String> testResults )
     {
-        File reportFile = getReportFile( reportsDirectory, report.getName(), reportNameSuffix, ".txt" );
+        File reportFile = getReportFile( reportsDirectory, report.getSourceName(), reportNameSuffix, ".txt" );
 
         File reportDir = reportFile.getParentFile();
 
@@ -74,7 +74,7 @@ public class FileReporter
             writer.write( "-------------------------------------------------------------------------------" );
             writer.newLine();
 
-            writer.write( "Test set: " + report.getName() );
+            writer.write( "Test set: " + report.getSourceName() );
             writer.newLine();
 
             writer.write( "-------------------------------------------------------------------------------" );
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 b89c4e9..c54943c 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
@@ -33,13 +33,14 @@ import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.util.Deque;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentLinkedDeque;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType;
@@ -96,11 +97,11 @@ public class StatelessXmlReporter
 
     // 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, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistoryMap;
+    private final Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistoryMap;
 
     public StatelessXmlReporter( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace,
                                  int rerunFailingTestsCount,
-                                 Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistoryMap,
+                                 Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistoryMap,
                                  String xsdSchemaLocation )
     {
         this.reportsDirectory = reportsDirectory;
@@ -113,15 +114,8 @@ public class StatelessXmlReporter
 
     public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
     {
-        String testClassName = testSetReportEntry.getName();
-
-        Map<String, List<WrappedReportEntry>> methodRunHistoryMap = getAddMethodRunHistoryMap( testClassName );
-
-        // Update testClassMethodRunHistoryMap
-        for ( WrappedReportEntry methodEntry : testSetStats.getReportEntries() )
-        {
-            getAddMethodEntryList( methodRunHistoryMap, methodEntry );
-        }
+        Map<String, Map<String, List<WrappedReportEntry>>> classMethodStatistics =
+                arrangeMethodStatistics( testSetReportEntry, testSetStats );
 
         OutputStream outputStream = getOutputStream( testSetReportEntry );
         try ( OutputStreamWriter fw = getWriter( outputStream ) )
@@ -129,111 +123,18 @@ public class StatelessXmlReporter
             XMLWriter ppw = new PrettyPrintXMLWriter( fw );
             ppw.setEncoding( UTF_8.name() );
 
-            createTestSuiteElement( ppw, testSetReportEntry, testSetStats, testSetReportEntry.elapsedTimeAsString() );
+            createTestSuiteElement( ppw, testSetReportEntry, testSetStats ); // TestSuite
 
             showProperties( ppw, testSetReportEntry.getSystemProperties() );
 
-            // Iterate through all the test methods in the test class
-            for ( Entry<String, List<WrappedReportEntry>> entry : methodRunHistoryMap.entrySet() )
+            for ( Entry<String, Map<String, List<WrappedReportEntry>>> statistics : classMethodStatistics.entrySet() )
             {
-                List<WrappedReportEntry> methodEntryList = entry.getValue();
-                if ( methodEntryList == null )
-                {
-                    throw new IllegalStateException( "Get null test method run history" );
-                }
-
-                if ( !methodEntryList.isEmpty() )
+                for ( Entry<String, List<WrappedReportEntry>> thisMethodRuns : statistics.getValue().entrySet() )
                 {
-                    if ( rerunFailingTestsCount > 0 )
-                    {
-                        switch ( getTestResultType( methodEntryList ) )
-                        {
-                            case success:
-                                for ( WrappedReportEntry methodEntry : methodEntryList )
-                                {
-                                    if ( methodEntry.getReportEntryType() == SUCCESS )
-                                    {
-                                        startTestElement( ppw, methodEntry, reportNameSuffix,
-                                                methodEntryList.get( 0 ).elapsedTimeAsString() );
-                                        ppw.endElement();
-                                    }
-                                }
-                                break;
-                            case error:
-                            case failure:
-                                // When rerunFailingTestsCount is set to larger than 0
-                                startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix,
-                                        methodEntryList.get( 0 ).elapsedTimeAsString() );
-                                boolean firstRun = true;
-                                for ( WrappedReportEntry singleRunEntry : methodEntryList )
-                                {
-                                    if ( firstRun )
-                                    {
-                                        firstRun = false;
-                                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
-                                                singleRunEntry.getReportEntryType().getXmlTag(), false );
-                                        createOutErrElements( fw, ppw, singleRunEntry, outputStream );
-                                    }
-                                    else
-                                    {
-                                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
-                                                singleRunEntry.getReportEntryType().getRerunXmlTag(), true );
-                                    }
-                                }
-                                ppw.endElement();
-                                break;
-                            case flake:
-                                String runtime = "";
-                                // Get the run time of the first successful run
-                                for ( WrappedReportEntry singleRunEntry : methodEntryList )
-                                {
-                                    if ( singleRunEntry.getReportEntryType() == SUCCESS )
-                                    {
-                                        runtime = singleRunEntry.elapsedTimeAsString();
-                                        break;
-                                    }
-                                }
-                                startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix, runtime );
-                                for ( WrappedReportEntry singleRunEntry : methodEntryList )
-                                {
-                                    if ( singleRunEntry.getReportEntryType() != SUCCESS )
-                                    {
-                                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
-                                                singleRunEntry.getReportEntryType().getFlakyXmlTag(), true );
-                                    }
-                                }
-                                ppw.endElement();
-
-                                break;
-                            case skipped:
-                                startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix,
-                                        methodEntryList.get( 0 ).elapsedTimeAsString() );
-                                getTestProblems( fw, ppw, methodEntryList.get( 0 ), trimStackTrace, outputStream,
-                                        methodEntryList.get( 0 ).getReportEntryType().getXmlTag(), false );
-                                ppw.endElement();
-                                break;
-                            default:
-                                throw new IllegalStateException( "Get unknown test result type" );
-                        }
-                    }
-                    else
-                    {
-                        // rerunFailingTestsCount is smaller than 1, but for some reasons a test could be run
-                        // for more than once
-                        for ( WrappedReportEntry methodEntry : methodEntryList )
-                        {
-                            startTestElement( ppw, methodEntry, reportNameSuffix, methodEntry.elapsedTimeAsString() );
-                            if ( methodEntry.getReportEntryType() != SUCCESS )
-                            {
-                                getTestProblems( fw, ppw, methodEntry, trimStackTrace, outputStream,
-                                        methodEntry.getReportEntryType().getXmlTag(), false );
-                                createOutErrElements( fw, ppw, methodEntry, outputStream );
-                            }
-                            ppw.endElement();
-                        }
-                    }
+                    serializeTestClass( outputStream, fw, ppw, thisMethodRuns.getValue() );
                 }
             }
+
             ppw.endElement(); // TestSuite
         }
         catch ( Exception e )
@@ -246,6 +147,143 @@ public class StatelessXmlReporter
         }
     }
 
+    private Map<String, Map<String, List<WrappedReportEntry>>> arrangeMethodStatistics(
+            WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
+    {
+        Map<String, Map<String, List<WrappedReportEntry>>> classMethodStatistics = new LinkedHashMap<>();
+        for ( WrappedReportEntry methodEntry : aggregateCacheFromMultipleReruns( testSetReportEntry, testSetStats ) )
+        {
+            String testClassName = methodEntry.getSourceName();
+            Map<String, List<WrappedReportEntry>> stats = classMethodStatistics.get( testClassName );
+            if ( stats == null )
+            {
+                stats = new LinkedHashMap<>();
+                classMethodStatistics.put( testClassName, stats );
+            }
+            String methodName = methodEntry.getName();
+            List<WrappedReportEntry> methodRuns = stats.get( methodName );
+            if ( methodRuns == null )
+            {
+                methodRuns = new ArrayList<>();
+                stats.put( methodName, methodRuns );
+            }
+            methodRuns.add( methodEntry );
+        }
+        return classMethodStatistics;
+    }
+
+    private Deque<WrappedReportEntry> aggregateCacheFromMultipleReruns( WrappedReportEntry testSetReportEntry,
+                                                                       TestSetStats testSetStats )
+    {
+        String suiteClassName = testSetReportEntry.getSourceName();
+        Deque<WrappedReportEntry> methodRunHistory = getAddMethodRunHistoryMap( suiteClassName );
+        methodRunHistory.addAll( testSetStats.getReportEntries() );
+        return methodRunHistory;
+    }
+
+    private void serializeTestClass( OutputStream outputStream, OutputStreamWriter fw, XMLWriter ppw,
+                                     List<WrappedReportEntry> methodEntries )
+    {
+        if ( rerunFailingTestsCount > 0 )
+        {
+            serializeTestClassWithRerun( outputStream, fw, ppw, methodEntries );
+        }
+        else
+        {
+            // rerunFailingTestsCount is smaller than 1, but for some reasons a test could be run
+            // for more than once
+            serializeTestClassWithoutRerun( outputStream, fw, ppw, methodEntries );
+        }
+    }
+
+    private void serializeTestClassWithoutRerun( OutputStream outputStream, OutputStreamWriter fw, XMLWriter ppw,
+                                                 List<WrappedReportEntry> methodEntries )
+    {
+        for ( WrappedReportEntry methodEntry : methodEntries )
+        {
+            startTestElement( ppw, methodEntry );
+            if ( methodEntry.getReportEntryType() != SUCCESS )
+            {
+                getTestProblems( fw, ppw, methodEntry, trimStackTrace, outputStream,
+                        methodEntry.getReportEntryType().getXmlTag(), false );
+                createOutErrElements( fw, ppw, methodEntry, outputStream );
+            }
+            ppw.endElement();
+        }
+    }
+
+    private void serializeTestClassWithRerun( OutputStream outputStream, OutputStreamWriter fw, XMLWriter ppw,
+                                              List<WrappedReportEntry> methodEntries )
+    {
+        WrappedReportEntry firstMethodEntry = methodEntries.get( 0 );
+        switch ( getTestResultType( methodEntries ) )
+        {
+            case success:
+                for ( WrappedReportEntry methodEntry : methodEntries )
+                {
+                    if ( methodEntry.getReportEntryType() == SUCCESS )
+                    {
+                        startTestElement( ppw, methodEntry );
+                        ppw.endElement();
+                    }
+                }
+                break;
+            case error:
+            case failure:
+                // When rerunFailingTestsCount is set to larger than 0
+                startTestElement( ppw, firstMethodEntry );
+                boolean firstRun = true;
+                for ( WrappedReportEntry singleRunEntry : methodEntries )
+                {
+                    if ( firstRun )
+                    {
+                        firstRun = false;
+                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
+                                singleRunEntry.getReportEntryType().getXmlTag(), false );
+                        createOutErrElements( fw, ppw, singleRunEntry, outputStream );
+                    }
+                    else
+                    {
+                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
+                                singleRunEntry.getReportEntryType().getRerunXmlTag(), true );
+                    }
+                }
+                ppw.endElement();
+                break;
+            case flake:
+                WrappedReportEntry successful = null;
+                // Get the run time of the first successful run
+                for ( WrappedReportEntry singleRunEntry : methodEntries )
+                {
+                    if ( singleRunEntry.getReportEntryType() == SUCCESS )
+                    {
+                        successful = singleRunEntry;
+                        break;
+                    }
+                }
+                WrappedReportEntry firstOrSuccessful = successful == null ? methodEntries.get( 0 ) : successful;
+                startTestElement( ppw, firstOrSuccessful );
+                for ( WrappedReportEntry singleRunEntry : methodEntries )
+                {
+                    if ( singleRunEntry.getReportEntryType() != SUCCESS )
+                    {
+                        getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
+                                singleRunEntry.getReportEntryType().getFlakyXmlTag(), true );
+                    }
+                }
+                ppw.endElement();
+                break;
+            case skipped:
+                startTestElement( ppw, firstMethodEntry );
+                getTestProblems( fw, ppw, firstMethodEntry, trimStackTrace, outputStream,
+                        firstMethodEntry.getReportEntryType().getXmlTag(), false );
+                ppw.endElement();
+                break;
+            default:
+                throw new IllegalStateException( "Get unknown test result type" );
+        }
+    }
+
     /**
      * Clean testClassMethodRunHistoryMap
      */
@@ -271,20 +309,20 @@ public class StatelessXmlReporter
         return DefaultReporterFactory.getTestResultType( testResultTypeList, rerunFailingTestsCount );
     }
 
-    private Map<String, List<WrappedReportEntry>> getAddMethodRunHistoryMap( String testClassName )
+    private Deque<WrappedReportEntry> getAddMethodRunHistoryMap( String testClassName )
     {
-        Map<String, List<WrappedReportEntry>> methodRunHistoryMap = testClassMethodRunHistoryMap.get( testClassName );
-        if ( methodRunHistoryMap == null )
+        Deque<WrappedReportEntry> methodRunHistory = testClassMethodRunHistoryMap.get( testClassName );
+        if ( methodRunHistory == null )
         {
-            methodRunHistoryMap = Collections.synchronizedMap( new LinkedHashMap<String, List<WrappedReportEntry>>() );
-            testClassMethodRunHistoryMap.put( testClassName, methodRunHistoryMap );
+            methodRunHistory = new ConcurrentLinkedDeque<>();
+            testClassMethodRunHistoryMap.put( testClassName == null ? "null" : testClassName, methodRunHistory );
         }
-        return methodRunHistoryMap;
+        return methodRunHistory;
     }
 
     private OutputStream getOutputStream( WrappedReportEntry testSetReportEntry )
     {
-        File reportFile = getReportFile( testSetReportEntry, reportsDirectory, reportNameSuffix );
+        File reportFile = getReportFile( testSetReportEntry );
 
         File reportDir = reportFile.getParentFile();
 
@@ -306,50 +344,33 @@ public class StatelessXmlReporter
         return new OutputStreamWriter( fos, UTF_8 );
     }
 
-    private static void getAddMethodEntryList( Map<String, List<WrappedReportEntry>> methodRunHistoryMap,
-                                               WrappedReportEntry methodEntry )
+    private File getReportFile( ReportEntry report )
     {
-        List<WrappedReportEntry> methodEntryList = methodRunHistoryMap.get( methodEntry.getName() );
-        if ( methodEntryList == null )
-        {
-            methodEntryList = new ArrayList<>();
-            methodRunHistoryMap.put( methodEntry.getName(), methodEntryList );
-        }
-        methodEntryList.add( methodEntry );
-    }
-
-    private static File getReportFile( ReportEntry report, File reportsDirectory, String reportNameSuffix )
-    {
-        String reportName = "TEST-" + report.getName();
+        String reportName = "TEST-" + report.getSourceName();
         String customizedReportName = isBlank( reportNameSuffix ) ? reportName : reportName + "-" + reportNameSuffix;
         return new File( reportsDirectory, stripIllegalFilenameChars( customizedReportName + ".xml" ) );
     }
 
-    private static void startTestElement( XMLWriter ppw, WrappedReportEntry report, String reportNameSuffix,
-                                          String timeAsString )
+    private void startTestElement( XMLWriter ppw, WrappedReportEntry report )
     {
         ppw.startElement( "testcase" );
-        ppw.addAttribute( "name", report.getReportName() );
+        ppw.addAttribute( "name", report.getName() == null ? "" : extraEscape( report.getName(), true ) );
+
         if ( report.getGroup() != null )
         {
             ppw.addAttribute( "group", report.getGroup() );
         }
-        if ( report.getSourceName() != null )
+
+        String className = report.getReportName( reportNameSuffix );
+        if ( className != null )
         {
-            if ( reportNameSuffix != null && !reportNameSuffix.isEmpty() )
-            {
-                ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
-            }
-            else
-            {
-                ppw.addAttribute( "classname", report.getSourceName() );
-            }
+            ppw.addAttribute( "classname", extraEscape( className, true ) );
         }
-        ppw.addAttribute( "time", timeAsString );
+
+        ppw.addAttribute( "time", report.elapsedTimeAsString() );
     }
 
-    private void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, TestSetStats testSetStats,
-                                         String timeAsString )
+    private void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, TestSetStats testSetStats )
     {
         ppw.startElement( "testsuite" );
 
@@ -357,14 +378,15 @@ public class StatelessXmlReporter
         ppw.addAttribute( "xsi:noNamespaceSchemaLocation", xsdSchemaLocation );
         ppw.addAttribute( "version", "3.0" );
 
-        ppw.addAttribute( "name", report.getReportName( reportNameSuffix ) );
+        String reportName = report.getReportName( reportNameSuffix );
+        ppw.addAttribute( "name", reportName == null ? "" : extraEscape( reportName, true ) );
 
         if ( report.getGroup() != null )
         {
             ppw.addAttribute( "group", report.getGroup() );
         }
 
-        ppw.addAttribute( "time", timeAsString );
+        ppw.addAttribute( "time", report.elapsedTimeAsString() );
 
         ppw.addAttribute( "tests", String.valueOf( testSetStats.getCompletedCount() ) );
 
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 3426e3a..efec3a6 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
@@ -28,6 +28,7 @@ import java.util.Map;
 
 import static java.util.Collections.unmodifiableMap;
 import static org.apache.maven.plugin.surefire.report.ReporterUtils.formatElapsedTime;
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
 import static org.apache.maven.surefire.util.internal.StringUtils.NL;
 
 /**
@@ -74,6 +75,12 @@ public class WrappedReportEntry
         return elapsed;
     }
 
+    @Override
+    public int getElapsed( int fallback )
+    {
+        return elapsed == null ? fallback : elapsed;
+    }
+
     public ReportEntryType getReportEntryType()
     {
         return reportEntryType;
@@ -137,13 +144,12 @@ public class WrappedReportEntry
 
     public String getReportName()
     {
-        final int i = getName().lastIndexOf( "(" );
-        return i > 0 ? getName().substring( 0, i ) : getName();
+        return getSourceName();
     }
 
     public String getReportName( String suffix )
     {
-        return suffix != null && !suffix.isEmpty() ? getReportName() + "(" + suffix + ")" : getReportName();
+        return isBlank( suffix ) ? getReportName() : getReportName() + "(" + suffix + ")";
     }
 
     public String getOutput( boolean trimStackTrace )
@@ -160,7 +166,8 @@ public class WrappedReportEntry
 
     public String getElapsedTimeSummary()
     {
-        return getName() + "  " + getElapsedTimeVerbose();
+        String description = getName() == null ? getSourceName() : getClassMethodName();
+        return description + "  " + getElapsedTimeVerbose();
     }
 
     public boolean isErrorOrFailure()
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
index a53db02..2d9a175 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/runorder/StatisticsReporter.java
@@ -20,7 +20,8 @@ package org.apache.maven.plugin.surefire.runorder;
  */
 
 import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.IOException;
+
 import org.apache.maven.surefire.report.ReportEntry;
 
 import static org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMap.fromFile;
@@ -54,7 +55,7 @@ public class StatisticsReporter
         {
             newResults.serialize( dataFile );
         }
-        catch ( FileNotFoundException e )
+        catch ( IOException e )
         {
             throw new RuntimeException( e );
         }
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 6e6f3d5..315b75f 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
@@ -32,9 +32,8 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.Deque;
 import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -43,7 +42,7 @@ import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
 
 @SuppressWarnings( "ResultOfMethodCallIgnored" )
 public class StatelessXmlReporterTest
-    extends TestCase
+        extends TestCase
 {
     private static final String XSD =
             "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd";
@@ -59,7 +58,7 @@ public class StatelessXmlReporterTest
 
     @Override
     protected void setUp()
-        throws Exception
+            throws Exception
     {
         stats = new TestSetStats( false, true );
         rerunStats = new TestSetStats( false, true );
@@ -84,29 +83,29 @@ public class StatelessXmlReporterTest
     public void testFileNameWithoutSuffix()
     {
         StatelessXmlReporter reporter =
-            new StatelessXmlReporter( reportDir, null, false, 0,
-                                      new ConcurrentHashMap<String, Map<String, List<WrappedReportEntry>>>(), XSD );
+                new StatelessXmlReporter( reportDir, null, false, 0,
+                        new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD );
         reporter.cleanTestHistoryMap();
 
         ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), getClass().getName(), 12 );
-        WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
+        WrappedReportEntry testSetReportEntry = new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS,
+                12, null, null, systemProps() );
         stats.testSucceeded( testSetReportEntry );
         reporter.testSetCompleted( testSetReportEntry, stats );
 
         expectedReportFile = new File( reportDir, "TEST-" + getClass().getName() + ".xml" );
         assertTrue( "Report file (" + expectedReportFile.getAbsolutePath() + ") doesn't exist",
-                    expectedReportFile.exists() );
+                expectedReportFile.exists() );
     }
 
 
     public void testAllFieldsSerialized()
-        throws IOException
+            throws IOException
     {
         ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 );
         WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
-        expectedReportFile = new File( reportDir, "TEST-" + TEST_ONE + ".xml" );
+                new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
+        expectedReportFile = new File( reportDir, "TEST-" + getClass().getName() + ".xml" );
 
         stats.testSucceeded( testSetReportEntry );
         StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "A fud msg", "trimmed", "fail at foo" );
@@ -133,12 +132,12 @@ public class StatelessXmlReporterTest
         byte[] stdErrBytes = (stdErrPrefix + "?&-&amp;&#163;\u0020\u0000\u001F").getBytes();
         stdErr.write( stdErrBytes, 0, stdErrBytes.length );
         WrappedReportEntry t2 =
-            new WrappedReportEntry( new SimpleReportEntry( Inner.class.getName(), TEST_TWO, stackTraceWriter, 13 ),
-                                    ReportEntryType.ERROR, 13, stdOut, stdErr );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriter, 13 ),
+                        ReportEntryType.ERROR, 13, stdOut, stdErr );
 
         stats.testSucceeded( t2 );
         StatelessXmlReporter reporter = new StatelessXmlReporter( reportDir, null, false, 0,
-                        new ConcurrentHashMap<String, Map<String, List<WrappedReportEntry>>>(), XSD );
+                new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(), XSD );
         reporter.testSetCompleted( testSetReportEntry, stats );
 
         FileInputStream fileInputStream = new FileInputStream( expectedReportFile );
@@ -153,14 +152,14 @@ public class StatelessXmlReporterTest
 
         Xpp3Dom[] testcase = testSuite.getChildren( "testcase" );
         Xpp3Dom tca = testcase[0];
-        assertEquals( TEST_ONE, tca.getAttribute( "name" ) ); // Hopefully same order on jdk5
+        assertEquals( TEST_ONE, tca.getAttribute( "name" ) );
         assertEquals( "0.012", tca.getAttribute( "time" ) );
         assertEquals( getClass().getName(), tca.getAttribute( "classname" ) );
 
         Xpp3Dom tcb = testcase[1];
         assertEquals( TEST_TWO, tcb.getAttribute( "name" ) );
         assertEquals( "0.013", tcb.getAttribute( "time" ) );
-        assertEquals( Inner.class.getName(), tcb.getAttribute( "classname" ) );
+        assertEquals( getClass().getName(), tcb.getAttribute( "classname" ) );
         Xpp3Dom errorNode = tcb.getChild( "error" );
         assertNotNull( errorNode );
         assertEquals( "A fud msg", errorNode.getAttribute( "message" ) );
@@ -172,19 +171,18 @@ public class StatelessXmlReporterTest
     }
 
     public void testOutputRerunFlakyFailure()
-        throws IOException
+            throws IOException
     {
-        ReportEntry reportEntry = new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 );
-
         WrappedReportEntry testSetReportEntry =
-            new WrappedReportEntry( reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps() );
-        expectedReportFile = new File( reportDir, "TEST-" + TEST_ONE + ".xml" );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_ONE, 12 ),
+                        ReportEntryType.SUCCESS, 12, null, null, systemProps() );
+        expectedReportFile = new File( reportDir, "TEST-" + getClass().getName() + ".xml" );
 
         stats.testSucceeded( testSetReportEntry );
         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter( "A fud msg", "trimmed",
-                                                                                 "fail at foo" );
+                "fail at foo" );
         StackTraceWriter stackTraceWriterTwo =
-            new DeserializedStacktraceWriter( "A fud msg two", "trimmed two", "fail at foo two" );
+                new DeserializedStacktraceWriter( "A fud msg two", "trimmed two", "fail at foo two" );
 
         String firstRunOut = "first run out";
         String firstRunErr = "first run err";
@@ -192,24 +190,24 @@ public class StatelessXmlReporterTest
         String secondRunErr = "second run err";
 
         WrappedReportEntry testTwoFirstError =
-            new WrappedReportEntry( new SimpleReportEntry( Inner.class.getName(), TEST_TWO, stackTraceWriterOne, 5 ),
-                                    ReportEntryType.ERROR, 5, createStdOutput( firstRunOut ),
-                                    createStdOutput( firstRunErr ) );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriterOne, 5 ),
+                        ReportEntryType.ERROR, 5, createStdOutput( firstRunOut ),
+                        createStdOutput( firstRunErr ) );
 
         WrappedReportEntry testTwoSecondError =
-            new WrappedReportEntry( new SimpleReportEntry( Inner.class.getName(), TEST_TWO, stackTraceWriterTwo, 13 ),
-                                    ReportEntryType.ERROR, 13, createStdOutput( secondRunOut ),
-                                    createStdOutput( secondRunErr ) );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_TWO, stackTraceWriterTwo, 13 ),
+                        ReportEntryType.ERROR, 13, createStdOutput( secondRunOut ),
+                        createStdOutput( secondRunErr ) );
 
         WrappedReportEntry testThreeFirstRun =
-            new WrappedReportEntry( new SimpleReportEntry( Inner.class.getName(), TEST_THREE, stackTraceWriterOne, 13 ),
-                                    ReportEntryType.FAILURE, 13, createStdOutput( firstRunOut ),
-                                    createStdOutput( firstRunErr ) );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_THREE, stackTraceWriterOne, 13 ),
+                        ReportEntryType.FAILURE, 13, createStdOutput( firstRunOut ),
+                        createStdOutput( firstRunErr ) );
 
         WrappedReportEntry testThreeSecondRun =
-            new WrappedReportEntry( new SimpleReportEntry( Inner.class.getName(), TEST_THREE, stackTraceWriterTwo, 2 ),
-                                    ReportEntryType.SUCCESS, 2, createStdOutput( secondRunOut ),
-                                    createStdOutput( secondRunErr ) );
+                new WrappedReportEntry( new SimpleReportEntry( getClass().getName(), TEST_THREE, stackTraceWriterTwo, 2 ),
+                        ReportEntryType.SUCCESS, 2, createStdOutput( secondRunOut ),
+                        createStdOutput( secondRunErr ) );
 
         stats.testSucceeded( testTwoFirstError );
         stats.testSucceeded( testThreeFirstRun );
@@ -217,8 +215,8 @@ public class StatelessXmlReporterTest
         rerunStats.testSucceeded( testThreeSecondRun );
 
         StatelessXmlReporter reporter =
-            new StatelessXmlReporter( reportDir, null, false, 1,
-                                      new HashMap<String, Map<String, List<WrappedReportEntry>>>(), XSD );
+                new StatelessXmlReporter( reportDir, null, false, 1,
+                        new HashMap<String, Deque<WrappedReportEntry>>(), XSD );
 
         reporter.testSetCompleted( testSetReportEntry, stats );
         reporter.testSetCompleted( testSetReportEntry, rerunStats );
@@ -244,7 +242,7 @@ public class StatelessXmlReporterTest
         assertEquals( TEST_TWO, testCaseTwo.getAttribute( "name" ) );
         // Run time for a rerun failing test is the run time of the first run
         assertEquals( "0.005", testCaseTwo.getAttribute( "time" ) );
-        assertEquals( Inner.class.getName(), testCaseTwo.getAttribute( "classname" ) );
+        assertEquals( getClass().getName(), testCaseTwo.getAttribute( "classname" ) );
         Xpp3Dom errorNode = testCaseTwo.getChild( "error" );
         Xpp3Dom rerunErrorNode = testCaseTwo.getChild( "rerunError" );
         assertNotNull( errorNode );
@@ -266,7 +264,7 @@ public class StatelessXmlReporterTest
         assertEquals( TEST_THREE, testCaseThree.getAttribute( "name" ) );
         // Run time for a flaky test is the run time of the first successful run
         assertEquals( "0.002", testCaseThree.getAttribute( "time" ) );
-        assertEquals( Inner.class.getName(), testCaseThree.getAttribute( "classname" ) );
+        assertEquals( getClass().getName(), testCaseThree.getAttribute( "classname" ) );
         Xpp3Dom flakyFailureNode = testCaseThree.getChild( "flakyFailure" );
         assertNotNull( flakyFailureNode );
         assertEquals( firstRunOut, flakyFailureNode.getChild( "system-out" ).getValue() );
@@ -283,15 +281,10 @@ public class StatelessXmlReporterTest
     }
 
     private Utf8RecodingDeferredFileOutputStream createStdOutput( String content )
-        throws IOException
+            throws IOException
     {
         Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream( "fds2" );
         stdOut.write( content.getBytes(), 0, content.length() );
         return stdOut;
     }
-
-    class Inner
-    {
-
-    }
 }
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 030fc2f..61080a1 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
@@ -29,45 +29,39 @@ import junit.framework.TestCase;
 public class WrappedReportEntryTest
     extends TestCase
 {
-
     public void testClassNameOnly()
-        throws Exception
     {
-        String category = "surefire.testcase.JunitParamsTest";
+        String className = "surefire.testcase.JunitParamsTest";
         WrappedReportEntry wr =
-            new WrappedReportEntry( new SimpleReportEntry( "fud", category ), null, 12, null, null );
+            new WrappedReportEntry( new SimpleReportEntry( className, null ), null, 12, null, null );
         final String reportName = wr.getReportName();
         assertEquals( "surefire.testcase.JunitParamsTest", reportName );
     }
 
     public void testRegular()
     {
-        ReportEntry reportEntry = new SimpleReportEntry( "fud", "testSum(surefire.testcase.NonJunitParamsTest)" );
+        ReportEntry reportEntry = new SimpleReportEntry( "surefire.testcase.JunitParamsTest", "testSum" );
         WrappedReportEntry wr = new WrappedReportEntry( reportEntry, null, 12, null, null );
         final String reportName = wr.getReportName();
-        assertEquals( "testSum", reportName );
+        assertEquals( "surefire.testcase.JunitParamsTest", reportName );
     }
 
     public void testGetReportNameWithParams()
-        throws Exception
     {
-        String category = "[0] 1\u002C 2\u002C 3 (testSum)(surefire.testcase.JunitParamsTest)";
-        ReportEntry reportEntry = new SimpleReportEntry( "fud", category );
+        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();
         assertEquals( "[0] 1, 2, 3 (testSum)", reportName );
     }
 
     public void testElapsed()
-        throws Exception
     {
-        String category = "[0] 1\u002C 2\u002C 3 (testSum)(surefire.testcase.JunitParamsTest)";
-        ReportEntry reportEntry = new SimpleReportEntry( "fud", category );
+        String className = "[0] 1\u002C 2\u002C 3 (testSum)";
+        ReportEntry reportEntry = new SimpleReportEntry( className, null );
         WrappedReportEntry wr = new WrappedReportEntry( reportEntry, null, 12, null, null );
         String elapsedTimeSummary = wr.getElapsedTimeSummary();
-        assertEquals( "[0] 1, 2, 3 (testSum)(surefire.testcase.JunitParamsTest)  Time elapsed: 0.012 s",
+        assertEquals( "[0] 1, 2, 3 (testSum)  Time elapsed: 0.012 s",
                       elapsedTimeSummary );
     }
-
-
 }
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 5a171ab..42698d7 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
@@ -20,15 +20,23 @@ package org.apache.maven.plugin.surefire.runorder;
  */
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.StringReader;
+import java.io.InputStream;
+import java.io.StringBufferInputStream;
 import java.util.Arrays;
 import java.util.List;
+
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 
 import junit.framework.TestCase;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.io.IOUtils.readLines;
+import static org.apache.maven.surefire.util.internal.StringUtils.NL;
+import static org.fest.assertions.Assertions.assertThat;
+
 /**
  * @author Kristian Rosenvold
  */
@@ -36,9 +44,8 @@ public class RunEntryStatisticsMapTest
     extends TestCase
 {
     public void testPrioritizedClassRuntime()
-        throws IOException
     {
-        final RunEntryStatisticsMap runEntryStatisticsMap = RunEntryStatisticsMap.fromReader( getStatisticsFile() );
+        final RunEntryStatisticsMap runEntryStatisticsMap = RunEntryStatisticsMap.fromStream( getStatisticsFile() );
         final List<Class<?>> list = Arrays.<Class<?>>asList( A.class, B.class, C.class );
         final List<Class<?>> prioritizedTestsClassRunTime =
             runEntryStatisticsMap.getPrioritizedTestsClassRunTime( list, 2 );
@@ -48,9 +55,8 @@ public class RunEntryStatisticsMapTest
     }
 
     public void testPrioritizedFailureFirst()
-        throws IOException
     {
-        final RunEntryStatisticsMap runEntryStatisticsMap = RunEntryStatisticsMap.fromReader( getStatisticsFile() );
+        final RunEntryStatisticsMap runEntryStatisticsMap = RunEntryStatisticsMap.fromStream( getStatisticsFile() );
         final List<Class<?>> list = Arrays.<Class<?>>asList( A.class, B.class, NewClass.class, C.class );
         final List<Class<?>> prioritizedTestsClassRunTime =
             runEntryStatisticsMap.getPrioritizedTestsByFailureFirst( list );
@@ -60,12 +66,12 @@ public class RunEntryStatisticsMapTest
         assertEquals( B.class, prioritizedTestsClassRunTime.get( 3 ) );
     }
 
-    private StringReader getStatisticsFile()
+    private InputStream getStatisticsFile()
     {
-        String content = "0,17,testA(org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$A)\n" +
-            "2,42,testB(org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$B)\n" +
-            "1,100,testC(org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$C)\n";
-        return new StringReader( content );
+        String content = "0,17,org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$A,testA\n" +
+            "2,42,org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$B,testB\n" +
+            "1,100,org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest$C,testC\n";
+        return new StringBufferInputStream( content );
     }
 
     public void testSerialize()
@@ -84,15 +90,111 @@ public class RunEntryStatisticsMapTest
         newResults.add( existingEntries.createNextGeneration( reportEntry3 ) );
 
         newResults.serialize( data );
+        try ( InputStream io = new FileInputStream( data) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "1,17,abc,willFail", "1,42,abc,method1", "1,100,abc,method3" );
+        }
 
         RunEntryStatisticsMap nextRun = RunEntryStatisticsMap.fromFile( data );
         newResults = new RunEntryStatisticsMap();
 
-        newResults.add( existingEntries.createNextGeneration( reportEntry1 ) );
-        newResults.add( existingEntries.createNextGenerationFailure( reportEntry2 ) );
-        newResults.add( existingEntries.createNextGeneration( reportEntry3 ) );
+        ReportEntry newRunReportEntry1 = new SimpleReportEntry( "abc", "method1", 52 );
+        ReportEntry newRunReportEntry2 = new SimpleReportEntry( "abc", "willFail", 27 );
+        ReportEntry newRunReportEntry3 = new SimpleReportEntry( "abc", "method3", 110 );
+
+        newResults.add( nextRun.createNextGeneration( newRunReportEntry1 ) );
+        newResults.add( nextRun.createNextGenerationFailure( newRunReportEntry2 ) );
+        newResults.add( nextRun.createNextGeneration( newRunReportEntry3 ) );
 
         newResults.serialize( data );
+        try ( InputStream io = new FileInputStream( data ) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "0,27,abc,willFail", "2,52,abc,method1", "2,110,abc,method3" );
+        }
+    }
+
+    public void testMultiLineTestMethodName() throws IOException
+    {
+        File data = File.createTempFile( "surefire-unit", "test" );
+        RunEntryStatisticsMap reportEntries = RunEntryStatisticsMap.fromFile( data );
+        ReportEntry reportEntry = new SimpleReportEntry( "abc", "line1\nline2" + NL + " line3", 42 );
+        reportEntries.add( reportEntries.createNextGeneration( reportEntry ) );
+
+        reportEntries.serialize( data );
+        try ( InputStream io = new FileInputStream( data ) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "1,42,abc,line1", " line2", "  line3" );
+        }
+
+        RunEntryStatisticsMap nextRun = RunEntryStatisticsMap.fromFile( data );
+        assertThat( data.delete() ).isTrue();
+        nextRun.serialize( data );
+        try ( InputStream io = new FileInputStream( data ) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "1,42,abc,line1", " line2", "  line3" );
+        }
+    }
+
+    public void testCombinedMethodNames() throws IOException
+    {
+        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.serialize( data );
+        try ( InputStream io = new FileInputStream( data ) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "1,10,abc,test",
+                                       "1,42,abc,line1",
+                                       " line2" );
+        }
+
+        RunEntryStatisticsMap nextRun = RunEntryStatisticsMap.fromFile( data );
+        assertThat( data.delete() ).isTrue();
+        nextRun.serialize( data );
+        try ( InputStream io = new FileInputStream( data ) )
+        {
+            List<String> lines = readLines( io, UTF_8 );
+
+            assertThat( lines )
+                    .hasSize( 3 );
+
+            assertThat( lines )
+                    .containsSequence( "1,10,abc,test",
+                                       "1,42,abc,line1",
+                                       " line2" );
+        }
     }
 
     class A
diff --git a/pom.xml b/pom.xml
index 72b9b5a..a048c46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -277,12 +277,12 @@
       <dependency>
         <groupId>org.junit.platform</groupId>
         <artifactId>junit-platform-launcher</artifactId>
-        <version>1.3.1</version>
+        <version>1.3.2</version>
       </dependency>
       <dependency>
         <groupId>org.junit.jupiter</groupId>
         <artifactId>junit-jupiter-engine</artifactId>
-        <version>5.3.1</version>
+        <version>5.3.2</version>
       </dependency>
       <dependency>
         <groupId>org.mockito</groupId>
diff --git a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatistics.java b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatistics.java
index 7c794dc..1ccc4ee 100644
--- a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatistics.java
+++ b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatistics.java
@@ -19,8 +19,7 @@ package org.apache.maven.plugin.surefire.runorder;
  * under the License.
  */
 
-import java.util.StringTokenizer;
-import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 
 /**
  * @author Kristian Rosenvold
@@ -31,39 +30,33 @@ public class RunEntryStatistics
 
     private final int successfulBuilds;
 
-    private final String testName;
+    private final ClassMethod classMethod;
 
-    private RunEntryStatistics( int runTime, int successfulBuilds, String testName )
+    RunEntryStatistics( int runTime, int successfulBuilds, String clazz, String method )
     {
-        this.runTime = runTime;
-        this.successfulBuilds = successfulBuilds;
-        this.testName = testName;
+        this( runTime, successfulBuilds, new ClassMethod( clazz, method ) );
     }
 
-    public static RunEntryStatistics fromReportEntry( ReportEntry previous )
+    RunEntryStatistics( int runTime, int successfulBuilds, ClassMethod classMethod )
     {
-        final Integer elapsed = previous.getElapsed();
-        return new RunEntryStatistics( elapsed != null ? elapsed : 0, 0, previous.getName() );
+        this.runTime = runTime;
+        this.successfulBuilds = successfulBuilds;
+        this.classMethod = classMethod;
     }
 
-    public static RunEntryStatistics fromValues( int runTime, int successfulBuilds, Class clazz, String testName )
+    public ClassMethod getClassMethod()
     {
-        return new RunEntryStatistics( runTime, successfulBuilds, testName + "(" + clazz.getName() + ")" );
+        return classMethod;
     }
 
     public RunEntryStatistics nextGeneration( int runTime )
     {
-        return new RunEntryStatistics( runTime, this.successfulBuilds + 1, this.testName );
+        return new RunEntryStatistics( runTime, successfulBuilds + 1, classMethod );
     }
 
     public RunEntryStatistics nextGenerationFailure( int runTime )
     {
-        return new RunEntryStatistics( runTime, 0, this.testName );
-    }
-
-    public String getTestName()
-    {
-        return testName;
+        return new RunEntryStatistics( runTime, 0, classMethod );
     }
 
     public int getRunTime()
@@ -75,20 +68,4 @@ public class RunEntryStatistics
     {
         return successfulBuilds;
     }
-
-    public static RunEntryStatistics fromString( String line )
-    {
-        StringTokenizer tok = new StringTokenizer( line, "," );
-        int successfulBuilds = Integer.parseInt( tok.nextToken() );
-        int runTime = Integer.parseInt( tok.nextToken() );
-        String className = tok.nextToken();
-        return new RunEntryStatistics( runTime, successfulBuilds, className );
-    }
-
-    @Override
-    public String toString()
-    {
-        return successfulBuilds + "," + runTime + "," + testName;
-    }
-
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
index 1a685dc..09a4445 100644
--- a/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
+++ b/surefire-api/src/main/java/org/apache/maven/plugin/surefire/runorder/RunEntryStatisticsMap.java
@@ -19,38 +19,41 @@ package org.apache.maven.plugin.surefire.runorder;
  * under the License.
  */
 
-
 import org.apache.maven.surefire.report.ReportEntry;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 
-import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileReader;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.Reader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Scanner;
+import java.util.StringTokenizer;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
+import static java.lang.Integer.parseInt;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.sort;
-import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromReportEntry;
-import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromString;
+import static org.apache.maven.surefire.util.internal.StringUtils.NL;
 
 /**
  * @author Kristian Rosenvold
  */
 public final class RunEntryStatisticsMap
 {
-    private final Map<String, RunEntryStatistics> runEntryStatistics;
+    private final Map<ClassMethod, RunEntryStatistics> runEntryStatistics;
 
-    public RunEntryStatisticsMap( Map<String, RunEntryStatistics> runEntryStatistics )
+    private RunEntryStatisticsMap( Map<ClassMethod, RunEntryStatistics> runEntryStatistics )
     {
         this.runEntryStatistics = new ConcurrentHashMap<>( runEntryStatistics );
     }
@@ -66,7 +69,7 @@ public final class RunEntryStatisticsMap
         {
             try
             {
-                return fromReader( new FileReader( file ) );
+                return fromStream( new FileInputStream( file ) );
             }
             catch ( IOException e )
             {
@@ -79,62 +82,124 @@ public final class RunEntryStatisticsMap
         }
     }
 
-    static RunEntryStatisticsMap fromReader( Reader fileReader )
-        throws IOException
+    static RunEntryStatisticsMap fromStream( InputStream fileReader )
     {
-        Map<String, RunEntryStatistics> result = new HashMap<>();
-        BufferedReader bufferedReader = new BufferedReader( fileReader );
-        String line = bufferedReader.readLine();
-        while ( line != null )
+        Map<ClassMethod, RunEntryStatistics> result = new HashMap<>();
+        try ( Scanner scanner = new Scanner( fileReader, "UTF-8" ) )
         {
-            if ( !line.startsWith( "#" ) )
+            RunEntryStatistics previous = null;
+            while ( scanner.hasNextLine() )
             {
-                final RunEntryStatistics stats = fromString( line );
-                result.put( stats.getTestName(), stats );
+                String line = scanner.nextLine();
+
+                if ( line.charAt( 0 ) == ' ' )
+                {
+                    previous = new RunEntryStatistics( previous.getRunTime(),
+                            previous.getSuccessfulBuilds(),
+                            previous.getClassMethod().getClazz(),
+                            previous.getClassMethod().getMethod() + NL + line.substring( 1 ) );
+                }
+                else
+                {
+                    if ( previous != null )
+                    {
+                        result.put( previous.getClassMethod(), previous );
+                    }
+                    StringTokenizer tokenizer = new StringTokenizer( line, "," );
+
+                    int methodIndex = 3;
+
+                    String successfulBuildsString = tokenizer.nextToken();
+                    int successfulBuilds = parseInt( successfulBuildsString );
+
+                    methodIndex += successfulBuildsString.length();
+
+                    String runTimeString = tokenizer.nextToken();
+                    int runTime = parseInt( runTimeString );
+
+                    methodIndex += runTimeString.length();
+
+                    String className = tokenizer.nextToken();
+
+                    methodIndex += className.length();
+
+                    String methodName = line.substring( methodIndex );
+
+                    ClassMethod classMethod = new ClassMethod( className, methodName );
+                    previous = new RunEntryStatistics( runTime, successfulBuilds, classMethod );
+                }
+            }
+            if ( previous != null )
+            {
+                result.put( previous.getClassMethod(), previous );
             }
-            line = bufferedReader.readLine();
         }
         return new RunEntryStatisticsMap( result );
     }
 
-    public void serialize( File file )
-        throws FileNotFoundException
+    public void serialize( File statsFile )
+        throws IOException
     {
-        FileOutputStream fos = new FileOutputStream( file );
-        try ( PrintWriter printWriter = new PrintWriter( fos ) )
+        if ( statsFile.isFile() )
+        {
+            //noinspection ResultOfMethodCallIgnored
+            statsFile.delete();
+        }
+        OutputStream os = new FileOutputStream( statsFile );
+        try ( BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( os, UTF_8 ), 64 * 1024 ) )
         {
             List<RunEntryStatistics> items = new ArrayList<>( runEntryStatistics.values() );
             sort( items, new RunCountComparator() );
-            for ( RunEntryStatistics item : items )
+            for ( Iterator<RunEntryStatistics> it = items.iterator(); it.hasNext(); )
             {
-                printWriter.println( item.toString() );
+                RunEntryStatistics item = it.next();
+                ClassMethod test = item.getClassMethod();
+                String line = item.getSuccessfulBuilds() + "," + item.getRunTime() + "," + test.getClazz() + ",";
+                writer.write( line );
+                boolean wasFirstLine = false;
+                for ( Scanner scanner = new Scanner( test.getMethod() ); scanner.hasNextLine(); wasFirstLine = true )
+                {
+                    String methodLine = scanner.nextLine();
+                    if ( wasFirstLine )
+                    {
+                        writer.write( ' ' );
+                    }
+                    writer.write( methodLine );
+                    if ( scanner.hasNextLine() )
+                    {
+                        writer.newLine();
+                    }
+                }
+                if ( it.hasNext() )
+                {
+                    writer.newLine();
+                }
             }
         }
     }
 
-    public RunEntryStatistics findOrCreate( ReportEntry reportEntry )
+    private RunEntryStatistics findOrCreate( ReportEntry reportEntry )
     {
-        final RunEntryStatistics item = runEntryStatistics.get( reportEntry.getName() );
-        return item != null ? item : fromReportEntry( reportEntry );
+        ClassMethod classMethod = new ClassMethod( reportEntry.getSourceName(), reportEntry.getName() );
+        RunEntryStatistics item = runEntryStatistics.get( classMethod );
+        return item != null ? item : new RunEntryStatistics( reportEntry.getElapsed( 0 ), 0, classMethod );
     }
 
     public RunEntryStatistics createNextGeneration( ReportEntry reportEntry )
     {
-        final RunEntryStatistics newItem = findOrCreate( reportEntry );
-        final Integer elapsed = reportEntry.getElapsed();
-        return newItem.nextGeneration( elapsed != null ? elapsed : 0 );
+        RunEntryStatistics newItem = findOrCreate( reportEntry );
+        return newItem.nextGeneration( reportEntry.getElapsed( 0 ) );
     }
 
     public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry )
     {
-        final RunEntryStatistics newItem = findOrCreate( reportEntry );
-        final Integer elapsed = reportEntry.getElapsed();
-        return newItem.nextGenerationFailure( elapsed != null ? elapsed : 0 );
+        RunEntryStatistics newItem = findOrCreate( reportEntry );
+        return newItem.nextGenerationFailure( reportEntry.getElapsed( 0 ) );
     }
 
     public void add( RunEntryStatistics item )
     {
-        runEntryStatistics.put( item.getTestName(), item );
+        runEntryStatistics.put( item.getClassMethod(), item );
     }
 
     static final class RunCountComparator
@@ -169,12 +234,12 @@ public final class RunEntryStatisticsMap
     private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
                                                        Comparator<Priority> priorityComparator )
     {
-        Map classPriorities = getPriorities( priorityComparator );
+        Map<String, Priority> classPriorities = getPriorities( priorityComparator );
 
         List<PrioritizedTest> tests = new ArrayList<>();
         for ( Class<?> clazz : testsToRun )
         {
-            Priority pri = (Priority) classPriorities.get( clazz.getName() );
+            Priority pri = classPriorities.get( clazz.getName() );
             if ( pri == null )
             {
                 pri = Priority.newTestClassPriority( clazz.getName() );
@@ -186,7 +251,7 @@ public final class RunEntryStatisticsMap
         return tests;
     }
 
-    private List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
+    private static List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
     {
         List<Class<?>> result = new ArrayList<>();
         for ( PrioritizedTest test : tests )
@@ -196,22 +261,19 @@ public final class RunEntryStatisticsMap
         return result;
     }
 
-    private Map getPriorities( Comparator<Priority> priorityComparator )
+    private Map<String, Priority> getPriorities( Comparator<Priority> priorityComparator )
     {
         Map<String, Priority> priorities = new HashMap<>();
-        for ( Object o : runEntryStatistics.keySet() )
+        for ( Entry<ClassMethod, RunEntryStatistics> testNames : runEntryStatistics.entrySet() )
         {
-            String testNames = (String) o;
-            String clazzName = extractClassName( testNames );
+            String clazzName = testNames.getKey().getClazz();
             Priority priority = priorities.get( clazzName );
             if ( priority == null )
             {
                 priority = new Priority( clazzName );
                 priorities.put( clazzName, priority );
             }
-
-            RunEntryStatistics itemStat = runEntryStatistics.get( testNames );
-            priority.addItem( itemStat );
+            priority.addItem( testNames.getValue() );
         }
 
         List<Priority> items = new ArrayList<>( priorities.values() );
@@ -255,16 +317,4 @@ public final class RunEntryStatisticsMap
             return o.getMinSuccessRate() - o1.getMinSuccessRate();
         }
     }
-
-
-    private static final Pattern PARENS = Pattern.compile( "^" + "[^\\(\\)]+" //non-parens
-                                                               + "\\((" // then an open-paren (start matching a group)
-                                                               + "[^\\\\(\\\\)]+" //non-parens
-                                                               + ")\\)" + "$" ); // then a close-paren (end group match)
-
-    String extractClassName( String displayName )
-    {
-        Matcher m = PARENS.matcher( displayName );
-        return m.find() ? m.group( 1 ) : displayName;
-    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index ce806b9..80d08db 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -417,7 +417,7 @@ public class ForkingRunListener
     @Override
     public void println( String message )
     {
-        byte[] buf = message.getBytes();
+        byte[] buf = ( message == null ? "null" : message ).getBytes();
         println( buf, 0, buf.length );
     }
 
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 47b0c48..cad41a2 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
@@ -78,7 +78,7 @@ public class CategorizedReportEntry
     @Override
     public String getNameWithGroup()
     {
-        return isNameWithGroup() ? getName() + GROUP_PREFIX + getGroup() + GROUP_SUFIX : getName();
+        return isNameWithGroup() ? getSourceName() + GROUP_PREFIX + getGroup() + GROUP_SUFIX : getSourceName();
     }
 
     @Override
@@ -113,6 +113,6 @@ public class CategorizedReportEntry
 
     private boolean isNameWithGroup()
     {
-        return getGroup() != null && !getGroup().equals( getName() );
+        return getGroup() != null && !getGroup().equals( getSourceName() );
     }
 }
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 ec0f782..6e4a04a 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
@@ -61,6 +61,14 @@ public interface ReportEntry
      */
     Integer getElapsed();
 
+    /**
+     * Returns same value as {@link #getElapsed()} and fallbacks to {@code fallback} for <tt>null</tt> elapsed timed.
+     *
+     * @param fallback usually 0
+     * @return elapsed time if {@link #getElapsed()} is not null; otherwise returns {@code fallback}
+     */
+    int getElapsed( int fallback );
+
 
     /**
      * A message relating to a non-successful termination.
@@ -71,7 +79,7 @@ public interface ReportEntry
     String getMessage();
 
     /**
-     * A name of the test case together with the group or category (if any exists).
+     * A source name of the test case together with the group or category (if any exists).
      *
      * @return A string with the test case name and group/category, or just the name.
      */
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 241c874..75831db 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
@@ -23,6 +23,7 @@ import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * @author Kristian Rosenvold
@@ -75,25 +76,11 @@ public class SimpleReportEntry
     protected SimpleReportEntry( String source, String name, StackTraceWriter stackTraceWriter, Integer elapsed,
                                  String message, Map<String, String> systemProperties )
     {
-        if ( source == null )
-        {
-            source = "null";
-        }
-        if ( name == null )
-        {
-            name = "null";
-        }
-
         this.source = source;
-
         this.name = name;
-
         this.stackTraceWriter = stackTraceWriter;
-
         this.message = message;
-
         this.elapsed = elapsed;
-
         this.systemProperties = new ImmutableMap<>( systemProperties );
     }
 
@@ -167,6 +154,12 @@ public class SimpleReportEntry
     }
 
     @Override
+    public int getElapsed( int fallback )
+    {
+        return elapsed == null ? fallback : elapsed;
+    }
+
+    @Override
     public String toString()
     {
         return "ReportEntry{" + "source='" + source + '\'' + ", name='" + name + '\'' + ", stackTraceWriter="
@@ -198,17 +191,17 @@ public class SimpleReportEntry
     @Override
     public int hashCode()
     {
-        int result = source != null ? source.hashCode() : 0;
-        result = 31 * result + ( name != null ? name.hashCode() : 0 );
-        result = 31 * result + ( stackTraceWriter != null ? stackTraceWriter.hashCode() : 0 );
-        result = 31 * result + ( elapsed != null ? elapsed.hashCode() : 0 );
+        int result = Objects.hashCode( source );
+        result = 31 * result + Objects.hashCode( name );
+        result = 31 * result + Objects.hashCode( stackTraceWriter );
+        result = 31 * result + Objects.hashCode( elapsed );
         return result;
     }
 
     @Override
     public String getNameWithGroup()
     {
-        return getName();
+        return getSourceName();
     }
 
     @Override
@@ -219,21 +212,21 @@ public class SimpleReportEntry
 
     private boolean isElapsedTimeEqual( SimpleReportEntry en )
     {
-        return elapsed != null ? elapsed.equals( en.elapsed ) : en.elapsed == null;
+        return Objects.equals( elapsed, en.elapsed );
     }
 
     private boolean isNameEqual( SimpleReportEntry en )
     {
-        return name != null ? name.equals( en.name ) : en.name == null;
+        return Objects.equals( name, en.name );
     }
 
     private boolean isSourceEqual( SimpleReportEntry en )
     {
-        return source != null ? source.equals( en.source ) : en.source == null;
+        return Objects.equals( source, en.source );
     }
 
     private boolean isStackEqual( SimpleReportEntry en )
     {
-        return stackTraceWriter != null ? stackTraceWriter.equals( en.stackTraceWriter ) : en.stackTraceWriter == null;
+        return Objects.equals( stackTraceWriter, en.stackTraceWriter );
     }
 }
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ClassMethod.java
similarity index 63%
copy from surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java
copy to surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ClassMethod.java
index a3cccca..03f9620 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/ClassMethod.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.common.junit4;
+package org.apache.maven.surefire.util.internal;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,7 +19,9 @@ package org.apache.maven.surefire.common.junit4;
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.StringUtils;
+import java.util.Objects;
+
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
 
 /**
  * Data transfer object of class and method literals.
@@ -36,9 +38,9 @@ public final class ClassMethod
         this.method = method;
     }
 
-    public boolean isValid()
+    public boolean isValidTest()
     {
-        return !StringUtils.isBlank( clazz ) && !StringUtils.isBlank( method );
+        return !isBlank( clazz ) && !isBlank( method );
     }
 
     public String getClazz()
@@ -50,4 +52,26 @@ public final class ClassMethod
     {
         return method;
     }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+        ClassMethod that = ( ClassMethod ) o;
+        return Objects.equals( clazz, that.clazz )
+                && Objects.equals( method, that.method );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash( clazz, method );
+    }
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java
index 23e72e1..94bf4c8 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/TestClassMethodNameUtils.java
@@ -19,24 +19,15 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * JUnit Description parser.
- * Used by JUnit Version lower than 4.7.
+ * Used by JUnit4+.
  *
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 2.20
  */
 public final class TestClassMethodNameUtils
 {
-    /**
-     * This pattern is verbatim copy from JUnit's code in class {@code Description}.
-     * Parsing class and method from junit description would provide identical result to JUnit internal parser.
-     */
-    private static final Pattern METHOD_CLASS_PATTERN = Pattern.compile( "([\\s\\S]*)\\((.*)\\)" );
-
     private TestClassMethodNameUtils()
     {
         throw new IllegalStateException( "no instantiable constructor" );
@@ -44,13 +35,26 @@ public final class TestClassMethodNameUtils
 
     public static String extractClassName( String displayName )
     {
-        Matcher m = METHOD_CLASS_PATTERN.matcher( displayName );
-        return m.matches() ? m.group( 2 ) : displayName;
+        String clazz = displayName;
+        if ( displayName.endsWith( ")" ) )
+        {
+            int paren = displayName.lastIndexOf( '(' );
+            if ( paren != -1 )
+            {
+                clazz = displayName.substring( paren + 1, displayName.length() - 1 );
+            }
+        }
+        return clazz;
     }
 
     public static String extractMethodName( String displayName )
     {
-        Matcher m = METHOD_CLASS_PATTERN.matcher( displayName );
-        return m.matches() ? m.group( 1 ) : displayName;
+        String method = null;
+        int parent = displayName.lastIndexOf( '(' );
+        if ( parent != -1 )
+        {
+            method = displayName.substring( 0, parent );
+        }
+        return method;
     }
 }
diff --git a/surefire-api/src/test/java/org/apache/maven/plugin/surefire/runorder/ThreadedExecutionSchedulerTest.java b/surefire-api/src/test/java/org/apache/maven/plugin/surefire/runorder/ThreadedExecutionSchedulerTest.java
index 15963d7..6647504 100644
--- a/surefire-api/src/test/java/org/apache/maven/plugin/surefire/runorder/ThreadedExecutionSchedulerTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/plugin/surefire/runorder/ThreadedExecutionSchedulerTest.java
@@ -21,6 +21,7 @@ package org.apache.maven.plugin.surefire.runorder;
 import java.util.List;
 
 import junit.framework.TestCase;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 
 /**
  * @author Kristian Rosenvold
@@ -29,24 +30,23 @@ public class ThreadedExecutionSchedulerTest
     extends TestCase
 {
 
-    private final RunEntryStatistics a1 = RunEntryStatistics.fromValues( 200, 2, A.class, "at1" );
+    private final RunEntryStatistics a1 = fromValues( 200, 2, A.class, "at1" );
 
-    private final RunEntryStatistics a2 = RunEntryStatistics.fromValues( 300, 2, A.class, "at2" );
+    private final RunEntryStatistics a2 = fromValues( 300, 2, A.class, "at2" );
 
-    private final RunEntryStatistics b1 = RunEntryStatistics.fromValues( 400, 2, B.class, "bt1" );
+    private final RunEntryStatistics b1 = fromValues( 400, 2, B.class, "bt1" );
 
-    private final RunEntryStatistics b2 = RunEntryStatistics.fromValues( 300, 2, B.class, "bt2" );
+    private final RunEntryStatistics b2 = fromValues( 300, 2, B.class, "bt2" );
 
-    private final RunEntryStatistics c1 = RunEntryStatistics.fromValues( 400, 2, C.class, "ct1" );
+    private final RunEntryStatistics c1 = fromValues( 400, 2, C.class, "ct1" );
 
-    private final RunEntryStatistics c2 = RunEntryStatistics.fromValues( 200, 2, C.class, "ct2" );
+    private final RunEntryStatistics c2 = fromValues( 200, 2, C.class, "ct2" );
 
-    private final RunEntryStatistics d1 = RunEntryStatistics.fromValues( 401, 2, D.class, "ct2" );
+    private final RunEntryStatistics d1 = fromValues( 401, 2, D.class, "ct2" );
 
-    private final RunEntryStatistics e1 = RunEntryStatistics.fromValues( 200, 2, E.class, "ct2" );
+    private final RunEntryStatistics e1 = fromValues( 200, 2, E.class, "ct2" );
 
     public void testAddTest()
-        throws Exception
     {
         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( 2 );
         addPrioritizedTests( threadedExecutionScheduler );
@@ -57,17 +57,14 @@ public class ThreadedExecutionSchedulerTest
         assertEquals( D.class, result.get( 2 ) );
         assertEquals( A.class, result.get( 3 ) );
         assertEquals( E.class, result.get( 4 ) );
-
     }
 
     public void testAddTestJaggedResult()
-        throws Exception
     {
         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( 4 );
         addPrioritizedTests( threadedExecutionScheduler );
         final List result = threadedExecutionScheduler.getResult();
         assertEquals( 5, result.size() );
-
     }
 
     private void addPrioritizedTests( ThreadedExecutionScheduler threadedExecutionScheduler )
@@ -94,6 +91,11 @@ public class ThreadedExecutionSchedulerTest
         return priority;
     }
 
+    private static RunEntryStatistics fromValues( int runTime, int successfulBuilds, Class clazz, String testName )
+    {
+        ClassMethod classMethod = new ClassMethod( clazz.getName(), testName );
+        return new RunEntryStatistics( runTime, successfulBuilds, classMethod );
+    }
 
     class A
     {
diff --git a/surefire-its/pom.xml b/surefire-its/pom.xml
index 0cea53e..a90df17 100644
--- a/surefire-its/pom.xml
+++ b/surefire-its/pom.xml
@@ -183,7 +183,7 @@
                         <jdk.home>${jdk.home}</jdk.home>
                         <jacoco.agent>${jacoco-it.agent}</jacoco.agent>
                     </systemPropertyVariables>
-                    <redirectTestOutputToFile>false</redirectTestOutputToFile>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
                 </configuration>
                 <dependencies>
                     <dependency>
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
index 7d6f560..b64edff 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4RerunFailingTestsIT.java
@@ -254,9 +254,9 @@ public class JUnit4RerunFailingTestsIT
     private void verifyFailuresNoRetry( OutputValidator outputValidator, int run, int failures, int errors, int flakes )
     {
         outputValidator.verifyTextInLog( "Failures:" );
-        outputValidator.verifyTextInLog( "testFailingTestOne(junit4.FlakyFirstTimeTest)" );
+        outputValidator.verifyTextInLog( "junit4.FlakyFirstTimeTest.testFailingTestOne" );
         outputValidator.verifyTextInLog( "ERROR" );
-        outputValidator.verifyTextInLog( "testErrorTestOne(junit4.FlakyFirstTimeTest)" );
+        outputValidator.verifyTextInLog( "junit4.FlakyFirstTimeTest.testErrorTestOne" );
 
         verifyStatistics( outputValidator, run, failures, errors, flakes );
     }
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 332cbb9..ae04143 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
@@ -59,10 +59,10 @@ public class JUnitPlatformEnginesIT
     public static Iterable<Object[]> regexVersions()
     {
         ArrayList<Object[]> args = new ArrayList<Object[]>();
-        args.add( new Object[] { "1.0.0", "5.0.0", "1.0.0", "1.0.0" } );
+        args.add( new Object[] { "1.0.3", "5.0.3", "1.0.0", "1.0.0" } );
         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.1", "5.3.1", "1.1.1", "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" } );
         return args;
     }
@@ -114,7 +114,7 @@ public class JUnitPlatformEnginesIT
                 + "  surefire-api-*.jar"
                 + "  surefire-logger-api-*.jar"
                 + "  common-java5-*.jar"
-                + "  junit-platform-launcher-1.3.1.jar";
+                + "  junit-platform-launcher-1.3.2.jar";
 
         lines = validator.loadLogLines( startsWith( "[DEBUG] provider(compact) classpath" ) );
 
@@ -138,7 +138,7 @@ public class JUnitPlatformEnginesIT
                 + "  opentest4j-" + opentest + ".jar"
                 + "  junit-jupiter-api-" + jupiter + ".jar"
                 + "  surefire-junit-platform-*.jar"
-                + "  junit-platform-launcher-1.3.1.jar";
+                + "  junit-platform-launcher-1.3.2.jar";
 
         lines = validator.loadLogLines( startsWith( "[DEBUG] boot(compact) classpath" ) );
 
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1152RerunFailingTestsInSuiteIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1152RerunFailingTestsInSuiteIT.java
index 58e951a..ded2361 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1152RerunFailingTestsInSuiteIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1152RerunFailingTestsInSuiteIT.java
@@ -35,9 +35,9 @@ public class Surefire1152RerunFailingTestsInSuiteIT
     private static final String RUNNING_WITH_PROVIDER47 =
         "Using configured provider org.apache.maven.surefire.junitcore.JUnitCoreProvider";
 
-    public OutputValidator runMethodPattern( String projectName, String... goals )
+    private OutputValidator runMethodPattern( String... goals )
     {
-        SurefireLauncher launcher = unpack( projectName );
+        SurefireLauncher launcher = unpack("surefire-1152-rerunFailingTestsCount-suite" );
         for ( String goal : goals )
         {
             launcher.addGoal( goal );
@@ -51,13 +51,13 @@ public class Surefire1152RerunFailingTestsInSuiteIT
     @Test
     public void testJUnit48Provider4()
     {
-        runMethodPattern( "surefire-1152-rerunFailingTestsCount-suite", "-P surefire-junit4" );
+        runMethodPattern("-P surefire-junit4" );
     }
 
     @Test
     public void testJUnit48Provider47()
     {
-        runMethodPattern( "surefire-1152-rerunFailingTestsCount-suite", "-P surefire-junit47" )
+        runMethodPattern("-P surefire-junit47" )
             .verifyTextInLog( RUNNING_WITH_PROVIDER47 );
     }
 
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
index 6ee87fb..1b5130f 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1209RerunAndForkCountIT.java
@@ -22,8 +22,11 @@ package org.apache.maven.surefire.its.jiras;
 import org.apache.maven.it.VerificationException;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.hamcrest.CoreMatchers;
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.*;
+
 /**
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @see <a href="https://issues.apache.org/jira/browse/SUREFIRE-1209">SUREFIRE-1209</a>
@@ -32,40 +35,39 @@ import org.junit.Test;
 public class Surefire1209RerunAndForkCountIT
         extends SurefireJUnit4IntegrationTestCase
 {
+    private static final String SUMMARY_COUNTS = "Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Flakes: 2";
+
     @Test
-    public void reusableForksJUnit47()
-            throws VerificationException
-    {
+    public void reusableForksJUnit47() throws VerificationException {
         unpack().executeTest()
-                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 )
+                .assertThatLogLine( containsString( SUMMARY_COUNTS ), is( 1 ) );
     }
 
     @Test
-    public void notReusableForksJUnit47()
-            throws VerificationException
-    {
+    public void notReusableForksJUnit47() throws VerificationException {
         unpack().reuseForks( false )
                 .executeTest()
-                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 )
+                .assertThatLogLine( containsString( SUMMARY_COUNTS ), is( 1 ) );
     }
 
     @Test
-    public void reusableForksJUnit4()
-            throws VerificationException
-    {
-        unpack().addGoal( "-Pjunit4" )
+    public void reusableForksJUnit4() throws VerificationException {
+        unpack().activateProfile( "junit4" )
                 .executeTest()
-                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 )
+                .assertThatLogLine( containsString( SUMMARY_COUNTS ), is( 1 ) );
     }
 
     @Test
     public void notReusableForksJUnit4()
-            throws VerificationException
-    {
-        unpack().addGoal( "-Pjunit4" )
+            throws VerificationException {
+        unpack().activateProfile( "junit4" )
                 .reuseForks( false )
                 .executeTest()
-                .assertTestSuiteResults( 5, 0, 0, 0, 4 );
+                .assertTestSuiteResults( 5, 0, 0, 0, 4 )
+                .assertThatLogLine( containsString( SUMMARY_COUNTS ), is( 1 ) );
     }
 
     private SurefireLauncher unpack()
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
index 59d2170..0d3c4a3 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
@@ -74,7 +74,7 @@ public class Surefire943ReportContentIT
         Xpp3Dom child = children[0];
 
         Assert.assertEquals( className, child.getAttribute( "classname" ) );
-        Assert.assertEquals( className, child.getAttribute( "name" ) );
+        Assert.assertEquals( "", child.getAttribute( "name" ) );
 
         Assert.assertEquals( "Expected error tag for failed BeforeClass method for " + className, 1,
                              child.getChildren( "error" ).length );
@@ -84,7 +84,6 @@ public class Surefire943ReportContentIT
 
         Assert.assertTrue( "time for test failure in BeforeClass is expected to be resonably low",
                            Double.compare( Double.parseDouble( child.getAttribute( "time" ) ), 2.0d ) <= 0 );
-
     }
 
     private void validateSkipped( OutputValidator validator, String className )
@@ -97,7 +96,7 @@ public class Surefire943ReportContentIT
         Xpp3Dom child = children[0];
 
         Assert.assertEquals( className, child.getAttribute( "classname" ) );
-        Assert.assertEquals( className, child.getAttribute( "name" ) );
+        Assert.assertEquals( "", child.getAttribute( "name" ) );
 
         Assert.assertEquals( "Expected skipped tag for ignored method for " + className, 1,
                              child.getChildren( "skipped" ).length );
diff --git a/surefire-its/src/test/resources/fixture/testsuitexmlparser/TEST-org.apache.maven.surefire.test.SucceedingTest.xml b/surefire-its/src/test/resources/fixture/testsuitexmlparser/TEST-org.apache.maven.surefire.test.SucceedingTest.xml
deleted file mode 100644
index 2801bf6..0000000
--- a/surefire-its/src/test/resources/fixture/testsuitexmlparser/TEST-org.apache.maven.surefire.test.SucceedingTest.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<testsuite failures="0" time="0.008" errors="0" skipped="0" tests="2" name="org.apache.maven.surefire.test.SucceedingTest">
-  <properties>
-    <property name="surefire.version" value="2.13-SNAPSHOT"/>
-    <property name="java.runtime.name" value="Java(TM) SE Runtime Environment"/>
-    <property name="sun.boot.library.path" value="c:\java\jdk1.7.0_09\jre\bin"/>
-    <property name="java.vm.version" value="23.5-b02"/>
-    <property name="user.country.format" value="NO"/>
-    <property name="java.vm.vendor" value="Oracle Corporation"/>
-    <property name="java.vendor.url" value="http://java.oracle.com/"/>
-    <property name="path.separator" value=";"/>
-    <property name="guice.disable.misplaced.annotation.check" value="true"/>
-    <property name="java.vm.name" value="Java HotSpot(TM) 64-Bit Server VM"/>
-    <property name="file.encoding.pkg" value="sun.io"/>
-    <property name="user.script" value=""/>
-    <property name="user.country" value="US"/>
-    <property name="sun.java.launcher" value="SUN_STANDARD"/>
-    <property name="sun.os.patch.level" value="Service Pack 1"/>
-    <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
-    <property name="user.dir" value="c:\workspace\maven-surefire\surefire-integration-tests\src\test\resources\surefire-803-multiFailsafeExec-failureInFirst"/>
-    <property name="java.runtime.version" value="1.7.0_09-b05"/>
-    <property name="java.awt.graphicsenv" value="sun.awt.Win32GraphicsEnvironment"/>
-    <property name="java.endorsed.dirs" value="c:\java\jdk1.7.0_09\jre\lib\endorsed"/>
-    <property name="os.arch" value="amd64"/>
-    <property name="java.io.tmpdir" value="C:\Users\krose\AppData\Local\Temp\"/>
-    <property name="line.separator" value="
-"/>
-    <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
-    <property name="user.variant" value=""/>
-    <property name="os.name" value="Windows 7"/>
-    <property name="classworlds.conf" value="c:/java/apache-maven-3.0.4/bin/m2.conf"/>
-    <property name="sun.jnu.encoding" value="Cp1252"/>
-    <property name="java.library.path" value="c:\java\jdk1.7.0_09\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;x:\bin;.;C:\java\Git\local\bin;C:\java\Git\mingw\bin;C:\java\Git\bin;c:\java\jdk1.7.0_09\bin;c:\java\Subversion;c:\Program Files\Common Files\Microsoft Shared\Microsoft Online Services;c:\Program Files (x86)\Common Files\Microsoft Shared\Microsoft Online Services;c:\Windows\system32;c:\Windows;c:\Windows\System32\Wbem;c:\Windows\System32\WindowsPowerShell\v1.0\;c:\ [...]
-    <property name="java.specification.name" value="Java Platform API Specification"/>
-    <property name="java.class.version" value="51.0"/>
-    <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
-    <property name="os.version" value="6.1"/>
-    <property name="user.home" value="C:\Users\krose"/>
-    <property name="user.timezone" value="Europe/Paris"/>
-    <property name="java.awt.printerjob" value="sun.awt.windows.WPrinterJob"/>
-    <property name="java.specification.version" value="1.7"/>
-    <property name="file.encoding" value="Cp1252"/>
-    <property name="user.name" value="krose"/>
-    <property name="java.class.path" value="c:/java/apache-maven-3.0.4/boot/plexus-classworlds-2.4.jar"/>
-    <property name="java.vm.specification.version" value="1.7"/>
-    <property name="sun.arch.data.model" value="64"/>
-    <property name="java.home" value="c:\java\jdk1.7.0_09\jre"/>
-    <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher -Dsurefire.version=2.13-SNAPSHOT clean verify"/>
-    <property name="java.specification.vendor" value="Oracle Corporation"/>
-    <property name="user.language" value="en"/>
-    <property name="user.language.format" value="no"/>
-    <property name="awt.toolkit" value="sun.awt.windows.WToolkit"/>
-    <property name="java.vm.info" value="mixed mode"/>
-    <property name="java.version" value="1.7.0_09"/>
-    <property name="java.ext.dirs" value="c:\java\jdk1.7.0_09\jre\lib\ext;C:\Windows\Sun\Java\lib\ext"/>
-    <property name="sun.boot.class.path" value="c:\java\jdk1.7.0_09\jre\lib\resources.jar;c:\java\jdk1.7.0_09\jre\lib\rt.jar;c:\java\jdk1.7.0_09\jre\lib\sunrsasign.jar;c:\java\jdk1.7.0_09\jre\lib\jsse.jar;c:\java\jdk1.7.0_09\jre\lib\jce.jar;c:\java\jdk1.7.0_09\jre\lib\charsets.jar;c:\java\jdk1.7.0_09\jre\lib\jfr.jar;c:\java\jdk1.7.0_09\jre\classes"/>
-    <property name="java.vendor" value="Oracle Corporation"/>
-    <property name="maven.home" value="c:\java\apache-maven-3.0.4"/>
-    <property name="file.separator" value="\"/>
-    <property name="java.vendor.url.bug" value="http://bugreport.sun.com/bugreport/"/>
-    <property name="sun.cpu.endian" value="little"/>
-    <property name="sun.io.unicode.encoding" value="UnicodeLittle"/>
-    <property name="sun.desktop" value="windows"/>
-    <property name="sun.cpu.isalist" value="amd64"/>
-  </properties>
-  <testcase time="0.007" classname="org.apache.maven.surefire.test.SucceedingTest" name="defaultTestValueIs_Value"/>
-  <testcase time="0.001" classname="org.apache.maven.surefire.test.SucceedingTest" name="setTestAndRetrieveValue"/>
-</testsuite>
\ No newline at end of file
diff --git a/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.FailingTest.txt b/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.FailingTest.txt
deleted file mode 100644
index 38abc1f..0000000
--- a/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.FailingTest.txt
+++ /dev/null
@@ -1,84 +0,0 @@
--------------------------------------------------------------------------------
-Test set: org.apache.maven.surefire.test.FailingTest
--------------------------------------------------------------------------------
-Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.046 sec <<< FAILURE!
-defaultTestValueIs_Value(org.apache.maven.surefire.test.FailingTest)  Time elapsed: 0.013 sec  <<< FAILURE!
-java.lang.AssertionError: 
-Expected: "wrong"
-     got: "value"
-
-	at org.junit.Assert.assertThat(Assert.java:778)
-	at org.junit.Assert.assertThat(Assert.java:736)
-	at org.apache.maven.surefire.test.FailingTest.defaultTestValueIs_Value(FailingTest.java:23)
-	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
-	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-	at java.lang.reflect.Method.invoke(Method.java:601)
-	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
-	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
-	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
-	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
-	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
-	at org.junit.rules.TestWatchman$1.evaluate(TestWatchman.java:48)
-	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
-	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
-	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
-	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
-	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
-	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
-	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
-	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
-	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:262)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:151)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:122)
-	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
-	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-	at java.lang.reflect.Method.invoke(Method.java:601)
-	at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
-	at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
-	at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
-	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:128)
-	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:88)
-
-setTestAndRetrieveValue(org.apache.maven.surefire.test.FailingTest)  Time elapsed: 0.001 sec  <<< FAILURE!
-java.lang.AssertionError: 
-Expected: "bar"
-     got: "foo"
-
-	at org.junit.Assert.assertThat(Assert.java:778)
-	at org.junit.Assert.assertThat(Assert.java:736)
-	at org.apache.maven.surefire.test.FailingTest.setTestAndRetrieveValue(FailingTest.java:34)
-	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
-	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-	at java.lang.reflect.Method.invoke(Method.java:601)
-	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
-	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
-	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
-	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
-	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
-	at org.junit.rules.TestWatchman$1.evaluate(TestWatchman.java:48)
-	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
-	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
-	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
-	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
-	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
-	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
-	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
-	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
-	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:262)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:151)
-	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:122)
-	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
-	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-	at java.lang.reflect.Method.invoke(Method.java:601)
-	at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
-	at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
-	at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
-	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:128)
-	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:88)
-
diff --git a/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.SucceedingTest.txt b/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.SucceedingTest.txt
deleted file mode 100644
index 9433827..0000000
--- a/surefire-its/src/test/resources/fixture/testsuitexmlparser/org.apache.maven.surefire.test.SucceedingTest.txt
+++ /dev/null
@@ -1,4 +0,0 @@
--------------------------------------------------------------------------------
-Test set: org.apache.maven.surefire.test.SucceedingTest
--------------------------------------------------------------------------------
-Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 sec
diff --git a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports2/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
similarity index 100%
rename from surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
rename to surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports2/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
diff --git a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
similarity index 86%
rename from surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
rename to surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
index 9964f13..7a2f989 100644
--- a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
+++ b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module1/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
@@ -2,7 +2,7 @@
 Test set: org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest
 -------------------------------------------------------------------------------
 Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.049 sec <<< FAILURE!
-testGetFooKO(org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest)  Time elapsed: 0.01 sec  <<< FAILURE!
+org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest.testGetFooKO  Time elapsed: 0.01 sec  <<< FAILURE!
 junit.framework.AssertionFailedError: expected:<18> but was:<42>
 	at junit.framework.Assert.fail(Assert.java:47)
 	at junit.framework.Assert.failNotEquals(Assert.java:280)
diff --git a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
similarity index 97%
rename from surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
rename to surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
index 2c9cbed..d90475a 100644
--- a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
+++ b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/TEST-org.apache.maven.surefireReport.surefireReportTest.MyClassTest.xml
@@ -57,7 +57,7 @@
     <property name="sun.desktop" value="gnome"/>
     <property name="sun.cpu.isalist" value=""/>
   </properties>
-  <testcase time="0.005" classname="org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassM2Test" name="testGetFooKO">
+  <testcase time="0.005" classname="org.apache.maven.surefireReport.surefireReportTest.module2.MyDummyClassM2Test" name="testGetFooKO">
     <failure message="expected:&lt;18&gt; but was:&lt;42&gt;" type="junit.framework.AssertionFailedError">junit.framework.AssertionFailedError: expected:&lt;18&gt; but was:&lt;42&gt;
 	at junit.framework.Assert.fail(Assert.java:47)
 	at junit.framework.Assert.failNotEquals(Assert.java:280)
@@ -67,5 +67,5 @@
 	at org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest.testGetFooKO(MyClassTest.java:10)
 </failure>
   </testcase>
-  <testcase time="0" classname="org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassM2Test" name="testGetFooOK"/>
+  <testcase time="0" classname="org.apache.maven.surefireReport.surefireReportTest.module2.MyDummyClassM2Test" name="testGetFooOK"/>
 </testsuite>
diff --git a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
similarity index 86%
rename from surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
rename to surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
index 9964f13..7a2f989 100644
--- a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
+++ b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/module2/src/test/resources/surefire-reports2/org.apache.maven.surefireReport.surefireReportTest.MyClassTest.txt
@@ -2,7 +2,7 @@
 Test set: org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest
 -------------------------------------------------------------------------------
 Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.049 sec <<< FAILURE!
-testGetFooKO(org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest)  Time elapsed: 0.01 sec  <<< FAILURE!
+org.apache.maven.surefireReport.surefireReportTest.module1.MyDummyClassTest.testGetFooKO  Time elapsed: 0.01 sec  <<< FAILURE!
 junit.framework.AssertionFailedError: expected:<18> but was:<42>
 	at junit.framework.Assert.fail(Assert.java:47)
 	at junit.framework.Assert.failNotEquals(Assert.java:280)
diff --git a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/pom.xml b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/pom.xml
index b3238ea..ba51625 100644
--- a/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/pom.xml
+++ b/surefire-its/src/test/resources/surefire-570-multipleReportDirectories/pom.xml
@@ -45,7 +45,7 @@
               <configuration>
                 <reportsDirectories>
                   <reportsDirectory>${basedir}/target/surefire-reports</reportsDirectory>
-                  <reportsDirectory>${basedir}/src/test/resources/surefire-reports</reportsDirectory>
+                  <reportsDirectory>${basedir}/src/test/resources/surefire-reports2</reportsDirectory>
                 </reportsDirectories>
               </configuration>
             </plugin>
@@ -96,7 +96,7 @@
         <configuration>
           <reportsDirectories>
             <reportsDirectory>${basedir}/target/surefire-reports</reportsDirectory>
-            <reportsDirectory>${basedir}/src/test/resources/surefire-reports</reportsDirectory>
+            <reportsDirectory>${basedir}/src/test/resources/surefire-reports2</reportsDirectory>
           </reportsDirectories>
         </configuration>
         <reportSets>
diff --git a/surefire-providers/common-java5/src/main/java/org/apache/maven/surefire/report/PojoStackTraceWriter.java b/surefire-providers/common-java5/src/main/java/org/apache/maven/surefire/report/PojoStackTraceWriter.java
index 626392b..2f369e8 100644
--- a/surefire-providers/common-java5/src/main/java/org/apache/maven/surefire/report/PojoStackTraceWriter.java
+++ b/surefire-providers/common-java5/src/main/java/org/apache/maven/surefire/report/PojoStackTraceWriter.java
@@ -24,6 +24,7 @@ import org.apache.maven.surefire.util.internal.StringUtils;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Objects;
 
 /**
  * Write the trace out for a POJO test.
@@ -109,4 +110,27 @@ public class PojoStackTraceWriter
         }
         return false;
     }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+        PojoStackTraceWriter that = ( PojoStackTraceWriter ) o;
+        return Objects.equals( t, that.t )
+                && Objects.equals( testClass, that.testClass )
+                && Objects.equals( testMethod, that.testMethod );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash( t, testClass, testMethod );
+    }
 }
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtil.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtil.java
index d19a0e1..702ee7f 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtil.java
+++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtil.java
@@ -20,14 +20,17 @@ package org.apache.maven.surefire.common.junit4;
  */
 
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.maven.surefire.util.internal.ClassMethod;
 import org.junit.runner.Description;
 import org.junit.runner.manipulation.Filter;
 import org.junit.runner.notification.Failure;
 
-import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractClassName;
+import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractMethodName;
 import static org.junit.runner.Description.TEST_MECHANISM;
 
 /**
@@ -75,33 +78,30 @@ public final class JUnit4ProviderUtil
      * @param description method(class) or method[#](class) or method[#whatever-literals](class)
      * @return method JUnit test method
      */
-    public static ClassMethod cutTestClassAndMethod( Description description )
+    public static ClassMethod toClassMethod( Description description )
     {
-        String name = description.getDisplayName();
-        String clazz = null;
-        String method = null;
-        if ( name != null )
+        String clazz = extractClassName( description.getDisplayName() );
+        if ( clazz == null || isInsaneJunitNullString( clazz ) )
         {
-            // The order is : 1.method and then 2.class
-            // method(class)
-            name = name.trim();
-            if ( name.endsWith( ")" ) )
+            // This can happen upon early failures (class instantiation error etc)
+            Iterator<Description> it = description.getChildren().iterator();
+            if ( it.hasNext() )
             {
-                int classBracket = name.lastIndexOf( '(' );
-                if ( classBracket != -1 )
-                {
-                    clazz = tryBlank( name.substring( classBracket + 1, name.length() - 1 ) );
-                    method = tryBlank( name.substring( 0, classBracket ) );
-                }
+                description = it.next();
+                clazz = extractClassName( description.getDisplayName() );
+            }
+            if ( clazz == null )
+            {
+                clazz = "Test Instantiation Error";
             }
         }
+        String method = extractMethodName( description.getDisplayName() );
         return new ClassMethod( clazz, method );
     }
 
-    private static String tryBlank( String s )
+    private static boolean isInsaneJunitNullString( String value )
     {
-        s = s.trim();
-        return isBlank( s ) ? null : s;
+        return "null".equals( value );
     }
 
     public static Filter createMatchAnyDescriptionFilter( Iterable<Description> descriptions )
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java
index bbdeabb..9efe425 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java
+++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java
@@ -117,7 +117,7 @@ public final class JUnit4Reflector
         @Override
         public boolean equals( Object obj )
         {
-            return obj instanceof Annotation && obj instanceof Ignore && equalValue( ( Ignore ) obj );
+            return obj instanceof Ignore && equalValue( ( Ignore ) obj );
         }
 
         @Override
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 7ed2ad9..648f910 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
@@ -24,17 +24,17 @@ import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 import org.junit.runner.Description;
 import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
 
 import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.isFailureInsideJUnitItself;
+import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
 import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.getAnnotatedIgnoreValue;
 import static org.apache.maven.surefire.report.SimpleReportEntry.assumption;
 import static org.apache.maven.surefire.report.SimpleReportEntry.ignored;
 import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
-import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractClassName;
-import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractMethodName;
 
 /**
  * RunListener for JUnit4, delegates to our own RunListener
@@ -74,7 +74,8 @@ public class JUnit4RunListener
         throws Exception
     {
         String reason = getAnnotatedIgnoreValue( description );
-        reporter.testSkipped( ignored( getClassName( description ), description.getDisplayName(), reason ) );
+        ClassMethod classMethod = toClassMethod( description );
+        reporter.testSkipped( ignored( classMethod.getClazz(), classMethod.getMethod(), reason ) );
     }
 
     /**
@@ -108,16 +109,9 @@ public class JUnit4RunListener
     {
         try
         {
-            String testHeader = failure.getTestHeader();
-            if ( isInsaneJunitNullString( testHeader ) )
-            {
-                testHeader = "Failure when constructing test";
-            }
-
-            String testClassName = getClassName( failure.getDescription() );
             StackTraceWriter stackTrace = createStackTraceWriter( failure );
-
-            ReportEntry report = withException( testClassName, testHeader, stackTrace );
+            ClassMethod classMethod = toClassMethod( failure.getDescription() );
+            ReportEntry report = withException( classMethod.getClazz(), classMethod.getMethod(), stackTrace );
 
             if ( failure.getException() instanceof AssertionError )
             {
@@ -140,8 +134,9 @@ public class JUnit4RunListener
         try
         {
             Description desc = failure.getDescription();
-            String test = getClassName( desc );
-            reporter.testAssumptionFailure( assumption( test, desc.getDisplayName(), failure.getMessage() ) );
+            ClassMethod classMethod = toClassMethod( desc );
+            ReportEntry report = assumption( classMethod.getClazz(), classMethod.getMethod(), failure.getMessage() );
+            reporter.testAssumptionFailure( report );
         }
         finally
         {
@@ -173,25 +168,6 @@ public class JUnit4RunListener
         reporter.testExecutionSkippedByUser();
     }
 
-    private String getClassName( Description description )
-    {
-        String name = extractDescriptionClassName( description );
-        if ( name == null || isInsaneJunitNullString( name ) )
-        {
-            // This can happen upon early failures (class instantiation error etc)
-            Description subDescription = description.getChildren().get( 0 );
-            if ( subDescription != null )
-            {
-                name = extractDescriptionClassName( subDescription );
-            }
-            if ( name == null )
-            {
-                name = "Test Instantiation Error";
-            }
-        }
-        return name;
-    }
-
     protected StackTraceWriter createStackTraceWriter( Failure failure )
     {
         return new JUnit4StackTraceWriter( failure );
@@ -199,17 +175,8 @@ public class JUnit4RunListener
 
     protected SimpleReportEntry createReportEntry( Description description )
     {
-        return new SimpleReportEntry( getClassName( description ), description.getDisplayName() );
-    }
-
-    protected String extractDescriptionClassName( Description description )
-    {
-        return extractClassName( description.getDisplayName() );
-    }
-
-    protected String extractDescriptionMethodName( Description description )
-    {
-        return extractMethodName( description.getDisplayName() );
+        ClassMethod classMethod = toClassMethod( description );
+        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getMethod() );
     }
 
     public static void rethrowAnyTestMechanismFailures( Result run )
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4StackTraceWriter.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4StackTraceWriter.java
index 78cefb6..f60a30d 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4StackTraceWriter.java
+++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4StackTraceWriter.java
@@ -22,10 +22,10 @@ package org.apache.maven.surefire.common.junit4;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.SmartStackTraceParser;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 import org.junit.runner.notification.Failure;
 
-import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractClassName;
-import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractMethodName;
+import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
 import static org.apache.maven.surefire.report.SmartStackTraceParser.stackTraceWithFocusOnClassAsString;
 
 /**
@@ -37,8 +37,7 @@ import static org.apache.maven.surefire.report.SmartStackTraceParser.stackTraceW
 public class JUnit4StackTraceWriter
     implements StackTraceWriter
 {
-    // Member Variables
-    protected final Failure junitFailure;
+    private final Failure junitFailure;
 
     /**
      * Constructor.
@@ -78,24 +77,14 @@ public class JUnit4StackTraceWriter
         return "";
     }
 
-    protected String getTestClassName()
-    {
-        return extractClassName( junitFailure.getDescription().getDisplayName() );
-    }
-
-    protected String getTestMethodName()
-    {
-        return extractMethodName( junitFailure.getDescription().getDisplayName() );
-    }
-
     @Override
-    @SuppressWarnings( "ThrowableResultOfMethodCallIgnored" )
     public String smartTrimmedStackTrace()
     {
         Throwable exception = junitFailure.getException();
+        ClassMethod classMethod = toClassMethod( junitFailure.getDescription() );
         return exception == null
             ? junitFailure.getMessage()
-            : new SmartStackTraceParser( getTestClassName(), exception, getTestMethodName() ).getString();
+            : new SmartStackTraceParser( classMethod.getClazz(), exception, classMethod.getMethod() ).getString();
     }
 
     /**
@@ -106,7 +95,7 @@ public class JUnit4StackTraceWriter
     @Override
     public String writeTrimmedTraceToString()
     {
-        String testClass = getTestClassName();
+        String testClass = toClassMethod( junitFailure.getDescription() ).getClazz();
         try
         {
             Throwable e = junitFailure.getException();
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/Notifier.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/Notifier.java
index c2c6df8..de0ddc4 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/Notifier.java
+++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/Notifier.java
@@ -32,7 +32,7 @@ import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.cutTestClassAndMethod;
+import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
 import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
 
 /**
@@ -100,7 +100,7 @@ public class Notifier
         super.fireTestStarted( description );
         if ( !testClassNames.isEmpty() )
         {
-            testClassNames.remove( cutTestClassAndMethod( description ).getClazz() );
+            testClassNames.remove( toClassMethod( description ).getClazz() );
         }
     }
 
diff --git a/surefire-providers/common-junit4/src/test/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtilTest.java b/surefire-providers/common-junit4/src/test/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtilTest.java
index 163308c..15adc32 100644
--- a/surefire-providers/common-junit4/src/test/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtilTest.java
+++ b/surefire-providers/common-junit4/src/test/java/org/apache/maven/surefire/common/junit4/JUnit4ProviderUtilTest.java
@@ -20,6 +20,7 @@ package org.apache.maven.surefire.common.junit4;
  */
 
 import junit.framework.TestCase;
+import org.apache.maven.surefire.util.internal.ClassMethod;
 import org.junit.runner.Description;
 import org.junit.runner.notification.Failure;
 
@@ -64,25 +65,25 @@ public class JUnit4ProviderUtilTest
     public void testIllegalTestDescription$NegativeTest()
     {
         Description test = Description.createSuiteDescription( "someTestMethod" );
-        ClassMethod classMethod = cutTestClassAndMethod( test );
-        assertFalse( classMethod.isValid() );
+        ClassMethod classMethod = JUnit4ProviderUtil.toClassMethod( test );
+        assertFalse( classMethod.isValidTest() );
     }
 
     public void testOldJUnitParameterizedDescriptionParser()
     {
         Description test = Description.createTestDescription( T1.class, " \n testMethod[5] " );
         assertEquals( " \n testMethod[5] (" + T1.class.getName() + ")", test.getDisplayName() );
-        ClassMethod classMethod = cutTestClassAndMethod( test );
-        assertTrue( classMethod.isValid() );
-        assertEquals( "testMethod[5]", classMethod.getMethod() );
+        ClassMethod classMethod = JUnit4ProviderUtil.toClassMethod( test );
+        assertTrue( classMethod.isValidTest() );
+        assertEquals( " \n testMethod[5] ", classMethod.getMethod() );
         assertEquals( T1.class.getName(), classMethod.getClazz() );
     }
 
     public void testNewJUnitParameterizedDescriptionParser()
     {
         Description test = Description.createTestDescription( T1.class, "flakyTest[3: (Test11); Test12; Test13;]" );
-        ClassMethod classMethod = cutTestClassAndMethod( test );
-        assertTrue( classMethod.isValid() );
+        ClassMethod classMethod = JUnit4ProviderUtil.toClassMethod( test );
+        assertTrue( classMethod.isValidTest() );
         assertEquals( "flakyTest[3: (Test11); Test12; Test13;]", classMethod.getMethod() );
         assertEquals( T1.class.getName(), classMethod.getClazz() );
     }
diff --git a/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/JUnit46StackTraceWriter.java b/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/JUnit46StackTraceWriter.java
deleted file mode 100644
index 4051e19..0000000
--- a/surefire-providers/common-junit48/src/main/java/org/apache/maven/surefire/common/junit48/JUnit46StackTraceWriter.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.apache.maven.surefire.common.junit48;
-
-/*
- * 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.common.junit4.JUnit4StackTraceWriter;
-import org.junit.runner.notification.Failure;
-
-/**
- * A stacktrace writer that requires at least junit 4.6 to run. Note that we only use this for 4.8 and higher
- * <br>
- * Writes out a specific {@link org.junit.runner.notification.Failure} for
- * surefire as a stacktrace.
- *
- * @author Karl M. Davis
- * @author Kristian Rosenvold
- */
-public class JUnit46StackTraceWriter
-    extends JUnit4StackTraceWriter
-{
-
-    /**
-     * Constructor.
-     *
-     * @param junitFailure the {@link org.junit.runner.notification.Failure} that this will be operating on
-     */
-    public JUnit46StackTraceWriter( Failure junitFailure )
-    {
-        super( junitFailure );
-    }
-
-
-    @Override
-    protected final String getTestClassName()
-    {
-        return junitFailure.getDescription().getClassName();
-    }
-
-    @Override
-    protected String getTestMethodName()
-    {
-        return junitFailure.getDescription().getMethodName();
-    }
-}
diff --git a/surefire-providers/surefire-junit-platform/pom.xml b/surefire-providers/surefire-junit-platform/pom.xml
index 76bc92c..e551fef 100644
--- a/surefire-providers/surefire-junit-platform/pom.xml
+++ b/surefire-providers/surefire-junit-platform/pom.xml
@@ -139,6 +139,10 @@
                 <version>3.0.0-M3</version> <!-- ${shadedVersion}, but resolved due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
                 <configuration>
                     <jvm>${java.home}/bin/java</jvm>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <includes>
+                        <include>**/JUnit47SuiteTest.java</include>
+                    </includes>
                 </configuration>
             </plugin>
         </plugins>
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 85227f3..9832b14 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
@@ -20,17 +20,15 @@ package org.apache.maven.surefire.junitplatform;
  */
 
 import static org.apache.maven.surefire.report.SimpleReportEntry.ignored;
-import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED;
-import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
+import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
 
 import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.maven.surefire.report.PojoStackTraceWriter;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 import org.junit.platform.engine.TestExecutionResult;
 import org.junit.platform.engine.TestSource;
 import org.junit.platform.engine.support.descriptor.ClassSource;
@@ -38,7 +36,6 @@ import org.junit.platform.engine.support.descriptor.MethodSource;
 import org.junit.platform.launcher.TestExecutionListener;
 import org.junit.platform.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
-import org.junit.platform.launcher.listeners.LegacyReportingUtils;
 
 /**
  * @since 2.22.0
@@ -46,12 +43,9 @@ import org.junit.platform.launcher.listeners.LegacyReportingUtils;
 final class RunListenerAdapter
     implements TestExecutionListener
 {
-
     private final RunListener runListener;
 
-    private TestPlan testPlan;
-
-    private Set<TestIdentifier> testSetNodes = ConcurrentHashMap.newKeySet();
+    private volatile TestPlan testPlan;
 
     RunListenerAdapter( RunListener runListener )
     {
@@ -61,13 +55,13 @@ final class RunListenerAdapter
     @Override
     public void testPlanExecutionStarted( TestPlan testPlan )
     {
-        updateTestPlan( testPlan );
+        this.testPlan = testPlan;
     }
 
     @Override
     public void testPlanExecutionFinished( TestPlan testPlan )
     {
-        updateTestPlan( null );
+        this.testPlan = null;
     }
 
     @Override
@@ -76,197 +70,173 @@ final class RunListenerAdapter
         if ( testIdentifier.isContainer()
                         && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
         {
-            startTestSetIfPossible( testIdentifier );
+            runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) );
         }
-        if ( testIdentifier.isTest() )
+        else if ( testIdentifier.isTest() )
         {
-            ensureTestSetStarted( testIdentifier );
             runListener.testStarting( createReportEntry( testIdentifier ) );
         }
     }
 
     @Override
-    public void executionSkipped( TestIdentifier testIdentifier, String reason )
-    {
-        ensureTestSetStarted( testIdentifier );
-        String source = getLegacyReportingClassName( testIdentifier );
-        runListener.testSkipped( ignored( source, getLegacyReportingName( testIdentifier ), reason ) );
-        completeTestSetIfNecessary( testIdentifier );
+    public void executionFinished( TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
+    {
+        boolean isClass = testIdentifier.isContainer()
+                && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent();
+
+        boolean isTest = testIdentifier.isTest();
+
+        if ( isClass || isTest )
+        {
+            switch ( testExecutionResult.getStatus() )
+            {
+                case ABORTED:
+                    TestSetReportEntry reportEntry = createReportEntry( testIdentifier, testExecutionResult );
+                    if ( isTest )
+                    {
+                        runListener.testAssumptionFailure( reportEntry );
+                    }
+                    else
+                    {
+                        runListener.testSetCompleted( reportEntry );
+                    }
+                    break;
+                case FAILED:
+                    reportEntry = createReportEntry( testIdentifier, testExecutionResult );
+                    if ( !isTest )
+                    {
+                        runListener.testSetCompleted( reportEntry );
+                    }
+                    else if ( testExecutionResult.getThrowable()
+                            .filter( AssertionError.class::isInstance ).isPresent() )
+                    {
+                        runListener.testFailed( reportEntry );
+                    }
+                    else
+                    {
+                        runListener.testError( reportEntry );
+                    }
+                    break;
+                default:
+                    reportEntry = createReportEntry( testIdentifier );
+                    if ( isTest )
+                    {
+                        runListener.testSucceeded( reportEntry );
+                    }
+                    else
+                    {
+                        runListener.testSetCompleted( reportEntry );
+                    }
+            }
+        }
     }
 
     @Override
-    public void executionFinished(
-                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
+    public void executionSkipped( TestIdentifier testIdentifier, String reason )
     {
-        if ( testExecutionResult.getStatus() == ABORTED )
-        {
-            runListener.testAssumptionFailure( createReportEntry( testIdentifier, testExecutionResult ) );
-        }
-        else if ( testExecutionResult.getStatus() == FAILED )
-        {
-            reportFailedTest( testIdentifier, testExecutionResult );
-        }
-        else if ( testIdentifier.isTest() )
-        {
-            runListener.testSucceeded( createReportEntry( testIdentifier ) );
-        }
-        completeTestSetIfNecessary( testIdentifier );
+        String[] classMethodName = toClassMethodName( testIdentifier );
+        String className = classMethodName[1];
+        String methodName = classMethodName[3];
+        runListener.testSkipped( ignored( className, methodName, reason ) );
     }
 
-    private void updateTestPlan( TestPlan testPlan )
+    private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier )
     {
-        this.testPlan = testPlan;
-        testSetNodes.clear();
+        String[] classMethodName = toClassMethodName( testIdentifier );
+        String className = classMethodName[1];
+        String methodName = classMethodName[3];
+        return new SimpleReportEntry( className, methodName );
     }
 
-    private void ensureTestSetStarted( TestIdentifier testIdentifier )
+    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier )
     {
-        if ( isTestSetStarted( testIdentifier ) )
-        {
-            return;
-        }
-        if ( testIdentifier.isTest() )
-        {
-            startTestSet( testPlan.getParent( testIdentifier ).orElse( testIdentifier ) );
-        }
-        else
-        {
-            startTestSet( testIdentifier );
-        }
+        return createReportEntry( testIdentifier, (StackTraceWriter) null );
     }
 
-    private boolean isTestSetStarted( TestIdentifier testIdentifier )
+    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
+                                                 TestExecutionResult testExecutionResult )
     {
-        return testSetNodes.contains( testIdentifier )
-                        || testPlan.getParent( testIdentifier ).map( this::isTestSetStarted ).orElse( false );
+        String[] classMethodNames = toClassMethodName( testIdentifier );
+        return createReportEntry( testIdentifier, toStackTraceWriter( classMethodNames, testExecutionResult ) );
     }
 
-    private void startTestSetIfPossible( TestIdentifier testIdentifier )
+    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier, StackTraceWriter stackTraceWriter )
     {
-        if ( !isTestSetStarted( testIdentifier ) )
-        {
-            startTestSet( testIdentifier );
-        }
+        String[] classMethodNames = toClassMethodName( testIdentifier );
+        String className = classMethodNames[1];
+        String methodName = classMethodNames[3];
+        return withException( className, methodName, stackTraceWriter );
     }
 
-    private void completeTestSetIfNecessary( TestIdentifier testIdentifier )
+    private StackTraceWriter toStackTraceWriter( String[] classMethodNames, TestExecutionResult testExecutionResult )
     {
-        if ( testSetNodes.contains( testIdentifier ) )
+        String realClassName = classMethodNames[0];
+        String realMethodName = classMethodNames[2];
+        switch ( testExecutionResult.getStatus() )
         {
-            completeTestSet( testIdentifier );
+            case ABORTED:
+            case FAILED:
+                // Failed tests must have a StackTraceWriter, otherwise Surefire will fail
+                Throwable exception = testExecutionResult.getThrowable().orElse( null );
+                return toStackTraceWriter( realClassName, realMethodName, exception );
+            default:
+                return testExecutionResult.getThrowable()
+                        .map( t -> toStackTraceWriter( realClassName, realMethodName, t ) )
+                        .orElse( null );
         }
     }
 
-    private void startTestSet( TestIdentifier testIdentifier )
+    private StackTraceWriter toStackTraceWriter( String realClassName, String realMethodName, Throwable throwable )
     {
-        runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) );
-        testSetNodes.add( testIdentifier );
+        return new PojoStackTraceWriter( realClassName, realMethodName, throwable );
     }
 
-    private void completeTestSet( TestIdentifier testIdentifier )
+    /**
+     * <ul>
+     *     <li>[0] class name - used in stacktrace parser</li>
+     *     <li>[1] class display name</li>
+     *     <li>[2] method signature - used in stacktrace parser</li>
+     *     <li>[3] method display name</li>
+     * </ul>
+     *
+     * @param testIdentifier a class or method
+     * @return 4 elements string array
+     */
+    private String[] toClassMethodName( TestIdentifier testIdentifier )
     {
-        runListener.testSetCompleted( createTestSetReportEntry( testIdentifier ) );
-        testSetNodes.remove( testIdentifier );
-    }
+        Optional<TestSource> testSource = testIdentifier.getSource();
+        String display = testIdentifier.getDisplayName();
 
-    private void reportFailedTest(
-                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
-    {
-        SimpleReportEntry reportEntry = createReportEntry( testIdentifier, testExecutionResult );
-        if ( testExecutionResult.getThrowable().filter( AssertionError.class::isInstance ).isPresent() )
-        {
-            runListener.testFailed( reportEntry );
-        }
-        else
+        if ( testSource.filter( MethodSource.class::isInstance ).isPresent() )
         {
-            runListener.testError( reportEntry );
-        }
-    }
+            MethodSource methodSource = testSource.map( MethodSource.class::cast ).get();
+            String realClassName = methodSource.getClassName();
 
-    private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier )
-    {
-        return new SimpleReportEntry(
-                        JUnitPlatformProvider.class.getName(), testIdentifier.getLegacyReportingName() );
-    }
-
-    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier )
-    {
-        return createReportEntry( testIdentifier, (StackTraceWriter) null );
-    }
-
-    private SimpleReportEntry createReportEntry(
-                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
-    {
-        return createReportEntry(
-                        testIdentifier, getStackTraceWriter( testIdentifier, testExecutionResult ) );
-    }
-
-    private SimpleReportEntry createReportEntry(
-                    TestIdentifier testIdentifier, StackTraceWriter stackTraceWriter )
-    {
-        String source = getLegacyReportingClassName( testIdentifier );
-        String name = getLegacyReportingName( testIdentifier );
-
-        return SimpleReportEntry.withException( source, name, stackTraceWriter );
-    }
-
-    private String getLegacyReportingName( TestIdentifier testIdentifier )
-    {
-        // Surefire cuts off the name at the first '(' character. Thus, we have to pick a different
-        // character to represent parentheses. "()" are removed entirely to maximize compatibility with
-        // existing reporting tools because in the old days test methods used to not have parameters.
-        return testIdentifier
-                        .getLegacyReportingName()
-                        .replace( "()", "" )
-                        .replace( '(', '{' )
-                        .replace( ')', '}' );
-    }
+            String[] source = testPlan.getParent( testIdentifier )
+                    .map( this::toClassMethodName )
+                    .map( s -> new String[] { s[0], s[1] } )
+                    .orElse( new String[] { realClassName, realClassName } );
 
-    private String getLegacyReportingClassName( TestIdentifier testIdentifier )
-    {
-        return LegacyReportingUtils.getClassName( testPlan, testIdentifier );
-    }
+            String methodName = methodSource.getMethodName();
+            boolean useMethod = display.equals( methodName ) || display.equals( methodName + "()" );
+            String resolvedMethodName = useMethod ? methodName : display;
 
-    private StackTraceWriter getStackTraceWriter(
-                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
-    {
-        Optional<Throwable> throwable = testExecutionResult.getThrowable();
-        if ( testExecutionResult.getStatus() == FAILED )
-        {
-            // Failed tests must have a StackTraceWriter, otherwise Surefire will fail
-            return getStackTraceWriter( testIdentifier, throwable.orElse( null ) );
+            return new String[] { source[0], source[1], methodName, resolvedMethodName };
         }
-        return throwable.map( t -> getStackTraceWriter( testIdentifier, t ) ).orElse( null );
-    }
-
-    private StackTraceWriter getStackTraceWriter( TestIdentifier testIdentifier, Throwable throwable )
-    {
-        String className = getClassName( testIdentifier );
-        String methodName = getMethodName( testIdentifier ).orElse( "" );
-        return new PojoStackTraceWriter( className, methodName, throwable );
-    }
-
-    private String getClassName( TestIdentifier testIdentifier )
-    {
-        TestSource testSource = testIdentifier.getSource().orElse( null );
-        if ( testSource instanceof ClassSource )
-        {
-            return ( (ClassSource) testSource ).getJavaClass().getName();
-        }
-        if ( testSource instanceof MethodSource )
+        else if ( testSource.filter( ClassSource.class::isInstance ).isPresent() )
         {
-            return ( (MethodSource) testSource ).getClassName();
+            ClassSource classSource = testSource.map( ClassSource.class::cast ).get();
+            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 testPlan.getParent( testIdentifier ).map( this::getClassName ).orElse( "" );
-    }
-
-    private Optional<String> getMethodName( TestIdentifier testIdentifier )
-    {
-        TestSource testSource = testIdentifier.getSource().orElse( null );
-        if ( testSource instanceof MethodSource )
+        else
         {
-            return Optional.of( ( (MethodSource) testSource ).getMethodName() );
+            String source = testPlan.getParent( testIdentifier )
+                    .map( TestIdentifier::getDisplayName )
+                    .orElse( display );
+            return new String[] { source, source, display, display };
         }
-        return Optional.empty();
     }
 }
diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
similarity index 56%
rename from surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java
rename to surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
index a3cccca..d0d4af9 100644
--- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/ClassMethod.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnit47SuiteTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.common.junit4;
+package org.apache.maven.surefire.junitplatform;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,35 +19,28 @@ package org.apache.maven.surefire.common.junit4;
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.StringUtils;
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
 
 /**
- * Data transfer object of class and method literals.
+ * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
+ *
+ * @since 3.0.0-M4
  */
-public final class ClassMethod
+@SuiteClasses( {
+        JUnitPlatformProviderTest.class,
+        RunListenerAdapterTest.class,
+        TestMethodFilterTest.class,
+        TestPlanScannerFilterTest.class
+} )
+@RunWith( Suite.class )
+public class JUnit47SuiteTest
 {
-    private final String clazz;
-
-    private final String method;
-
-    public ClassMethod( String clazz, String method )
-    {
-        this.clazz = clazz;
-        this.method = method;
-    }
-
-    public boolean isValid()
-    {
-        return !StringUtils.isBlank( clazz ) && !StringUtils.isBlank( method );
-    }
-
-    public String getClazz()
-    {
-        return clazz;
-    }
-
-    public String getMethod()
+    public static Test suite()
     {
-        return method;
+        return new JUnit4TestAdapter( JUnit47SuiteTest.class );
     }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
index c162905..1c03bc0 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
@@ -163,30 +163,17 @@ public class JUnitPlatformProviderTest
         invokeProvider( provider, null );
 
         InOrder inOrder = inOrder( runListener );
-        inOrder
-                        .verify( runListener )
-                        .testSetStarting(
-                                        new SimpleReportEntry(
-                                                        JUnitPlatformProvider.class.getName(),
-                                                        TestClass1.class.getName() ) );
-        inOrder
-                        .verify( runListener )
-                        .testSetCompleted(
-                                        new SimpleReportEntry(
-                                                        JUnitPlatformProvider.class.getName(),
-                                                        TestClass1.class.getName() ) );
-        inOrder
-                        .verify( runListener )
-                        .testSetStarting(
-                                        new SimpleReportEntry(
-                                                        JUnitPlatformProvider.class.getName(),
-                                                        TestClass2.class.getName() ) );
-        inOrder
-                        .verify( runListener )
-                        .testSetCompleted(
-                                        new SimpleReportEntry(
-                                                        JUnitPlatformProvider.class.getName(),
-                                                        TestClass2.class.getName() ) );
+        inOrder.verify( runListener )
+                .testSetStarting( new SimpleReportEntry( TestClass1.class.getName(), null ) );
+
+        inOrder.verify( runListener )
+                .testSetCompleted( new SimpleReportEntry( TestClass1.class.getName(), null ) );
+
+        inOrder.verify( runListener )
+                .testSetStarting( new SimpleReportEntry( TestClass2.class.getName(), null ) );
+
+        inOrder.verify( runListener )
+                .testSetCompleted( new SimpleReportEntry( TestClass2.class.getName(), null ) );
 
         assertThat( executionListener.summaries ).hasSize( 1 );
         TestExecutionSummary summary = executionListener.summaries.get( 0 );
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 0ccb4fe..270ef3e 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
@@ -22,15 +22,17 @@ package org.apache.maven.surefire.junitplatform;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
+import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER;
 import static org.junit.platform.engine.TestDescriptor.Type.TEST;
+import static org.junit.platform.engine.TestExecutionResult.aborted;
+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.inOrder;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -38,11 +40,15 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import java.lang.reflect.Method;
 import java.util.Optional;
 
+import org.apache.maven.surefire.report.PojoStackTraceWriter;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.apache.maven.surefire.report.TestSetReportEntry;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.jupiter.api.DisplayName;
@@ -51,16 +57,16 @@ 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.TestExecutionResult;
 import org.junit.platform.engine.TestSource;
 import org.junit.platform.engine.UniqueId;
 import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
 import org.junit.platform.engine.support.descriptor.ClassSource;
 import org.junit.platform.engine.support.descriptor.EngineDescriptor;
+import org.junit.platform.engine.support.descriptor.MethodSource;
 import org.junit.platform.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
+import org.opentest4j.TestSkippedException;
 
 /**
  * Unit tests for {@link RunListenerAdapter}.
@@ -69,7 +75,7 @@ import org.mockito.InOrder;
  */
 public class RunListenerAdapterTest
 {
-    private static final ConfigurationParameters CONFIG_PARAMS = mock(ConfigurationParameters.class);
+    private static final ConfigurationParameters CONFIG_PARAMS = mock( ConfigurationParameters.class );
 
     private RunListener listener;
 
@@ -120,7 +126,7 @@ 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 + "(String)", entry.getName() );
         assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
         assertNull( entry.getStackTraceWriter() );
     }
@@ -139,8 +145,7 @@ public class RunListenerAdapterTest
         adapter.testPlanExecutionStarted( plan );
         adapter.executionStarted( TestIdentifier.from( engine ) );
         adapter.executionStarted( TestIdentifier.from( parent ) );
-        verify( listener ).testSetStarting( new SimpleReportEntry( JUnitPlatformProvider.class.getName(),
-                                                        MyTestClass.class.getName() ) );
+        verify( listener ).testSetStarting( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionStarted( TestIdentifier.from( child ) );
@@ -152,8 +157,7 @@ public class RunListenerAdapterTest
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( parent ), successful() );
-        verify( listener ).testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(),
-                                                        MyTestClass.class.getName() ) );
+        verify( listener ).testSetCompleted( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( engine ), successful() );
@@ -161,11 +165,10 @@ public class RunListenerAdapterTest
     }
 
     @Test
-    public void notifiedLazilyForTestSetWhenFirstTestWithoutClassDescriptorParentStarted()
+    public void displayNamesInClassAndMethods()
     {
         EngineDescriptor engine = newEngineDescriptor();
-        TestDescriptor parent = newTestDescriptor( engine.getUniqueId().append( "container", "noClass" ), "parent",
-                                        CONTAINER );
+        TestDescriptor parent = newClassDescriptor( "parent" );
         engine.addChild( parent );
         TestDescriptor child1 = newTestDescriptor( parent.getUniqueId().append( "test", "child1" ), "child1", TEST );
         parent.addChild( child1 );
@@ -176,14 +179,12 @@ public class RunListenerAdapterTest
         adapter.testPlanExecutionStarted( plan );
         adapter.executionStarted( TestIdentifier.from( engine ) );
         adapter.executionStarted( TestIdentifier.from( parent ) );
+        verify( listener ).testSetStarting( new SimpleReportEntry( "parent", null ) );
         verifyZeroInteractions( listener );
 
         adapter.executionStarted( TestIdentifier.from( child1 ) );
-        InOrder inOrder = inOrder( listener );
-        inOrder.verify( listener )
-                .testSetStarting( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "parent" ) );
-        inOrder.verify( listener ).testStarting( new SimpleReportEntry( "parent", "child1" ) );
-        inOrder.verifyNoMoreInteractions();
+        verify( listener ).testStarting( new SimpleReportEntry( "parent", "child1" ) );
+        verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( child1 ), successful() );
         verify( listener ).testSucceeded( new SimpleReportEntry( "parent", "child1" ) );
@@ -198,8 +199,7 @@ public class RunListenerAdapterTest
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( parent ), successful() );
-        verify( listener )
-                        .testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "parent" ) );
+        verify( listener ).testSetCompleted( new SimpleReportEntry( "parent", null ) );
         verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( engine ), successful() );
@@ -207,7 +207,7 @@ public class RunListenerAdapterTest
     }
 
     @Test
-    public void notifiedForTestSetForSingleNodeEngine()
+    public void notifiedForUnclassifiedTestIdentifier()
     {
         EngineDescriptor engine = new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" )
         {
@@ -221,18 +221,12 @@ public class RunListenerAdapterTest
 
         adapter.testPlanExecutionStarted( plan );
         adapter.executionStarted( TestIdentifier.from( engine ) );
-        InOrder inOrder = inOrder( listener );
-        inOrder.verify( listener )
-                .testSetStarting( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "engine" ) );
-        inOrder.verify( listener ).testStarting( new SimpleReportEntry( "<unrooted>", "engine" ) );
-        inOrder.verifyNoMoreInteractions();
+        verify( listener ).testStarting( new SimpleReportEntry( "engine", "engine" ) );
+        verifyNoMoreInteractions( listener );
 
         adapter.executionFinished( TestIdentifier.from( engine ), successful() );
-        inOrder = inOrder( listener );
-        inOrder.verify( listener ).testSucceeded( new SimpleReportEntry( "<unrooted>", "engine" ) );
-        inOrder.verify( listener )
-                .testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "engine" ) );
-        inOrder.verifyNoMoreInteractions();
+        verify( listener ).testSucceeded( new SimpleReportEntry( "engine", "engine" ) );
+        verifyNoMoreInteractions( listener );
     }
 
     @Test
@@ -264,7 +258,7 @@ public class RunListenerAdapterTest
         verify( listener ).testSkipped( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
-        assertTrue( MyTestClass.class.getTypeName().contains( entry.getName() ) );
+        assertNull( entry.getName() );
         assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() );
     }
 
@@ -279,48 +273,54 @@ public class RunListenerAdapterTest
     public void notifiedWhenMethodExecutionAborted()
                     throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.aborted( null ) );
+        adapter.executionFinished( newMethodIdentifier(), aborted( null ) );
         verify( listener ).testAssumptionFailure( any() );
     }
 
     @Test
     public void notifiedWhenClassExecutionAborted()
     {
-        adapter.executionFinished( newClassIdentifier(), TestExecutionResult.aborted( null ) );
-        verify( listener ).testAssumptionFailure( any() );
+        TestSkippedException t = new TestSkippedException( "skipped" );
+        adapter.executionFinished( newClassIdentifier(), aborted( t ) );
+        String name = MyTestClass.class.getName();
+        StackTraceWriter stw = new PojoStackTraceWriter( name, null, t );
+        verify( listener ).testSetCompleted( eq( new SimpleReportEntry( name, null, stw, null ) ) );
     }
 
     @Test
-    public void notifiedWhenMethodExecutionFailedWithAnAssertionError()
+    public void notifiedWhenMethodExecutionFailed()
                     throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( new AssertionError() ) );
+        adapter.executionFinished( newMethodIdentifier(), failed( new AssertionError() ) );
         verify( listener ).testFailed( any() );
     }
 
     @Test
-    public void notifiedWhenMethodExecutionFailedWithANonAssertionError()
+    public void notifiedWhenMethodExecutionFailedWithError()
                     throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( new RuntimeException() ) );
+        adapter.executionFinished( newMethodIdentifier(), failed( new RuntimeException() ) );
         verify( listener ).testError( any() );
     }
 
     @Test
     public void notifiedWithCorrectNamesWhenClassExecutionFailed()
     {
-        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        ArgumentCaptor<TestSetReportEntry> entryCaptor = ArgumentCaptor.forClass( TestSetReportEntry.class );
         TestPlan testPlan = TestPlan.from( singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
         adapter.testPlanExecutionStarted( testPlan );
 
-        adapter.executionFinished(
-                        identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() ),
-                        TestExecutionResult.failed( new AssertionError() ) );
-        verify( listener ).testFailed( entryCaptor.capture() );
+        adapter.executionFinished( identifiersAsParentOnTestPlan( testPlan, newClassDescriptor() ),
+                failed( new AssertionError() ) );
+        verify( listener ).testSetCompleted( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
         assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() );
+        assertNull( entry.getName() );
         assertNotNull( entry.getStackTraceWriter() );
+        assertNotNull( entry.getStackTraceWriter().getThrowable() );
+        assertThat( entry.getStackTraceWriter().getThrowable().getTarget() )
+                .isInstanceOf( AssertionError.class );
     }
 
     @Test
@@ -342,8 +342,9 @@ public class RunListenerAdapterTest
 
         adapter.executionFinished( TestIdentifier.from( classDescriptor ), successful() );
 
-        verify( listener ).testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(),
-                MyTestClass.class.getName() ) );
+        verify( listener ).testSetStarting( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
+
+        verify( listener ).testSetCompleted( new SimpleReportEntry( MyTestClass.class.getName(), null ) );
 
         verify( listener, never() ).testSucceeded( any() );
     }
@@ -375,7 +376,7 @@ public class RunListenerAdapterTest
 
         TestIdentifier child =
                 newSourcelessChildIdentifierWithParent( plan, "Parent", ClassSource.from( MyTestClass.class ) );
-        adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException() ) );
+        adapter.executionFinished( child, failed( new RuntimeException() ) );
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
         verify( listener ).testError( entryCaptor.capture() );
         assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
@@ -388,7 +389,7 @@ public class RunListenerAdapterTest
         adapter.testPlanExecutionStarted( plan );
 
         TestIdentifier child = newSourcelessChildIdentifierWithParent( plan, "Parent", null );
-        adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException( "message" ) ) );
+        adapter.executionFinished( child, failed( new RuntimeException( "message" ) ) );
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
         verify( listener ).testError( entryCaptor.capture() );
         assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
@@ -401,7 +402,7 @@ public class RunListenerAdapterTest
     public void stackTraceWriterPresentEvenWithoutException()
                     throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( null ) );
+        adapter.executionFinished( newMethodIdentifier(), failed( null ) );
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
         verify( listener ).testError( entryCaptor.capture() );
         assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
@@ -411,8 +412,22 @@ public class RunListenerAdapterTest
     public void displayNamesIgnoredInReport()
                     throws NoSuchMethodException
     {
-        TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor( newId(), MyTestClass.class,
-                MyTestClass.class.getDeclaredMethod( "myNamedTestMethod" ) );
+        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" ) );
 
         TestIdentifier factoryIdentifier = TestIdentifier.from( descriptor );
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
@@ -422,7 +437,7 @@ public class RunListenerAdapterTest
 
         ReportEntry value = entryCaptor.getValue();
 
-        assertEquals( "myNamedTestMethod", value.getName() );
+        assertEquals( "some display name", value.getName() );
     }
 
     private static TestIdentifier newMethodIdentifier()
@@ -445,9 +460,16 @@ public class RunListenerAdapterTest
         return TestIdentifier.from( newClassDescriptor() );
     }
 
+    private static TestDescriptor newClassDescriptor( String displayName )
+    {
+        return new ClassTestDescriptor( UniqueId.root( "class", MyTestClass.class.getName() ),
+                c -> displayName, MyTestClass.class, CONFIG_PARAMS ) {};
+    }
+
     private static TestDescriptor newClassDescriptor()
     {
-        return new ClassTestDescriptor( UniqueId.root( "class", MyTestClass.class.getName() ), MyTestClass.class, CONFIG_PARAMS );
+        return new ClassTestDescriptor( UniqueId.root( "class", MyTestClass.class.getName() ),
+                MyTestClass.class, CONFIG_PARAMS );
     }
 
     private static TestIdentifier newSourcelessChildIdentifierWithParent(
@@ -516,6 +538,13 @@ public class RunListenerAdapterTest
         return childIdentifier;
     }
 
+    private static TestIdentifier identifiersAsParentOnTestPlan( TestPlan plan, TestDescriptor root )
+    {
+        TestIdentifier rootIdentifier = TestIdentifier.from( root );
+        plan.add( rootIdentifier );
+        return rootIdentifier;
+    }
+
     private static UniqueId newId()
     {
         return UniqueId.forEngine( "engine" );
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 b8c83e1..f7bb710 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,7 +133,7 @@ public class JUnit3Provider
                                  Map<String, String> systemProperties )
         throws TestSetFailedException
     {
-        SimpleReportEntry report = new SimpleReportEntry( getClass().getName(), testSet.getName(), systemProperties );
+        SimpleReportEntry report = new SimpleReportEntry( testSet.getName(), null, systemProperties );
 
         reporter.testSetStarting( report );
 
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 78e78ad..e5d2232 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
@@ -20,14 +20,19 @@ package org.apache.maven.surefire.junit;
  */
 
 import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;
+
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
+import org.apache.maven.surefire.report.StackTraceWriter;
+
+import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
+import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractClassName;
+import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractMethodName;
 
 /**
  * Invocation Handler for TestListener proxies to delegate to our {@link RunListener}
@@ -149,25 +154,24 @@ public class TestListenerInvocationHandler
     // Handler for TestListener.startTest(Test)
     private void handleStartTest( Object[] args )
     {
-        ReportEntry report = new SimpleReportEntry( args[0].getClass().getName(), args[0].toString() );
+        ReportEntry report = createStartEndReportEntry( args );
 
         reporter.testStarting( report );
     }
 
     // Handler for TestListener.addFailure(Test, Throwable)
     private void handleAddError( Object[] args )
-        throws IllegalAccessException, InvocationTargetException
+        throws ReflectiveOperationException
     {
-        ReportEntry report = SimpleReportEntry.withException( args[0].getClass().getName(), args[0].toString(),
-                                                              getStackTraceWriter( args ) );
+        ReportEntry report = toReportEntryWithException( args );
 
         reporter.testError( report );
 
         failedTestsSet.add( new FailedTest( args[0], Thread.currentThread() ) );
     }
 
-    private LegacyPojoStackTraceWriter getStackTraceWriter( Object[] args )
-        throws IllegalAccessException, InvocationTargetException
+    private static LegacyPojoStackTraceWriter toStackTraceWriter( Object[] args )
+        throws ReflectiveOperationException
     {
         String testName;
 
@@ -185,10 +189,9 @@ public class TestListenerInvocationHandler
     }
 
     private void handleAddFailure( Object[] args )
-        throws IllegalAccessException, InvocationTargetException
+        throws ReflectiveOperationException
     {
-        ReportEntry report = SimpleReportEntry.withException( args[0].getClass().getName(), args[0].toString(),
-                                                              getStackTraceWriter( args ) );
+        ReportEntry report = toReportEntryWithException( args );
 
         reporter.testFailed( report );
 
@@ -201,9 +204,25 @@ public class TestListenerInvocationHandler
 
         if ( !testHadFailed )
         {
-            ReportEntry report = new SimpleReportEntry( args[0].getClass().getName(), args[0].toString() );
+            ReportEntry report = createStartEndReportEntry( args );
 
             reporter.testSucceeded( report );
         }
     }
+
+    private static ReportEntry toReportEntryWithException( Object[] args )
+            throws ReflectiveOperationException
+    {
+        String description = args[0].toString();
+        String className = extractClassName( description );
+        String methodName = extractMethodName( description );
+        StackTraceWriter stackTraceWriter = toStackTraceWriter( args );
+        return withException( className, methodName, stackTraceWriter );
+    }
+
+    private static SimpleReportEntry createStartEndReportEntry( Object[] args )
+    {
+        String description = args[0].toString();
+        return new SimpleReportEntry( extractClassName( description ), extractMethodName( description ) );
+    }
 }
diff --git a/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java b/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
index 977ac9e..1381303 100644
--- a/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
+++ b/surefire-providers/surefire-junit3/src/test/java/org/apache/maven/surefire/junit/JUnitTestSetTest.java
@@ -42,10 +42,12 @@ public class JUnitTestSetTest
         JUnitTestSet testSet = new JUnitTestSet( Suite.class, reflector );
         SuccessListener listener = new SuccessListener();
         testSet.execute( listener, testClassLoader );
-        List succeededTests = listener.getSucceededTests();
+        List<ReportEntry> succeededTests = listener.getSucceededTests();
         assertEquals( 1, succeededTests.size() );
-        assertEquals( "testSuccess(org.apache.maven.surefire.junit.JUnitTestSetTest$AlwaysSucceeds)",
-                      ( (ReportEntry) succeededTests.get( 0 ) ).getName() );
+        assertEquals( "org.apache.maven.surefire.junit.JUnitTestSetTest$AlwaysSucceeds",
+                succeededTests.get( 0 ).getSourceName() );
+        assertEquals( "testSuccess",
+                      succeededTests.get( 0 ).getName() );
     }
 
     public static final class AlwaysSucceeds
@@ -81,7 +83,7 @@ public class JUnitTestSetTest
         @Override
         public void testSucceeded( ReportEntry report )
         {
-            this.succeededTests.add( report );
+            succeededTests.add( report );
         }
 
         @Override
@@ -118,7 +120,7 @@ public class JUnitTestSetTest
             testSkipped( report );
         }
 
-        public List getSucceededTests()
+        List<ReportEntry> getSucceededTests()
         {
             return succeededTests;
         }
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 c31a019..f64b93d 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( getClass().getName(), clazz.getName(), systemProps() );
+        final SimpleReportEntry report = new SimpleReportEntry( clazz.getName(), null, systemProps() );
         reporter.testSetStarting( report );
         try
         {
diff --git a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/TestResolverFilter.java b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/TestResolverFilter.java
index 4205b14..72669a8 100644
--- a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/TestResolverFilter.java
+++ b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/TestResolverFilter.java
@@ -1 +1 @@
-package org.apache.maven.surefire.junit4;

/*
 * 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.common.junit4.ClassMethod;
import org.apache.maven.surefire.testset.TestListResolver;
import org.junit.runner.
 Description;
import org.junit.runner.manipulation.Filter;

import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.cutTestClassAndMethod;
import static org.apache.maven.surefire.testset.TestListResolver.toClassFileName;

/**
 * Method filter used in {@link JUnit4Provider}.
 */
final class TestResolverFilter
    extends Filter
{
    private final TestListResolver methodFilter;

    TestResolverFilter( TestListResolver methodFilter )
    {
        this.methodFilter = methodFilter;
    }

    @Override
    public boolean shouldRun( Description description )
    {
        // class: Java class name; method: 1. "testMethod" or 2. "testMethod[5+whatever]" in @Parameterized
        final ClassMethod cm = cutTestClassAndMethod( description );
        final boolean isSuite = description.isSuite();
        final boolean isValidTest = description.isTest() && cm.isValid();
        final String clazz = cm.getClazz();
        final String method = cm.getMethod();
        return is
 Suite || isValidTest && methodFilter.shouldRun( toClassFileName( clazz ), method );
    }

    @Override
    public String describe()
    {
        return methodFilter.toString();
    }
}
\ No newline at end of file
+package org.apache.maven.surefire.junit4;

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

import org.apache.maven.surefire.util.internal.ClassMethod;
import org.apache.maven.surefire.testset.TestListResolver;
import org.junit.runner.
 Description;
import org.junit.runner.manipulation.Filter;

import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
import static org.apache.maven.surefire.testset.TestListResolver.toClassFileName;

/**
 * Method filter used in {@link JUnit4Provider}.
 */
final class TestResolverFilter
    extends Filter
{
    private final TestListResolver methodFilter;

    TestResolverFilter( TestListResolver methodFilter )
    {
        this.methodFilter = methodFilter;
    }

    @Override
    public boolean shouldRun( Description description )
    {
        // class: Java class name; method: 1. "testMethod" or 2. "testMethod[5+whatever]" in @Parameterized
        final ClassMethod cm = toClassMethod( description );
        final boolean isSuite = description.isSuite();
        final boolean isValidTest = description.isTest() && cm.isValidTest();
        final String clazz = cm.getClazz();
        final String method = cm.getMethod();
        return isSuite || isV
 alidTest && methodFilter.shouldRun( toClassFileName( clazz ), method );
    }

    @Override
    public String describe()
    {
        return methodFilter.toString();
    }
}
\ No newline at end of file
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListener.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListener.java
index ae00fdb..03e314e 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListener.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListener.java
@@ -20,7 +20,7 @@ package org.apache.maven.surefire.junitcore;
  */
 
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
-import org.apache.maven.surefire.common.junit48.JUnit46StackTraceWriter;
+import org.apache.maven.surefire.common.junit4.JUnit4StackTraceWriter;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.junit.runner.Description;
@@ -29,6 +29,8 @@ import org.junit.runner.notification.Failure;
 
 import java.util.Map;
 
+import static org.apache.maven.surefire.util.internal.TestClassMethodNameUtils.extractClassName;
+
 /**
  * Noteworthy things about JUnit4 listening:
  * <br>
@@ -95,7 +97,7 @@ public class JUnitCoreRunListener
     {
         if ( description.isTest() )
         {
-            final String testClassName = extractDescriptionClassName( description );
+            final String testClassName = extractClassName( description.getDisplayName() );
             if ( testClassName != null )
             {
                 final TestSet testSet;
@@ -121,18 +123,6 @@ public class JUnitCoreRunListener
     @Override
     protected StackTraceWriter createStackTraceWriter( Failure failure )
     {
-        return new JUnit46StackTraceWriter( failure );
-    }
-
-    @Override
-    protected String extractDescriptionClassName( Description description )
-    {
-        return description.getClassName();
-    }
-
-    @Override
-    protected String extractDescriptionMethodName( Description description )
-    {
-        return description.getMethodName();
+        return new JUnit4StackTraceWriter( failure );
     }
 }
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 f8d2fb7..71ec93d 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
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.junitcore;
  * under the License.
  */
 
+import org.apache.maven.surefire.util.internal.ClassMethod;
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.RunListener;
@@ -32,6 +33,7 @@ import org.junit.runner.notification.Failure;
 import java.util.Collections;
 import java.util.Map;
 
+import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.toClassMethod;
 import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
 
 /**
@@ -63,13 +65,14 @@ public class NonConcurrentRunListener
     @Override
     protected SimpleReportEntry createReportEntry( Description description )
     {
-        return new SimpleReportEntry( extractDescriptionClassName( description ), description.getDisplayName() );
+        ClassMethod classMethod = toClassMethod( description );
+        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getMethod() );
     }
 
     private TestSetReportEntry createReportEntryForTestSet( Description description, Map<String, String> systemProps )
     {
-        String testClassName = extractDescriptionClassName( description );
-        return new SimpleReportEntry( testClassName, testClassName, systemProps );
+        ClassMethod classMethod = toClassMethod( description );
+        return new SimpleReportEntry( classMethod.getClazz(), classMethod.getClazz(), systemProps );
     }
 
     private TestSetReportEntry createTestSetReportEntryStarted( Description description )
@@ -83,18 +86,6 @@ public class NonConcurrentRunListener
     }
 
     @Override
-    protected String extractDescriptionClassName( Description description )
-    {
-        return description.getClassName();
-    }
-
-    @Override
-    protected String extractDescriptionMethodName( Description description )
-    {
-        return description.getMethodName();
-    }
-
-    @Override
     public void testStarted( Description description )
         throws Exception
     {
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListenerTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListenerTest.java
index b467f63..08bbd2a 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListenerTest.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreRunListenerTest.java
@@ -43,7 +43,6 @@ public class JUnitCoreRunListenerTest
     extends TestCase
 {
     public void testTestRunStarted()
-        throws Exception
     {
         RunListener jUnit4TestSetReporter =
             new JUnitCoreRunListener( new MockReporter(), new HashMap<String, TestSet>() );
@@ -55,7 +54,6 @@ public class JUnitCoreRunListenerTest
     }
 
     public void testFailedAssumption()
-        throws Exception
     {
         RunListener jUnit4TestSetReporter =
             new JUnitCoreRunListener( new MockReporter(), new HashMap<String, TestSet>() );
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 19cd22d..8cb7641 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
@@ -25,6 +25,7 @@ import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 
+import org.testng.IClass;
 import org.testng.ISuite;
 import org.testng.ISuiteListener;
 import org.testng.ITestContext;
@@ -64,54 +65,45 @@ public class TestNGReporter
     @Override
     public void onTestStart( ITestResult result )
     {
-        String group = groupString( result.getMethod().getGroups(), result.getTestClass().getName() );
-        String userFriendlyTestName = getUserFriendlyTestName( result );
-        reporter.testStarting( new CategorizedReportEntry( getSource( result ), userFriendlyTestName, group ) );
-    }
-
-    private String getSource( ITestResult result )
-    {
-        return result.getTestClass().getName();
+        String clazz = result.getTestClass().getName();
+        String group = groupString( result.getMethod().getGroups(), clazz );
+        reporter.testStarting( new CategorizedReportEntry( clazz, result.getName(), group ) );
     }
 
     @Override
     public void onTestSuccess( ITestResult result )
     {
-        ReportEntry report = new SimpleReportEntry( getSource( result ), getUserFriendlyTestName( result ) );
+        ReportEntry report = new SimpleReportEntry( result.getTestClass().getName(), result.getName() );
         reporter.testSucceeded( report );
     }
 
     @Override
     public void onTestFailure( ITestResult result )
     {
-        ReportEntry report = withException( getSource( result ), getUserFriendlyTestName( result ),
-                new PojoStackTraceWriter( result.getTestClass().getRealClass().getName(),
+        IClass clazz = result.getTestClass();
+        ReportEntry report = withException( clazz.getName(), result.getName(),
+                new PojoStackTraceWriter( clazz.getRealClass().getName(),
                         result.getMethod().getMethodName(),
                         result.getThrowable() ) );
 
         reporter.testFailed( report );
     }
 
-    private static String getUserFriendlyTestName( ITestResult result )
-    {
-        // This is consistent with the JUnit output
-        return result.getName() + "(" + result.getTestClass().getName() + ")";
-    }
-
     @Override
     public void onTestSkipped( ITestResult result )
     {
         Throwable t = result.getThrowable();
         String reason = t == null ? null : t.getMessage();
-        ReportEntry report = ignored( getSource( result ), getUserFriendlyTestName( result ), reason );
+        ReportEntry report = ignored( result.getTestClass().getName(), result.getName(), reason );
         reporter.testSkipped( report );
     }
 
     @Override
     public void onTestFailedButWithinSuccessPercentage( ITestResult result )
     {
-        ReportEntry report = withException( getSource( result ), getUserFriendlyTestName( result ),
-                new PojoStackTraceWriter( result.getTestClass().getRealClass().getName(),
+        IClass clazz = result.getTestClass();
+        ReportEntry report = withException( clazz.getName(), result.getName(),
+                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 ca40f7c..e9b0424 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( getClass().getName(), getSuiteName(), systemProps() );
+        TestSetReportEntry report = new SimpleReportEntry( getSuiteName(), null, systemProps() );
 
         try
         {
@@ -57,7 +57,7 @@ abstract class TestSuite
 
     final void finishTestSuite( RunListener reporterManager )
     {
-        SimpleReportEntry report = new SimpleReportEntry( getClass().getName(), getSuiteName(), systemProps() );
+        SimpleReportEntry report = new SimpleReportEntry( getSuiteName(), null, systemProps() );
         reporterManager.testSetCompleted( report );
     }
 }
diff --git a/surefire-report-parser/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportParser.java b/surefire-report-parser/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportParser.java
index da1b255..f6e644f 100644
--- a/surefire-report-parser/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportParser.java
+++ b/surefire-report-parser/src/main/java/org/apache/maven/plugins/surefire/report/SurefireReportParser.java
@@ -33,11 +33,10 @@ import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.reporting.MavenReportException;
-import org.apache.maven.shared.utils.StringUtils;
 import org.apache.maven.shared.utils.io.DirectoryScanner;
 import org.xml.sax.SAXException;
 
-import static java.util.Collections.singletonList;
+import static org.apache.maven.shared.utils.StringUtils.split;
 
 /**
  *
@@ -57,7 +56,7 @@ public final class SurefireReportParser
 
     private final ConsoleLogger consoleLogger;
 
-    private List<File> reportsDirectories;
+    private final List<File> reportsDirectories;
 
     public SurefireReportParser( List<File> reportsDirectories, Locale locale, ConsoleLogger consoleLogger )
     {
@@ -104,21 +103,6 @@ public final class SurefireReportParser
         return testSuites;
     }
 
-    protected String parseTestSuiteName( String lineString )
-    {
-        return lineString.substring( lineString.lastIndexOf( "." ) + 1, lineString.length() );
-    }
-
-    protected String parseTestSuitePackageName( String lineString )
-    {
-        return lineString.substring( lineString.indexOf( ":" ) + 2, lineString.lastIndexOf( "." ) );
-    }
-
-    protected String parseTestCaseName( String lineString )
-    {
-        return lineString.substring( 0, lineString.indexOf( "(" ) );
-    }
-
     public Map<String, String> getSummary( List<ReportTestSuite> suites )
     {
         Map<String, String> totalSummary = new HashMap<>();
@@ -164,11 +148,6 @@ public final class SurefireReportParser
         return totalSummary;
     }
 
-    public void setReportsDirectory( File reportsDirectory )
-    {
-        reportsDirectories = singletonList( reportsDirectory );
-    }
-
     public NumberFormat getNumberFormat()
     {
         return numberFormat;
@@ -238,9 +217,9 @@ public final class SurefireReportParser
 
         scanner.setBasedir( directory );
 
-        scanner.setIncludes( StringUtils.split( includes, "," ) );
+        scanner.setIncludes( split( includes, "," ) );
 
-        scanner.setExcludes( StringUtils.split( excludes, "," ) );
+        scanner.setExcludes( split( excludes, "," ) );
 
         scanner.scan();
 
diff --git a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/SurefireReportParserTest.java b/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/SurefireReportParserTest.java
index 870cd92..7176c4d 100644
--- a/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/SurefireReportParserTest.java
+++ b/surefire-report-parser/src/test/java/org/apache/maven/plugins/surefire/report/SurefireReportParserTest.java
@@ -29,10 +29,10 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
-import org.apache.maven.reporting.MavenReportException;
 
 import junit.framework.TestCase;
 
+import static java.util.Collections.singletonList;
 import static java.util.Locale.ENGLISH;
 
 /**
@@ -41,23 +41,11 @@ import static java.util.Locale.ENGLISH;
 public class SurefireReportParserTest
     extends TestCase
 {
-    private SurefireReportParser report;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void setUp()
-        throws Exception
-    {
-        super.setUp();
-        report = new SurefireReportParser( null, ENGLISH, new NullConsoleLogger() );
-    }
-
     public void testParseXMLReportFiles()
-        throws MavenReportException, UnsupportedEncodingException
+        throws Exception
     {
-        report.setReportsDirectory( getTestDir() );
+        SurefireReportParser report =
+                new SurefireReportParser( singletonList( getTestDir() ), ENGLISH, new NullConsoleLogger() );
 
         List<ReportTestSuite> suites = report.parseXMLReportFiles();
 
@@ -79,21 +67,6 @@ public class SurefireReportParserTest
         return new File( URLDecoder.decode( resource.getPath(), "UTF-8" ) ).getAbsoluteFile();
     }
 
-    public void testParseTestSuiteName()
-    {
-        assertEquals( "CircleTest", report.parseTestSuiteName( "Battery: com.shape.CircleTest" ) );
-    }
-
-    public void testParseTestSuitePackageName()
-    {
-        assertEquals( "com.shape", report.parseTestSuitePackageName( "Battery: com.shape.CircleTest" ) );
-    }
-
-    public void testParseTestCaseName()
-    {
-        assertEquals( "testCase", report.parseTestCaseName( "testCase(com.shape.CircleTest)" ) );
-    }
-
     public void testGetSummary()
         throws Exception
     {
@@ -117,6 +90,8 @@ public class SurefireReportParserTest
 
         suites.add( tSuite2 );
 
+        SurefireReportParser report = new SurefireReportParser( null, ENGLISH, new NullConsoleLogger() );
+
         Map<String, String> testMap = report.getSummary( suites );
 
         assertEquals( 20, Integer.parseInt( testMap.get( "totalErrors" ) ) );
@@ -156,6 +131,8 @@ public class SurefireReportParserTest
 
         suites.add( tSuite3 );
 
+        SurefireReportParser report = new SurefireReportParser( null, ENGLISH, new NullConsoleLogger() );
+
         Map<String, List<ReportTestSuite>> groupMap = report.getSuitesGroupByPackage( suites );
 
         assertEquals( 2, groupMap.size() );
@@ -170,6 +147,7 @@ public class SurefireReportParserTest
     public void testComputePercentage()
         throws Exception
     {
+        SurefireReportParser report = new SurefireReportParser( null, ENGLISH, new NullConsoleLogger() );
         NumberFormat numberFormat = report.getNumberFormat();
 
         assertEquals( 70.00f, numberFormat.parse( report.computePercentage( 100, 20, 10, 0 ) ).floatValue(), 0 );
@@ -211,6 +189,8 @@ public class SurefireReportParserTest
 
         suites.add( tSuite2 );
 
+        SurefireReportParser report = new SurefireReportParser( null, ENGLISH, new NullConsoleLogger() );
+
         List<ReportTestCase> failures = report.getFailureDetails( suites );
 
         assertEquals( 2, failures.size() );
diff --git a/surefire-report-parser/src/test/resources/test-reports/com.shape.CircleTest.txt b/surefire-report-parser/src/test/resources/test-reports/com.shape.CircleTest.txt
index 5030dee..0e2ee04 100644
--- a/surefire-report-parser/src/test/resources/test-reports/com.shape.CircleTest.txt
+++ b/surefire-report-parser/src/test/resources/test-reports/com.shape.CircleTest.txt
@@ -1,10 +1,10 @@
 -------------------------------------------------------------------------------
 Battery: com.shape.CircleTest
 -------------------------------------------------------------------------------
-testX(com.shape.CircleTest)
-testY(com.shape.CircleTest)
-testXY(com.shape.CircleTest)
-testRadius(com.shape.CircleTest)
+com.shape.CircleTest.testX
+com.shape.CircleTest.testY
+com.shape.CircleTest.testXY
+com.shape.CircleTest.testRadius
 
 [ stdout ] ---------------------------------------------------------------
 
@@ -63,7 +63,7 @@ junit.framework.AssertionFailedError: expected:<20> but was:<10>
 	at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
 	at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
 	at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
-testProperties(com.shape.CircleTest)
+com.shape.CircleTest.testProperties
 
 [ stdout ] ---------------------------------------------------------------
 
@@ -117,6 +117,6 @@ java.lang.ArithmeticException: / by zero
 	at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
 	at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
 	at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
-testPI(com.shape.CircleTest)
-testCircumference(com.shape.CircleTest)
-testDiameter(com.shape.CircleTest)
+com.shape.CircleTest.testPI
+com.shape.CircleTest.testCircumference
+com.shape.CircleTest.testDiameter
diff --git a/surefire-report-parser/src/test/resources/test-reports/com.shapeclone.CircleTest.txt b/surefire-report-parser/src/test/resources/test-reports/com.shapeclone.CircleTest.txt
index 90e3239..e190044 100644
--- a/surefire-report-parser/src/test/resources/test-reports/com.shapeclone.CircleTest.txt
+++ b/surefire-report-parser/src/test/resources/test-reports/com.shapeclone.CircleTest.txt
@@ -1,10 +1,10 @@
 -------------------------------------------------------------------------------
 Battery: com.shapeclone.CircleTest
 -------------------------------------------------------------------------------
-testX(com.shapeclone.CircleTest)
-testY(com.shapeclone.CircleTest)
-testXY(com.shapeclone.CircleTest)
-testRadius(com.shapeclone.CircleTest)
+com.shapeclone.CircleTest.testX
+com.shapeclone.CircleTest.testY
+com.shapeclone.CircleTest.testXY
+com.shapeclone.CircleTest.testRadius
 
 [ stdout ] ---------------------------------------------------------------
 
@@ -63,7 +63,7 @@ junit.framework.AssertionFailedError: expected:<20> but was:<10>
 	at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
 	at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
 	at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
-testProperties(com.shapeclone.CircleTest)
+com.shapeclone.CircleTest.testProperties
 
 [ stdout ] ---------------------------------------------------------------
 
@@ -117,6 +117,6 @@ java.lang.ArithmeticException: / by zero
 	at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
 	at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
 	at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
-testPI(com.shapeclone.CircleTest)
-testCircumference(com.shapeclone.CircleTest)
-testDiameter(com.shapeclone.CircleTest)
+com.shapeclone.CircleTest.testPI
+com.shapeclone.CircleTest.testCircumference
+com.shapeclone.CircleTest.testDiameter