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

[maven-surefire] branch 1330 updated (4576847 -> 790f01a)

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

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


 discard 4576847  [SUREFIRE-1330] JUnit 5 surefire-provider code donation
     new 790f01a  [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   (4576847)
            \
             N -- N -- N   refs/heads/1330 (790f01a)

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:
 .../maven/surefire/its/JUnit4VersionsIT.java       |  2 --
 .../apache/maven/surefire/its/JUnitVersion.java    | 20 +----------
 surefire-its/src/test/resources/junit4/pom.xml     | 39 +++++++++-------------
 3 files changed, 17 insertions(+), 44 deletions(-)

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

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

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

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

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

    [SUREFIRE-1330] JUnit 5 surefire-provider code donation
    
    Rename JUnitPlattformProviderInfo to JUnitPlatformProviderInfo
    Bump JUnit versions and remove unnecessary dependency
    
    - JUnit Platform 1.2.0
    - JUnit Jupiter 5.2.0
    
    Copy current sources from junit-platform-surefire-provider
    
    This commit includes enhancements and bug fixes that were applied since
    the initial code donation.
    
    https://issues.apache.org/jira/browse/SUREFIRE-1330
    
    Upgrade ITS dependencies to JUnit 5.2.0 and JUnit Platform 1.2.0
    
    Format and ignore org.junit.platform.commons.* signatures check
    
    Auto-detect JUnit Platform
    
    Prior to this commit the JUnit Platform was not detected automatically.
    Now, when "org.junit.platform:junit-platform-commons" is present in the
    projects dependencies, the JUnitPlatformProvider is instantiated and
    executed.
    
    This commit also enhances the basic JUnit Platform integration test with
    Jupiter-specific features.
    
    Add JUnit Platform integration tests:
    - JUnit Platform 1.0.0 + Jupiter 5.0.0
    - JUnit Platform 1.1.1 + Jupiter 5.1.1
    - JUnit Platform 1.2.0 + Jupiter 5.2.0
    - JUnit Jupiter Engine and Params 5.2.0
    - JUnit Vintage Engine 5.2.0 (with JUnit 4.12)
    - JQwik Engine 0.8.10
    
    Use 'junit-platform-engine' artifact name to identify JUnit Platform
    
    Set since tag to Maven Surefire version 2.22.0
    
    Upgrade to JaCoCo 0.8.1 w/ JDK 10 support
    
    Remove "JUnit 5" test from JUnit4VersionsIT suite
---
 .../plugin/surefire/AbstractSurefireMojo.java      |  71 +-
 pom.xml                                            |   9 +-
 surefire-its/pom.xml                               |  23 +-
 .../maven/surefire/its/JUnit4VersionsIT.java       |  70 +-
 .../org/apache/maven/surefire/its/JUnit5IT.java}   |  50 +-
 .../apache/maven/surefire/its/JUnitPlatformIT.java |  76 +++
 .../apache/maven/surefire/its/JUnitVersion.java}   |  67 +-
 .../test/resources/junit-platform-1.0.0/pom.xml    |  60 ++
 .../JUnitPlatform_1_0_0_Test.java}                 |  42 +-
 .../test/resources/junit-platform-1.1.1/pom.xml    |  60 ++
 .../JUnitPlatform_1_1_1_Test.java}                 |  42 +-
 .../test/resources/junit-platform-1.2.0/pom.xml    |  60 ++
 .../JUnitPlatform_1_2_0_Test.java}                 |  42 +-
 .../pom.xml                                        |  60 +-
 .../junitplatformenginejqwik/BasicJQwikTest.java}  |  44 +-
 .../junit-platform-engine-jupiter/pom.xml          |  67 ++
 .../BasicJupiterTest.java                          |  82 +++
 .../junit-platform-engine-vintage/pom.xml          |  60 ++
 .../BasicVintageTest.java}                         |  12 +-
 surefire-its/src/test/resources/junit4/pom.xml     |  56 +-
 .../junit4/src/test/java/junit4/BasicTest.java     |   4 +-
 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 | 164 +++++
 .../junitplatform/JUnitPlatformProvider.java       | 246 +++++++
 .../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 +++++
 35 files changed, 3262 insertions(+), 360 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..53d5bf9 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
@@ -486,6 +486,15 @@ public abstract class AbstractSurefireMojo
     private String junitArtifactName;
 
     /**
+     * Allows you to specify the name of the JUnit Platform artifact.
+     * If not set, {@code org.junit.platform:junit-platform-engine} will be used.
+     *
+     * @since 2.22.0
+     */
+    @Parameter( property = "junitPlatformArtifactName", defaultValue = "org.junit.platform:junit-platform-engine" )
+    private String junitPlatformArtifactName;
+
+    /**
      * Allows you to specify the name of the TestNG artifact. If not set, {@code org.testng:testng} will be used.
      *
      * @since 2.3.1
@@ -1022,6 +1031,7 @@ public abstract class AbstractSurefireMojo
         Artifact junitDepArtifact = getJunitDepArtifact();
         return new ProviderList( new DynamicProviderInfo( null ),
                               new TestNgProviderInfo( getTestNgArtifact() ),
+                              new JUnitPlatformProviderInfo( getJunitPlatformArtifact() ),
                               new JUnitCoreProviderInfo( getJunitArtifact(), junitDepArtifact ),
                               new JUnit4ProviderInfo( getJunitArtifact(), junitDepArtifact ),
                               new JUnit3ProviderInfo() )
@@ -1540,7 +1550,7 @@ public abstract class AbstractSurefireMojo
         return dependencyResolver.isWithinVersionSpec( artifact, "[4.0,)" );
     }
 
-    static boolean isForkModeNever( String forkMode )
+    private static boolean isForkModeNever( String forkMode )
     {
         return FORK_NEVER.equals( forkMode );
     }
@@ -2033,6 +2043,21 @@ public abstract class AbstractSurefireMojo
         return getProjectArtifactMap().get( "junit:junit-dep" );
     }
 
+
+    private Artifact getJunitPlatformArtifact()
+    {
+        Artifact artifact = getProjectArtifactMap().get( getJunitPlatformArtifactName() );
+        Artifact projectArtifact = project.getArtifact();
+        String projectArtifactName = projectArtifact.getGroupId() + ":" + projectArtifact.getArtifactId();
+
+        if ( artifact == null && projectArtifactName.equals( getJunitPlatformArtifactName() ) )
+        {
+            artifact = projectArtifact;
+        }
+
+        return artifact;
+    }
+
     private ForkStarter createForkStarter( @Nonnull ProviderInfo provider, @Nonnull ForkConfiguration forkConfiguration,
                                            @Nonnull ClassLoaderConfiguration classLoaderConfiguration,
                                            @Nonnull RunOrderParameters runOrderParameters, @Nonnull ConsoleLogger log,
@@ -2873,6 +2898,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 junitArtifact != null;
+        }
+
+        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
     {
@@ -3329,6 +3387,17 @@ public abstract class AbstractSurefireMojo
         this.junitArtifactName = junitArtifactName;
     }
 
+    public String getJunitPlatformArtifactName()
+    {
+        return junitPlatformArtifactName;
+    }
+
+    @SuppressWarnings( "UnusedDeclaration" )
+    public void setJunitPlatformArtifactName( String junitPlatformArtifactName )
+    {
+        this.junitPlatformArtifactName = junitPlatformArtifactName;
+    }
+
     public String getTestNGArtifactName()
     {
         return testNGArtifactName;
diff --git a/pom.xml b/pom.xml
index 5fdefd4..e63baeb 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>
@@ -466,7 +468,7 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.7.9</version>
+          <version>0.8.1</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -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..24d4d63 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,26 @@ 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;
+
 /**
  * Basic suite test using all known versions of JUnit 4.x
  *
@@ -44,44 +61,39 @@ 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 }
         } );
     }
 
     @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/java/org/apache/maven/surefire/its/JUnitPlatformIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
new file mode 100644
index 0000000..db40002
--- /dev/null
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java
@@ -0,0 +1,76 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.junit.Before;
+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 JUnitPlatformIT
+    extends SurefireJUnit4IntegrationTestCase
+{
+    @Before
+    public void setUp()
+    {
+        assumeThat( "java.specification.version: ",
+                    getProperty( "java.specification.version" ), is( greaterThanOrEqualTo( "1.8" ) ) );
+    }
+
+    @Test
+    public void testJupiterEngine()
+    {
+        unpack( "/junit-platform-engine-jupiter" ).executeTest().verifyErrorFree( 5 );
+    }
+
+    @Test
+    public void testVintageEngine()
+    {
+        unpack( "/junit-platform-engine-vintage" ).executeTest().verifyErrorFree( 1 );
+    }
+
+    @Test
+    public void testJQwikEngine()
+    {
+        unpack( "/junit-platform-engine-jqwik" ).executeTest().verifyErrorFree( 1 );
+    }
+
+    @Test
+    public void testJUnitPlatform_1_0_0()
+    {
+        unpack( "/junit-platform-1.0.0" ).executeTest().verifyErrorFree( 1 );
+    }
+
+    @Test
+    public void testJUnitPlatform_1_1_1()
+    {
+        unpack( "/junit-platform-1.1.1" ).executeTest().verifyErrorFree( 1 );
+    }
+
+    @Test
+    public void testJUnitPlatform_1_2_0()
+    {
+        unpack( "/junit-platform-1.2.0" ).executeTest().verifyErrorFree( 1 );
+    }
+}
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/JUnitVersion.java
similarity index 50%
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/JUnitVersion.java
index e9234f2..9549016 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/JUnitVersion.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,44 @@ 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.apache.maven.surefire.its.fixture.SurefireLauncher;
 
-
-public class BasicTest
-{
-
-    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()
+/**
+ * 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" );
+
+    private final String version;
+
+    JUnitVersion( String version )
     {
-        setUpCalled = false;
-        tearDownCalled = true;
-        System.out.println( "Called tearDown" );
+        this.version = version;
     }
 
-    @Test
-    public void testSetUp()
+    public SurefireLauncher configure( SurefireLauncher launcher )
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        return launcher.setJUnitVersion( version );
     }
-  
 
-    @AfterClass
-    public static void oneTimeTearDown()
+    public String toString()
     {
-        
+        return version;
     }
-
 }
diff --git a/surefire-its/src/test/resources/junit-platform-1.0.0/pom.xml b/surefire-its/src/test/resources/junit-platform-1.0.0/pom.xml
new file mode 100644
index 0000000..1b6fd59
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-1.0.0/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform-1.0.0</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform 1.0.0 + Jupiter 5.0.0</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <!--
+        Declare "junit-jupiter-engine" dependency because the
+        Jupiter Engine is needed at test runtime. Artifacts
+        needed for test compilation, like "junit-jupiter-api",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.0.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-1.0.0/src/test/java/junitplatform_1_0_0/JUnitPlatform_1_0_0_Test.java
similarity index 52%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform-1.0.0/src/test/java/junitplatform_1_0_0/JUnitPlatform_1_0_0_Test.java
index e9234f2..760d874 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform-1.0.0/src/test/java/junitplatform_1_0_0/JUnitPlatform_1_0_0_Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatform_1_0_0;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,19 @@ 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.assertEquals;
 
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
 
-public class BasicTest
+class JUnitPlatform_1_0_0_Test
 {
 
-    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()
+    void test(TestInfo info)
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        assertEquals( "test(TestInfo)", info.getDisplayName(), "display name mismatch" );
     }
-  
 
-    @AfterClass
-    public static void oneTimeTearDown()
-    {
-        
-    }
 
 }
diff --git a/surefire-its/src/test/resources/junit-platform-1.1.1/pom.xml b/surefire-its/src/test/resources/junit-platform-1.1.1/pom.xml
new file mode 100644
index 0000000..2555d8b
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-1.1.1/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform-1.1.1</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform 1.1.1 + Jupiter 5.1.1</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <!--
+        Declare "junit-jupiter-engine" dependency because the
+        Jupiter Engine is needed at test runtime. Artifacts
+        needed for test compilation, like "junit-jupiter-api",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.1.1</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-1.1.1/src/test/java/junitplatform_1_1_1/JUnitPlatform_1_1_1_Test.java
similarity index 52%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform-1.1.1/src/test/java/junitplatform_1_1_1/JUnitPlatform_1_1_1_Test.java
index e9234f2..e1bc05e 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform-1.1.1/src/test/java/junitplatform_1_1_1/JUnitPlatform_1_1_1_Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatform_1_1_1;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,19 @@ 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.assertEquals;
 
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
 
-public class BasicTest
+class JUnitPlatform_1_1_1_Test
 {
 
-    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()
+    void test(TestInfo info)
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        assertEquals( "test(TestInfo)", info.getDisplayName(), "display name mismatch" );
     }
-  
 
-    @AfterClass
-    public static void oneTimeTearDown()
-    {
-        
-    }
 
 }
diff --git a/surefire-its/src/test/resources/junit-platform-1.2.0/pom.xml b/surefire-its/src/test/resources/junit-platform-1.2.0/pom.xml
new file mode 100644
index 0000000..8bc5638
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-1.2.0/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform-1.2.0</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform 1.2.0 + Jupiter 5.2.0</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <!--
+        Declare "junit-jupiter-engine" dependency because the
+        Jupiter Engine is needed at test runtime. Artifacts
+        needed for test compilation, like "junit-jupiter-api",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</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-1.2.0/src/test/java/junitplatform_1_2_0/JUnitPlatform_1_2_0_Test.java
similarity index 52%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform-1.2.0/src/test/java/junitplatform_1_2_0/JUnitPlatform_1_2_0_Test.java
index e9234f2..c6ccbfc 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform-1.2.0/src/test/java/junitplatform_1_2_0/JUnitPlatform_1_2_0_Test.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatform_1_2_0;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,19 @@ 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.assertEquals;
 
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
 
-public class BasicTest
+class JUnitPlatform_1_2_0_Test
 {
 
-    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()
+    void test(TestInfo info)
     {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
+        assertEquals( "test(TestInfo)", info.getDisplayName(), "display name mismatch" );
     }
-  
 
-    @AfterClass
-    public static void oneTimeTearDown()
-    {
-        
-    }
 
 }
diff --git a/surefire-its/src/test/resources/junit4/pom.xml b/surefire-its/src/test/resources/junit-platform-engine-jqwik/pom.xml
similarity index 50%
copy from surefire-its/src/test/resources/junit4/pom.xml
copy to surefire-its/src/test/resources/junit-platform-engine-jqwik/pom.xml
index 751a0e6..8e27d56 100644
--- a/surefire-its/src/test/resources/junit4/pom.xml
+++ b/surefire-its/src/test/resources/junit-platform-engine-jqwik/pom.xml
@@ -17,39 +17,43 @@
   ~ 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-engine-jqwik</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform + JQwik</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>
+    <!--
+        Declare "net.jqwik:jqwik" as test dependency.
+        Artifacts needed for test runtime, like "JUnit Platform",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <dependencies>
+        <dependency>
+            <groupId>net.jqwik</groupId>
+            <artifactId>jqwik</artifactId>
+            <version>0.8.10</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-engine-jqwik/src/test/java/junitplatformenginejqwik/BasicJQwikTest.java
similarity index 52%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform-engine-jqwik/src/test/java/junitplatformenginejqwik/BasicJQwikTest.java
index e9234f2..27120ee 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform-engine-jqwik/src/test/java/junitplatformenginejqwik/BasicJQwikTest.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatformenginejqwik;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,47 +19,15 @@ 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 net.jqwik.api.Example;
 
-
-public class BasicTest
+class BasicJQwikTest
 {
 
-    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()
-    {
-        Assert.assertTrue( "setUp was not called", setUpCalled );
-    }
-  
-
-    @AfterClass
-    public static void oneTimeTearDown()
+    @Example
+    boolean exampleFor1Plus3Equals4()
     {
-        
+        return ( 1 + 3 ) == 4;
     }
 
 }
diff --git a/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml b/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml
new file mode 100644
index 0000000..192cc8a
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-engine-jupiter/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform-engine-jupiter</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform + Jupiter</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <junit.jupiter.version>5.2.0</junit.jupiter.version>
+    </properties>
+
+    <!--
+        Declare "junit-jupiter-engine" dependency because the
+        Jupiter Engine is needed at test runtime. Artifacts
+        needed for test compilation, like "junit-jupiter-api",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <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.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${junit.jupiter.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>
+</project>
diff --git a/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/BasicJupiterTest.java b/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/BasicJupiterTest.java
new file mode 100644
index 0000000..d85db86
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-engine-jupiter/src/test/java/junitplatformenginejupiter/BasicJupiterTest.java
@@ -0,0 +1,82 @@
+package junitplatformenginejupiter;
+
+/*
+ * 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.jupiter.api.Assertions.assertEquals;
+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;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class BasicJupiterTest
+{
+
+    private boolean setUpCalled;
+
+    private static boolean tearDownCalled;
+
+    @BeforeEach
+    void setUp()
+    {
+        setUpCalled = true;
+        tearDownCalled = false;
+        System.out.println( "Called setUp" );
+    }
+
+    @AfterEach
+    void tearDown()
+    {
+        setUpCalled = false;
+        tearDownCalled = true;
+        System.out.println( "Called tearDown" );
+    }
+
+    @Test
+    void test(TestInfo info)
+    {
+        assertTrue( setUpCalled, "setUp was not called" );
+        assertEquals( "test(TestInfo)", info.getDisplayName(), "display name mismatch" );
+    }
+
+    @ParameterizedTest(name = "{0} + {1} = {2}")
+    @CsvSource({
+                    "0,    1,   1",
+                    "1,    2,   3",
+                    "49,  51, 100",
+                    "1,  100, 101"
+    })
+    void add(int first, int second, int expectedResult)
+    {
+        assertEquals(expectedResult, first + second, () -> first + " + " + second + " should equal " + expectedResult);
+    }
+
+
+    @AfterAll
+    static void oneTimeTearDown()
+    {
+        assertTrue( tearDownCalled );
+    }
+
+}
diff --git a/surefire-its/src/test/resources/junit-platform-engine-vintage/pom.xml b/surefire-its/src/test/resources/junit-platform-engine-vintage/pom.xml
new file mode 100644
index 0000000..38b788f
--- /dev/null
+++ b/surefire-its/src/test/resources/junit-platform-engine-vintage/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>junit-platform-engine-vintage</artifactId>
+    <version>1.0</version>
+    <name>Test for JUnit 5: Platform + Vintage</name>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <!--
+        Declare "junit-vintage-engine" dependency because the
+        Vintage Engine is needed at test runtime. Artifacts
+        needed for test compilation, like "junit:junit",
+        are pulled-in via transitive dependency resolution.
+    -->
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.vintage</groupId>
+            <artifactId>junit-vintage-engine</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-engine-vintage/src/test/java/junitplatformenginevintage/BasicVintageTest.java
similarity index 91%
copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
copy to surefire-its/src/test/resources/junit-platform-engine-vintage/src/test/java/junitplatformenginevintage/BasicVintageTest.java
index e9234f2..04e9aaf 100644
--- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java
+++ b/surefire-its/src/test/resources/junit-platform-engine-vintage/src/test/java/junitplatformenginevintage/BasicVintageTest.java
@@ -1,4 +1,4 @@
-package junit4;
+package junitplatformenginevintage;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -25,14 +25,12 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-
-public class BasicTest
+public class BasicVintageTest
 {
+    private static boolean tearDownCalled = false;
 
     private boolean setUpCalled = false;
 
-    private static boolean tearDownCalled = false;
-    
     @Before
     public void setUp()
     {
@@ -54,12 +52,12 @@ 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/pom.xml b/surefire-its/src/test/resources/junit4/pom.xml
index 751a0e6..d38e6fc 100644
--- a/surefire-its/src/test/resources/junit4/pom.xml
+++ b/surefire-its/src/test/resources/junit4/pom.xml
@@ -20,36 +20,36 @@
 <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>
+    <properties>
+        <junitVersion>4.12</junitVersion>
+        <maven.compiler.source>1.7</maven.compiler.source>
+        <maven.compiler.target>1.7</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>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>
 
 </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/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..d8c6d63
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/pom.xml
@@ -0,0 +1,164 @@
+<!--
+  ~ 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>
+                            <ignores>
+                                <param>org.junit.platform.commons.*</param>
+                            </ignores>
+                        </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..ded085e
--- /dev/null
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
@@ -0,0 +1,246 @@
+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;
+
+/**
+ * JUnitPlatformProvider.
+ *
+ * @since 2.22.0
+ */
+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;
+
+    public 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..85227f3
--- /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 2.22.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..932c5ed
--- /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 2.22.0
+ */
+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..c054c85
--- /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 2.22.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..ea1df19
--- /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 2.22.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..97f6ffc
--- /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 2.22.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..3e8b533
--- /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 2.22.0
+ */
+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..a4f0883
--- /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 2.22.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
sor@apache.org.