You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2018/05/03 20:24:13 UTC

[maven-surefire] branch 1330 updated (e61f2be -> 2c0a9c2)

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

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


    omit e61f2be  [SUREFIRE-1330] JUnit 5 surefire-provider code donation
     new 2c0a9c2  [SUREFIRE-1330] JUnit 5 surefire-provider code donation

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (e61f2be)
            \
             N -- N -- N   refs/heads/1330 (2c0a9c2)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/maven/surefire/its/JUnit4VersionsIT.java   |  8 ++++----
 .../test/java/org/apache/maven/surefire/its/JUnitVersion.java  |  4 ++--
 surefire-its/src/test/resources/junit-platform/pom.xml         |  2 +-
 surefire-its/src/test/resources/junit4/pom.xml                 | 10 +++++-----
 surefire-its/src/test/resources/junit5/pom.xml                 |  6 +++---
 5 files changed, 15 insertions(+), 15 deletions(-)

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

[maven-surefire] 01/01: [SUREFIRE-1330] JUnit 5 surefire-provider code donation

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2c0a9c23f578363a2ac297516eb41c064bd5f6f1
Author: Tibor17 <ti...@apache.org>
AuthorDate: Fri Apr 13 23:51:33 2018 +0200

    [SUREFIRE-1330] JUnit 5 surefire-provider code donation
---
 .../plugin/surefire/AbstractSurefireMojo.java      |  46 +-
 pom.xml                                            |   7 +-
 surefire-its/pom.xml                               |  23 +-
 .../maven/surefire/its/JUnit4VersionsIT.java       |  74 +-
 .../org/apache/maven/surefire/its/JUnit5IT.java}   |  50 +-
 .../maven/surefire/its/JUnitPlatformIT.java}       |  48 +-
 .../apache/maven/surefire/its/JUnitVersion.java    |  80 +++
 .../resources/{junit4 => junit-platform}/pom.xml   |  55 +-
 .../src/test/java/junitplatform}/BasicTest.java    |  30 +-
 surefire-its/src/test/resources/junit4/pom.xml     | 141 +++-
 .../junit4/src/test/java/junit4/BasicTest.java     |   4 +-
 .../BasicTest.java => junit5/JUnit5Test.java}      |  35 +-
 surefire-its/src/test/resources/junit5/pom.xml     |  77 +++
 .../src/test/java/junit5/JUnit4Test.java}          |  19 +-
 .../src/test/java/junit5/JUnit5Test.java}          |  36 +-
 surefire-providers/pom.xml                         |   1 +
 surefire-providers/surefire-junit-platform/pom.xml | 161 +++++
 .../junitplatform/JUnitPlatformProvider.java       | 241 +++++++
 .../surefire/junitplatform/RunListenerAdapter.java | 272 ++++++++
 .../surefire/junitplatform/TestMethodFilter.java   |  60 ++
 .../junitplatform/TestPlanScannerFilter.java       |  60 ++
 ...che.maven.surefire.providerapi.SurefireProvider |   1 +
 .../junitplatform/JUnitPlatformProviderTests.java  | 756 +++++++++++++++++++++
 .../junitplatform/RunListenerAdapterTests.java     | 580 ++++++++++++++++
 .../junitplatform/TestMethodFilterTests.java       | 105 +++
 .../junitplatform/TestPlanScannerFilterTests.java  | 188 +++++
 26 files changed, 2910 insertions(+), 240 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 34f805a..414e084 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -1024,6 +1024,7 @@ public abstract class AbstractSurefireMojo
                               new TestNgProviderInfo( getTestNgArtifact() ),
                               new JUnitCoreProviderInfo( getJunitArtifact(), junitDepArtifact ),
                               new JUnit4ProviderInfo( getJunitArtifact(), junitDepArtifact ),
+                              new JUnitPlatformProviderInfo( getJunitArtifact() ),
                               new JUnit3ProviderInfo() )
             .resolve();
     }
@@ -1540,7 +1541,17 @@ public abstract class AbstractSurefireMojo
         return dependencyResolver.isWithinVersionSpec( artifact, "[4.0,)" );
     }
 
-    static boolean isForkModeNever( String forkMode )
+    private boolean isJunitJupiter( Artifact artifact )
+    {
+        return dependencyResolver.isWithinVersionSpec( artifact, "[5.0.0,)" );
+    }
+
+    private boolean isJunitVintage( Artifact artifact )
+    {
+        return dependencyResolver.isWithinVersionSpec( artifact, "[4.12.0,5.0.0)" );
+    }
+
+    private static boolean isForkModeNever( String forkMode )
     {
         return FORK_NEVER.equals( forkMode );
     }
@@ -2873,6 +2884,39 @@ public abstract class AbstractSurefireMojo
 
     }
 
+    final class JUnitPlatformProviderInfo
+        implements ProviderInfo
+    {
+        private final Artifact junitArtifact;
+
+        JUnitPlatformProviderInfo( Artifact junitArtifact )
+        {
+            this.junitArtifact = junitArtifact;
+        }
+
+        @Nonnull public String getProviderName()
+        {
+            return "org.apache.maven.surefire.junitplatform.JUnitPlatformProvider";
+        }
+
+        public boolean isApplicable()
+        {
+            return isJunitJupiter( junitArtifact ) || isJunitVintage( junitArtifact );
+        }
+
+        public void addProviderProperties() throws MojoExecutionException
+        {
+        }
+
+        public Classpath getProviderClasspath()
+            throws ArtifactResolutionException, ArtifactNotFoundException
+        {
+            return dependencyResolver.getProviderClasspath( "surefire-junit-platform",
+                                                            surefireBooterArtifact.getBaseVersion(),
+                                                            null );
+        }
+    }
+
     final class JUnitCoreProviderInfo
         implements ProviderInfo
     {
diff --git a/pom.xml b/pom.xml
index 5fdefd4..96a75a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,8 +97,9 @@
     <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path>
     <!-- Override with Jigsaw JRE 9 -->
     <jdk.home>${java.home}/..</jdk.home>
-    <maven.compiler.testSource>1.6</maven.compiler.testSource>
-    <maven.compiler.testTarget>1.6</maven.compiler.testTarget>
+    <javaVersion>6</javaVersion>
+    <maven.compiler.testSource>1.${javaVersion}</maven.compiler.testSource>
+    <maven.compiler.testTarget>1.${javaVersion}</maven.compiler.testTarget>
     <jvm.args.tests>-server -XX:+UseG1GC -Xms128m -Xmx144m -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:SoftRefLRUPolicyMSPerMB=50 -Djava.awt.headless=true</jvm.args.tests>
   </properties>
 
@@ -419,6 +420,7 @@
                 <excludeDependencies>
                   <param>org.codehaus.plexus:plexus-java</param>
                   <param>org.ow2.asm:asm</param>
+                  <param>org.junit.platform:junit-platform-commons</param>
                 </excludeDependencies>
                 <ignores>
                   <param>org.objectweb.asm.*</param>
@@ -550,6 +552,7 @@
                   <excludes>
                     <exclude>org.codehaus.plexus:plexus-java</exclude>
                     <exclude>org.ow2.asm:asm</exclude>
+                    <exclude>org.junit.platform:junit-platform-commons</exclude>
                   </excludes>
                 </enforceBytecodeVersion>
               </rules>
diff --git a/surefire-its/pom.xml b/surefire-its/pom.xml
index 88a4870..d4c7df4 100644
--- a/surefire-its/pom.xml
+++ b/surefire-its/pom.xml
@@ -104,7 +104,9 @@
           <forkMode>once</forkMode>
           <argLine>-server -Xmx64m -XX:+UseG1GC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Djava.awt.headless=true</argLine>
           <includes>
-            <include>org/apache/**/*IT*.java</include>
+            <include>org/apache/**/JUnit4VersionsIT.java</include>
+            <include>org/apache/**/JUnit5IT.java</include>
+            <include>org/apache/**/JUnitPlatformIT.java</include>
           </includes>
           <!-- Pass current surefire version to the main suite so that it -->
           <!-- can forward to all integration test projects. SUREFIRE-513 -->
@@ -143,25 +145,6 @@
         </executions>
       </plugin>
       <plugin>
-        <artifactId>maven-enforcer-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>require-maven-2.1.0</id>
-            <goals>
-              <goal>enforce</goal>
-            </goals>
-            <configuration>
-              <rules>
-                <requireMavenVersion>
-                  <!-- Some plugin features require a recent Maven runtime to work (e.g. SystemPropertiesTest) -->
-                  <version>[2.1.0,)</version>
-                </requireMavenVersion>
-              </rules>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
         <artifactId>maven-jar-plugin</artifactId>
         <!-- todo dont skip since of failsafe:2.19 internal use if having src/main/java/... -->
         <configuration>
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java
index 8dd8f0c..39c4cc1 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java
@@ -19,7 +19,6 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
-import java.util.Arrays;
 import java.util.Collection;
 
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
@@ -29,8 +28,28 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 
+import static java.util.Arrays.asList;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_10;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_11;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_12;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8_1;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8_2;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_9;
 import static org.junit.runners.Parameterized.*;
 
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_0;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_1;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_2;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_3;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_3_1;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_4;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_5;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_6;
+import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_7;
+import static org.apache.maven.surefire.its.JUnitVersion.JUPITER_5_2_0;
+import static org.apache.maven.surefire.its.JUnitVersion.VINTAGE_5_2_0;
+
 /**
  * Basic suite test using all known versions of JUnit 4.x
  *
@@ -44,44 +63,41 @@ public class JUnit4VersionsIT
     @Parameters( name = "{index}: JUnit {0}" )
     public static Collection<Object[]> junitVersions()
     {
-        return Arrays.asList( new Object[][] {
-                { "4.0" },
-                { "4.1" },
-                { "4.2" },
-                { "4.3" },
-                { "4.3.1" },
-                { "4.4" },
-                { "4.5" },
-                { "4.6" },
-                { "4.7" },
-                { "4.8" },
-                { "4.8.1" },
-                { "4.8.2" },
-                { "4.9" },
-                { "4.10" },
-                { "4.11" },
-                { "4.12" }
+        return asList( new Object[][] {
+                { JUNIT_4_0 },
+                { JUNIT_4_1 },
+                { JUNIT_4_2 },
+                { JUNIT_4_3 },
+                { JUNIT_4_3_1 },
+                { JUNIT_4_4 },
+                { JUNIT_4_5 },
+                { JUNIT_4_6 },
+                { JUNIT_4_7 },
+                { JUNIT_4_8 },
+                { JUNIT_4_8_1 },
+                { JUNIT_4_8_2 },
+                { JUNIT_4_9 },
+                { JUNIT_4_10 },
+                { JUNIT_4_11 },
+                { JUNIT_4_12 },
+                { VINTAGE_5_2_0 },
+                { JUPITER_5_2_0 }
         } );
     }
 
     @Parameter
-    public String version;
-
-    private SurefireLauncher unpack()
-    {
-        return unpack( "/junit4", version );
-    }
+    public JUnitVersion version;
 
     @Test
     public void testJunit()
-        throws Exception
     {
-        runJUnitTest( version );
+        version.configure( unpack() )
+                .executeTest()
+                .verifyErrorFree( 1 );
     }
 
-    public void runJUnitTest( String version )
-        throws Exception
+    private SurefireLauncher unpack()
     {
-        unpack().setJUnitVersion( version ).executeTest().verifyErrorFree( 1 );
+        return unpack( "/junit4", version.toString() );
     }
 }
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java
similarity index 54%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java
index e9234f2..8c5eb3a 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java
@@ -1,4 +1,4 @@
-package junit4;
+package org.apache.maven.surefire.its;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,27 @@ package junit4;
  * under the License.
  */
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.junit.Test;
 
+import static org.apache.maven.surefire.its.fixture.HelperAssertions.assumeJavaVersion;
 
-public class BasicTest
+/**
+ * Basic suite test currently only running against JUnit 5 M2
+ *
+ * @author <a href="mailto:britter@apache.org">Benedikt Ritter</a>
+ */
+public class JUnit5IT
+    extends SurefireJUnit4IntegrationTestCase
 {
 
-    private boolean setUpCalled = false;
-
-    private static boolean tearDownCalled = false;
-    
-    @Before
-    public void setUp()
-    {
-        setUpCalled = true;
-        tearDownCalled = false;
-        System.out.println( "Called setUp" );
-    }
-
-    @After
-    public void tearDown()
-    {
-        setUpCalled = false;
-        tearDownCalled = true;
-        System.out.println( "Called tearDown" );
-    }
-
     @Test
-    public void testSetUp()
+    public void test()
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
-    }
-  
+        assumeJavaVersion( 1.8d );
 
-    @AfterClass
-    public static void oneTimeTearDown()
-    {
-        
+        unpack( "/junit5" )
+                .executeTest()
+                .verifyErrorFree( 2 );
     }
-
 }
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
similarity index 53%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
index e9234f2..f82206c 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
@@ -1,4 +1,4 @@
-package junit4;
+package org.apache.maven.surefire.its;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,29 @@ package junit4;
  * under the License.
  */
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.junit.Test;
 
+import static java.lang.System.getProperty;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assume.assumeThat;
 
-public class BasicTest
+public class JUnitPlatformIT
+    extends SurefireJUnit4IntegrationTestCase
 {
-
-    private boolean setUpCalled = false;
-
-    private static boolean tearDownCalled = false;
-    
-    @Before
-    public void setUp()
-    {
-        setUpCalled = true;
-        tearDownCalled = false;
-        System.out.println( "Called setUp" );
-    }
-
-    @After
-    public void tearDown()
+    private SurefireLauncher unpack()
     {
-        setUpCalled = false;
-        tearDownCalled = true;
-        System.out.println( "Called tearDown" );
+        return unpack( "/junit-platform" );
     }
 
     @Test
-    public void testSetUp()
+    public void test40()
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
-    }
-  
+        assumeThat( "java.specification.version: ",
+                getProperty( "java.specification.version" ), is( greaterThanOrEqualTo( "1.8" ) ) );
 
-    @AfterClass
-    public static void oneTimeTearDown()
-    {
-        
+        unpack().executeTest().verifyErrorFree( 1 );
     }
-
 }
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java
new file mode 100644
index 0000000..4831b21
--- /dev/null
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java
@@ -0,0 +1,80 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+
+/**
+ * Enum listing all the JUnit version.
+ */
+public enum JUnitVersion {
+
+    JUNIT_4_0( "4.0" ),
+    JUNIT_4_1( "4.1" ),
+    JUNIT_4_2( "4.2" ),
+    JUNIT_4_3( "4.3" ),
+    JUNIT_4_3_1( "4.3.1" ),
+    JUNIT_4_4( "4.4" ),
+    JUNIT_4_5( "4.5" ),
+    JUNIT_4_6( "4.6" ),
+    JUNIT_4_7( "4.7" ),
+    JUNIT_4_8( "4.8" ),
+    JUNIT_4_8_1( "4.8.1" ),
+    JUNIT_4_8_2( "4.8.2" ),
+    JUNIT_4_9( "4.9" ),
+    JUNIT_4_10( "4.10" ),
+    JUNIT_4_11( "4.11" ),
+    JUNIT_4_12( "4.12" ),
+    VINTAGE_5_2_0( "5.2.0" )
+    {
+        @Override
+        public SurefireLauncher configure( SurefireLauncher launcher )
+        {
+            return super.configure( launcher )
+                    .activateProfile( "junit5-vintage" );
+        }
+    },
+    JUPITER_5_2_0( "5.2.0" )
+    {
+        @Override
+        public SurefireLauncher configure( SurefireLauncher launcher )
+        {
+            return super.configure( launcher )
+                    .activateProfile( "junit5-jupiter" );
+        }
+    };
+
+    private final String version;
+
+    JUnitVersion( String version )
+    {
+        this.version = version;
+    }
+
+    public SurefireLauncher configure( SurefireLauncher launcher )
+    {
+        return launcher.setJUnitVersion( version );
+    }
+
+    public String toString()
+    {
+        return version;
+    }
+}
diff --git a/surefire-its/src/test/resources/junit4/pom.xml b/surefire-its/src/test/resources/junit-platform/pom.xml
similarity index 55%
copy from surefire-its/src/test/resources/junit4/pom.xml
copy to surefire-its/src/test/resources/junit-platform/pom.xml
index 751a0e6..9a4bdeb 100644
--- a/surefire-its/src/test/resources/junit4/pom.xml
+++ b/surefire-its/src/test/resources/junit-platform/pom.xml
@@ -17,39 +17,38 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
   -->
+
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+    <modelVersion>4.0.0</modelVersion>
 
-  <groupId>org.apache.maven.plugins.surefire</groupId>
-  <artifactId>junit4</artifactId>
-  <version>1.0-SNAPSHOT</version>
-  <name>Test for JUnit 4</name>
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5 Platform</name>
 
-  <properties>
-    <junitVersion>4.4</junitVersion>
-    <maven.compiler.source>1.6</maven.compiler.source>
-    <maven.compiler.target>1.6</maven.compiler.target>
-  </properties>
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
 
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>${junit.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-  
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <version>${surefire.version}</version>
-      </plugin>
-    </plugins>
-  </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.2.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surefire.version}</version>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java
similarity index 73%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java
index e9234f2..2c9d119 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatform;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,21 +19,21 @@ package junit4;
  * under the License.
  */
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
 public class BasicTest
 {
 
-    private boolean setUpCalled = false;
+    private boolean setUpCalled;
 
-    private static boolean tearDownCalled = false;
-    
-    @Before
+    private static boolean tearDownCalled;
+
+    @BeforeEach
     public void setUp()
     {
         setUpCalled = true;
@@ -41,7 +41,7 @@ public class BasicTest
         System.out.println( "Called setUp" );
     }
 
-    @After
+    @AfterEach
     public void tearDown()
     {
         setUpCalled = false;
@@ -52,14 +52,14 @@ public class BasicTest
     @Test
     public void testSetUp()
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        assertTrue( setUpCalled, "setUp was not called" );
     }
-  
 
-    @AfterClass
+
+    @AfterAll
     public static void oneTimeTearDown()
     {
-        
+        assertTrue( tearDownCalled );
     }
 
 }
diff --git a/surefire-its/src/test/resources/junit4/pom.xml b/surefire-its/src/test/resources/junit4/pom.xml
index 751a0e6..548eed6 100644
--- a/surefire-its/src/test/resources/junit4/pom.xml
+++ b/surefire-its/src/test/resources/junit4/pom.xml
@@ -20,36 +20,121 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+    <modelVersion>4.0.0</modelVersion>
 
-  <groupId>org.apache.maven.plugins.surefire</groupId>
-  <artifactId>junit4</artifactId>
-  <version>1.0-SNAPSHOT</version>
-  <name>Test for JUnit 4</name>
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit4</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>Test for JUnit 4</name>
 
-  <properties>
-    <junitVersion>4.4</junitVersion>
-    <maven.compiler.source>1.6</maven.compiler.source>
-    <maven.compiler.target>1.6</maven.compiler.target>
-  </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>${junit.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-  
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <version>${surefire.version}</version>
-      </plugin>
-    </plugins>
-  </build>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>junit4</id>
+            <dependencies>
+                <dependency>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                    <version>${junit.version}</version>
+                    <scope>test</scope>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${surefire.version}</version>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>junit5-jupiter</id>
+            <properties>
+                <maven.compiler.source>1.8</maven.compiler.source>
+                <maven.compiler.target>1.8</maven.compiler.target>
+                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+                <junit.jupiter.version>5.2.0</junit.jupiter.version>
+                <junit.vintage.version>5.2.0</junit.vintage.version>
+                <junit.platform.version>1.2.0</junit.platform.version>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${surefire.version}</version>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.junit.platform</groupId>
+                                <artifactId>junit-platform-surefire-provider</artifactId>
+                                <version>${junit.platform.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.junit.jupiter</groupId>
+                                <artifactId>junit-jupiter-engine</artifactId>
+                                <version>${junit.jupiter.version}</version>
+                            </dependency>
+                        </dependencies>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>junit5-vintage</id>
+            <properties>
+                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+                <junit.platform.version>1.2.0</junit.platform.version>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${surefire.version}</version>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.junit.platform</groupId>
+                                <artifactId>junit-platform-surefire-provider</artifactId>
+                                <version>${junit.platform.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.junit.vintage</groupId>
+                                <artifactId>junit-vintage-engine</artifactId>
+                                <version>${junit.version}</version>
+                            </dependency>
+                        </dependencies>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 
 </project>
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
index e9234f2..144df74 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
@@ -25,14 +25,12 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-
 public class BasicTest
 {
+    private static boolean tearDownCalled = false;
 
     private boolean setUpCalled = false;
 
-    private static boolean tearDownCalled = false;
-    
     @Before
     public void setUp()
     {
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java
similarity index 66%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java
index e9234f2..0dd000c 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junit5;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,32 +19,32 @@ package junit4;
  * under the License.
  */
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class BasicTest
+/**
+ * A test using the JUnit 5 API, which should be executed by JUnit jupiter enigne
+ */
+public class JUnit5Test
 {
+    private static boolean tearDownCalled;
 
-    private boolean setUpCalled = false;
+    private boolean setUpCalled;
 
-    private static boolean tearDownCalled = false;
-    
-    @Before
+    @BeforeEach
     public void setUp()
     {
         setUpCalled = true;
-        tearDownCalled = false;
         System.out.println( "Called setUp" );
     }
 
-    @After
+    @AfterEach
     public void tearDown()
     {
-        setUpCalled = false;
         tearDownCalled = true;
         System.out.println( "Called tearDown" );
     }
@@ -52,14 +52,13 @@ public class BasicTest
     @Test
     public void testSetUp()
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        assertTrue( setUpCalled, "setUp was not called" );
     }
-  
 
-    @AfterClass
+    @AfterAll
     public static void oneTimeTearDown()
     {
-        
+        assertTrue( tearDownCalled, "tearDown was not called" );
     }
 
 }
diff --git a/surefire-its/src/test/resources/junit5/pom.xml b/surefire-its/src/test/resources/junit5/pom.xml
new file mode 100644
index 0000000..379a5bd
--- /dev/null
+++ b/surefire-its/src/test/resources/junit5/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit5</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>Test for JUnit 5</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>1.8</java.version>
+        <junit.jupiter.version>5.2.0</junit.jupiter.version>
+        <junit.vintage.version>5.2.0</junit.vintage.version>
+        <junit.platform.version>1.2.0</junit.platform.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${junit.jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.vintage</groupId>
+            <artifactId>junit-vintage-engine</artifactId>
+            <version>${junit.vintage.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surefire.version}</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.junit.platform</groupId>
+                        <artifactId>junit-platform-surefire-provider</artifactId>
+                        <version>${junit.platform.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java
similarity index 83%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java
index e9234f2..0a94b1a 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junit5;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -25,26 +25,26 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-
-public class BasicTest
+/**
+ * A test using the JUnit 4 API, which should be executed by JUnit vintage enigne
+ */
+public class JUnit4Test
 {
 
-    private boolean setUpCalled = false;
+    private boolean setUpCalled;
+
+    private static boolean tearDownCalled;
 
-    private static boolean tearDownCalled = false;
-    
     @Before
     public void setUp()
     {
         setUpCalled = true;
-        tearDownCalled = false;
         System.out.println( "Called setUp" );
     }
 
     @After
     public void tearDown()
     {
-        setUpCalled = false;
         tearDownCalled = true;
         System.out.println( "Called tearDown" );
     }
@@ -54,12 +54,11 @@ public class BasicTest
     {
         Assert.assertTrue( "setUp was not called", setUpCalled );
     }
-  
 
     @AfterClass
     public static void oneTimeTearDown()
     {
-        
+        Assert.assertTrue( "tearDown was not called", tearDownCalled );
     }
 
 }
diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java
similarity index 66%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java
index e9234f2..558546d 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junit5;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,32 +19,33 @@ package junit4;
  * under the License.
  */
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
 
-public class BasicTest
+/**
+ * A test using the JUnit 5 API, which should be executed by JUnit jupiter enigne
+ */
+public class JUnit5Test
 {
 
-    private boolean setUpCalled = false;
+    private boolean setUpCalled;
+
+    private static boolean tearDownCalled;
 
-    private static boolean tearDownCalled = false;
-    
-    @Before
+    @BeforeEach
     public void setUp()
     {
         setUpCalled = true;
-        tearDownCalled = false;
         System.out.println( "Called setUp" );
     }
 
-    @After
+    @AfterEach
     public void tearDown()
     {
-        setUpCalled = false;
         tearDownCalled = true;
         System.out.println( "Called tearDown" );
     }
@@ -52,14 +53,13 @@ public class BasicTest
     @Test
     public void testSetUp()
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        Assertions.assertTrue( setUpCalled, "setUp was not called" );
     }
-  
 
-    @AfterClass
+    @AfterAll
     public static void oneTimeTearDown()
     {
-        
+        Assertions.assertTrue( tearDownCalled, "tearDown was not called" );
     }
 
 }
diff --git a/surefire-providers/pom.xml b/surefire-providers/pom.xml
index 0322bcc..47b0f40 100644
--- a/surefire-providers/pom.xml
+++ b/surefire-providers/pom.xml
@@ -41,6 +41,7 @@
     <module>surefire-junit3</module>
     <module>surefire-junit4</module>
     <module>surefire-junit47</module>
+    <module>surefire-junit-platform</module>
     <module>surefire-testng-utils</module>
     <module>surefire-testng</module>
   </modules>
diff --git a/surefire-providers/surefire-junit-platform/pom.xml b/surefire-providers/surefire-junit-platform/pom.xml
new file mode 100644
index 0000000..bf4869b
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/pom.xml
@@ -0,0 +1,161 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.surefire</groupId>
+        <artifactId>surefire-providers</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>surefire-junit-platform</artifactId>
+    <name>SureFire JUnit Platform Runner</name>
+    <description>SureFire JUnit Platform Runner</description>
+    <properties>
+        <javaVersion>8</javaVersion>
+    </properties>
+    <contributors>
+        <contributor>
+            <name>Konstantin Lutovich</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Shintaro Katafuchi</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Sam Brannen</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Stefan Bechtold</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Marc Philipp</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Matthias Merdes</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+        <contributor>
+            <name>Johannes Link</name>
+            <roles>
+                <role>Contributed to the original provider implementation</role>
+            </roles>
+        </contributor>
+    </contributors>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>common-java5</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <version>1.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.2.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.6.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>animal-sniffer-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>signature-check</id>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <configuration>
+                            <signature combine.self="override">
+                                <groupId>org.codehaus.mojo.signature</groupId>
+                                <artifactId>java18</artifactId>
+                                <version>1.0</version>
+                            </signature>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <jvm>${java.home}/bin/java</jvm>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    <include>org.apache.maven.surefire:common-java5</include>
+                                </includes>
+                            </artifactSet>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
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
new file mode 100644
index 0000000..510266e
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
@@ -0,0 +1,241 @@
+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 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.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;
+
+public class JUnitPlatformProvider
+    extends AbstractProvider
+{
+
+    // Parameter names processed to determine which @Tags should be executed.
+    static final String EXCLUDE_GROUPS = "excludedGroups";
+
+    static final String EXCLUDE_TAGS = "excludeTags";
+
+    static final String INCLUDE_GROUPS = "groups";
+
+    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).";
+
+    private final ProviderParameters parameters;
+
+    private final Launcher launcher;
+
+    final Filter<?>[] filters;
+
+    final Map<String, String> configurationParameters;
+
+    JUnitPlatformProvider( ProviderParameters parameters )
+    {
+        this( parameters, LauncherFactory.create() );
+    }
+
+    JUnitPlatformProvider( ProviderParameters parameters, Launcher launcher )
+    {
+        this.parameters = parameters;
+        this.launcher = launcher;
+        this.filters = getFilters();
+        this.configurationParameters = getConfigurationParameters();
+        Logger.getLogger( "org.junit" ).setLevel( Level.WARNING );
+    }
+
+    @Override
+    public Iterable<Class<?>> getSuites()
+    {
+        return scanClasspath();
+    }
+
+    @Override
+    public RunResult invoke( Object forkTestSet )
+                    throws TestSetFailedException, ReporterException
+    {
+        if ( forkTestSet instanceof TestsToRun )
+        {
+            return invokeAllTests( (TestsToRun) forkTestSet );
+        }
+        else if ( forkTestSet instanceof Class )
+        {
+            return invokeAllTests( TestsToRun.fromClass( (Class<?>) forkTestSet ) );
+        }
+        else if ( forkTestSet == null )
+        {
+            return invokeAllTests( scanClasspath() );
+        }
+        else
+        {
+            throw new IllegalArgumentException( "Unexpected value of forkTestSet: " + forkTestSet );
+        }
+    }
+
+    private TestsToRun scanClasspath()
+    {
+        TestPlanScannerFilter filter = new TestPlanScannerFilter( launcher, filters );
+        ScanResult scanResult = parameters.getScanResult();
+        TestsToRun scannedClasses = scanResult.applyFilter( filter, parameters.getTestClassLoader() );
+        return parameters.getRunOrderCalculator().orderTestClasses( scannedClasses );
+    }
+
+    private RunResult invokeAllTests( TestsToRun testsToRun )
+    {
+        RunResult runResult;
+        ReporterFactory reporterFactory = parameters.getReporterFactory();
+        try
+        {
+            RunListener runListener = reporterFactory.createReporter();
+            ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) runListener );
+            LauncherDiscoveryRequest discoveryRequest = buildLauncherDiscoveryRequest( testsToRun );
+            launcher.execute( discoveryRequest, new RunListenerAdapter( runListener ) );
+        }
+        finally
+        {
+            runResult = reporterFactory.close();
+        }
+        return runResult;
+    }
+
+    private LauncherDiscoveryRequest buildLauncherDiscoveryRequest( TestsToRun testsToRun )
+    {
+        LauncherDiscoveryRequestBuilder builder =
+                        request().filters( filters ).configurationParameters( configurationParameters );
+        for ( Class<?> testClass : testsToRun )
+        {
+            builder.selectors( selectClass( testClass ) );
+        }
+        return builder.build();
+    }
+
+    private Filter<?>[] getFilters()
+    {
+        List<Filter<?>> filters = new ArrayList<>();
+
+        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 ) );
+        excludes.map( TagFilter::excludeTags ).ifPresent( filters::add );
+
+        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 ( StringUtils.isNotBlank( property ) )
+        {
+            compoundProperties =
+                            Arrays.stream( property.split( "[,]+" ) )
+                                  .filter( StringUtils::isNotBlank )
+                                  .map( String::trim )
+                                  .collect( toList() );
+        }
+        return Optional.ofNullable( compoundProperties );
+    }
+
+    private Optional<List<String>> getGroupsOrTags( Optional<List<String>> groups, Optional<List<String>> tags )
+    {
+        Optional<List<String>> elements = Optional.empty();
+
+        Preconditions.condition(!groups.isPresent() || !tags.isPresent(), EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED );
+
+        if ( groups.isPresent() )
+        {
+            elements = groups;
+        }
+        else if ( tags.isPresent() )
+        {
+            elements = tags;
+        }
+
+        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
new file mode 100644
index 0000000..71a70c3
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java
@@ -0,0 +1,272 @@
+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.report.SimpleReportEntry.ignored;
+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;
+import org.apache.maven.surefire.report.SimpleReportEntry;
+import org.apache.maven.surefire.report.StackTraceWriter;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+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
+ */
+final class RunListenerAdapter
+    implements TestExecutionListener
+{
+
+    private final RunListener runListener;
+
+    private TestPlan testPlan;
+
+    private Set<TestIdentifier> testSetNodes = ConcurrentHashMap.newKeySet();
+
+    RunListenerAdapter( RunListener runListener )
+    {
+        this.runListener = runListener;
+    }
+
+    @Override
+    public void testPlanExecutionStarted( TestPlan testPlan )
+    {
+        updateTestPlan( testPlan );
+    }
+
+    @Override
+    public void testPlanExecutionFinished( TestPlan testPlan )
+    {
+        updateTestPlan( null );
+    }
+
+    @Override
+    public void executionStarted( TestIdentifier testIdentifier )
+    {
+        if ( testIdentifier.isContainer()
+                        && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() )
+        {
+            startTestSetIfPossible( testIdentifier );
+        }
+        if ( testIdentifier.isTest() )
+        {
+            ensureTestSetStarted( testIdentifier );
+            runListener.testStarting( createReportEntry( testIdentifier ) );
+        }
+    }
+
+    @Override
+    public void executionSkipped( TestIdentifier testIdentifier, String reason )
+    {
+        ensureTestSetStarted( testIdentifier );
+        String source = getLegacyReportingClassName( testIdentifier );
+        runListener.testSkipped( ignored( source, getLegacyReportingName( testIdentifier ), reason ) );
+        completeTestSetIfNecessary( testIdentifier );
+    }
+
+    @Override
+    public void executionFinished(
+                    TestIdentifier testIdentifier, TestExecutionResult testExecutionResult )
+    {
+        if ( testExecutionResult.getStatus() == ABORTED )
+        {
+            runListener.testAssumptionFailure( createReportEntry( testIdentifier, testExecutionResult ) );
+        }
+        else if ( testExecutionResult.getStatus() == FAILED )
+        {
+            reportFailedTest( testIdentifier, testExecutionResult );
+        }
+        else if ( testIdentifier.isTest() )
+        {
+            runListener.testSucceeded( createReportEntry( testIdentifier ) );
+        }
+        completeTestSetIfNecessary( testIdentifier );
+    }
+
+    private void updateTestPlan( TestPlan testPlan )
+    {
+        this.testPlan = testPlan;
+        testSetNodes.clear();
+    }
+
+    private void ensureTestSetStarted( TestIdentifier testIdentifier )
+    {
+        if ( isTestSetStarted( testIdentifier ) )
+        {
+            return;
+        }
+        if ( testIdentifier.isTest() )
+        {
+            startTestSet( testPlan.getParent( testIdentifier ).orElse( testIdentifier ) );
+        }
+        else
+        {
+            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 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 ( (ClassSource) testSource ).getJavaClass().getName();
+        }
+        if ( testSource instanceof MethodSource )
+        {
+            return ( (MethodSource) testSource ).getClassName();
+        }
+        return testPlan.getParent( testIdentifier ).map( this::getClassName ).orElse( "" );
+    }
+
+    private Optional<String> getMethodName( TestIdentifier testIdentifier )
+    {
+        TestSource testSource = testIdentifier.getSource().orElse( null );
+        if ( testSource instanceof MethodSource )
+        {
+            return Optional.of( ( (MethodSource) testSource ).getMethodName() );
+        }
+        return Optional.empty();
+    }
+}
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
new file mode 100644
index 0000000..4b488c1
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.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 static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
+
+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.TestPlan;
+
+/**
+ * @since 1.0
+ */
+final class TestPlanScannerFilter
+    implements ScannerFilter
+{
+
+    private final Launcher launcher;
+
+    private final Filter<?>[] includeAndExcludeFilters;
+
+    TestPlanScannerFilter( Launcher launcher, Filter<?>[] includeAndExcludeFilters )
+    {
+        this.launcher = launcher;
+        this.includeAndExcludeFilters = includeAndExcludeFilters;
+    }
+
+    @Override
+    @SuppressWarnings( "rawtypes" )
+    public boolean accept( Class testClass )
+    {
+        LauncherDiscoveryRequest discoveryRequest = request()
+                        .selectors( selectClass( testClass ) )
+                        .filters( includeAndExcludeFilters ).build();
+
+        TestPlan testPlan = launcher.discover( discoveryRequest );
+
+        return testPlan.containsTests();
+    }
+}
diff --git a/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider b/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider
new file mode 100644
index 0000000..dbe73cf
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider
@@ -0,0 +1 @@
+org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
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
new file mode 100644
index 0000000..e0db97e
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java
@@ -0,0 +1,756 @@
+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 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;
+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}.
+ *
+ * @since 1.0
+ */
+class JUnitPlatformProviderTests
+{
+
+    @Test
+    void getSuitesReturnsScannedClasses()
+                    throws Exception
+    {
+        ProviderParameters providerParameters =
+                        providerParametersMock( TestClass1.class, TestClass2.class );
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertThat( provider.getSuites() ).containsOnly( TestClass1.class, TestClass2.class );
+    }
+
+    @Test
+    void invokeThrowsForWrongForkTestSet()
+                    throws Exception
+    {
+        ProviderParameters providerParameters = providerParametersMock( Integer.class );
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertThrows(
+                        IllegalArgumentException.class, () -> invokeProvider( provider, "wrong forkTestSet" ) );
+    }
+
+    @Test
+    void allGivenTestsToRunAreInvoked()
+                    throws Exception
+    {
+        Launcher launcher = LauncherFactory.create();
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher );
+
+        TestPlanSummaryListener executionListener = new TestPlanSummaryListener();
+        launcher.registerTestExecutionListeners( executionListener );
+
+        TestsToRun testsToRun = newTestsToRun( TestClass1.class, TestClass2.class );
+        invokeProvider( provider, testsToRun );
+
+        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
+    {
+        Launcher launcher = LauncherFactory.create();
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher );
+
+        TestPlanSummaryListener executionListener = new TestPlanSummaryListener();
+        launcher.registerTestExecutionListeners( executionListener );
+
+        invokeProvider( provider, TestClass1.class );
+
+        assertThat( executionListener.summaries ).hasSize( 1 );
+        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
+    {
+        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 );
+
+        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() );
+    }
+
+    @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
+    void bothGroupsAndIncludeTagsThrowsException()
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "groupOne, groupTwo" );
+        properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne, tagTwo" );
+        verifyPreconditionViolationException( properties );
+    }
+
+    @Test
+    void bothExcludedGroupsAndExcludeTagsThrowsException()
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.EXCLUDE_GROUPS, "groupOne, groupTwo" );
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" );
+        verifyPreconditionViolationException( properties );
+    }
+
+    @Test
+    void onlyGroupsIsDeclared()
+                    throws Exception
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "groupOne, groupTwo" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        assertEquals( 1, provider.filters.length );
+    }
+
+    @Test
+    void onlyExcludeTagsIsDeclared()
+                    throws Exception
+    {
+        Map<String, String> properties = new HashMap<>();
+        properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" );
+
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters );
+
+        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
+    {
+        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 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
+    {
+        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 );
+
+        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 )
+    {
+        ProviderParameters providerParameters = providerParametersMock( TestClass1.class );
+        when( providerParameters.getProviderProperties() ).thenReturn( properties );
+
+        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 );
+        when( scanResult.applyFilter( any(), any() ) ).thenReturn( testsToRun );
+
+        RunOrderCalculator runOrderCalculator = mock( RunOrderCalculator.class );
+        when( runOrderCalculator.orderTestClasses( any() ) ).thenReturn( testsToRun );
+
+        ReporterFactory reporterFactory = mock( ReporterFactory.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 );
+        return new TestsToRun( new LinkedHashSet<>( classesList ) );
+    }
+
+    private class TestPlanSummaryListener
+                    extends SummaryGeneratingListener
+    {
+
+        final List<TestExecutionSummary> summaries = new ArrayList<>();
+
+        @Override
+        public void testPlanExecutionFinished( TestPlan testPlan )
+        {
+            super.testPlanExecutionFinished( testPlan );
+            summaries.add( getSummary() );
+        }
+    }
+
+    /**
+     * 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()
+        {
+        }
+
+        @Test
+        void test2()
+        {
+        }
+
+        @Disabled
+        @Test
+        void test3()
+        {
+        }
+
+        @Test
+        void test4()
+        {
+            throw new RuntimeException();
+        }
+    }
+
+    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()
+        {
+        }
+
+        @Test
+        void test2()
+        {
+            throw new RuntimeException();
+        }
+
+        @Test
+        void test3()
+        {
+            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();
+        }
+
+        @Test
+        void prefix2Suffix1()
+        {
+            throw new RuntimeException();
+        }
+
+        @Test
+        void prefix1Suffix2()
+        {
+            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
new file mode 100644
index 0000000..d3ec7a4
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java
@@ -0,0 +1,580 @@
+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 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}.
+ *
+ * @since 1.0
+ */
+class RunListenerAdapterTests
+{
+
+    private RunListener listener;
+
+    private RunListenerAdapter adapter;
+
+    @BeforeEach
+    void setUp()
+    {
+        listener = mock( RunListener.class );
+        adapter = new RunListenerAdapter( listener );
+        adapter.testPlanExecutionStarted( TestPlan.from( emptyList() ) );
+    }
+
+    @Test
+    void notifiedWithCorrectNamesWhenMethodExecutionStarted()
+                    throws Exception
+    {
+        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() );
+
+        adapter.executionStarted( methodIdentifier );
+        verify( listener ).testStarting( entryCaptor.capture() );
+
+        ReportEntry entry = entryCaptor.getValue();
+        assertEquals( MY_TEST_METHOD_NAME, entry.getName() );
+        assertEquals( MyTestClass.class.getName(), entry.getSourceName() );
+        assertNull( entry.getStackTraceWriter() );
+    }
+
+    @Test
+    void notifiedWithCompatibleNameForMethodWithArguments()
+                    throws Exception
+    {
+        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() );
+    }
+
+    @Test
+    void notifiedWhenMethodExecutionSkipped()
+                    throws Exception
+    {
+        adapter.executionSkipped( newMethodIdentifier(), "test" );
+        verify( listener ).testSkipped( any() );
+    }
+
+    @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 );
+
+        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.getTypeName(), entry.getSourceName() );
+    }
+
+    @Test
+    void notifiedWhenEngineExecutionSkipped()
+                    throws Exception
+    {
+        adapter.executionSkipped( newEngineIdentifier(), "test" );
+        verify( listener ).testSkipped( any() );
+    }
+
+    @Test
+    void notifiedWhenMethodExecutionAborted()
+                    throws Exception
+    {
+        adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.aborted( null ) );
+        verify( listener ).testAssumptionFailure( any() );
+    }
+
+    @Test
+    void notifiedWhenClassExecutionAborted()
+                    throws Exception
+    {
+        adapter.executionFinished( newClassIdentifier(), TestExecutionResult.aborted( null ) );
+        verify( listener ).testAssumptionFailure( any() );
+    }
+
+    @Test
+    void notifiedWhenMethodExecutionFailedWithAnAssertionError()
+                    throws Exception
+    {
+        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(
+                        identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() ),
+                        TestExecutionResult.failed( new AssertionError() ) );
+        verify( listener ).testFailed( entryCaptor.capture() );
+
+        ReportEntry entry = entryCaptor.getValue();
+        assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() );
+        assertNotNull( entry.getStackTraceWriter() );
+    }
+
+    @Test
+    void notifiedWhenMethodExecutionSucceeded()
+                    throws Exception
+    {
+        adapter.executionFinished( newMethodIdentifier(), successful() );
+        verify( listener ).testSucceeded( any() );
+    }
+
+    @Test
+    void notifiedForTestSetWhenClassExecutionSucceeded()
+                    throws Exception
+    {
+        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( 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 = newSourcelessChildIdentifierWithParent( plan, parentDisplay, null );
+        adapter.executionStarted( child );
+
+        // Check that the adapter has informed Surefire that the test has been invoked,
+        // with the parent name as source (since the test case itself had no source).
+        ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class );
+        verify( listener ).testStarting( entryCaptor.capture() );
+        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
+    {
+        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()
+    {
+        return TestIdentifier.from( newClassDescriptor() );
+    }
+
+    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.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 ) );
+        TestIdentifier childId = TestIdentifier.from( child );
+
+        testPlan.add( childId );
+        testPlan.add( parentId );
+
+        return childId;
+    }
+
+    private static TestIdentifier newEngineIdentifier()
+    {
+        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" );
+    }
+
+    private static final String MY_TEST_METHOD_NAME = "myTestMethod";
+
+    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
new file mode 100644
index 0000000..8ecedc7
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java
@@ -0,0 +1,188 @@
+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 java.util.Collections.emptyList;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.platform.engine.Filter;
+import org.junit.platform.launcher.core.LauncherFactory;
+
+/**
+ * Unit tests for {@link TestPlanScannerFilter}.
+ *
+ * @since 1.0
+ */
+class TestPlanScannerFilterTests
+{
+
+    @Test
+    void emptyClassIsNotAccepted()
+    {
+        assertFalse( newFilter().accept( EmptyClass.class ), "does not accept empty class" );
+    }
+
+    @Test
+    void classWithNoTestMethodsIsNotAccepted()
+    {
+        assertFalse(
+                        newFilter().accept( ClassWithMethods.class ), "does not accept class with no @Test methods" );
+    }
+
+    @Test
+    void classWithTestMethodsIsAccepted()
+    {
+        assertTrue( newFilter().accept( ClassWithTestMethods.class ) );
+    }
+
+    @Test
+    void classWithNestedTestClassIsAccepted()
+    {
+        assertTrue( newFilter().accept( ClassWithNestedTestClass.class ) );
+    }
+
+    @Test
+    void classWithDeeplyNestedTestClassIsAccepted()
+    {
+        assertTrue( newFilter().accept( ClassWithDeeplyNestedTestClass.class ) );
+    }
+
+    @Test
+    void classWithTestFactoryIsAccepted()
+    {
+        assertTrue( newFilter().accept( ClassWithTestFactory.class ) );
+    }
+
+    @Test
+    void classWithNestedTestFactoryIsAccepted()
+    {
+        assertTrue( newFilter().accept( ClassWithNestedTestFactory.class ) );
+    }
+
+    private TestPlanScannerFilter newFilter()
+    {
+        return new TestPlanScannerFilter( LauncherFactory.create(), new Filter<?>[0] );
+    }
+
+    static class EmptyClass
+    {
+    }
+
+    static class ClassWithMethods
+    {
+
+        void method1()
+        {
+        }
+
+        void method2()
+        {
+        }
+    }
+
+    static class ClassWithTestMethods
+    {
+
+        @Test
+        void test1()
+        {
+        }
+
+        @Test
+        public void test2()
+        {
+        }
+    }
+
+    static class ClassWithNestedTestClass
+    {
+
+        void method()
+        {
+        }
+
+        @Nested
+        class TestClass
+        {
+
+            @Test
+            void test1()
+            {
+            }
+        }
+    }
+
+    static class ClassWithDeeplyNestedTestClass
+    {
+
+        @Nested
+        class Level1
+        {
+
+            @Nested
+            class Level2
+            {
+
+                @Nested
+                class TestClass
+                {
+
+                    @Test
+                    void test1()
+                    {
+                    }
+                }
+            }
+        }
+    }
+
+    static class ClassWithTestFactory
+    {
+
+        @TestFactory
+        Stream<DynamicTest> tests()
+        {
+            return Stream.empty();
+        }
+    }
+
+    static class ClassWithNestedTestFactory
+    {
+
+        @Nested
+        class TestClass
+        {
+
+            @TestFactory
+            List<DynamicTest> tests()
+            {
+                return emptyList();
+            }
+        }
+    }
+}

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