You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by so...@apache.org on 2018/05/05 12:57:27 UTC

[maven-surefire] 04/10: Copy current sources from junit-platform-surefire-provider

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

sor pushed a commit to branch 1330
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 5976a8cbd6e9289198c8a882d99fb9d8f931435e
Author: Christian Stein <so...@gmail.com>
AuthorDate: Thu May 3 11:04:45 2018 +0200

    Copy current sources from junit-platform-surefire-provider
    
    This commit includes enhancements and bug fixes that were applied since
    the initial code donation.
    
    https://issues.apache.org/jira/browse/SUREFIRE-1330
---
 .../junitplatform/JUnitPlatformProvider.java       | 137 ++++--
 .../surefire/junitplatform/RunListenerAdapter.java | 188 ++++++--
 .../surefire/junitplatform/TestMethodFilter.java   |  60 +++
 .../junitplatform/TestPlanScannerFilter.java       |  18 +-
 .../junitplatform/JUnitPlatformProviderTests.java  | 537 +++++++++++++++++++--
 .../junitplatform/RunListenerAdapterTests.java     | 400 +++++++++++++--
 .../junitplatform/TestMethodFilterTests.java       | 105 ++++
 .../junitplatform/TestPlanScannerFilterTests.java  |  30 +-
 8 files changed, 1277 insertions(+), 198 deletions(-)

diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
index 7f58921..510266e 100644
--- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
@@ -19,35 +19,45 @@ package org.apache.maven.surefire.junitplatform;
  * under the License.
  */
 
+import static java.util.Collections.emptyMap;
+import static java.util.stream.Collectors.toList;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import org.apache.maven.surefire.providerapi.AbstractProvider;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
+import org.apache.maven.surefire.report.ConsoleOutputCapture;
+import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReporterException;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
-import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.TestListResolver;
 import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.apache.maven.surefire.util.ScanResult;
 import org.apache.maven.surefire.util.TestsToRun;
+import org.junit.platform.commons.util.Preconditions;
+import org.junit.platform.commons.util.StringUtils;
 import org.junit.platform.engine.Filter;
 import org.junit.platform.launcher.Launcher;
 import org.junit.platform.launcher.LauncherDiscoveryRequest;
 import org.junit.platform.launcher.TagFilter;
+import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
 import org.junit.platform.launcher.core.LauncherFactory;
 
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
-import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
-
-/**
- * @since 1.0
- */
 public class JUnitPlatformProvider
     extends AbstractProvider
 {
@@ -61,6 +71,8 @@ public class JUnitPlatformProvider
 
     static final String INCLUDE_TAGS = "includeTags";
 
+    static final String CONFIGURATION_PARAMETERS = "configurationParameters";
+
     static final String EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED = "The " + INCLUDE_GROUPS + " and " + INCLUDE_TAGS
             + " parameters (or the " + EXCLUDE_GROUPS + " and " + EXCLUDE_TAGS + " parameters) are synonyms - "
             + "only one of each is allowed (though neither is required).";
@@ -69,9 +81,11 @@ public class JUnitPlatformProvider
 
     private final Launcher launcher;
 
-    final Filter<?>[] includeAndExcludeFilters;
+    final Filter<?>[] filters;
 
-    public JUnitPlatformProvider( ProviderParameters parameters )
+    final Map<String, String> configurationParameters;
+
+    JUnitPlatformProvider( ProviderParameters parameters )
     {
         this( parameters, LauncherFactory.create() );
     }
@@ -80,7 +94,8 @@ public class JUnitPlatformProvider
     {
         this.parameters = parameters;
         this.launcher = launcher;
-        this.includeAndExcludeFilters = getIncludeAndExcludeFilters();
+        this.filters = getFilters();
+        this.configurationParameters = getConfigurationParameters();
         Logger.getLogger( "org.junit" ).setLevel( Level.WARNING );
     }
 
@@ -92,7 +107,7 @@ public class JUnitPlatformProvider
 
     @Override
     public RunResult invoke( Object forkTestSet )
-            throws TestSetFailedException, ReporterException, InvocationTargetException
+                    throws TestSetFailedException, ReporterException
     {
         if ( forkTestSet instanceof TestsToRun )
         {
@@ -114,8 +129,9 @@ public class JUnitPlatformProvider
 
     private TestsToRun scanClasspath()
     {
-        TestsToRun scannedClasses = parameters.getScanResult().applyFilter(
-            new TestPlanScannerFilter( launcher, includeAndExcludeFilters ), parameters.getTestClassLoader() );
+        TestPlanScannerFilter filter = new TestPlanScannerFilter( launcher, filters );
+        ScanResult scanResult = parameters.getScanResult();
+        TestsToRun scannedClasses = scanResult.applyFilter( filter, parameters.getTestClassLoader() );
         return parameters.getRunOrderCalculator().orderTestClasses( scannedClasses );
     }
 
@@ -126,12 +142,9 @@ public class JUnitPlatformProvider
         try
         {
             RunListener runListener = reporterFactory.createReporter();
-            launcher.registerTestExecutionListeners( new RunListenerAdapter( runListener ) );
-
-            for ( Class<?> testClass : testsToRun )
-            {
-                invokeSingleClass( testClass, runListener );
-            }
+            ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) runListener );
+            LauncherDiscoveryRequest discoveryRequest = buildLauncherDiscoveryRequest( testsToRun );
+            launcher.execute( discoveryRequest, new RunListenerAdapter( runListener ) );
         }
         finally
         {
@@ -140,40 +153,70 @@ public class JUnitPlatformProvider
         return runResult;
     }
 
-    private void invokeSingleClass( Class<?> testClass, RunListener runListener )
+    private LauncherDiscoveryRequest buildLauncherDiscoveryRequest( TestsToRun testsToRun )
     {
-        SimpleReportEntry classEntry = new SimpleReportEntry( getClass().getName(), testClass.getName() );
-        runListener.testSetStarting( classEntry );
-
-        LauncherDiscoveryRequest discoveryRequest = request().selectors( selectClass( testClass ) ).filters(
-            includeAndExcludeFilters ).build();
-        launcher.execute( discoveryRequest );
-
-        runListener.testSetCompleted( classEntry );
+        LauncherDiscoveryRequestBuilder builder =
+                        request().filters( filters ).configurationParameters( configurationParameters );
+        for ( Class<?> testClass : testsToRun )
+        {
+            builder.selectors( selectClass( testClass ) );
+        }
+        return builder.build();
     }
 
-    private Filter<?>[] getIncludeAndExcludeFilters()
+    private Filter<?>[] getFilters()
     {
         List<Filter<?>> filters = new ArrayList<>();
 
-        Optional<List<String>> includes = getGroupsOrTags( getPropertiesList( INCLUDE_GROUPS ),
-            getPropertiesList( INCLUDE_TAGS ) );
+        Optional<List<String>> includes =
+                        getGroupsOrTags( getPropertiesList( INCLUDE_GROUPS ), getPropertiesList( INCLUDE_TAGS ) );
         includes.map( TagFilter::includeTags ).ifPresent( filters::add );
 
-        Optional<List<String>> excludes = getGroupsOrTags( getPropertiesList( EXCLUDE_GROUPS ),
-            getPropertiesList( EXCLUDE_TAGS ) );
+        Optional<List<String>> excludes =
+                        getGroupsOrTags( getPropertiesList( EXCLUDE_GROUPS ), getPropertiesList( EXCLUDE_TAGS ) );
         excludes.map( TagFilter::excludeTags ).ifPresent( filters::add );
 
-        return filters.toArray( new Filter<?>[filters.size()] );
+        TestListResolver testListResolver = parameters.getTestRequest().getTestListResolver();
+        if ( !testListResolver.isEmpty() )
+        {
+            filters.add( new TestMethodFilter( testListResolver ) );
+        }
+
+        return filters.toArray( new Filter<?>[0] );
+    }
+
+    private Map<String, String> getConfigurationParameters()
+    {
+        String content = parameters.getProviderProperties().get( CONFIGURATION_PARAMETERS );
+        if ( content == null )
+        {
+            return emptyMap();
+        }
+        try ( StringReader reader = new StringReader( content ) )
+        {
+            Map<String, String> result = new HashMap<>();
+            Properties props = new Properties();
+            props.load( reader );
+            props.stringPropertyNames().forEach( key -> result.put( key, props.getProperty( key ) ) );
+            return result;
+        }
+        catch ( IOException ex )
+        {
+            throw new UncheckedIOException( "Error reading " + CONFIGURATION_PARAMETERS, ex );
+        }
     }
 
     private Optional<List<String>> getPropertiesList( String key )
     {
         List<String> compoundProperties = null;
         String property = parameters.getProviderProperties().get( key );
-        if ( property != null )
+        if ( StringUtils.isNotBlank( property ) )
         {
-            compoundProperties = Arrays.asList( property.split( "[, ]+" ) );
+            compoundProperties =
+                            Arrays.stream( property.split( "[,]+" ) )
+                                  .filter( StringUtils::isNotBlank )
+                                  .map( String::trim )
+                                  .collect( toList() );
         }
         return Optional.ofNullable( compoundProperties );
     }
@@ -182,10 +225,7 @@ public class JUnitPlatformProvider
     {
         Optional<List<String>> elements = Optional.empty();
 
-        if ( groups.isPresent() && tags.isPresent() )
-        {
-            throw new IllegalStateException( EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED );
-        }
+        Preconditions.condition(!groups.isPresent() || !tags.isPresent(), EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED );
 
         if ( groups.isPresent() )
         {
@@ -198,5 +238,4 @@ public class JUnitPlatformProvider
 
         return elements;
     }
-
 }
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 9191864..71a70c3 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
@@ -24,6 +24,8 @@ import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED;
 import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
 
 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;
@@ -36,6 +38,7 @@ 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 1.0
@@ -46,9 +49,11 @@ final class RunListenerAdapter
 
     private final RunListener runListener;
 
-    private Optional<TestPlan> testPlan = Optional.empty();
+    private TestPlan testPlan;
 
-    public RunListenerAdapter( RunListener runListener )
+    private Set<TestIdentifier> testSetNodes = ConcurrentHashMap.newKeySet();
+
+    RunListenerAdapter( RunListener runListener )
     {
         this.runListener = runListener;
     }
@@ -56,78 +61,203 @@ final class RunListenerAdapter
     @Override
     public void testPlanExecutionStarted( TestPlan testPlan )
     {
-        this.testPlan = Optional.of( testPlan );
+        updateTestPlan( testPlan );
     }
 
     @Override
     public void testPlanExecutionFinished( TestPlan testPlan )
     {
-        this.testPlan = Optional.empty();
+        updateTestPlan( null );
     }
 
     @Override
     public void executionStarted( TestIdentifier testIdentifier )
     {
+        if ( testIdentifier.isContainer()
+                        && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
+        {
+            startTestSetIfPossible( testIdentifier );
+        }
         if ( testIdentifier.isTest() )
         {
-            runListener.testStarting( createReportEntry( testIdentifier, Optional.empty() ) );
+            ensureTestSetStarted( testIdentifier );
+            runListener.testStarting( createReportEntry( testIdentifier ) );
         }
     }
 
     @Override
     public void executionSkipped( TestIdentifier testIdentifier, String reason )
     {
-        String source = getClassName( testIdentifier ).orElseGet( () -> parentDisplayName( testIdentifier ) );
-        runListener.testSkipped( ignored( source, testIdentifier.getDisplayName(), reason ) );
+        ensureTestSetStarted( testIdentifier );
+        String source = getLegacyReportingClassName( testIdentifier );
+        runListener.testSkipped( ignored( source, getLegacyReportingName( testIdentifier ), reason ) );
+        completeTestSetIfNecessary( testIdentifier );
     }
 
     @Override
-    public void executionFinished( TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
+    public void executionFinished(
+                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
     {
         if ( testExecutionResult.getStatus() == ABORTED )
         {
-            runListener.testAssumptionFailure( createReportEntry( testIdentifier,
-                    testExecutionResult.getThrowable() ) );
+            runListener.testAssumptionFailure( createReportEntry( testIdentifier, testExecutionResult ) );
         }
         else if ( testExecutionResult.getStatus() == FAILED )
         {
-            runListener.testFailed( createReportEntry( testIdentifier, testExecutionResult.getThrowable() ) );
+            reportFailedTest( testIdentifier, testExecutionResult );
         }
         else if ( testIdentifier.isTest() )
         {
-            runListener.testSucceeded( createReportEntry( testIdentifier, Optional.empty() ) );
+            runListener.testSucceeded( createReportEntry( testIdentifier ) );
         }
+        completeTestSetIfNecessary( testIdentifier );
     }
 
-    private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier, Optional<Throwable> throwable )
+    private void updateTestPlan( TestPlan testPlan )
     {
-        Optional<String> className = getClassName( testIdentifier );
-        if ( className.isPresent() )
+        this.testPlan = testPlan;
+        testSetNodes.clear();
+    }
+
+    private void ensureTestSetStarted( TestIdentifier testIdentifier )
+    {
+        if ( isTestSetStarted( testIdentifier ) )
+        {
+            return;
+        }
+        if ( testIdentifier.isTest() )
         {
-            StackTraceWriter traceWriter = new PojoStackTraceWriter( className.get(),
-                getMethodName( testIdentifier ).orElse( "" ), throwable.orElse( null ) );
-            return new SimpleReportEntry( className.get(), testIdentifier.getDisplayName(), traceWriter,
-                    (Integer) null );
+            startTestSet( testPlan.getParent( testIdentifier ).orElse( testIdentifier ) );
         }
         else
         {
-            return new SimpleReportEntry( parentDisplayName( testIdentifier ), testIdentifier.getDisplayName(),
-                    (Integer) null );
+            startTestSet( testIdentifier );
+        }
+    }
+
+    private boolean isTestSetStarted( TestIdentifier testIdentifier )
+    {
+        return testSetNodes.contains( testIdentifier )
+                        || testPlan.getParent( testIdentifier ).map( this::isTestSetStarted ).orElse( false );
+    }
+
+    private void startTestSetIfPossible( TestIdentifier testIdentifier )
+    {
+        if ( !isTestSetStarted( testIdentifier ) )
+        {
+            startTestSet( testIdentifier );
         }
     }
 
-    private Optional<String> getClassName( TestIdentifier testIdentifier )
+    private void completeTestSetIfNecessary( TestIdentifier testIdentifier )
+    {
+        if ( testSetNodes.contains( testIdentifier ) )
+        {
+            completeTestSet( testIdentifier );
+        }
+    }
+
+    private void startTestSet( TestIdentifier testIdentifier )
+    {
+        runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) );
+        testSetNodes.add( testIdentifier );
+    }
+
+    private void completeTestSet( TestIdentifier testIdentifier )
+    {
+        runListener.testSetCompleted( createTestSetReportEntry( testIdentifier ) );
+        testSetNodes.remove( testIdentifier );
+    }
+
+    private void reportFailedTest(
+                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
+    {
+        SimpleReportEntry reportEntry = createReportEntry( testIdentifier, testExecutionResult );
+        if ( testExecutionResult.getThrowable().filter( AssertionError.class::isInstance ).isPresent() )
+        {
+            runListener.testFailed( reportEntry );
+        }
+        else
+        {
+            runListener.testError( reportEntry );
+        }
+    }
+
+    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( ')', '}' );
+    }
+
+    private String getLegacyReportingClassName( TestIdentifier testIdentifier )
+    {
+        return LegacyReportingUtils.getClassName( testPlan, testIdentifier );
+    }
+
+    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 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 Optional.of( ( (ClassSource) testSource ).getJavaClass().getName() );
+            return ( (ClassSource) testSource ).getJavaClass().getName();
         }
         if ( testSource instanceof MethodSource )
         {
-            return Optional.of( ( (MethodSource) testSource ).getClassName() );
+            return ( (MethodSource) testSource ).getClassName();
         }
-        return Optional.empty();
+        return testPlan.getParent( testIdentifier ).map( this::getClassName ).orElse( "" );
     }
 
     private Optional<String> getMethodName( TestIdentifier testIdentifier )
@@ -139,12 +269,4 @@ final class RunListenerAdapter
         }
         return Optional.empty();
     }
-
-    private String parentDisplayName( TestIdentifier testIdentifier )
-    {
-        return testPlan
-            .flatMap( plan -> plan.getParent( testIdentifier ) )
-            .map( TestIdentifier::getDisplayName )
-            .orElseGet( testIdentifier::getUniqueId );
-    }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java
new file mode 100644
index 0000000..45e32db
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java
@@ -0,0 +1,60 @@
+package org.apache.maven.surefire.junitplatform;
+
+/*
+ * 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.testset.TestListResolver;
+import org.junit.platform.engine.FilterResult;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.support.descriptor.MethodSource;
+import org.junit.platform.launcher.PostDiscoveryFilter;
+
+/**
+ * @since 1.0.3
+ */
+class TestMethodFilter
+    implements PostDiscoveryFilter
+{
+
+    private final TestListResolver testListResolver;
+
+    TestMethodFilter( TestListResolver testListResolver )
+    {
+        this.testListResolver = testListResolver;
+    }
+
+    @Override
+    public FilterResult apply( TestDescriptor descriptor )
+    {
+        boolean shouldRun = descriptor.getSource()
+                                      .filter( MethodSource.class::isInstance )
+                                      .map( MethodSource.class::cast )
+                                      .map( this::shouldRun )
+                                      .orElse( true );
+
+        return FilterResult.includedIf( shouldRun );
+    }
+
+    private boolean shouldRun( MethodSource source )
+    {
+        String testClass = TestListResolver.toClassFileName( source.getClassName() );
+        String testMethod = source.getMethodName();
+        return this.testListResolver.shouldRun( testClass, testMethod );
+    }
+}
diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java
index 4a95d6e..4b488c1 100644
--- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java
@@ -22,13 +22,10 @@ package org.apache.maven.surefire.junitplatform;
 import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
 import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
 
-import java.util.function.Predicate;
-
 import org.apache.maven.surefire.util.ScannerFilter;
 import org.junit.platform.engine.Filter;
 import org.junit.platform.launcher.Launcher;
 import org.junit.platform.launcher.LauncherDiscoveryRequest;
-import org.junit.platform.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
 
 /**
@@ -38,14 +35,11 @@ final class TestPlanScannerFilter
     implements ScannerFilter
 {
 
-    private static final Predicate<TestIdentifier> HAS_TESTS = testIdentifier -> testIdentifier.isTest()
-            || testIdentifier.isContainer();
-
     private final Launcher launcher;
 
     private final Filter<?>[] includeAndExcludeFilters;
 
-    public TestPlanScannerFilter( Launcher launcher, Filter<?>[] includeAndExcludeFilters )
+    TestPlanScannerFilter( Launcher launcher, Filter<?>[] includeAndExcludeFilters )
     {
         this.launcher = launcher;
         this.includeAndExcludeFilters = includeAndExcludeFilters;
@@ -55,10 +49,12 @@ final class TestPlanScannerFilter
     @SuppressWarnings( "rawtypes" )
     public boolean accept( Class testClass )
     {
-        LauncherDiscoveryRequest discoveryRequest = request().selectors( selectClass( testClass ) ).filters(
-            includeAndExcludeFilters ).build();
+        LauncherDiscoveryRequest discoveryRequest = request()
+                        .selectors( selectClass( testClass ) )
+                        .filters( includeAndExcludeFilters ).build();
+
         TestPlan testPlan = launcher.discover( discoveryRequest );
-        return testPlan.countTestIdentifiers( HAS_TESTS ) > 0;
-    }
 
+        return testPlan.containsTests();
+    }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java
index a32aade..e0db97e 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java
@@ -19,24 +19,43 @@ package org.apache.maven.surefire.junitplatform;
  * under the License.
  */
 
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static java.util.stream.Collectors.toSet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.mockito.AdditionalMatchers.gt;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.maven.surefire.providerapi.ProviderParameters;
+import org.apache.maven.surefire.report.ConsoleOutputReceiver;
+import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.SimpleReportEntry;
+import org.apache.maven.surefire.testset.TestListResolver;
+import org.apache.maven.surefire.testset.TestRequest;
+import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.apache.maven.surefire.util.RunOrderCalculator;
 import org.apache.maven.surefire.util.ScanResult;
 import org.apache.maven.surefire.util.TestsToRun;
@@ -44,10 +63,14 @@ import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.platform.commons.util.PreconditionViolationException;
 import org.junit.platform.launcher.Launcher;
+import org.junit.platform.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
 import org.junit.platform.launcher.core.LauncherFactory;
 import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
 import org.junit.platform.launcher.listeners.TestExecutionSummary;
+import org.junit.platform.launcher.listeners.TestExecutionSummary.Failure;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 
 /**
  * Unit tests for {@link JUnitPlatformProvider}.
@@ -59,9 +82,10 @@ class JUnitPlatformProviderTests
 
     @Test
     void getSuitesReturnsScannedClasses()
-        throws Exception
+                    throws Exception
     {
-        ProviderParameters providerParameters = providerParametersMock( TestClass1.class, TestClass2.class );
+        ProviderParameters providerParameters =
+                        providerParametersMock( TestClass1.class, TestClass2.class );
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
         assertThat( provider.getSuites() ).containsOnly( TestClass1.class, TestClass2.class );
@@ -69,17 +93,18 @@ class JUnitPlatformProviderTests
 
     @Test
     void invokeThrowsForWrongForkTestSet()
-        throws Exception
+                    throws Exception
     {
         ProviderParameters providerParameters = providerParametersMock( Integer.class );
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
-        assertThrows( IllegalArgumentException.class, () -> provider.invoke( "wrong forkTestSet" ) );
+        assertThrows(
+                        IllegalArgumentException.class, () -> invokeProvider( provider, "wrong forkTestSet" ) );
     }
 
     @Test
     void allGivenTestsToRunAreInvoked()
-        throws Exception
+                    throws Exception
     {
         Launcher launcher = LauncherFactory.create();
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher );
@@ -88,16 +113,25 @@ class JUnitPlatformProviderTests
         launcher.registerTestExecutionListeners( executionListener );
 
         TestsToRun testsToRun = newTestsToRun( TestClass1.class, TestClass2.class );
-        provider.invoke( testsToRun );
+        invokeProvider( provider, testsToRun );
 
-        assertThat( executionListener.summaries ).hasSize( 2 );
-        TestClass1.verifyExecutionSummary( executionListener.summaries.get( 0 ) );
-        TestClass2.verifyExecutionSummary( executionListener.summaries.get( 1 ) );
+        assertThat( executionListener.summaries ).hasSize( 1 );
+        TestExecutionSummary summary = executionListener.summaries.get( 0 );
+        assertEquals( TestClass1.TESTS_FOUND + TestClass2.TESTS_FOUND, summary.getTestsFoundCount() );
+        assertEquals(
+                        TestClass1.TESTS_STARTED + TestClass2.TESTS_STARTED, summary.getTestsStartedCount() );
+        assertEquals(
+                        TestClass1.TESTS_SKIPPED + TestClass2.TESTS_SKIPPED, summary.getTestsSkippedCount() );
+        assertEquals(
+                        TestClass1.TESTS_SUCCEEDED + TestClass2.TESTS_SUCCEEDED, summary.getTestsSucceededCount() );
+        assertEquals(
+                        TestClass1.TESTS_ABORTED + TestClass2.TESTS_ABORTED, summary.getTestsAbortedCount() );
+        assertEquals( TestClass1.TESTS_FAILED + TestClass2.TESTS_FAILED, summary.getTestsFailedCount() );
     }
 
     @Test
     void singleTestClassIsInvoked()
-        throws Exception
+                    throws Exception
     {
         Launcher launcher = LauncherFactory.create();
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher );
@@ -105,28 +139,94 @@ class JUnitPlatformProviderTests
         TestPlanSummaryListener executionListener = new TestPlanSummaryListener();
         launcher.registerTestExecutionListeners( executionListener );
 
-        provider.invoke( TestClass1.class );
+        invokeProvider( provider, TestClass1.class );
 
         assertThat( executionListener.summaries ).hasSize( 1 );
-        TestClass1.verifyExecutionSummary( executionListener.summaries.get( 0 ) );
+        TestExecutionSummary summary = executionListener.summaries.get( 0 );
+        assertEquals( TestClass1.TESTS_FOUND, summary.getTestsFoundCount() );
+        assertEquals( TestClass1.TESTS_STARTED, summary.getTestsStartedCount() );
+        assertEquals( TestClass1.TESTS_SKIPPED, summary.getTestsSkippedCount() );
+        assertEquals( TestClass1.TESTS_SUCCEEDED, summary.getTestsSucceededCount() );
+        assertEquals( TestClass1.TESTS_ABORTED, summary.getTestsAbortedCount() );
+        assertEquals( TestClass1.TESTS_FAILED, summary.getTestsFailedCount() );
     }
 
     @Test
     void allDiscoveredTestsAreInvokedForNullArgument()
-        throws Exception
+                    throws Exception
     {
-        ProviderParameters providerParameters = providerParametersMock( TestClass1.class, TestClass2.class );
+        RunListener runListener = runListenerMock();
+        ProviderParameters providerParameters =
+                        providerParametersMock( runListener, TestClass1.class, TestClass2.class );
         Launcher launcher = LauncherFactory.create();
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters, launcher );
 
         TestPlanSummaryListener executionListener = new TestPlanSummaryListener();
         launcher.registerTestExecutionListeners( executionListener );
 
-        provider.invoke( null );
+        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() ) );
+
+        assertThat( executionListener.summaries ).hasSize( 1 );
+        TestExecutionSummary summary = executionListener.summaries.get( 0 );
+        assertEquals( TestClass1.TESTS_FOUND + TestClass2.TESTS_FOUND, summary.getTestsFoundCount() );
+        assertEquals(
+                        TestClass1.TESTS_STARTED + TestClass2.TESTS_STARTED, summary.getTestsStartedCount() );
+        assertEquals(
+                        TestClass1.TESTS_SKIPPED + TestClass2.TESTS_SKIPPED, summary.getTestsSkippedCount() );
+        assertEquals(
+                        TestClass1.TESTS_SUCCEEDED + TestClass2.TESTS_SUCCEEDED, summary.getTestsSucceededCount() );
+        assertEquals(
+                        TestClass1.TESTS_ABORTED + TestClass2.TESTS_ABORTED, summary.getTestsAbortedCount() );
+        assertEquals( TestClass1.TESTS_FAILED + TestClass2.TESTS_FAILED, summary.getTestsFailedCount() );
+    }
 
-        assertThat( executionListener.summaries ).hasSize( 2 );
-        TestClass1.verifyExecutionSummary( executionListener.summaries.get( 0 ) );
-        TestClass2.verifyExecutionSummary( executionListener.summaries.get( 1 ) );
+    @Test
+    void outputIsCaptured()
+                    throws Exception
+    {
+        Launcher launcher = LauncherFactory.create();
+        RunListener runListener = runListenerMock();
+        JUnitPlatformProvider provider =
+                        new JUnitPlatformProvider( providerParametersMock( runListener ), launcher );
+
+        invokeProvider( provider, VerboseTestClass.class );
+
+        ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass( byte[].class );
+        // @formatter:off
+        verify( (ConsoleOutputReceiver) runListener )
+                        .writeTestOutput( captor.capture(), eq( 0 ), gt( 6 ), eq( true ) );
+        verify( (ConsoleOutputReceiver) runListener )
+                        .writeTestOutput( captor.capture(), eq( 0 ), gt( 6 ), eq( false ) );
+        assertThat( captor.getAllValues() )
+                        .extracting( bytes -> new String( bytes, 0, 6 ) )
+                        .containsExactly( "stdout", "stderr" );
+        // @formatter:on
     }
 
     @Test
@@ -139,16 +239,17 @@ class JUnitPlatformProviderTests
     }
 
     @Test
-    void bothExcludedGroupsAndExcludeTagsThrowsException() {
+    void bothExcludedGroupsAndExcludeTagsThrowsException()
+    {
         Map<String, String> properties = new HashMap<>();
-        properties.put(JUnitPlatformProvider.EXCLUDE_GROUPS, "groupOne, groupTwo");
-        properties.put(JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo");
-        verifyPreconditionViolationException(properties);
+        properties.put( JUnitPlatformProvider.EXCLUDE_GROUPS, "groupOne, groupTwo" );
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" );
+        verifyPreconditionViolationException( properties );
     }
 
     @Test
     void onlyGroupsIsDeclared()
-        throws Exception
+                    throws Exception
     {
         Map<String, String> properties = new HashMap<>();
         properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "groupOne, groupTwo" );
@@ -158,12 +259,12 @@ class JUnitPlatformProviderTests
 
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
-        assertEquals( 1, provider.includeAndExcludeFilters.length );
+        assertEquals( 1, provider.filters.length );
     }
 
     @Test
     void onlyExcludeTagsIsDeclared()
-        throws Exception
+                    throws Exception
     {
         Map<String, String> properties = new HashMap<>();
         properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" );
@@ -173,12 +274,44 @@ class JUnitPlatformProviderTests
 
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
-        assertEquals( 1, provider.includeAndExcludeFilters.length );
+        assertEquals( 1, provider.filters.length );
+    }
+
+    @Test
+    void noFiltersAreCreatedIfTagsAreEmpty()
+                    throws Exception
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "" );
+        properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+        assertEquals( 0, provider.filters.length );
+    }
+
+    @Test
+    void filtersWithEmptyTagsAreNotRegistered()
+                    throws Exception
+    {
+        Map<String, String> properties = new HashMap<>();
+
+        // Here only tagOne is registered as a valid tag and other tags are ignored as they are empty
+        properties.put( JUnitPlatformProvider.EXCLUDE_GROUPS, "tagOne," );
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+        assertEquals( 1, provider.filters.length );
     }
 
     @Test
     void bothIncludeAndExcludeAreAllowed()
-        throws Exception
+                    throws Exception
     {
         Map<String, String> properties = new HashMap<>();
         properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne, tagTwo" );
@@ -189,18 +322,173 @@ class JUnitPlatformProviderTests
 
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
-        assertEquals( 2, provider.includeAndExcludeFilters.length );
+        assertEquals( 2, provider.filters.length );
+    }
+
+    @Test
+    void tagExpressionsAreSupportedForIncludeTagsContainingVerticalBar()
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne | tagTwo" );
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagThree | tagFour" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertEquals( 2, provider.filters.length );
+    }
+
+    @Test
+    void tagExpressionsAreSupportedForIncludeTagsContainingAmpersand()
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne & !tagTwo" );
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagThree & !tagFour" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertEquals( 2, provider.filters.length );
     }
 
     @Test
     void noFiltersAreCreatedIfNoPropertiesAreDeclared()
-        throws Exception
+                    throws Exception
+    {
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertEquals( 0, provider.filters.length );
+    }
+
+    @Test
+    void defaultConfigurationParametersAreEmpty()
     {
         ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( emptyMap() );
 
         JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
 
-        assertEquals( 0, provider.includeAndExcludeFilters.length );
+        assertTrue( provider.configurationParameters.isEmpty() );
+    }
+
+    @Test
+    void parsesConfigurationParameters()
+    {
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() )
+                        .thenReturn( //
+                                     singletonMap(
+                                                     JUnitPlatformProvider.CONFIGURATION_PARAMETERS,
+                                                     "foo = true\nbar 42\rbaz: *\r\nqux: EOF" ) );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertEquals( 4, provider.configurationParameters.size() );
+        assertEquals( "true", provider.configurationParameters.get( "foo" ) );
+        assertEquals( "42", provider.configurationParameters.get( "bar" ) );
+        assertEquals( "*", provider.configurationParameters.get( "baz" ) );
+        assertEquals( "EOF", provider.configurationParameters.get( "qux" ) );
+    }
+
+    @Test
+    void executesSingleTestIncludedByName()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=TestClass3#prefix1Suffix1'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "*TestClass3#prefix1Suffix1";
+
+        testExecutionOfMatchingTestMethods( TestClass3.class, pattern, "prefix1Suffix1()" );
+    }
+
+    @Test
+    void executesMultipleTestsIncludedByName()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=TestClass3#prefix1Suffix1+prefix2Suffix1'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "*TestClass3#prefix1Suffix1+prefix2Suffix1";
+
+        testExecutionOfMatchingTestMethods(
+                        TestClass3.class, pattern, "prefix1Suffix1()", "prefix2Suffix1()" );
+    }
+
+    @Test
+    void executesMultipleTestsIncludedByNamePattern()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=TestClass3#prefix1*'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "*TestClass3#prefix1*";
+
+        testExecutionOfMatchingTestMethods(
+                        TestClass3.class, pattern, "prefix1Suffix1()", "prefix1Suffix2()" );
+    }
+
+    @Test
+    void executesMultipleTestsIncludedByNamePatternWithQuestionMark()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=TestClass3#prefix?Suffix2'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "*TestClass3#prefix?Suffix2";
+
+        testExecutionOfMatchingTestMethods(
+                        TestClass3.class, pattern, "prefix1Suffix2()", "prefix2Suffix2()" );
+    }
+
+    @Test
+    void doesNotExecuteTestsExcludedByName()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=!TestClass3#prefix1Suffix2'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "!*TestClass3#prefix1Suffix2";
+
+        testExecutionOfMatchingTestMethods(
+                        TestClass3.class, pattern, "prefix1Suffix1()", "prefix2Suffix1()", "prefix2Suffix2()" );
+    }
+
+    @Test
+    void doesNotExecuteTestsExcludedByNamePattern()
+                    throws Exception
+    {
+        // following is equivalent of adding '-Dtest=!TestClass3#prefix2*'
+        // '*' needed because it's a nested class and thus has name prefixed with '$'
+        String pattern = "!*TestClass3#prefix2*";
+
+        testExecutionOfMatchingTestMethods(
+                        TestClass3.class, pattern, "prefix1Suffix1()", "prefix1Suffix2()" );
+    }
+
+    void testExecutionOfMatchingTestMethods(
+                    Class<?> testClass, String pattern, String... expectedTestNames )
+                    throws Exception
+    {
+        TestListResolver testListResolver = new TestListResolver( pattern );
+        ProviderParameters providerParameters = providerParametersMock( testListResolver, testClass );
+        Launcher launcher = LauncherFactory.create();
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters, launcher );
+
+        TestPlanSummaryListener executionListener = new TestPlanSummaryListener();
+        launcher.registerTestExecutionListeners( executionListener );
+
+        invokeProvider( provider, null );
+
+        assertEquals( 1, executionListener.summaries.size() );
+        TestExecutionSummary summary = executionListener.summaries.get( 0 );
+        int expectedCount = expectedTestNames.length;
+        assertEquals( expectedCount, summary.getTestsFoundCount() );
+        assertEquals( expectedCount, summary.getTestsFailedCount() );
+        assertEquals( expectedCount, summary.getFailures().size() );
+
+        assertThat( failedTestDisplayNames( summary ) ).contains( expectedTestNames );
     }
 
     private void verifyPreconditionViolationException( Map<String, String> properties )
@@ -208,14 +496,35 @@ class JUnitPlatformProviderTests
         ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
         when( providerParameters.getProviderProperties() ).thenReturn( properties );
 
-        Throwable throwable = assertThrows( PreconditionViolationException.class, () ->
-                new JUnitPlatformProvider(providerParameters) );
+        Throwable throwable =
+                        assertThrows(
+                                        PreconditionViolationException.class,
+                                        () -> new JUnitPlatformProvider( providerParameters ) );
 
         assertEquals( JUnitPlatformProvider.EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED, throwable.getMessage() );
     }
 
     private static ProviderParameters providerParametersMock( Class<?>... testClasses )
     {
+        return providerParametersMock( runListenerMock(), testClasses );
+    }
+
+    private static ProviderParameters providerParametersMock(
+                    RunListener runListener, Class<?>... testClasses )
+    {
+        TestListResolver testListResolver = new TestListResolver( "" );
+        return providerParametersMock( runListener, testListResolver, testClasses );
+    }
+
+    private static ProviderParameters providerParametersMock(
+                    TestListResolver testListResolver, Class<?>... testClasses )
+    {
+        return providerParametersMock( runListenerMock(), testListResolver, testClasses );
+    }
+
+    private static ProviderParameters providerParametersMock(
+                    RunListener runListener, TestListResolver testListResolver, Class<?>... testClasses )
+    {
         TestsToRun testsToRun = newTestsToRun( testClasses );
 
         ScanResult scanResult = mock( ScanResult.class );
@@ -225,17 +534,37 @@ class JUnitPlatformProviderTests
         when( runOrderCalculator.orderTestClasses( any() ) ).thenReturn( testsToRun );
 
         ReporterFactory reporterFactory = mock( ReporterFactory.class );
-        RunListener runListener = mock( RunListener.class );
         when( reporterFactory.createReporter() ).thenReturn( runListener );
 
+        TestRequest testRequest = mock( TestRequest.class );
+        when( testRequest.getTestListResolver() ).thenReturn( testListResolver );
+
         ProviderParameters providerParameters = mock( ProviderParameters.class );
         when( providerParameters.getScanResult() ).thenReturn( scanResult );
         when( providerParameters.getRunOrderCalculator() ).thenReturn( runOrderCalculator );
         when( providerParameters.getReporterFactory() ).thenReturn( reporterFactory );
+        when( providerParameters.getTestRequest() ).thenReturn( testRequest );
 
         return providerParameters;
     }
 
+    private static RunListener runListenerMock()
+    {
+        return mock( RunListener.class, withSettings().extraInterfaces( ConsoleOutputReceiver.class ) );
+    }
+
+    private static Set<String> failedTestDisplayNames( TestExecutionSummary summary )
+    {
+        // @formatter:off
+        return summary
+                        .getFailures()
+                        .stream()
+                        .map( Failure::getTestIdentifier )
+                        .map( TestIdentifier::getDisplayName )
+                        .collect( toSet() );
+        // @formatter:on
+    }
+
     private static TestsToRun newTestsToRun( Class<?>... testClasses )
     {
         List<Class<?>> classesList = Arrays.asList( testClasses );
@@ -243,7 +572,7 @@ class JUnitPlatformProviderTests
     }
 
     private class TestPlanSummaryListener
-        extends SummaryGeneratingListener
+                    extends SummaryGeneratingListener
     {
 
         final List<TestExecutionSummary> summaries = new ArrayList<>();
@@ -256,9 +585,42 @@ class JUnitPlatformProviderTests
         }
     }
 
-    private static class TestClass1
+    /**
+     * Invokes the provider, then restores system out and system error.
+     *
+     * @see <a href="https://github.com/junit-team/junit5/issues/986">#986</a>
+     */
+    private void invokeProvider( JUnitPlatformProvider provider, Object forkTestSet )
+                    throws TestSetFailedException, InvocationTargetException
+    {
+        PrintStream systemOut = System.out;
+        PrintStream systemErr = System.err;
+        try
+        {
+            provider.invoke( forkTestSet );
+        }
+        finally
+        {
+            System.setOut( systemOut );
+            System.setErr( systemErr );
+        }
+    }
+
+    static class TestClass1
     {
 
+        static final int TESTS_FOUND = 4;
+
+        static final int TESTS_STARTED = 3;
+
+        static final int TESTS_SKIPPED = 1;
+
+        static final int TESTS_SUCCEEDED = 2;
+
+        static final int TESTS_ABORTED = 0;
+
+        static final int TESTS_FAILED = 1;
+
         @Test
         void test1()
         {
@@ -280,21 +642,23 @@ class JUnitPlatformProviderTests
         {
             throw new RuntimeException();
         }
-
-        static void verifyExecutionSummary( TestExecutionSummary summary )
-        {
-            assertEquals( 4, summary.getTestsFoundCount() );
-            assertEquals( 3, summary.getTestsStartedCount() );
-            assertEquals( 2, summary.getTestsSucceededCount() );
-            assertEquals( 1, summary.getTestsSkippedCount() );
-            assertEquals( 0, summary.getTestsAbortedCount() );
-            assertEquals( 1, summary.getTestsFailedCount() );
-        }
     }
 
-    private static class TestClass2
+    static class TestClass2
     {
 
+        static final int TESTS_FOUND = 3;
+
+        static final int TESTS_STARTED = 3;
+
+        static final int TESTS_SKIPPED = 0;
+
+        static final int TESTS_SUCCEEDED = 1;
+
+        static final int TESTS_ABORTED = 1;
+
+        static final int TESTS_FAILED = 1;
+
         @Test
         void test1()
         {
@@ -311,15 +675,82 @@ class JUnitPlatformProviderTests
         {
             assumeTrue( false );
         }
+    }
+
+    static class VerboseTestClass
+    {
+        @Test
+        void test()
+        {
+            System.out.println( "stdout" );
+            System.err.println( "stderr" );
+        }
+    }
+
+    @Test
+    void usesClassNamesForXmlReport()
+                    throws TestSetFailedException, InvocationTargetException
+    {
+        String[] classNames = { Sub1Tests.class.getName(), Sub2Tests.class.getName() };
+        ProviderParameters providerParameters =
+                        providerParametersMock( Sub1Tests.class, Sub2Tests.class );
+
+        JUnitPlatformProvider jUnitPlatformProvider = new JUnitPlatformProvider( providerParameters );
+        TestsToRun testsToRun = newTestsToRun( Sub1Tests.class, Sub2Tests.class );
+
+        invokeProvider( jUnitPlatformProvider, testsToRun );
+        RunListener reporter = providerParameters.getReporterFactory().createReporter();
+
+        ArgumentCaptor<ReportEntry> reportEntryArgumentCaptor =
+                        ArgumentCaptor.forClass( ReportEntry.class );
+        verify( reporter, times( 2 ) ).testSucceeded( reportEntryArgumentCaptor.capture() );
+
+        List<ReportEntry> allValues = reportEntryArgumentCaptor.getAllValues();
+        assertThat( allValues ).extracting( ReportEntry::getSourceName ).containsExactly( classNames );
+    }
+
+    static class AbstractTestClass
+    {
+        @Test
+        void test()
+        {
+        }
+    }
+
+    static class Sub1Tests
+                    extends AbstractTestClass
+    {
+    }
+
+    static class Sub2Tests
+                    extends AbstractTestClass
+    {
+    }
+
+    static class TestClass3
+    {
+        @Test
+        void prefix1Suffix1()
+        {
+            throw new RuntimeException();
+        }
 
-        static void verifyExecutionSummary( TestExecutionSummary summary )
+        @Test
+        void prefix2Suffix1()
+        {
+            throw new RuntimeException();
+        }
+
+        @Test
+        void prefix1Suffix2()
         {
-            assertEquals( 3, summary.getTestsFoundCount() );
-            assertEquals( 3, summary.getTestsStartedCount() );
-            assertEquals( 1, summary.getTestsSucceededCount() );
-            assertEquals( 0, summary.getTestsSkippedCount() );
-            assertEquals( 1, summary.getTestsAbortedCount() );
-            assertEquals( 1, summary.getTestsFailedCount() );
+            throw new RuntimeException();
+        }
+
+        @Test
+        void prefix2Suffix2()
+        {
+            throw new RuntimeException();
         }
     }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java
index 0a37eb5..d3ec7a4 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java
@@ -19,31 +19,45 @@ package org.apache.maven.surefire.junitplatform;
  * under the License.
  */
 
-import static java.util.Collections.singletonList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
 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.TestExecutionResult.successful;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import java.util.Collections;
 import java.util.Optional;
 
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
+import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.engine.descriptor.ClassTestDescriptor;
 import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
 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.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 
 /**
  * Unit tests for {@link RunListenerAdapter}.
@@ -52,7 +66,9 @@ import org.mockito.ArgumentCaptor;
  */
 class RunListenerAdapterTests
 {
+
     private RunListener listener;
+
     private RunListenerAdapter adapter;
 
     @BeforeEach
@@ -60,32 +76,183 @@ class RunListenerAdapterTests
     {
         listener = mock( RunListener.class );
         adapter = new RunListenerAdapter( listener );
+        adapter.testPlanExecutionStarted( TestPlan.from( emptyList() ) );
     }
 
     @Test
     void notifiedWithCorrectNamesWhenMethodExecutionStarted()
-        throws Exception
+                    throws Exception
     {
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
 
-        adapter.executionStarted( newMethodIdentifier() );
+        TestPlan testPlan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
+        adapter.testPlanExecutionStarted( testPlan );
+
+        TestIdentifier methodIdentifier =
+                        identifiersAsParentOnTestPlan( testPlan, newClassDescriptor(), newMethodDescriptor() );
+
+        adapter.executionStarted( methodIdentifier );
         verify( listener ).testStarting( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
-        assertEquals( MY_TEST_METHOD_NAME + "()", entry.getName() );
+        assertEquals( MY_TEST_METHOD_NAME, entry.getName() );
         assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
-        assertNotNull( entry.getStackTraceWriter() );
+        assertNull( entry.getStackTraceWriter() );
     }
 
     @Test
-    void notNotifiedWhenClassExecutionStarted()
+    void notifiedWithCompatibleNameForMethodWithArguments()
+                    throws Exception
     {
-        adapter.executionStarted( newClassIdentifier() );
-        verify( listener, never() ).testStarting( any() );
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+
+        TestPlan testPlan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
+        adapter.testPlanExecutionStarted( testPlan );
+
+        TestIdentifier methodIdentifier =
+                        identifiersAsParentOnTestPlan(
+                                        testPlan, newClassDescriptor(), newMethodDescriptor( String.class ) );
+
+        adapter.executionStarted( methodIdentifier );
+        verify( listener ).testStarting( entryCaptor.capture() );
+
+        ReportEntry entry = entryCaptor.getValue();
+        assertEquals( MY_TEST_METHOD_NAME + "{String}", entry.getName() );
+        assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
+        assertNull( entry.getStackTraceWriter() );
+    }
+
+    @Test
+    void notifiedEagerlyForTestSetWhenClassExecutionStarted()
+                    throws Exception
+    {
+        EngineDescriptor engine = newEngineDescriptor();
+        TestDescriptor parent = newClassDescriptor();
+        engine.addChild( parent );
+        TestDescriptor child = newMethodDescriptor();
+        parent.addChild( child );
+        TestPlan plan = TestPlan.from( Collections.singletonList( engine ) );
+
+        adapter.testPlanExecutionStarted( plan );
+        adapter.executionStarted( TestIdentifier.from( engine ) );
+        adapter.executionStarted( TestIdentifier.from( parent ) );
+        verify( listener )
+                        .testSetStarting(
+                                        new SimpleReportEntry(
+                                                        JUnitPlatformProvider.class.getName(),
+                                                        MyTestClass.class.getName() ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionStarted( TestIdentifier.from( child ) );
+        verify( listener )
+                        .testStarting( new SimpleReportEntry( MyTestClass.class.getName(), MY_TEST_METHOD_NAME ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( child ), successful() );
+        verify( listener )
+                        .testSucceeded( new SimpleReportEntry( MyTestClass.class.getName(), MY_TEST_METHOD_NAME ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( parent ), successful() );
+        verify( listener )
+                        .testSetCompleted(
+                                        new SimpleReportEntry(
+                                                        JUnitPlatformProvider.class.getName(),
+                                                        MyTestClass.class.getName() ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( engine ), successful() );
+        verifyNoMoreInteractions( listener );
+    }
+
+    @Test
+    void notifiedLazilyForTestSetWhenFirstTestWithoutClassDescriptorParentStarted()
+    {
+        EngineDescriptor engine = newEngineDescriptor();
+        TestDescriptor parent =
+                        newTestDescriptor(
+                                        engine.getUniqueId().append( "container", "noClass" ), "parent",
+                                        Type.CONTAINER );
+        engine.addChild( parent );
+        TestDescriptor child1 =
+                        newTestDescriptor( parent.getUniqueId().append( "test", "child1" ), "child1", Type.TEST );
+        parent.addChild( child1 );
+        TestDescriptor child2 =
+                        newTestDescriptor( parent.getUniqueId().append( "test", "child2" ), "child2", Type.TEST );
+        parent.addChild( child2 );
+        TestPlan plan = TestPlan.from( Collections.singletonList( engine ) );
+
+        adapter.testPlanExecutionStarted( plan );
+        adapter.executionStarted( TestIdentifier.from( engine ) );
+        adapter.executionStarted( TestIdentifier.from( parent ) );
+        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();
+
+        adapter.executionFinished( TestIdentifier.from( child1 ), successful() );
+        verify( listener ).testSucceeded( new SimpleReportEntry( "parent", "child1" ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionStarted( TestIdentifier.from( child2 ) );
+        verify( listener ).testStarting( new SimpleReportEntry( "parent", "child2" ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( child2 ), successful() );
+        verify( listener ).testSucceeded( new SimpleReportEntry( "parent", "child2" ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( parent ), successful() );
+        verify( listener )
+                        .testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "parent" ) );
+        verifyNoMoreInteractions( listener );
+
+        adapter.executionFinished( TestIdentifier.from( engine ), successful() );
+        verifyNoMoreInteractions( listener );
+    }
+
+    @Test
+    void notifiedForTestSetForSingleNodeEngine()
+    {
+        EngineDescriptor engine =
+                        new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" )
+                        {
+                            @Override
+                            public Type getType()
+                            {
+                                return Type.TEST;
+                            }
+                        };
+        TestPlan plan = TestPlan.from( Collections.singletonList( engine ) );
+
+        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();
+
+        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();
     }
 
     @Test
     void notNotifiedWhenEngineExecutionStarted()
+                    throws Exception
     {
         adapter.executionStarted( newEngineIdentifier() );
         verify( listener, never() ).testStarting( any() );
@@ -93,7 +260,7 @@ class RunListenerAdapterTests
 
     @Test
     void notifiedWhenMethodExecutionSkipped()
-        throws Exception
+                    throws Exception
     {
         adapter.executionSkipped( newMethodIdentifier(), "test" );
         verify( listener ).testSkipped( any() );
@@ -101,19 +268,27 @@ class RunListenerAdapterTests
 
     @Test
     void notifiedWithCorrectNamesWhenClassExecutionSkipped()
+                    throws Exception
     {
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        TestPlan testPlan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
+        adapter.testPlanExecutionStarted( testPlan );
 
-        adapter.executionSkipped( newClassIdentifier(), "test" );
+        TestIdentifier classIdentifier =
+                        identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() );
+
+        adapter.executionSkipped( classIdentifier, "test" );
         verify( listener ).testSkipped( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
         assertTrue( MyTestClass.class.getTypeName().contains( entry.getName() ) );
-        assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
+        assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() );
     }
 
     @Test
     void notifiedWhenEngineExecutionSkipped()
+                    throws Exception
     {
         adapter.executionSkipped( newEngineIdentifier(), "test" );
         verify( listener ).testSkipped( any() );
@@ -121,7 +296,7 @@ class RunListenerAdapterTests
 
     @Test
     void notifiedWhenMethodExecutionAborted()
-        throws Exception
+                    throws Exception
     {
         adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.aborted( null ) );
         verify( listener ).testAssumptionFailure( any() );
@@ -129,57 +304,89 @@ class RunListenerAdapterTests
 
     @Test
     void notifiedWhenClassExecutionAborted()
+                    throws Exception
     {
         adapter.executionFinished( newClassIdentifier(), TestExecutionResult.aborted( null ) );
         verify( listener ).testAssumptionFailure( any() );
     }
 
     @Test
-    void notifiedWhenMethodExecutionFailed()
-        throws Exception
+    void notifiedWhenMethodExecutionFailedWithAnAssertionError()
+                    throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( new RuntimeException() ) );
+        adapter.executionFinished(
+                        newMethodIdentifier(), TestExecutionResult.failed( new AssertionError() ) );
         verify( listener ).testFailed( any() );
     }
 
     @Test
+    void notifiedWhenMethodExecutionFailedWithANonAssertionError()
+                    throws Exception
+    {
+        adapter.executionFinished(
+                        newMethodIdentifier(), TestExecutionResult.failed( new RuntimeException() ) );
+        verify( listener ).testError( any() );
+    }
+
+    @Test
     void notifiedWithCorrectNamesWhenClassExecutionFailed()
+                    throws Exception
     {
         ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        TestPlan testPlan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
+        adapter.testPlanExecutionStarted( testPlan );
 
-        adapter.executionFinished( newClassIdentifier(), TestExecutionResult.failed( new RuntimeException() ) );
+        adapter.executionFinished(
+                        identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() ),
+                        TestExecutionResult.failed( new AssertionError() ) );
         verify( listener ).testFailed( entryCaptor.capture() );
 
         ReportEntry entry = entryCaptor.getValue();
-        assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
+        assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() );
         assertNotNull( entry.getStackTraceWriter() );
     }
 
     @Test
     void notifiedWhenMethodExecutionSucceeded()
-        throws Exception
+                    throws Exception
     {
-        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.successful() );
+        adapter.executionFinished( newMethodIdentifier(), successful() );
         verify( listener ).testSucceeded( any() );
     }
 
     @Test
-    void notNotifiedWhenClassExecutionSucceeded()
+    void notifiedForTestSetWhenClassExecutionSucceeded()
+                    throws Exception
     {
-        adapter.executionFinished( newClassIdentifier(), TestExecutionResult.successful() );
+        EngineDescriptor engineDescriptor = newEngineDescriptor();
+        TestDescriptor classDescriptor = newClassDescriptor();
+        engineDescriptor.addChild( classDescriptor );
+        adapter.testPlanExecutionStarted( TestPlan.from( singleton( engineDescriptor ) ) );
+        adapter.executionStarted( TestIdentifier.from( classDescriptor ) );
+
+        adapter.executionFinished( TestIdentifier.from( classDescriptor ), successful() );
+
+        verify( listener )
+                        .testSetCompleted(
+                                        new SimpleReportEntry(
+                                                        JUnitPlatformProvider.class.getName(),
+                                                        MyTestClass.class.getName() ) );
         verify( listener, never() ).testSucceeded( any() );
     }
 
     @Test
     void notifiedWithParentDisplayNameWhenTestClassUnknown()
+                    throws Exception
     {
         // Set up a test plan
-        TestPlan plan = TestPlan.from( singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
+        TestPlan plan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) );
         adapter.testPlanExecutionStarted( plan );
 
         // Use the test plan to set up child with parent.
         final String parentDisplay = "I am your father";
-        TestIdentifier child = newSourcelessIdentifierWithParent( plan, parentDisplay );
+        TestIdentifier child = newSourcelessChildIdentifierWithParent( plan, parentDisplay, null );
         adapter.executionStarted( child );
 
         // Check that the adapter has informed Surefire that the test has been invoked,
@@ -189,36 +396,117 @@ class RunListenerAdapterTests
         assertEquals( parentDisplay, entryCaptor.getValue().getSourceName() );
     }
 
+    @Test
+    void stackTraceWriterPresentWhenParentHasSource()
+                    throws Exception
+    {
+        TestPlan plan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Some Plan" ) ) );
+        adapter.testPlanExecutionStarted( plan );
+
+        TestIdentifier child =
+                        newSourcelessChildIdentifierWithParent( plan, "Parent", ClassSource.from( MyTestClass.class ) );
+        adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException() ) );
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        verify( listener ).testError( entryCaptor.capture() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
+    }
+
+    @Test
+    void stackTraceWriterDefaultsToTestClass()
+                    throws Exception
+    {
+        TestPlan plan =
+                        TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Some Plan" ) ) );
+        adapter.testPlanExecutionStarted( plan );
+
+        TestIdentifier child = newSourcelessChildIdentifierWithParent( plan, "Parent", null );
+        adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException( "message" ) ) );
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        verify( listener ).testError( entryCaptor.capture() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter().smartTrimmedStackTrace() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter().writeTraceToString() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter().writeTrimmedTraceToString() );
+    }
+
+    @Test
+    void stackTraceWriterPresentEvenWithoutException()
+                    throws Exception
+    {
+        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( null ) );
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        verify( listener ).testError( entryCaptor.capture() );
+        assertNotNull( entryCaptor.getValue().getStackTraceWriter() );
+    }
+
+    @Test
+    void displayNamesIgnoredInReport()
+                    throws NoSuchMethodException
+    {
+        TestMethodTestDescriptor descriptor =
+                        new TestMethodTestDescriptor(
+                                        newId(), MyTestClass.class,
+                                        MyTestClass.class.getDeclaredMethod( "myNamedTestMethod" ) );
+
+        TestIdentifier factoryIdentifier = TestIdentifier.from( descriptor );
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+
+        adapter.executionSkipped( factoryIdentifier, "" );
+        verify( listener ).testSkipped( entryCaptor.capture() );
+
+        ReportEntry value = entryCaptor.getValue();
+
+        assertEquals( "myNamedTestMethod", value.getName() );
+    }
+
     private static TestIdentifier newMethodIdentifier()
-        throws Exception
+                    throws Exception
     {
-        TestDescriptor testDescriptor = new TestMethodTestDescriptor( newId(), MyTestClass.class,
-            MyTestClass.class.getDeclaredMethod( MY_TEST_METHOD_NAME ) );
-        return TestIdentifier.from( testDescriptor );
+        return TestIdentifier.from( newMethodDescriptor() );
+    }
+
+    private static TestDescriptor newMethodDescriptor( Class<?>... parameterTypes )
+                    throws Exception
+    {
+        return new TestMethodTestDescriptor(
+                        UniqueId.forEngine( "method" ),
+                        MyTestClass.class,
+                        MyTestClass.class.getDeclaredMethod( MY_TEST_METHOD_NAME, parameterTypes ) );
     }
 
     private static TestIdentifier newClassIdentifier()
     {
-        TestDescriptor testDescriptor = new ClassTestDescriptor( newId(), MyTestClass.class );
-        return TestIdentifier.from( testDescriptor );
+        return TestIdentifier.from( newClassDescriptor() );
     }
 
-    private static TestIdentifier newSourcelessIdentifierWithParent( TestPlan testPlan, String parentDisplay )
+    private static TestDescriptor newClassDescriptor()
+    {
+        return new ClassTestDescriptor(
+                        UniqueId.root( "class", MyTestClass.class.getName() ), MyTestClass.class );
+    }
+
+    private static TestIdentifier newSourcelessChildIdentifierWithParent(
+                    TestPlan testPlan, String parentDisplay, TestSource parentTestSource )
     {
         // A parent test identifier with a name.
         TestDescriptor parent = mock( TestDescriptor.class );
         when( parent.getUniqueId() ).thenReturn( newId() );
         when( parent.getDisplayName() ).thenReturn( parentDisplay );
+        when( parent.getLegacyReportingName() ).thenReturn( parentDisplay );
+        when( parent.getSource() ).thenReturn( Optional.ofNullable( parentTestSource ) );
+        when( parent.getType() ).thenReturn( Type.CONTAINER );
         TestIdentifier parentId = TestIdentifier.from( parent );
 
         // The (child) test case that is to be executed as part of a test plan.
         TestDescriptor child = mock( TestDescriptor.class );
         when( child.getUniqueId() ).thenReturn( newId() );
-        when( child.isTest() ).thenReturn( true );
+        when( child.getType() ).thenReturn( Type.TEST );
+        when( child.getLegacyReportingName() ).thenReturn( "child" );
 
         // Ensure the child source is null yet that there is a parent -- the special case to be tested.
         when( child.getSource() ).thenReturn( Optional.empty() );
-        when( child.getParent() ).thenReturn( Optional.of(parent) );
+        when( child.getParent() ).thenReturn( Optional.of( parent ) );
         TestIdentifier childId = TestIdentifier.from( child );
 
         testPlan.add( childId );
@@ -229,10 +517,41 @@ class RunListenerAdapterTests
 
     private static TestIdentifier newEngineIdentifier()
     {
-        TestDescriptor testDescriptor = new EngineDescriptor( newId(), "engine" );
+        TestDescriptor testDescriptor = newEngineDescriptor();
         return TestIdentifier.from( testDescriptor );
     }
 
+    private static EngineDescriptor newEngineDescriptor()
+    {
+        return new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" );
+    }
+
+    private TestDescriptor newTestDescriptor( UniqueId uniqueId, String displayName, Type type )
+    {
+        return new AbstractTestDescriptor( uniqueId, displayName )
+        {
+            @Override
+            public Type getType()
+            {
+                return type;
+            }
+        };
+    }
+
+    private static TestIdentifier identifiersAsParentOnTestPlan(
+                    TestPlan plan, TestDescriptor parent, TestDescriptor child )
+    {
+        child.setParent( parent );
+
+        TestIdentifier parentIdentifier = TestIdentifier.from( parent );
+        TestIdentifier childIdentifier = TestIdentifier.from( child );
+
+        plan.add( parentIdentifier );
+        plan.add( childIdentifier );
+
+        return childIdentifier;
+    }
+
     private static UniqueId newId()
     {
         return UniqueId.forEngine( "engine" );
@@ -240,13 +559,22 @@ class RunListenerAdapterTests
 
     private static final String MY_TEST_METHOD_NAME = "myTestMethod";
 
-    private static class MyTestClass {
-
+    private static class MyTestClass
+    {
         @Test
         void myTestMethod()
         {
         }
 
-    }
+        @Test
+        void myTestMethod( String foo )
+        {
+        }
 
+        @DisplayName( "name" )
+        @Test
+        void myNamedTestMethod()
+        {
+        }
+    }
 }
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java
new file mode 100644
index 0000000..29ca326
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java
@@ -0,0 +1,105 @@
+package org.apache.maven.surefire.junitplatform;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.apache.maven.surefire.testset.TestListResolver.toClassFileName;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+
+import org.apache.maven.surefire.testset.TestListResolver;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.engine.descriptor.ClassTestDescriptor;
+import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
+import org.junit.platform.engine.FilterResult;
+import org.junit.platform.engine.UniqueId;
+
+/**
+ * Unit tests for {@link TestMethodFilter}.
+ *
+ * @since 1.0.3
+ */
+class TestMethodFilterTests
+{
+
+    private final TestListResolver resolver = mock( TestListResolver.class );
+
+    private final TestMethodFilter filter = new TestMethodFilter( this.resolver );
+
+    @Test
+    void includesBasedOnTestListResolver()
+                    throws Exception
+    {
+        when( resolver.shouldRun( toClassFileName( TestClass.class ), "testMethod" ) ).thenReturn( true );
+
+        FilterResult result = filter.apply( newTestMethodDescriptor() );
+
+        assertTrue( result.included() );
+        assertFalse( result.excluded() );
+    }
+
+    @Test
+    void excludesBasedOnTestListResolver()
+                    throws Exception
+    {
+        when( resolver.shouldRun( toClassFileName( TestClass.class ), "testMethod" ) ).thenReturn( false );
+
+        FilterResult result = filter.apply( newTestMethodDescriptor() );
+
+        assertFalse( result.included() );
+        assertTrue( result.excluded() );
+    }
+
+    @Test
+    void includesTestDescriptorWithClassSource()
+                    throws Exception
+    {
+        FilterResult result = filter.apply( newClassTestDescriptor() );
+
+        assertTrue( result.included() );
+        assertFalse( result.excluded() );
+    }
+
+    private static TestMethodTestDescriptor newTestMethodDescriptor()
+                    throws Exception
+    {
+        UniqueId uniqueId = UniqueId.forEngine( "method" );
+        Class<TestClass> testClass = TestClass.class;
+        Method testMethod = testClass.getMethod( "testMethod" );
+        return new TestMethodTestDescriptor( uniqueId, testClass, testMethod );
+    }
+
+    private static ClassTestDescriptor newClassTestDescriptor()
+                    throws Exception
+    {
+        UniqueId uniqueId = UniqueId.forEngine( "class" );
+        return new ClassTestDescriptor( uniqueId, TestClass.class );
+    }
+
+    public static class TestClass
+    {
+        public void testMethod()
+        {
+        }
+    }
+}
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java
index 98f5b2b..8ecedc7 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java
@@ -20,6 +20,7 @@ package org.apache.maven.surefire.junitplatform;
  */
 
 import static java.util.Collections.emptyList;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.List;
@@ -37,20 +38,20 @@ import org.junit.platform.launcher.core.LauncherFactory;
  *
  * @since 1.0
  */
-public class TestPlanScannerFilterTests
+class TestPlanScannerFilterTests
 {
 
     @Test
-    void emptyClassAccepted()
+    void emptyClassIsNotAccepted()
     {
-        assertTrue( newFilter().accept( EmptyClass.class ), "accepts empty class because it is a container" );
+        assertFalse( newFilter().accept( EmptyClass.class ), "does not accept empty class" );
     }
 
     @Test
-    void classWithNoTestMethodsIsAccepted()
+    void classWithNoTestMethodsIsNotAccepted()
     {
-        assertTrue( newFilter().accept( ClassWithMethods.class ),
-            "accepts class with no @Test methods because it is a container" );
+        assertFalse(
+                        newFilter().accept( ClassWithMethods.class ), "does not accept class with no @Test methods" );
     }
 
     @Test
@@ -88,12 +89,11 @@ public class TestPlanScannerFilterTests
         return new TestPlanScannerFilter( LauncherFactory.create(), new Filter<?>[0] );
     }
 
-    private static class EmptyClass
+    static class EmptyClass
     {
     }
 
-    @SuppressWarnings("unused")
-    private static class ClassWithMethods
+    static class ClassWithMethods
     {
 
         void method1()
@@ -105,7 +105,7 @@ public class TestPlanScannerFilterTests
         }
     }
 
-    private static class ClassWithTestMethods
+    static class ClassWithTestMethods
     {
 
         @Test
@@ -119,10 +119,9 @@ public class TestPlanScannerFilterTests
         }
     }
 
-    private static class ClassWithNestedTestClass
+    static class ClassWithNestedTestClass
     {
 
-        @SuppressWarnings("unused")
         void method()
         {
         }
@@ -138,7 +137,7 @@ public class TestPlanScannerFilterTests
         }
     }
 
-    private static class ClassWithDeeplyNestedTestClass
+    static class ClassWithDeeplyNestedTestClass
     {
 
         @Nested
@@ -162,7 +161,7 @@ public class TestPlanScannerFilterTests
         }
     }
 
-    private static class ClassWithTestFactory
+    static class ClassWithTestFactory
     {
 
         @TestFactory
@@ -172,7 +171,7 @@ public class TestPlanScannerFilterTests
         }
     }
 
-    private static class ClassWithNestedTestFactory
+    static class ClassWithNestedTestFactory
     {
 
         @Nested
@@ -186,5 +185,4 @@ public class TestPlanScannerFilterTests
             }
         }
     }
-
 }

-- 
To stop receiving notification emails like this one, please contact
sor@apache.org.