This is an automated email from the ASF dual-hosted git repository. tibordigana pushed a commit to branch 1330 in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit 2c0a9c23f578363a2ac297516eb41c064bd5f6f1 Author: Tibor17 <tibordig...@apache.org> AuthorDate: Fri Apr 13 23:51:33 2018 +0200 [SUREFIRE-1330] JUnit 5 surefire-provider code donation --- .../plugin/surefire/AbstractSurefireMojo.java | 46 +- pom.xml | 7 +- surefire-its/pom.xml | 23 +- .../maven/surefire/its/JUnit4VersionsIT.java | 74 +- .../org/apache/maven/surefire/its/JUnit5IT.java} | 50 +- .../maven/surefire/its/JUnitPlatformIT.java} | 48 +- .../apache/maven/surefire/its/JUnitVersion.java | 80 +++ .../resources/{junit4 => junit-platform}/pom.xml | 55 +- .../src/test/java/junitplatform}/BasicTest.java | 30 +- surefire-its/src/test/resources/junit4/pom.xml | 141 +++- .../junit4/src/test/java/junit4/BasicTest.java | 4 +- .../BasicTest.java => junit5/JUnit5Test.java} | 35 +- surefire-its/src/test/resources/junit5/pom.xml | 77 +++ .../src/test/java/junit5/JUnit4Test.java} | 19 +- .../src/test/java/junit5/JUnit5Test.java} | 36 +- surefire-providers/pom.xml | 1 + surefire-providers/surefire-junit-platform/pom.xml | 161 +++++ .../junitplatform/JUnitPlatformProvider.java | 241 +++++++ .../surefire/junitplatform/RunListenerAdapter.java | 272 ++++++++ .../surefire/junitplatform/TestMethodFilter.java | 60 ++ .../junitplatform/TestPlanScannerFilter.java | 60 ++ ...che.maven.surefire.providerapi.SurefireProvider | 1 + .../junitplatform/JUnitPlatformProviderTests.java | 756 +++++++++++++++++++++ .../junitplatform/RunListenerAdapterTests.java | 580 ++++++++++++++++ .../junitplatform/TestMethodFilterTests.java | 105 +++ .../junitplatform/TestPlanScannerFilterTests.java | 188 +++++ 26 files changed, 2910 insertions(+), 240 deletions(-) diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java index 34f805a..414e084 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java @@ -1024,6 +1024,7 @@ public abstract class AbstractSurefireMojo new TestNgProviderInfo( getTestNgArtifact() ), new JUnitCoreProviderInfo( getJunitArtifact(), junitDepArtifact ), new JUnit4ProviderInfo( getJunitArtifact(), junitDepArtifact ), + new JUnitPlatformProviderInfo( getJunitArtifact() ), new JUnit3ProviderInfo() ) .resolve(); } @@ -1540,7 +1541,17 @@ public abstract class AbstractSurefireMojo return dependencyResolver.isWithinVersionSpec( artifact, "[4.0,)" ); } - static boolean isForkModeNever( String forkMode ) + private boolean isJunitJupiter( Artifact artifact ) + { + return dependencyResolver.isWithinVersionSpec( artifact, "[5.0.0,)" ); + } + + private boolean isJunitVintage( Artifact artifact ) + { + return dependencyResolver.isWithinVersionSpec( artifact, "[4.12.0,5.0.0)" ); + } + + private static boolean isForkModeNever( String forkMode ) { return FORK_NEVER.equals( forkMode ); } @@ -2873,6 +2884,39 @@ public abstract class AbstractSurefireMojo } + final class JUnitPlatformProviderInfo + implements ProviderInfo + { + private final Artifact junitArtifact; + + JUnitPlatformProviderInfo( Artifact junitArtifact ) + { + this.junitArtifact = junitArtifact; + } + + @Nonnull public String getProviderName() + { + return "org.apache.maven.surefire.junitplatform.JUnitPlatformProvider"; + } + + public boolean isApplicable() + { + return isJunitJupiter( junitArtifact ) || isJunitVintage( junitArtifact ); + } + + public void addProviderProperties() throws MojoExecutionException + { + } + + public Classpath getProviderClasspath() + throws ArtifactResolutionException, ArtifactNotFoundException + { + return dependencyResolver.getProviderClasspath( "surefire-junit-platform", + surefireBooterArtifact.getBaseVersion(), + null ); + } + } + final class JUnitCoreProviderInfo implements ProviderInfo { diff --git a/pom.xml b/pom.xml index 5fdefd4..96a75a4 100644 --- a/pom.xml +++ b/pom.xml @@ -97,8 +97,9 @@ <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path> <!-- Override with Jigsaw JRE 9 --> <jdk.home>${java.home}/..</jdk.home> - <maven.compiler.testSource>1.6</maven.compiler.testSource> - <maven.compiler.testTarget>1.6</maven.compiler.testTarget> + <javaVersion>6</javaVersion> + <maven.compiler.testSource>1.${javaVersion}</maven.compiler.testSource> + <maven.compiler.testTarget>1.${javaVersion}</maven.compiler.testTarget> <jvm.args.tests>-server -XX:+UseG1GC -Xms128m -Xmx144m -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:SoftRefLRUPolicyMSPerMB=50 -Djava.awt.headless=true</jvm.args.tests> </properties> @@ -419,6 +420,7 @@ <excludeDependencies> <param>org.codehaus.plexus:plexus-java</param> <param>org.ow2.asm:asm</param> + <param>org.junit.platform:junit-platform-commons</param> </excludeDependencies> <ignores> <param>org.objectweb.asm.*</param> @@ -550,6 +552,7 @@ <excludes> <exclude>org.codehaus.plexus:plexus-java</exclude> <exclude>org.ow2.asm:asm</exclude> + <exclude>org.junit.platform:junit-platform-commons</exclude> </excludes> </enforceBytecodeVersion> </rules> diff --git a/surefire-its/pom.xml b/surefire-its/pom.xml index 88a4870..d4c7df4 100644 --- a/surefire-its/pom.xml +++ b/surefire-its/pom.xml @@ -104,7 +104,9 @@ <forkMode>once</forkMode> <argLine>-server -Xmx64m -XX:+UseG1GC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Djava.awt.headless=true</argLine> <includes> - <include>org/apache/**/*IT*.java</include> + <include>org/apache/**/JUnit4VersionsIT.java</include> + <include>org/apache/**/JUnit5IT.java</include> + <include>org/apache/**/JUnitPlatformIT.java</include> </includes> <!-- Pass current surefire version to the main suite so that it --> <!-- can forward to all integration test projects. SUREFIRE-513 --> @@ -143,25 +145,6 @@ </executions> </plugin> <plugin> - <artifactId>maven-enforcer-plugin</artifactId> - <executions> - <execution> - <id>require-maven-2.1.0</id> - <goals> - <goal>enforce</goal> - </goals> - <configuration> - <rules> - <requireMavenVersion> - <!-- Some plugin features require a recent Maven runtime to work (e.g. SystemPropertiesTest) --> - <version>[2.1.0,)</version> - </requireMavenVersion> - </rules> - </configuration> - </execution> - </executions> - </plugin> - <plugin> <artifactId>maven-jar-plugin</artifactId> <!-- todo dont skip since of failsafe:2.19 internal use if having src/main/java/... --> <configuration> diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java index 8dd8f0c..39c4cc1 100644 --- a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit4VersionsIT.java @@ -19,7 +19,6 @@ package org.apache.maven.surefire.its; * under the License. */ -import java.util.Arrays; import java.util.Collection; import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase; @@ -29,8 +28,28 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; +import static java.util.Arrays.asList; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_10; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_11; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_12; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8_1; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_8_2; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_9; import static org.junit.runners.Parameterized.*; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_0; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_1; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_2; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_3; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_3_1; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_4; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_5; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_6; +import static org.apache.maven.surefire.its.JUnitVersion.JUNIT_4_7; +import static org.apache.maven.surefire.its.JUnitVersion.JUPITER_5_2_0; +import static org.apache.maven.surefire.its.JUnitVersion.VINTAGE_5_2_0; + /** * Basic suite test using all known versions of JUnit 4.x * @@ -44,44 +63,41 @@ public class JUnit4VersionsIT @Parameters( name = "{index}: JUnit {0}" ) public static Collection<Object[]> junitVersions() { - return Arrays.asList( new Object[][] { - { "4.0" }, - { "4.1" }, - { "4.2" }, - { "4.3" }, - { "4.3.1" }, - { "4.4" }, - { "4.5" }, - { "4.6" }, - { "4.7" }, - { "4.8" }, - { "4.8.1" }, - { "4.8.2" }, - { "4.9" }, - { "4.10" }, - { "4.11" }, - { "4.12" } + return asList( new Object[][] { + { JUNIT_4_0 }, + { JUNIT_4_1 }, + { JUNIT_4_2 }, + { JUNIT_4_3 }, + { JUNIT_4_3_1 }, + { JUNIT_4_4 }, + { JUNIT_4_5 }, + { JUNIT_4_6 }, + { JUNIT_4_7 }, + { JUNIT_4_8 }, + { JUNIT_4_8_1 }, + { JUNIT_4_8_2 }, + { JUNIT_4_9 }, + { JUNIT_4_10 }, + { JUNIT_4_11 }, + { JUNIT_4_12 }, + { VINTAGE_5_2_0 }, + { JUPITER_5_2_0 } } ); } @Parameter - public String version; - - private SurefireLauncher unpack() - { - return unpack( "/junit4", version ); - } + public JUnitVersion version; @Test public void testJunit() - throws Exception { - runJUnitTest( version ); + version.configure( unpack() ) + .executeTest() + .verifyErrorFree( 1 ); } - public void runJUnitTest( String version ) - throws Exception + private SurefireLauncher unpack() { - unpack().setJUnitVersion( version ).executeTest().verifyErrorFree( 1 ); + return unpack( "/junit4", version.toString() ); } } diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java similarity index 54% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java index e9234f2..8c5eb3a 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnit5IT.java @@ -1,4 +1,4 @@ -package junit4; +package org.apache.maven.surefire.its; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,47 +19,27 @@ package junit4; * under the License. */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; +import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase; import org.junit.Test; +import static org.apache.maven.surefire.its.fixture.HelperAssertions.assumeJavaVersion; -public class BasicTest +/** + * Basic suite test currently only running against JUnit 5 M2 + * + * @author <a href="mailto:brit...@apache.org">Benedikt Ritter</a> + */ +public class JUnit5IT + extends SurefireJUnit4IntegrationTestCase { - private boolean setUpCalled = false; - - private static boolean tearDownCalled = false; - - @Before - public void setUp() - { - setUpCalled = true; - tearDownCalled = false; - System.out.println( "Called setUp" ); - } - - @After - public void tearDown() - { - setUpCalled = false; - tearDownCalled = true; - System.out.println( "Called tearDown" ); - } - @Test - public void testSetUp() + public void test() { - Assert.assertTrue( "setUp was not called", setUpCalled ); - } - + assumeJavaVersion( 1.8d ); - @AfterClass - public static void oneTimeTearDown() - { - + unpack( "/junit5" ) + .executeTest() + .verifyErrorFree( 2 ); } - } diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java similarity index 53% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java index e9234f2..f82206c 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitPlatformIT.java @@ -1,4 +1,4 @@ -package junit4; +package org.apache.maven.surefire.its; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,47 +19,29 @@ package junit4; * under the License. */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; +import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase; +import org.apache.maven.surefire.its.fixture.SurefireLauncher; import org.junit.Test; +import static java.lang.System.getProperty; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assume.assumeThat; -public class BasicTest +public class JUnitPlatformIT + extends SurefireJUnit4IntegrationTestCase { - - private boolean setUpCalled = false; - - private static boolean tearDownCalled = false; - - @Before - public void setUp() - { - setUpCalled = true; - tearDownCalled = false; - System.out.println( "Called setUp" ); - } - - @After - public void tearDown() + private SurefireLauncher unpack() { - setUpCalled = false; - tearDownCalled = true; - System.out.println( "Called tearDown" ); + return unpack( "/junit-platform" ); } @Test - public void testSetUp() + public void test40() { - Assert.assertTrue( "setUp was not called", setUpCalled ); - } - + assumeThat( "java.specification.version: ", + getProperty( "java.specification.version" ), is( greaterThanOrEqualTo( "1.8" ) ) ); - @AfterClass - public static void oneTimeTearDown() - { - + unpack().executeTest().verifyErrorFree( 1 ); } - } diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java new file mode 100644 index 0000000..4831b21 --- /dev/null +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/JUnitVersion.java @@ -0,0 +1,80 @@ +package org.apache.maven.surefire.its; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.surefire.its.fixture.SurefireLauncher; + +/** + * Enum listing all the JUnit version. + */ +public enum JUnitVersion { + + JUNIT_4_0( "4.0" ), + JUNIT_4_1( "4.1" ), + JUNIT_4_2( "4.2" ), + JUNIT_4_3( "4.3" ), + JUNIT_4_3_1( "4.3.1" ), + JUNIT_4_4( "4.4" ), + JUNIT_4_5( "4.5" ), + JUNIT_4_6( "4.6" ), + JUNIT_4_7( "4.7" ), + JUNIT_4_8( "4.8" ), + JUNIT_4_8_1( "4.8.1" ), + JUNIT_4_8_2( "4.8.2" ), + JUNIT_4_9( "4.9" ), + JUNIT_4_10( "4.10" ), + JUNIT_4_11( "4.11" ), + JUNIT_4_12( "4.12" ), + VINTAGE_5_2_0( "5.2.0" ) + { + @Override + public SurefireLauncher configure( SurefireLauncher launcher ) + { + return super.configure( launcher ) + .activateProfile( "junit5-vintage" ); + } + }, + JUPITER_5_2_0( "5.2.0" ) + { + @Override + public SurefireLauncher configure( SurefireLauncher launcher ) + { + return super.configure( launcher ) + .activateProfile( "junit5-jupiter" ); + } + }; + + private final String version; + + JUnitVersion( String version ) + { + this.version = version; + } + + public SurefireLauncher configure( SurefireLauncher launcher ) + { + return launcher.setJUnitVersion( version ); + } + + public String toString() + { + return version; + } +} diff --git a/surefire-its/src/test/resources/junit4/pom.xml b/surefire-its/src/test/resources/junit-platform/pom.xml similarity index 55% copy from surefire-its/src/test/resources/junit4/pom.xml copy to surefire-its/src/test/resources/junit-platform/pom.xml index 751a0e6..9a4bdeb 100644 --- a/surefire-its/src/test/resources/junit4/pom.xml +++ b/surefire-its/src/test/resources/junit-platform/pom.xml @@ -17,39 +17,38 @@ ~ specific language governing permissions and limitations ~ under the License. --> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> + <modelVersion>4.0.0</modelVersion> - <groupId>org.apache.maven.plugins.surefire</groupId> - <artifactId>junit4</artifactId> - <version>1.0-SNAPSHOT</version> - <name>Test for JUnit 4</name> + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>junit-platform</artifactId> + <version>1.0</version> + <name>Test for JUnit 5 Platform</name> - <properties> - <junitVersion>4.4</junitVersion> - <maven.compiler.source>1.6</maven.compiler.source> - <maven.compiler.target>1.6</maven.compiler.target> - </properties> + <properties> + <maven.compiler.source>1.8</maven.compiler.source> + <maven.compiler.target>1.8</maven.compiler.target> + </properties> - <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>${junit.version}</version> - <scope>test</scope> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire.version}</version> - </plugin> - </plugins> - </build> + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.2.0</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + </plugin> + </plugins> + </build> </project> diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java similarity index 73% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java index e9234f2..2c9d119 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/resources/junit-platform/src/test/java/junitplatform/BasicTest.java @@ -1,4 +1,4 @@ -package junit4; +package junitplatform; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,21 +19,21 @@ package junit4; * under the License. */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class BasicTest { - private boolean setUpCalled = false; + private boolean setUpCalled; - private static boolean tearDownCalled = false; - - @Before + private static boolean tearDownCalled; + + @BeforeEach public void setUp() { setUpCalled = true; @@ -41,7 +41,7 @@ public class BasicTest System.out.println( "Called setUp" ); } - @After + @AfterEach public void tearDown() { setUpCalled = false; @@ -52,14 +52,14 @@ public class BasicTest @Test public void testSetUp() { - Assert.assertTrue( "setUp was not called", setUpCalled ); + assertTrue( setUpCalled, "setUp was not called" ); } - - @AfterClass + + @AfterAll public static void oneTimeTearDown() { - + assertTrue( tearDownCalled ); } } diff --git a/surefire-its/src/test/resources/junit4/pom.xml b/surefire-its/src/test/resources/junit4/pom.xml index 751a0e6..548eed6 100644 --- a/surefire-its/src/test/resources/junit4/pom.xml +++ b/surefire-its/src/test/resources/junit4/pom.xml @@ -20,36 +20,121 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> + <modelVersion>4.0.0</modelVersion> - <groupId>org.apache.maven.plugins.surefire</groupId> - <artifactId>junit4</artifactId> - <version>1.0-SNAPSHOT</version> - <name>Test for JUnit 4</name> + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>junit4</artifactId> + <version>1.0-SNAPSHOT</version> + <name>Test for JUnit 4</name> - <properties> - <junitVersion>4.4</junitVersion> - <maven.compiler.source>1.6</maven.compiler.source> - <maven.compiler.target>1.6</maven.compiler.target> - </properties> + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.2.0</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + </dependencies> - <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>${junit.version}</version> - <scope>test</scope> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire.version}</version> - </plugin> - </plugins> - </build> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>junit4</id> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>junit5-jupiter</id> + <properties> + <maven.compiler.source>1.8</maven.compiler.source> + <maven.compiler.target>1.8</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <junit.jupiter.version>5.2.0</junit.jupiter.version> + <junit.vintage.version>5.2.0</junit.vintage.version> + <junit.platform.version>1.2.0</junit.platform.version> + </properties> + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <dependencies> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-surefire-provider</artifactId> + <version>${junit.platform.version}</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.jupiter.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>junit5-vintage</id> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <junit.platform.version>1.2.0</junit.platform.version> + </properties> + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <dependencies> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-surefire-provider</artifactId> + <version>${junit.platform.version}</version> + </dependency> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <version>${junit.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java index e9234f2..144df74 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java @@ -25,14 +25,12 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; - public class BasicTest { + private static boolean tearDownCalled = false; private boolean setUpCalled = false; - private static boolean tearDownCalled = false; - @Before public void setUp() { diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java similarity index 66% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java index e9234f2..0dd000c 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/resources/junit4/src/test/java/junit5/JUnit5Test.java @@ -1,4 +1,4 @@ -package junit4; +package junit5; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,32 +19,32 @@ package junit4; * under the License. */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class BasicTest +/** + * A test using the JUnit 5 API, which should be executed by JUnit jupiter enigne + */ +public class JUnit5Test { + private static boolean tearDownCalled; - private boolean setUpCalled = false; + private boolean setUpCalled; - private static boolean tearDownCalled = false; - - @Before + @BeforeEach public void setUp() { setUpCalled = true; - tearDownCalled = false; System.out.println( "Called setUp" ); } - @After + @AfterEach public void tearDown() { - setUpCalled = false; tearDownCalled = true; System.out.println( "Called tearDown" ); } @@ -52,14 +52,13 @@ public class BasicTest @Test public void testSetUp() { - Assert.assertTrue( "setUp was not called", setUpCalled ); + assertTrue( setUpCalled, "setUp was not called" ); } - - @AfterClass + @AfterAll public static void oneTimeTearDown() { - + assertTrue( tearDownCalled, "tearDown was not called" ); } } diff --git a/surefire-its/src/test/resources/junit5/pom.xml b/surefire-its/src/test/resources/junit5/pom.xml new file mode 100644 index 0000000..379a5bd --- /dev/null +++ b/surefire-its/src/test/resources/junit5/pom.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>junit5</artifactId> + <version>1.0-SNAPSHOT</version> + <name>Test for JUnit 5</name> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>1.8</java.version> + <junit.jupiter.version>5.2.0</junit.jupiter.version> + <junit.vintage.version>5.2.0</junit.vintage.version> + <junit.platform.version>1.2.0</junit.platform.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.jupiter.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <version>${junit.vintage.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <dependencies> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-surefire-provider</artifactId> + <version>${junit.platform.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + +</project> diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java similarity index 83% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java index e9234f2..0a94b1a 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit4Test.java @@ -1,4 +1,4 @@ -package junit4; +package junit5; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -25,26 +25,26 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; - -public class BasicTest +/** + * A test using the JUnit 4 API, which should be executed by JUnit vintage enigne + */ +public class JUnit4Test { - private boolean setUpCalled = false; + private boolean setUpCalled; + + private static boolean tearDownCalled; - private static boolean tearDownCalled = false; - @Before public void setUp() { setUpCalled = true; - tearDownCalled = false; System.out.println( "Called setUp" ); } @After public void tearDown() { - setUpCalled = false; tearDownCalled = true; System.out.println( "Called tearDown" ); } @@ -54,12 +54,11 @@ public class BasicTest { Assert.assertTrue( "setUp was not called", setUpCalled ); } - @AfterClass public static void oneTimeTearDown() { - + Assert.assertTrue( "tearDown was not called", tearDownCalled ); } } diff --git a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java similarity index 66% copy from surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java copy to surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java index e9234f2..558546d 100644 --- a/surefire-its/src/test/resources/junit4/src/test/java/junit4/BasicTest.java +++ b/surefire-its/src/test/resources/junit5/src/test/java/junit5/JUnit5Test.java @@ -1,4 +1,4 @@ -package junit4; +package junit5; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,32 +19,33 @@ package junit4; * under the License. */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -public class BasicTest +/** + * A test using the JUnit 5 API, which should be executed by JUnit jupiter enigne + */ +public class JUnit5Test { - private boolean setUpCalled = false; + private boolean setUpCalled; + + private static boolean tearDownCalled; - private static boolean tearDownCalled = false; - - @Before + @BeforeEach public void setUp() { setUpCalled = true; - tearDownCalled = false; System.out.println( "Called setUp" ); } - @After + @AfterEach public void tearDown() { - setUpCalled = false; tearDownCalled = true; System.out.println( "Called tearDown" ); } @@ -52,14 +53,13 @@ public class BasicTest @Test public void testSetUp() { - Assert.assertTrue( "setUp was not called", setUpCalled ); + Assertions.assertTrue( setUpCalled, "setUp was not called" ); } - - @AfterClass + @AfterAll public static void oneTimeTearDown() { - + Assertions.assertTrue( tearDownCalled, "tearDown was not called" ); } } diff --git a/surefire-providers/pom.xml b/surefire-providers/pom.xml index 0322bcc..47b0f40 100644 --- a/surefire-providers/pom.xml +++ b/surefire-providers/pom.xml @@ -41,6 +41,7 @@ <module>surefire-junit3</module> <module>surefire-junit4</module> <module>surefire-junit47</module> + <module>surefire-junit-platform</module> <module>surefire-testng-utils</module> <module>surefire-testng</module> </modules> diff --git a/surefire-providers/surefire-junit-platform/pom.xml b/surefire-providers/surefire-junit-platform/pom.xml new file mode 100644 index 0000000..bf4869b --- /dev/null +++ b/surefire-providers/surefire-junit-platform/pom.xml @@ -0,0 +1,161 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-providers</artifactId> + <version>3.0.0-SNAPSHOT</version> + </parent> + + <artifactId>surefire-junit-platform</artifactId> + <name>SureFire JUnit Platform Runner</name> + <description>SureFire JUnit Platform Runner</description> + <properties> + <javaVersion>8</javaVersion> + </properties> + <contributors> + <contributor> + <name>Konstantin Lutovich</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Shintaro Katafuchi</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Sam Brannen</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Stefan Bechtold</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Marc Philipp</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Matthias Merdes</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + <contributor> + <name>Johannes Link</name> + <roles> + <role>Contributed to the original provider implementation</role> + </roles> + </contributor> + </contributors> + + <dependencies> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>common-java5</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> + <version>1.2.0</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.2.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>3.6.0</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>animal-sniffer-maven-plugin</artifactId> + <executions> + <execution> + <id>signature-check</id> + <goals> + <goal>check</goal> + </goals> + <configuration> + <signature combine.self="override"> + <groupId>org.codehaus.mojo.signature</groupId> + <artifactId>java18</artifactId> + <version>1.0</version> + </signature> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <jvm>${java.home}/bin/java</jvm> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <artifactSet> + <includes> + <include>org.apache.maven.surefire:common-java5</include> + </includes> + </artifactSet> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java new file mode 100644 index 0000000..510266e --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java @@ -0,0 +1,241 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toList; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.io.IOException; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.maven.surefire.providerapi.AbstractProvider; +import org.apache.maven.surefire.providerapi.ProviderParameters; +import org.apache.maven.surefire.report.ConsoleOutputCapture; +import org.apache.maven.surefire.report.ConsoleOutputReceiver; +import org.apache.maven.surefire.report.ReporterException; +import org.apache.maven.surefire.report.ReporterFactory; +import org.apache.maven.surefire.report.RunListener; +import org.apache.maven.surefire.suite.RunResult; +import org.apache.maven.surefire.testset.TestListResolver; +import org.apache.maven.surefire.testset.TestSetFailedException; +import org.apache.maven.surefire.util.ScanResult; +import org.apache.maven.surefire.util.TestsToRun; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.Filter; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TagFilter; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; + +public class JUnitPlatformProvider + extends AbstractProvider +{ + + // Parameter names processed to determine which @Tags should be executed. + static final String EXCLUDE_GROUPS = "excludedGroups"; + + static final String EXCLUDE_TAGS = "excludeTags"; + + static final String INCLUDE_GROUPS = "groups"; + + static final String INCLUDE_TAGS = "includeTags"; + + static final String CONFIGURATION_PARAMETERS = "configurationParameters"; + + static final String EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED = "The " + INCLUDE_GROUPS + " and " + INCLUDE_TAGS + + " parameters (or the " + EXCLUDE_GROUPS + " and " + EXCLUDE_TAGS + " parameters) are synonyms - " + + "only one of each is allowed (though neither is required)."; + + private final ProviderParameters parameters; + + private final Launcher launcher; + + final Filter<?>[] filters; + + final Map<String, String> configurationParameters; + + JUnitPlatformProvider( ProviderParameters parameters ) + { + this( parameters, LauncherFactory.create() ); + } + + JUnitPlatformProvider( ProviderParameters parameters, Launcher launcher ) + { + this.parameters = parameters; + this.launcher = launcher; + this.filters = getFilters(); + this.configurationParameters = getConfigurationParameters(); + Logger.getLogger( "org.junit" ).setLevel( Level.WARNING ); + } + + @Override + public Iterable<Class<?>> getSuites() + { + return scanClasspath(); + } + + @Override + public RunResult invoke( Object forkTestSet ) + throws TestSetFailedException, ReporterException + { + if ( forkTestSet instanceof TestsToRun ) + { + return invokeAllTests( (TestsToRun) forkTestSet ); + } + else if ( forkTestSet instanceof Class ) + { + return invokeAllTests( TestsToRun.fromClass( (Class<?>) forkTestSet ) ); + } + else if ( forkTestSet == null ) + { + return invokeAllTests( scanClasspath() ); + } + else + { + throw new IllegalArgumentException( "Unexpected value of forkTestSet: " + forkTestSet ); + } + } + + private TestsToRun scanClasspath() + { + TestPlanScannerFilter filter = new TestPlanScannerFilter( launcher, filters ); + ScanResult scanResult = parameters.getScanResult(); + TestsToRun scannedClasses = scanResult.applyFilter( filter, parameters.getTestClassLoader() ); + return parameters.getRunOrderCalculator().orderTestClasses( scannedClasses ); + } + + private RunResult invokeAllTests( TestsToRun testsToRun ) + { + RunResult runResult; + ReporterFactory reporterFactory = parameters.getReporterFactory(); + try + { + RunListener runListener = reporterFactory.createReporter(); + ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) runListener ); + LauncherDiscoveryRequest discoveryRequest = buildLauncherDiscoveryRequest( testsToRun ); + launcher.execute( discoveryRequest, new RunListenerAdapter( runListener ) ); + } + finally + { + runResult = reporterFactory.close(); + } + return runResult; + } + + private LauncherDiscoveryRequest buildLauncherDiscoveryRequest( TestsToRun testsToRun ) + { + LauncherDiscoveryRequestBuilder builder = + request().filters( filters ).configurationParameters( configurationParameters ); + for ( Class<?> testClass : testsToRun ) + { + builder.selectors( selectClass( testClass ) ); + } + return builder.build(); + } + + private Filter<?>[] getFilters() + { + List<Filter<?>> filters = new ArrayList<>(); + + Optional<List<String>> includes = + getGroupsOrTags( getPropertiesList( INCLUDE_GROUPS ), getPropertiesList( INCLUDE_TAGS ) ); + includes.map( TagFilter::includeTags ).ifPresent( filters::add ); + + Optional<List<String>> excludes = + getGroupsOrTags( getPropertiesList( EXCLUDE_GROUPS ), getPropertiesList( EXCLUDE_TAGS ) ); + excludes.map( TagFilter::excludeTags ).ifPresent( filters::add ); + + TestListResolver testListResolver = parameters.getTestRequest().getTestListResolver(); + if ( !testListResolver.isEmpty() ) + { + filters.add( new TestMethodFilter( testListResolver ) ); + } + + return filters.toArray( new Filter<?>[0] ); + } + + private Map<String, String> getConfigurationParameters() + { + String content = parameters.getProviderProperties().get( CONFIGURATION_PARAMETERS ); + if ( content == null ) + { + return emptyMap(); + } + try ( StringReader reader = new StringReader( content ) ) + { + Map<String, String> result = new HashMap<>(); + Properties props = new Properties(); + props.load( reader ); + props.stringPropertyNames().forEach( key -> result.put( key, props.getProperty( key ) ) ); + return result; + } + catch ( IOException ex ) + { + throw new UncheckedIOException( "Error reading " + CONFIGURATION_PARAMETERS, ex ); + } + } + + private Optional<List<String>> getPropertiesList( String key ) + { + List<String> compoundProperties = null; + String property = parameters.getProviderProperties().get( key ); + if ( StringUtils.isNotBlank( property ) ) + { + compoundProperties = + Arrays.stream( property.split( "[,]+" ) ) + .filter( StringUtils::isNotBlank ) + .map( String::trim ) + .collect( toList() ); + } + return Optional.ofNullable( compoundProperties ); + } + + private Optional<List<String>> getGroupsOrTags( Optional<List<String>> groups, Optional<List<String>> tags ) + { + Optional<List<String>> elements = Optional.empty(); + + Preconditions.condition(!groups.isPresent() || !tags.isPresent(), EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED ); + + if ( groups.isPresent() ) + { + elements = groups; + } + else if ( tags.isPresent() ) + { + elements = tags; + } + + return elements; + } +} diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java new file mode 100644 index 0000000..71a70c3 --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java @@ -0,0 +1,272 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.apache.maven.surefire.report.SimpleReportEntry.ignored; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.maven.surefire.report.PojoStackTraceWriter; +import org.apache.maven.surefire.report.RunListener; +import org.apache.maven.surefire.report.SimpleReportEntry; +import org.apache.maven.surefire.report.StackTraceWriter; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.listeners.LegacyReportingUtils; + +/** + * @since 1.0 + */ +final class RunListenerAdapter + implements TestExecutionListener +{ + + private final RunListener runListener; + + private TestPlan testPlan; + + private Set<TestIdentifier> testSetNodes = ConcurrentHashMap.newKeySet(); + + RunListenerAdapter( RunListener runListener ) + { + this.runListener = runListener; + } + + @Override + public void testPlanExecutionStarted( TestPlan testPlan ) + { + updateTestPlan( testPlan ); + } + + @Override + public void testPlanExecutionFinished( TestPlan testPlan ) + { + updateTestPlan( null ); + } + + @Override + public void executionStarted( TestIdentifier testIdentifier ) + { + if ( testIdentifier.isContainer() + && testIdentifier.getSource().filter( ClassSource.class::isInstance ).isPresent() ) + { + startTestSetIfPossible( testIdentifier ); + } + if ( testIdentifier.isTest() ) + { + ensureTestSetStarted( testIdentifier ); + runListener.testStarting( createReportEntry( testIdentifier ) ); + } + } + + @Override + public void executionSkipped( TestIdentifier testIdentifier, String reason ) + { + ensureTestSetStarted( testIdentifier ); + String source = getLegacyReportingClassName( testIdentifier ); + runListener.testSkipped( ignored( source, getLegacyReportingName( testIdentifier ), reason ) ); + completeTestSetIfNecessary( testIdentifier ); + } + + @Override + public void executionFinished( + TestIdentifier testIdentifier, TestExecutionResult testExecutionResult ) + { + if ( testExecutionResult.getStatus() == ABORTED ) + { + runListener.testAssumptionFailure( createReportEntry( testIdentifier, testExecutionResult ) ); + } + else if ( testExecutionResult.getStatus() == FAILED ) + { + reportFailedTest( testIdentifier, testExecutionResult ); + } + else if ( testIdentifier.isTest() ) + { + runListener.testSucceeded( createReportEntry( testIdentifier ) ); + } + completeTestSetIfNecessary( testIdentifier ); + } + + private void updateTestPlan( TestPlan testPlan ) + { + this.testPlan = testPlan; + testSetNodes.clear(); + } + + private void ensureTestSetStarted( TestIdentifier testIdentifier ) + { + if ( isTestSetStarted( testIdentifier ) ) + { + return; + } + if ( testIdentifier.isTest() ) + { + startTestSet( testPlan.getParent( testIdentifier ).orElse( testIdentifier ) ); + } + else + { + startTestSet( testIdentifier ); + } + } + + private boolean isTestSetStarted( TestIdentifier testIdentifier ) + { + return testSetNodes.contains( testIdentifier ) + || testPlan.getParent( testIdentifier ).map( this::isTestSetStarted ).orElse( false ); + } + + private void startTestSetIfPossible( TestIdentifier testIdentifier ) + { + if ( !isTestSetStarted( testIdentifier ) ) + { + startTestSet( testIdentifier ); + } + } + + private void completeTestSetIfNecessary( TestIdentifier testIdentifier ) + { + if ( testSetNodes.contains( testIdentifier ) ) + { + completeTestSet( testIdentifier ); + } + } + + private void startTestSet( TestIdentifier testIdentifier ) + { + runListener.testSetStarting( createTestSetReportEntry( testIdentifier ) ); + testSetNodes.add( testIdentifier ); + } + + private void completeTestSet( TestIdentifier testIdentifier ) + { + runListener.testSetCompleted( createTestSetReportEntry( testIdentifier ) ); + testSetNodes.remove( testIdentifier ); + } + + private void reportFailedTest( + TestIdentifier testIdentifier, TestExecutionResult testExecutionResult ) + { + SimpleReportEntry reportEntry = createReportEntry( testIdentifier, testExecutionResult ); + if ( testExecutionResult.getThrowable().filter( AssertionError.class::isInstance ).isPresent() ) + { + runListener.testFailed( reportEntry ); + } + else + { + runListener.testError( reportEntry ); + } + } + + private SimpleReportEntry createTestSetReportEntry( TestIdentifier testIdentifier ) + { + return new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), testIdentifier.getLegacyReportingName() ); + } + + private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier ) + { + return createReportEntry( testIdentifier, (StackTraceWriter) null ); + } + + private SimpleReportEntry createReportEntry( + TestIdentifier testIdentifier, TestExecutionResult testExecutionResult ) + { + return createReportEntry( + testIdentifier, getStackTraceWriter( testIdentifier, testExecutionResult ) ); + } + + private SimpleReportEntry createReportEntry( + TestIdentifier testIdentifier, StackTraceWriter stackTraceWriter ) + { + String source = getLegacyReportingClassName( testIdentifier ); + String name = getLegacyReportingName( testIdentifier ); + + return SimpleReportEntry.withException( source, name, stackTraceWriter ); + } + + private String getLegacyReportingName( TestIdentifier testIdentifier ) + { + // Surefire cuts off the name at the first '(' character. Thus, we have to pick a different + // character to represent parentheses. "()" are removed entirely to maximize compatibility with + // existing reporting tools because in the old days test methods used to not have parameters. + return testIdentifier + .getLegacyReportingName() + .replace( "()", "" ) + .replace( '(', '{' ) + .replace( ')', '}' ); + } + + private String getLegacyReportingClassName( TestIdentifier testIdentifier ) + { + return LegacyReportingUtils.getClassName( testPlan, testIdentifier ); + } + + private StackTraceWriter getStackTraceWriter( + TestIdentifier testIdentifier, TestExecutionResult testExecutionResult ) + { + Optional<Throwable> throwable = testExecutionResult.getThrowable(); + if ( testExecutionResult.getStatus() == FAILED ) + { + // Failed tests must have a StackTraceWriter, otherwise Surefire will fail + return getStackTraceWriter( testIdentifier, throwable.orElse( null ) ); + } + return throwable.map( t -> getStackTraceWriter( testIdentifier, t ) ).orElse( null ); + } + + private StackTraceWriter getStackTraceWriter( TestIdentifier testIdentifier, Throwable throwable ) + { + String className = getClassName( testIdentifier ); + String methodName = getMethodName( testIdentifier ).orElse( "" ); + return new PojoStackTraceWriter( className, methodName, throwable ); + } + + private String getClassName( TestIdentifier testIdentifier ) + { + TestSource testSource = testIdentifier.getSource().orElse( null ); + if ( testSource instanceof ClassSource ) + { + return ( (ClassSource) testSource ).getJavaClass().getName(); + } + if ( testSource instanceof MethodSource ) + { + return ( (MethodSource) testSource ).getClassName(); + } + return testPlan.getParent( testIdentifier ).map( this::getClassName ).orElse( "" ); + } + + private Optional<String> getMethodName( TestIdentifier testIdentifier ) + { + TestSource testSource = testIdentifier.getSource().orElse( null ); + if ( testSource instanceof MethodSource ) + { + return Optional.of( ( (MethodSource) testSource ).getMethodName() ); + } + return Optional.empty(); + } +} diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java new file mode 100644 index 0000000..45e32db --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestMethodFilter.java @@ -0,0 +1,60 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.surefire.testset.TestListResolver; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.PostDiscoveryFilter; + +/** + * @since 1.0.3 + */ +class TestMethodFilter + implements PostDiscoveryFilter +{ + + private final TestListResolver testListResolver; + + TestMethodFilter( TestListResolver testListResolver ) + { + this.testListResolver = testListResolver; + } + + @Override + public FilterResult apply( TestDescriptor descriptor ) + { + boolean shouldRun = descriptor.getSource() + .filter( MethodSource.class::isInstance ) + .map( MethodSource.class::cast ) + .map( this::shouldRun ) + .orElse( true ); + + return FilterResult.includedIf( shouldRun ); + } + + private boolean shouldRun( MethodSource source ) + { + String testClass = TestListResolver.toClassFileName( source.getClassName() ); + String testMethod = source.getMethodName(); + return this.testListResolver.shouldRun( testClass, testMethod ); + } +} diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java new file mode 100644 index 0000000..4b488c1 --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilter.java @@ -0,0 +1,60 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.apache.maven.surefire.util.ScannerFilter; +import org.junit.platform.engine.Filter; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +final class TestPlanScannerFilter + implements ScannerFilter +{ + + private final Launcher launcher; + + private final Filter<?>[] includeAndExcludeFilters; + + TestPlanScannerFilter( Launcher launcher, Filter<?>[] includeAndExcludeFilters ) + { + this.launcher = launcher; + this.includeAndExcludeFilters = includeAndExcludeFilters; + } + + @Override + @SuppressWarnings( "rawtypes" ) + public boolean accept( Class testClass ) + { + LauncherDiscoveryRequest discoveryRequest = request() + .selectors( selectClass( testClass ) ) + .filters( includeAndExcludeFilters ).build(); + + TestPlan testPlan = launcher.discover( discoveryRequest ); + + return testPlan.containsTests(); + } +} diff --git a/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider b/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider new file mode 100644 index 0000000..dbe73cf --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/main/resources/META-INF/services/org.apache.maven.surefire.providerapi.SurefireProvider @@ -0,0 +1 @@ +org.apache.maven.surefire.junitplatform.JUnitPlatformProvider diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java new file mode 100644 index 0000000..e0db97e --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTests.java @@ -0,0 +1,756 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.AdditionalMatchers.gt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.surefire.providerapi.ProviderParameters; +import org.apache.maven.surefire.report.ConsoleOutputReceiver; +import org.apache.maven.surefire.report.ReportEntry; +import org.apache.maven.surefire.report.ReporterFactory; +import org.apache.maven.surefire.report.RunListener; +import org.apache.maven.surefire.report.SimpleReportEntry; +import org.apache.maven.surefire.testset.TestListResolver; +import org.apache.maven.surefire.testset.TestRequest; +import org.apache.maven.surefire.testset.TestSetFailedException; +import org.apache.maven.surefire.util.RunOrderCalculator; +import org.apache.maven.surefire.util.ScanResult; +import org.apache.maven.surefire.util.TestsToRun; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.PreconditionViolationException; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.launcher.listeners.TestExecutionSummary.Failure; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; + +/** + * Unit tests for {@link JUnitPlatformProvider}. + * + * @since 1.0 + */ +class JUnitPlatformProviderTests +{ + + @Test + void getSuitesReturnsScannedClasses() + throws Exception + { + ProviderParameters providerParameters = + providerParametersMock( TestClass1.class, TestClass2.class ); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertThat( provider.getSuites() ).containsOnly( TestClass1.class, TestClass2.class ); + } + + @Test + void invokeThrowsForWrongForkTestSet() + throws Exception + { + ProviderParameters providerParameters = providerParametersMock( Integer.class ); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertThrows( + IllegalArgumentException.class, () -> invokeProvider( provider, "wrong forkTestSet" ) ); + } + + @Test + void allGivenTestsToRunAreInvoked() + throws Exception + { + Launcher launcher = LauncherFactory.create(); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher ); + + TestPlanSummaryListener executionListener = new TestPlanSummaryListener(); + launcher.registerTestExecutionListeners( executionListener ); + + TestsToRun testsToRun = newTestsToRun( TestClass1.class, TestClass2.class ); + invokeProvider( provider, testsToRun ); + + assertThat( executionListener.summaries ).hasSize( 1 ); + TestExecutionSummary summary = executionListener.summaries.get( 0 ); + assertEquals( TestClass1.TESTS_FOUND + TestClass2.TESTS_FOUND, summary.getTestsFoundCount() ); + assertEquals( + TestClass1.TESTS_STARTED + TestClass2.TESTS_STARTED, summary.getTestsStartedCount() ); + assertEquals( + TestClass1.TESTS_SKIPPED + TestClass2.TESTS_SKIPPED, summary.getTestsSkippedCount() ); + assertEquals( + TestClass1.TESTS_SUCCEEDED + TestClass2.TESTS_SUCCEEDED, summary.getTestsSucceededCount() ); + assertEquals( + TestClass1.TESTS_ABORTED + TestClass2.TESTS_ABORTED, summary.getTestsAbortedCount() ); + assertEquals( TestClass1.TESTS_FAILED + TestClass2.TESTS_FAILED, summary.getTestsFailedCount() ); + } + + @Test + void singleTestClassIsInvoked() + throws Exception + { + Launcher launcher = LauncherFactory.create(); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParametersMock(), launcher ); + + TestPlanSummaryListener executionListener = new TestPlanSummaryListener(); + launcher.registerTestExecutionListeners( executionListener ); + + invokeProvider( provider, TestClass1.class ); + + assertThat( executionListener.summaries ).hasSize( 1 ); + TestExecutionSummary summary = executionListener.summaries.get( 0 ); + assertEquals( TestClass1.TESTS_FOUND, summary.getTestsFoundCount() ); + assertEquals( TestClass1.TESTS_STARTED, summary.getTestsStartedCount() ); + assertEquals( TestClass1.TESTS_SKIPPED, summary.getTestsSkippedCount() ); + assertEquals( TestClass1.TESTS_SUCCEEDED, summary.getTestsSucceededCount() ); + assertEquals( TestClass1.TESTS_ABORTED, summary.getTestsAbortedCount() ); + assertEquals( TestClass1.TESTS_FAILED, summary.getTestsFailedCount() ); + } + + @Test + void allDiscoveredTestsAreInvokedForNullArgument() + throws Exception + { + RunListener runListener = runListenerMock(); + ProviderParameters providerParameters = + providerParametersMock( runListener, TestClass1.class, TestClass2.class ); + Launcher launcher = LauncherFactory.create(); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters, launcher ); + + TestPlanSummaryListener executionListener = new TestPlanSummaryListener(); + launcher.registerTestExecutionListeners( executionListener ); + + invokeProvider( provider, null ); + + InOrder inOrder = inOrder( runListener ); + inOrder + .verify( runListener ) + .testSetStarting( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + TestClass1.class.getName() ) ); + inOrder + .verify( runListener ) + .testSetCompleted( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + TestClass1.class.getName() ) ); + inOrder + .verify( runListener ) + .testSetStarting( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + TestClass2.class.getName() ) ); + inOrder + .verify( runListener ) + .testSetCompleted( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + TestClass2.class.getName() ) ); + + assertThat( executionListener.summaries ).hasSize( 1 ); + TestExecutionSummary summary = executionListener.summaries.get( 0 ); + assertEquals( TestClass1.TESTS_FOUND + TestClass2.TESTS_FOUND, summary.getTestsFoundCount() ); + assertEquals( + TestClass1.TESTS_STARTED + TestClass2.TESTS_STARTED, summary.getTestsStartedCount() ); + assertEquals( + TestClass1.TESTS_SKIPPED + TestClass2.TESTS_SKIPPED, summary.getTestsSkippedCount() ); + assertEquals( + TestClass1.TESTS_SUCCEEDED + TestClass2.TESTS_SUCCEEDED, summary.getTestsSucceededCount() ); + assertEquals( + TestClass1.TESTS_ABORTED + TestClass2.TESTS_ABORTED, summary.getTestsAbortedCount() ); + assertEquals( TestClass1.TESTS_FAILED + TestClass2.TESTS_FAILED, summary.getTestsFailedCount() ); + } + + @Test + void outputIsCaptured() + throws Exception + { + Launcher launcher = LauncherFactory.create(); + RunListener runListener = runListenerMock(); + JUnitPlatformProvider provider = + new JUnitPlatformProvider( providerParametersMock( runListener ), launcher ); + + invokeProvider( provider, VerboseTestClass.class ); + + ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass( byte[].class ); + // @formatter:off + verify( (ConsoleOutputReceiver) runListener ) + .writeTestOutput( captor.capture(), eq( 0 ), gt( 6 ), eq( true ) ); + verify( (ConsoleOutputReceiver) runListener ) + .writeTestOutput( captor.capture(), eq( 0 ), gt( 6 ), eq( false ) ); + assertThat( captor.getAllValues() ) + .extracting( bytes -> new String( bytes, 0, 6 ) ) + .containsExactly( "stdout", "stderr" ); + // @formatter:on + } + + @Test + void bothGroupsAndIncludeTagsThrowsException() + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "groupOne, groupTwo" ); + properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne, tagTwo" ); + verifyPreconditionViolationException( properties ); + } + + @Test + void bothExcludedGroupsAndExcludeTagsThrowsException() + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.EXCLUDE_GROUPS, "groupOne, groupTwo" ); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" ); + verifyPreconditionViolationException( properties ); + } + + @Test + void onlyGroupsIsDeclared() + throws Exception + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "groupOne, groupTwo" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 1, provider.filters.length ); + } + + @Test + void onlyExcludeTagsIsDeclared() + throws Exception + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagOne, tagTwo" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 1, provider.filters.length ); + } + + @Test + void noFiltersAreCreatedIfTagsAreEmpty() + throws Exception + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "" ); + properties.put( JUnitPlatformProvider.INCLUDE_GROUPS, "" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + assertEquals( 0, provider.filters.length ); + } + + @Test + void filtersWithEmptyTagsAreNotRegistered() + throws Exception + { + Map<String, String> properties = new HashMap<>(); + + // Here only tagOne is registered as a valid tag and other tags are ignored as they are empty + properties.put( JUnitPlatformProvider.EXCLUDE_GROUPS, "tagOne," ); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + assertEquals( 1, provider.filters.length ); + } + + @Test + void bothIncludeAndExcludeAreAllowed() + throws Exception + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne, tagTwo" ); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagThree, tagFour" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 2, provider.filters.length ); + } + + @Test + void tagExpressionsAreSupportedForIncludeTagsContainingVerticalBar() + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne | tagTwo" ); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagThree | tagFour" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 2, provider.filters.length ); + } + + @Test + void tagExpressionsAreSupportedForIncludeTagsContainingAmpersand() + { + Map<String, String> properties = new HashMap<>(); + properties.put( JUnitPlatformProvider.INCLUDE_TAGS, "tagOne & !tagTwo" ); + properties.put( JUnitPlatformProvider.EXCLUDE_TAGS, "tagThree & !tagFour" ); + + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 2, provider.filters.length ); + } + + @Test + void noFiltersAreCreatedIfNoPropertiesAreDeclared() + throws Exception + { + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 0, provider.filters.length ); + } + + @Test + void defaultConfigurationParametersAreEmpty() + { + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( emptyMap() ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertTrue( provider.configurationParameters.isEmpty() ); + } + + @Test + void parsesConfigurationParameters() + { + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ) + .thenReturn( // + singletonMap( + JUnitPlatformProvider.CONFIGURATION_PARAMETERS, + "foo = true\nbar 42\rbaz: *\r\nqux: EOF" ) ); + + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters ); + + assertEquals( 4, provider.configurationParameters.size() ); + assertEquals( "true", provider.configurationParameters.get( "foo" ) ); + assertEquals( "42", provider.configurationParameters.get( "bar" ) ); + assertEquals( "*", provider.configurationParameters.get( "baz" ) ); + assertEquals( "EOF", provider.configurationParameters.get( "qux" ) ); + } + + @Test + void executesSingleTestIncludedByName() + throws Exception + { + // following is equivalent of adding '-Dtest=TestClass3#prefix1Suffix1' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "*TestClass3#prefix1Suffix1"; + + testExecutionOfMatchingTestMethods( TestClass3.class, pattern, "prefix1Suffix1()" ); + } + + @Test + void executesMultipleTestsIncludedByName() + throws Exception + { + // following is equivalent of adding '-Dtest=TestClass3#prefix1Suffix1+prefix2Suffix1' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "*TestClass3#prefix1Suffix1+prefix2Suffix1"; + + testExecutionOfMatchingTestMethods( + TestClass3.class, pattern, "prefix1Suffix1()", "prefix2Suffix1()" ); + } + + @Test + void executesMultipleTestsIncludedByNamePattern() + throws Exception + { + // following is equivalent of adding '-Dtest=TestClass3#prefix1*' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "*TestClass3#prefix1*"; + + testExecutionOfMatchingTestMethods( + TestClass3.class, pattern, "prefix1Suffix1()", "prefix1Suffix2()" ); + } + + @Test + void executesMultipleTestsIncludedByNamePatternWithQuestionMark() + throws Exception + { + // following is equivalent of adding '-Dtest=TestClass3#prefix?Suffix2' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "*TestClass3#prefix?Suffix2"; + + testExecutionOfMatchingTestMethods( + TestClass3.class, pattern, "prefix1Suffix2()", "prefix2Suffix2()" ); + } + + @Test + void doesNotExecuteTestsExcludedByName() + throws Exception + { + // following is equivalent of adding '-Dtest=!TestClass3#prefix1Suffix2' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "!*TestClass3#prefix1Suffix2"; + + testExecutionOfMatchingTestMethods( + TestClass3.class, pattern, "prefix1Suffix1()", "prefix2Suffix1()", "prefix2Suffix2()" ); + } + + @Test + void doesNotExecuteTestsExcludedByNamePattern() + throws Exception + { + // following is equivalent of adding '-Dtest=!TestClass3#prefix2*' + // '*' needed because it's a nested class and thus has name prefixed with '$' + String pattern = "!*TestClass3#prefix2*"; + + testExecutionOfMatchingTestMethods( + TestClass3.class, pattern, "prefix1Suffix1()", "prefix1Suffix2()" ); + } + + void testExecutionOfMatchingTestMethods( + Class<?> testClass, String pattern, String... expectedTestNames ) + throws Exception + { + TestListResolver testListResolver = new TestListResolver( pattern ); + ProviderParameters providerParameters = providerParametersMock( testListResolver, testClass ); + Launcher launcher = LauncherFactory.create(); + JUnitPlatformProvider provider = new JUnitPlatformProvider( providerParameters, launcher ); + + TestPlanSummaryListener executionListener = new TestPlanSummaryListener(); + launcher.registerTestExecutionListeners( executionListener ); + + invokeProvider( provider, null ); + + assertEquals( 1, executionListener.summaries.size() ); + TestExecutionSummary summary = executionListener.summaries.get( 0 ); + int expectedCount = expectedTestNames.length; + assertEquals( expectedCount, summary.getTestsFoundCount() ); + assertEquals( expectedCount, summary.getTestsFailedCount() ); + assertEquals( expectedCount, summary.getFailures().size() ); + + assertThat( failedTestDisplayNames( summary ) ).contains( expectedTestNames ); + } + + private void verifyPreconditionViolationException( Map<String, String> properties ) + { + ProviderParameters providerParameters = providerParametersMock( TestClass1.class ); + when( providerParameters.getProviderProperties() ).thenReturn( properties ); + + Throwable throwable = + assertThrows( + PreconditionViolationException.class, + () -> new JUnitPlatformProvider( providerParameters ) ); + + assertEquals( JUnitPlatformProvider.EXCEPTION_MESSAGE_BOTH_NOT_ALLOWED, throwable.getMessage() ); + } + + private static ProviderParameters providerParametersMock( Class<?>... testClasses ) + { + return providerParametersMock( runListenerMock(), testClasses ); + } + + private static ProviderParameters providerParametersMock( + RunListener runListener, Class<?>... testClasses ) + { + TestListResolver testListResolver = new TestListResolver( "" ); + return providerParametersMock( runListener, testListResolver, testClasses ); + } + + private static ProviderParameters providerParametersMock( + TestListResolver testListResolver, Class<?>... testClasses ) + { + return providerParametersMock( runListenerMock(), testListResolver, testClasses ); + } + + private static ProviderParameters providerParametersMock( + RunListener runListener, TestListResolver testListResolver, Class<?>... testClasses ) + { + TestsToRun testsToRun = newTestsToRun( testClasses ); + + ScanResult scanResult = mock( ScanResult.class ); + when( scanResult.applyFilter( any(), any() ) ).thenReturn( testsToRun ); + + RunOrderCalculator runOrderCalculator = mock( RunOrderCalculator.class ); + when( runOrderCalculator.orderTestClasses( any() ) ).thenReturn( testsToRun ); + + ReporterFactory reporterFactory = mock( ReporterFactory.class ); + when( reporterFactory.createReporter() ).thenReturn( runListener ); + + TestRequest testRequest = mock( TestRequest.class ); + when( testRequest.getTestListResolver() ).thenReturn( testListResolver ); + + ProviderParameters providerParameters = mock( ProviderParameters.class ); + when( providerParameters.getScanResult() ).thenReturn( scanResult ); + when( providerParameters.getRunOrderCalculator() ).thenReturn( runOrderCalculator ); + when( providerParameters.getReporterFactory() ).thenReturn( reporterFactory ); + when( providerParameters.getTestRequest() ).thenReturn( testRequest ); + + return providerParameters; + } + + private static RunListener runListenerMock() + { + return mock( RunListener.class, withSettings().extraInterfaces( ConsoleOutputReceiver.class ) ); + } + + private static Set<String> failedTestDisplayNames( TestExecutionSummary summary ) + { + // @formatter:off + return summary + .getFailures() + .stream() + .map( Failure::getTestIdentifier ) + .map( TestIdentifier::getDisplayName ) + .collect( toSet() ); + // @formatter:on + } + + private static TestsToRun newTestsToRun( Class<?>... testClasses ) + { + List<Class<?>> classesList = Arrays.asList( testClasses ); + return new TestsToRun( new LinkedHashSet<>( classesList ) ); + } + + private class TestPlanSummaryListener + extends SummaryGeneratingListener + { + + final List<TestExecutionSummary> summaries = new ArrayList<>(); + + @Override + public void testPlanExecutionFinished( TestPlan testPlan ) + { + super.testPlanExecutionFinished( testPlan ); + summaries.add( getSummary() ); + } + } + + /** + * Invokes the provider, then restores system out and system error. + * + * @see <a href="https://github.com/junit-team/junit5/issues/986">#986</a> + */ + private void invokeProvider( JUnitPlatformProvider provider, Object forkTestSet ) + throws TestSetFailedException, InvocationTargetException + { + PrintStream systemOut = System.out; + PrintStream systemErr = System.err; + try + { + provider.invoke( forkTestSet ); + } + finally + { + System.setOut( systemOut ); + System.setErr( systemErr ); + } + } + + static class TestClass1 + { + + static final int TESTS_FOUND = 4; + + static final int TESTS_STARTED = 3; + + static final int TESTS_SKIPPED = 1; + + static final int TESTS_SUCCEEDED = 2; + + static final int TESTS_ABORTED = 0; + + static final int TESTS_FAILED = 1; + + @Test + void test1() + { + } + + @Test + void test2() + { + } + + @Disabled + @Test + void test3() + { + } + + @Test + void test4() + { + throw new RuntimeException(); + } + } + + static class TestClass2 + { + + static final int TESTS_FOUND = 3; + + static final int TESTS_STARTED = 3; + + static final int TESTS_SKIPPED = 0; + + static final int TESTS_SUCCEEDED = 1; + + static final int TESTS_ABORTED = 1; + + static final int TESTS_FAILED = 1; + + @Test + void test1() + { + } + + @Test + void test2() + { + throw new RuntimeException(); + } + + @Test + void test3() + { + assumeTrue( false ); + } + } + + static class VerboseTestClass + { + @Test + void test() + { + System.out.println( "stdout" ); + System.err.println( "stderr" ); + } + } + + @Test + void usesClassNamesForXmlReport() + throws TestSetFailedException, InvocationTargetException + { + String[] classNames = { Sub1Tests.class.getName(), Sub2Tests.class.getName() }; + ProviderParameters providerParameters = + providerParametersMock( Sub1Tests.class, Sub2Tests.class ); + + JUnitPlatformProvider jUnitPlatformProvider = new JUnitPlatformProvider( providerParameters ); + TestsToRun testsToRun = newTestsToRun( Sub1Tests.class, Sub2Tests.class ); + + invokeProvider( jUnitPlatformProvider, testsToRun ); + RunListener reporter = providerParameters.getReporterFactory().createReporter(); + + ArgumentCaptor<ReportEntry> reportEntryArgumentCaptor = + ArgumentCaptor.forClass( ReportEntry.class ); + verify( reporter, times( 2 ) ).testSucceeded( reportEntryArgumentCaptor.capture() ); + + List<ReportEntry> allValues = reportEntryArgumentCaptor.getAllValues(); + assertThat( allValues ).extracting( ReportEntry::getSourceName ).containsExactly( classNames ); + } + + static class AbstractTestClass + { + @Test + void test() + { + } + } + + static class Sub1Tests + extends AbstractTestClass + { + } + + static class Sub2Tests + extends AbstractTestClass + { + } + + static class TestClass3 + { + @Test + void prefix1Suffix1() + { + throw new RuntimeException(); + } + + @Test + void prefix2Suffix1() + { + throw new RuntimeException(); + } + + @Test + void prefix1Suffix2() + { + throw new RuntimeException(); + } + + @Test + void prefix2Suffix2() + { + throw new RuntimeException(); + } + } +} diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java new file mode 100644 index 0000000..d3ec7a4 --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/RunListenerAdapterTests.java @@ -0,0 +1,580 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Optional; + +import org.apache.maven.surefire.report.ReportEntry; +import org.apache.maven.surefire.report.RunListener; +import org.apache.maven.surefire.report.SimpleReportEntry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestDescriptor.Type; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; + +/** + * Unit tests for {@link RunListenerAdapter}. + * + * @since 1.0 + */ +class RunListenerAdapterTests +{ + + private RunListener listener; + + private RunListenerAdapter adapter; + + @BeforeEach + void setUp() + { + listener = mock( RunListener.class ); + adapter = new RunListenerAdapter( listener ); + adapter.testPlanExecutionStarted( TestPlan.from( emptyList() ) ); + } + + @Test + void notifiedWithCorrectNamesWhenMethodExecutionStarted() + throws Exception + { + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + + TestPlan testPlan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) ); + adapter.testPlanExecutionStarted( testPlan ); + + TestIdentifier methodIdentifier = + identifiersAsParentOnTestPlan( testPlan, newClassDescriptor(), newMethodDescriptor() ); + + adapter.executionStarted( methodIdentifier ); + verify( listener ).testStarting( entryCaptor.capture() ); + + ReportEntry entry = entryCaptor.getValue(); + assertEquals( MY_TEST_METHOD_NAME, entry.getName() ); + assertEquals( MyTestClass.class.getName(), entry.getSourceName() ); + assertNull( entry.getStackTraceWriter() ); + } + + @Test + void notifiedWithCompatibleNameForMethodWithArguments() + throws Exception + { + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + + TestPlan testPlan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) ); + adapter.testPlanExecutionStarted( testPlan ); + + TestIdentifier methodIdentifier = + identifiersAsParentOnTestPlan( + testPlan, newClassDescriptor(), newMethodDescriptor( String.class ) ); + + adapter.executionStarted( methodIdentifier ); + verify( listener ).testStarting( entryCaptor.capture() ); + + ReportEntry entry = entryCaptor.getValue(); + assertEquals( MY_TEST_METHOD_NAME + "{String}", entry.getName() ); + assertEquals( MyTestClass.class.getName(), entry.getSourceName() ); + assertNull( entry.getStackTraceWriter() ); + } + + @Test + void notifiedEagerlyForTestSetWhenClassExecutionStarted() + throws Exception + { + EngineDescriptor engine = newEngineDescriptor(); + TestDescriptor parent = newClassDescriptor(); + engine.addChild( parent ); + TestDescriptor child = newMethodDescriptor(); + parent.addChild( child ); + TestPlan plan = TestPlan.from( Collections.singletonList( engine ) ); + + adapter.testPlanExecutionStarted( plan ); + adapter.executionStarted( TestIdentifier.from( engine ) ); + adapter.executionStarted( TestIdentifier.from( parent ) ); + verify( listener ) + .testSetStarting( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + MyTestClass.class.getName() ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionStarted( TestIdentifier.from( child ) ); + verify( listener ) + .testStarting( new SimpleReportEntry( MyTestClass.class.getName(), MY_TEST_METHOD_NAME ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( child ), successful() ); + verify( listener ) + .testSucceeded( new SimpleReportEntry( MyTestClass.class.getName(), MY_TEST_METHOD_NAME ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( parent ), successful() ); + verify( listener ) + .testSetCompleted( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + MyTestClass.class.getName() ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( engine ), successful() ); + verifyNoMoreInteractions( listener ); + } + + @Test + void notifiedLazilyForTestSetWhenFirstTestWithoutClassDescriptorParentStarted() + { + EngineDescriptor engine = newEngineDescriptor(); + TestDescriptor parent = + newTestDescriptor( + engine.getUniqueId().append( "container", "noClass" ), "parent", + Type.CONTAINER ); + engine.addChild( parent ); + TestDescriptor child1 = + newTestDescriptor( parent.getUniqueId().append( "test", "child1" ), "child1", Type.TEST ); + parent.addChild( child1 ); + TestDescriptor child2 = + newTestDescriptor( parent.getUniqueId().append( "test", "child2" ), "child2", Type.TEST ); + parent.addChild( child2 ); + TestPlan plan = TestPlan.from( Collections.singletonList( engine ) ); + + adapter.testPlanExecutionStarted( plan ); + adapter.executionStarted( TestIdentifier.from( engine ) ); + adapter.executionStarted( TestIdentifier.from( parent ) ); + verifyZeroInteractions( listener ); + + adapter.executionStarted( TestIdentifier.from( child1 ) ); + InOrder inOrder = inOrder( listener ); + inOrder + .verify( listener ) + .testSetStarting( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "parent" ) ); + inOrder.verify( listener ).testStarting( new SimpleReportEntry( "parent", "child1" ) ); + inOrder.verifyNoMoreInteractions(); + + adapter.executionFinished( TestIdentifier.from( child1 ), successful() ); + verify( listener ).testSucceeded( new SimpleReportEntry( "parent", "child1" ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionStarted( TestIdentifier.from( child2 ) ); + verify( listener ).testStarting( new SimpleReportEntry( "parent", "child2" ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( child2 ), successful() ); + verify( listener ).testSucceeded( new SimpleReportEntry( "parent", "child2" ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( parent ), successful() ); + verify( listener ) + .testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "parent" ) ); + verifyNoMoreInteractions( listener ); + + adapter.executionFinished( TestIdentifier.from( engine ), successful() ); + verifyNoMoreInteractions( listener ); + } + + @Test + void notifiedForTestSetForSingleNodeEngine() + { + EngineDescriptor engine = + new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" ) + { + @Override + public Type getType() + { + return Type.TEST; + } + }; + TestPlan plan = TestPlan.from( Collections.singletonList( engine ) ); + + adapter.testPlanExecutionStarted( plan ); + adapter.executionStarted( TestIdentifier.from( engine ) ); + InOrder inOrder = inOrder( listener ); + inOrder + .verify( listener ) + .testSetStarting( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "engine" ) ); + inOrder.verify( listener ).testStarting( new SimpleReportEntry( "<unrooted>", "engine" ) ); + inOrder.verifyNoMoreInteractions(); + + adapter.executionFinished( TestIdentifier.from( engine ), successful() ); + inOrder = inOrder( listener ); + inOrder.verify( listener ).testSucceeded( new SimpleReportEntry( "<unrooted>", "engine" ) ); + inOrder + .verify( listener ) + .testSetCompleted( new SimpleReportEntry( JUnitPlatformProvider.class.getName(), "engine" ) ); + inOrder.verifyNoMoreInteractions(); + } + + @Test + void notNotifiedWhenEngineExecutionStarted() + throws Exception + { + adapter.executionStarted( newEngineIdentifier() ); + verify( listener, never() ).testStarting( any() ); + } + + @Test + void notifiedWhenMethodExecutionSkipped() + throws Exception + { + adapter.executionSkipped( newMethodIdentifier(), "test" ); + verify( listener ).testSkipped( any() ); + } + + @Test + void notifiedWithCorrectNamesWhenClassExecutionSkipped() + throws Exception + { + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + TestPlan testPlan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) ); + adapter.testPlanExecutionStarted( testPlan ); + + TestIdentifier classIdentifier = + identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() ); + + adapter.executionSkipped( classIdentifier, "test" ); + verify( listener ).testSkipped( entryCaptor.capture() ); + + ReportEntry entry = entryCaptor.getValue(); + assertTrue( MyTestClass.class.getTypeName().contains( entry.getName() ) ); + assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() ); + } + + @Test + void notifiedWhenEngineExecutionSkipped() + throws Exception + { + adapter.executionSkipped( newEngineIdentifier(), "test" ); + verify( listener ).testSkipped( any() ); + } + + @Test + void notifiedWhenMethodExecutionAborted() + throws Exception + { + adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.aborted( null ) ); + verify( listener ).testAssumptionFailure( any() ); + } + + @Test + void notifiedWhenClassExecutionAborted() + throws Exception + { + adapter.executionFinished( newClassIdentifier(), TestExecutionResult.aborted( null ) ); + verify( listener ).testAssumptionFailure( any() ); + } + + @Test + void notifiedWhenMethodExecutionFailedWithAnAssertionError() + throws Exception + { + adapter.executionFinished( + newMethodIdentifier(), TestExecutionResult.failed( new AssertionError() ) ); + verify( listener ).testFailed( any() ); + } + + @Test + void notifiedWhenMethodExecutionFailedWithANonAssertionError() + throws Exception + { + adapter.executionFinished( + newMethodIdentifier(), TestExecutionResult.failed( new RuntimeException() ) ); + verify( listener ).testError( any() ); + } + + @Test + void notifiedWithCorrectNamesWhenClassExecutionFailed() + throws Exception + { + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + TestPlan testPlan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) ); + adapter.testPlanExecutionStarted( testPlan ); + + adapter.executionFinished( + identifiersAsParentOnTestPlan( testPlan, newEngineDescriptor(), newClassDescriptor() ), + TestExecutionResult.failed( new AssertionError() ) ); + verify( listener ).testFailed( entryCaptor.capture() ); + + ReportEntry entry = entryCaptor.getValue(); + assertEquals( MyTestClass.class.getTypeName(), entry.getSourceName() ); + assertNotNull( entry.getStackTraceWriter() ); + } + + @Test + void notifiedWhenMethodExecutionSucceeded() + throws Exception + { + adapter.executionFinished( newMethodIdentifier(), successful() ); + verify( listener ).testSucceeded( any() ); + } + + @Test + void notifiedForTestSetWhenClassExecutionSucceeded() + throws Exception + { + EngineDescriptor engineDescriptor = newEngineDescriptor(); + TestDescriptor classDescriptor = newClassDescriptor(); + engineDescriptor.addChild( classDescriptor ); + adapter.testPlanExecutionStarted( TestPlan.from( singleton( engineDescriptor ) ) ); + adapter.executionStarted( TestIdentifier.from( classDescriptor ) ); + + adapter.executionFinished( TestIdentifier.from( classDescriptor ), successful() ); + + verify( listener ) + .testSetCompleted( + new SimpleReportEntry( + JUnitPlatformProvider.class.getName(), + MyTestClass.class.getName() ) ); + verify( listener, never() ).testSucceeded( any() ); + } + + @Test + void notifiedWithParentDisplayNameWhenTestClassUnknown() + throws Exception + { + // Set up a test plan + TestPlan plan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Luke's Plan" ) ) ); + adapter.testPlanExecutionStarted( plan ); + + // Use the test plan to set up child with parent. + final String parentDisplay = "I am your father"; + TestIdentifier child = newSourcelessChildIdentifierWithParent( plan, parentDisplay, null ); + adapter.executionStarted( child ); + + // Check that the adapter has informed Surefire that the test has been invoked, + // with the parent name as source (since the test case itself had no source). + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + verify( listener ).testStarting( entryCaptor.capture() ); + assertEquals( parentDisplay, entryCaptor.getValue().getSourceName() ); + } + + @Test + void stackTraceWriterPresentWhenParentHasSource() + throws Exception + { + TestPlan plan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Some Plan" ) ) ); + adapter.testPlanExecutionStarted( plan ); + + TestIdentifier child = + newSourcelessChildIdentifierWithParent( plan, "Parent", ClassSource.from( MyTestClass.class ) ); + adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException() ) ); + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + verify( listener ).testError( entryCaptor.capture() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter() ); + } + + @Test + void stackTraceWriterDefaultsToTestClass() + throws Exception + { + TestPlan plan = + TestPlan.from( Collections.singletonList( new EngineDescriptor( newId(), "Some Plan" ) ) ); + adapter.testPlanExecutionStarted( plan ); + + TestIdentifier child = newSourcelessChildIdentifierWithParent( plan, "Parent", null ); + adapter.executionFinished( child, TestExecutionResult.failed( new RuntimeException( "message" ) ) ); + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + verify( listener ).testError( entryCaptor.capture() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter().smartTrimmedStackTrace() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter().writeTraceToString() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter().writeTrimmedTraceToString() ); + } + + @Test + void stackTraceWriterPresentEvenWithoutException() + throws Exception + { + adapter.executionFinished( newMethodIdentifier(), TestExecutionResult.failed( null ) ); + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + verify( listener ).testError( entryCaptor.capture() ); + assertNotNull( entryCaptor.getValue().getStackTraceWriter() ); + } + + @Test + void displayNamesIgnoredInReport() + throws NoSuchMethodException + { + TestMethodTestDescriptor descriptor = + new TestMethodTestDescriptor( + newId(), MyTestClass.class, + MyTestClass.class.getDeclaredMethod( "myNamedTestMethod" ) ); + + TestIdentifier factoryIdentifier = TestIdentifier.from( descriptor ); + ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass( ReportEntry.class ); + + adapter.executionSkipped( factoryIdentifier, "" ); + verify( listener ).testSkipped( entryCaptor.capture() ); + + ReportEntry value = entryCaptor.getValue(); + + assertEquals( "myNamedTestMethod", value.getName() ); + } + + private static TestIdentifier newMethodIdentifier() + throws Exception + { + return TestIdentifier.from( newMethodDescriptor() ); + } + + private static TestDescriptor newMethodDescriptor( Class<?>... parameterTypes ) + throws Exception + { + return new TestMethodTestDescriptor( + UniqueId.forEngine( "method" ), + MyTestClass.class, + MyTestClass.class.getDeclaredMethod( MY_TEST_METHOD_NAME, parameterTypes ) ); + } + + private static TestIdentifier newClassIdentifier() + { + return TestIdentifier.from( newClassDescriptor() ); + } + + private static TestDescriptor newClassDescriptor() + { + return new ClassTestDescriptor( + UniqueId.root( "class", MyTestClass.class.getName() ), MyTestClass.class ); + } + + private static TestIdentifier newSourcelessChildIdentifierWithParent( + TestPlan testPlan, String parentDisplay, TestSource parentTestSource ) + { + // A parent test identifier with a name. + TestDescriptor parent = mock( TestDescriptor.class ); + when( parent.getUniqueId() ).thenReturn( newId() ); + when( parent.getDisplayName() ).thenReturn( parentDisplay ); + when( parent.getLegacyReportingName() ).thenReturn( parentDisplay ); + when( parent.getSource() ).thenReturn( Optional.ofNullable( parentTestSource ) ); + when( parent.getType() ).thenReturn( Type.CONTAINER ); + TestIdentifier parentId = TestIdentifier.from( parent ); + + // The (child) test case that is to be executed as part of a test plan. + TestDescriptor child = mock( TestDescriptor.class ); + when( child.getUniqueId() ).thenReturn( newId() ); + when( child.getType() ).thenReturn( Type.TEST ); + when( child.getLegacyReportingName() ).thenReturn( "child" ); + + // Ensure the child source is null yet that there is a parent -- the special case to be tested. + when( child.getSource() ).thenReturn( Optional.empty() ); + when( child.getParent() ).thenReturn( Optional.of( parent ) ); + TestIdentifier childId = TestIdentifier.from( child ); + + testPlan.add( childId ); + testPlan.add( parentId ); + + return childId; + } + + private static TestIdentifier newEngineIdentifier() + { + TestDescriptor testDescriptor = newEngineDescriptor(); + return TestIdentifier.from( testDescriptor ); + } + + private static EngineDescriptor newEngineDescriptor() + { + return new EngineDescriptor( UniqueId.forEngine( "engine" ), "engine" ); + } + + private TestDescriptor newTestDescriptor( UniqueId uniqueId, String displayName, Type type ) + { + return new AbstractTestDescriptor( uniqueId, displayName ) + { + @Override + public Type getType() + { + return type; + } + }; + } + + private static TestIdentifier identifiersAsParentOnTestPlan( + TestPlan plan, TestDescriptor parent, TestDescriptor child ) + { + child.setParent( parent ); + + TestIdentifier parentIdentifier = TestIdentifier.from( parent ); + TestIdentifier childIdentifier = TestIdentifier.from( child ); + + plan.add( parentIdentifier ); + plan.add( childIdentifier ); + + return childIdentifier; + } + + private static UniqueId newId() + { + return UniqueId.forEngine( "engine" ); + } + + private static final String MY_TEST_METHOD_NAME = "myTestMethod"; + + private static class MyTestClass + { + @Test + void myTestMethod() + { + } + + @Test + void myTestMethod( String foo ) + { + } + + @DisplayName( "name" ) + @Test + void myNamedTestMethod() + { + } + } +} diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java new file mode 100644 index 0000000..29ca326 --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestMethodFilterTests.java @@ -0,0 +1,105 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.apache.maven.surefire.testset.TestListResolver.toClassFileName; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; + +import org.apache.maven.surefire.testset.TestListResolver; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.UniqueId; + +/** + * Unit tests for {@link TestMethodFilter}. + * + * @since 1.0.3 + */ +class TestMethodFilterTests +{ + + private final TestListResolver resolver = mock( TestListResolver.class ); + + private final TestMethodFilter filter = new TestMethodFilter( this.resolver ); + + @Test + void includesBasedOnTestListResolver() + throws Exception + { + when( resolver.shouldRun( toClassFileName( TestClass.class ), "testMethod" ) ).thenReturn( true ); + + FilterResult result = filter.apply( newTestMethodDescriptor() ); + + assertTrue( result.included() ); + assertFalse( result.excluded() ); + } + + @Test + void excludesBasedOnTestListResolver() + throws Exception + { + when( resolver.shouldRun( toClassFileName( TestClass.class ), "testMethod" ) ).thenReturn( false ); + + FilterResult result = filter.apply( newTestMethodDescriptor() ); + + assertFalse( result.included() ); + assertTrue( result.excluded() ); + } + + @Test + void includesTestDescriptorWithClassSource() + throws Exception + { + FilterResult result = filter.apply( newClassTestDescriptor() ); + + assertTrue( result.included() ); + assertFalse( result.excluded() ); + } + + private static TestMethodTestDescriptor newTestMethodDescriptor() + throws Exception + { + UniqueId uniqueId = UniqueId.forEngine( "method" ); + Class<TestClass> testClass = TestClass.class; + Method testMethod = testClass.getMethod( "testMethod" ); + return new TestMethodTestDescriptor( uniqueId, testClass, testMethod ); + } + + private static ClassTestDescriptor newClassTestDescriptor() + throws Exception + { + UniqueId uniqueId = UniqueId.forEngine( "class" ); + return new ClassTestDescriptor( uniqueId, TestClass.class ); + } + + public static class TestClass + { + public void testMethod() + { + } + } +} diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java new file mode 100644 index 0000000..8ecedc7 --- /dev/null +++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/TestPlanScannerFilterTests.java @@ -0,0 +1,188 @@ +package org.apache.maven.surefire.junitplatform; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.engine.Filter; +import org.junit.platform.launcher.core.LauncherFactory; + +/** + * Unit tests for {@link TestPlanScannerFilter}. + * + * @since 1.0 + */ +class TestPlanScannerFilterTests +{ + + @Test + void emptyClassIsNotAccepted() + { + assertFalse( newFilter().accept( EmptyClass.class ), "does not accept empty class" ); + } + + @Test + void classWithNoTestMethodsIsNotAccepted() + { + assertFalse( + newFilter().accept( ClassWithMethods.class ), "does not accept class with no @Test methods" ); + } + + @Test + void classWithTestMethodsIsAccepted() + { + assertTrue( newFilter().accept( ClassWithTestMethods.class ) ); + } + + @Test + void classWithNestedTestClassIsAccepted() + { + assertTrue( newFilter().accept( ClassWithNestedTestClass.class ) ); + } + + @Test + void classWithDeeplyNestedTestClassIsAccepted() + { + assertTrue( newFilter().accept( ClassWithDeeplyNestedTestClass.class ) ); + } + + @Test + void classWithTestFactoryIsAccepted() + { + assertTrue( newFilter().accept( ClassWithTestFactory.class ) ); + } + + @Test + void classWithNestedTestFactoryIsAccepted() + { + assertTrue( newFilter().accept( ClassWithNestedTestFactory.class ) ); + } + + private TestPlanScannerFilter newFilter() + { + return new TestPlanScannerFilter( LauncherFactory.create(), new Filter<?>[0] ); + } + + static class EmptyClass + { + } + + static class ClassWithMethods + { + + void method1() + { + } + + void method2() + { + } + } + + static class ClassWithTestMethods + { + + @Test + void test1() + { + } + + @Test + public void test2() + { + } + } + + static class ClassWithNestedTestClass + { + + void method() + { + } + + @Nested + class TestClass + { + + @Test + void test1() + { + } + } + } + + static class ClassWithDeeplyNestedTestClass + { + + @Nested + class Level1 + { + + @Nested + class Level2 + { + + @Nested + class TestClass + { + + @Test + void test1() + { + } + } + } + } + } + + static class ClassWithTestFactory + { + + @TestFactory + Stream<DynamicTest> tests() + { + return Stream.empty(); + } + } + + static class ClassWithNestedTestFactory + { + + @Nested + class TestClass + { + + @TestFactory + List<DynamicTest> tests() + { + return emptyList(); + } + } + } +} -- To stop receiving notification emails like this one, please contact tibordig...@apache.org.