You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ol...@apache.org on 2026/09/10 05:08:56 UTC
(maven-surefire) branch master updated: [SUREFIRE-3465] Match includes/excludes the way the scanner does (#3466)
This is an automated email from the ASF dual-hosted git repository.
olamy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
The following commit(s) were added to refs/heads/master by this push:
new 71c96c050 [SUREFIRE-3465] Match includes/excludes the way the scanner does (#3466)
71c96c050 is described below
commit 71c96c050d490ce5c50fdfe7039e3e47af6cc40d
Author: orion0616 <10...@users.noreply.github.com>
AuthorDate: Thu Sep 10 14:08:50 2026 +0900
[SUREFIRE-3465] Match includes/excludes the way the scanner does (#3466)
* Add failing IT for #3465
An <includes> entry with a package directory prefix and `**/`, such as
issue3465/inner/**/*Test.java, or a %regex[...] include, does not run
the matching tests with the JUnit Platform provider, although the very
same configuration runs them with 3.5.6. The build stays green because
the tests are silently not executed.
Refs #3465
* [SUREFIRE-3465] Match includes/excludes the way the scanner does
The provider re-applies <includes>/<excludes> (and -Dtest) as a class
name filter, so that classes an engine discovers on its own are filtered
the same way as the ones the plugin has scanned. That filter matched the
patterns against the dot separated class name, while the patterns
address the class file path. Patterns such as com/example/it/**/*IT.java
therefore never matched, the classes the plugin had selected were
dropped again inside the fork, and the build stayed green with
Tests run: 0.
The filter now delegates to the very same TestListResolver the plugin
builds from <includes> and <excludes> for its directory scanner, and
asks it about the class file path (pkg/Name.class). As a consequence
%regex[...] patterns are evaluated against the .class path as the
documentation describes them, instead of against the class name. Ant
style and regex patterns are also alternatives of one filter now, rather
than two filters that had to match both, and -Dtest=!Class#method
excludes only that method instead of the whole class.
The enclosing classes of the scanned classes stay included
unconditionally, now for excludes as well as for includes. Once the
scanner has decided that a nested class is to be run, rejecting its
enclosing class would contradict that decision and the nested class
would not be discovered at all (#3446).
This also makes the default exclude **/*$* effective in the provider,
where it previously never matched anything. Jupiter applies a
ClassNameFilter to the classes it resolves from a ClassSelector, never
to @Nested ones, so nested tests keep being discovered through their
enclosing class.
Adds unit tests for the pattern shapes the provider receives from the
plugin.
Fixes #3465
---
.../maven/surefire/its/jiras/Surefire3465IT.java | 65 ++++++
.../surefire-3465-scan-list-patterns/pom.xml | 127 ++++++++++
.../test/java/issue3465/inner/IncludedTest.java | 26 +++
.../test/java/issue3465/inner/deep/DeepTest.java | 26 +++
.../src/test/java/issue3465/other/OtherTest.java | 26 +++
.../junitplatform/JUnitPlatformProvider.java | 257 ++-------------------
.../junitplatform/JUnitPlatformProviderTest.java | 169 ++++++++++++++
7 files changed, 460 insertions(+), 236 deletions(-)
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3465IT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3465IT.java
new file mode 100644
index 000000000..25695fa9d
--- /dev/null
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3465IT.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package org.apache.maven.surefire.its.jiras;
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration Test for #3465.
+ */
+public class Surefire3465IT extends SurefireJUnit4IntegrationTestCase {
+ @Test
+ void shouldRunTestsIncludedByPatternWithPackageDirectory() {
+ testWithProfile("directory-include");
+ }
+
+ @Test
+ void shouldRunTestsIncludedByPatternWithWildcardDirectory() {
+ testWithProfile("wildcard-directory-include");
+ }
+
+ @Test
+ void shouldRunTestsIncludedByRegex() {
+ testWithProfile("regex-include");
+ }
+
+ @Test
+ void shouldKeepRunningTestsNotMatchedByExcludePatternWithPackageDirectory() {
+ testWithProfile("directory-exclude");
+ }
+
+ private void testWithProfile(String profile) {
+ OutputValidator outputValidator = unpack("surefire-3465-scan-list-patterns")
+ .activateProfile(profile)
+ .executeTest()
+ .verifyErrorFree(2);
+
+ outputValidator
+ .getSurefireReportsXmlFile("TEST-issue3465.inner.IncludedTest.xml")
+ .assertFileExists();
+ outputValidator
+ .getSurefireReportsXmlFile("TEST-issue3465.inner.deep.DeepTest.xml")
+ .assertFileExists();
+ outputValidator
+ .getSurefireReportsXmlFile("TEST-issue3465.other.OtherTest.xml")
+ .assertFileNotExists();
+ }
+}
diff --git a/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/pom.xml b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/pom.xml
new file mode 100644
index 000000000..4703db941
--- /dev/null
+++ b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.plugins.surefire</groupId>
+ <artifactId>surefire-3465-scan-list-patterns</artifactId>
+ <version>1.0</version>
+ <name>Test for scanner style and regex includes/excludes patterns</name>
+
+ <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>
+ <junit5.version>5.14.4</junit5.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${junit5.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.0</version>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.version}</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>directory-include</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>issue3465/inner/**/*Test.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>wildcard-directory-include</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/inner/**/*Test.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>regex-include</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>%regex[.*inner.*Test\.class]</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>directory-exclude</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>issue3465/other/**/*Test.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/IncludedTest.java b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/IncludedTest.java
new file mode 100644
index 000000000..9cf56bf24
--- /dev/null
+++ b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/IncludedTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package issue3465.inner;
+
+import org.junit.jupiter.api.Test;
+
+class IncludedTest {
+ @Test
+ void includedTest() {}
+}
diff --git a/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/deep/DeepTest.java b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/deep/DeepTest.java
new file mode 100644
index 000000000..2efb1cb9c
--- /dev/null
+++ b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/inner/deep/DeepTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package issue3465.inner.deep;
+
+import org.junit.jupiter.api.Test;
+
+class DeepTest {
+ @Test
+ void deepTest() {}
+}
diff --git a/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/other/OtherTest.java b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/other/OtherTest.java
new file mode 100644
index 000000000..353ddfcea
--- /dev/null
+++ b/surefire-its/src/test/resources/surefire-3465-scan-list-patterns/src/test/java/issue3465/other/OtherTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package issue3465.other;
+
+import org.junit.jupiter.api.Test;
+
+class OtherTest {
+ @Test
+ void otherTestMustNotRun() {}
+}
diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
index ad725460b..f8e51799b 100644
--- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
+++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java
@@ -32,15 +32,12 @@
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
-import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.api.booter.ProviderParameterNames;
import org.apache.maven.surefire.api.provider.AbstractProvider;
import org.apache.maven.surefire.api.provider.CommandChainReader;
import org.apache.maven.surefire.api.provider.ProviderParameters;
@@ -50,12 +47,12 @@
import org.apache.maven.surefire.api.report.TestOutputReportEntry;
import org.apache.maven.surefire.api.report.TestReportListener;
import org.apache.maven.surefire.api.suite.RunResult;
+import org.apache.maven.surefire.api.testset.TestListResolver;
import org.apache.maven.surefire.api.testset.TestSetFailedException;
import org.apache.maven.surefire.api.util.ReflectionUtils;
import org.apache.maven.surefire.api.util.ScanResult;
import org.apache.maven.surefire.api.util.TestsToRun;
import org.apache.maven.surefire.shared.utils.StringUtils;
-import org.apache.maven.surefire.shared.utils.io.SelectorUtils;
import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.Filter;
import org.junit.platform.engine.FilterResult;
@@ -77,8 +74,10 @@
import static java.util.logging.Level.WARNING;
import static java.util.stream.Collectors.toList;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDEDGROUPS_PROP;
+import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDES_SCAN_LIST;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDE_JUNIT5_ENGINES_PROP;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.GROUPS_PROP;
+import static org.apache.maven.surefire.api.booter.ProviderParameterNames.INCLUDES_SCAN_LIST;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.INCLUDE_JUNIT5_ENGINES_PROP;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.JUNIT_VINTAGE_DETECTED;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.RUN_ORDER_PROP;
@@ -90,7 +89,6 @@
import static org.apache.maven.surefire.api.util.TestsToRun.fromClass;
import static org.apache.maven.surefire.api.util.internal.ConcurrencyUtils.runIfZeroCountDown;
import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
-import static org.apache.maven.surefire.shared.utils.io.SelectorUtils.match;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
@@ -399,122 +397,29 @@ private LauncherDiscoveryRequestBuilder newRequest() {
return request().filters(filters).configurationParameters(getConfigurationParameters());
}
- private boolean matchClassName(String className, String pattern) {
- boolean reverse = pattern.startsWith("!");
- if (reverse) {
- pattern = pattern.substring(1);
- }
- // pattern can be either fully qualified or simple class name or package + simple class name + #method
- int hashIndex = pattern.indexOf('#');
- // we receive only -Dtest=#method (weird but possible)
- if (hashIndex == 0) {
- return true;
- }
- if (hashIndex != -1) {
- pattern = pattern.substring(0, hashIndex);
- }
-
- boolean match = className.endsWith("." + pattern)
- || SelectorUtils.matchPath(pattern, className)
- || matchAntPathPattern(pattern, className, true);
-
- if (className.contains(".")) {
- String simpleName = className.substring(className.lastIndexOf('.') + 1);
- match = match || SelectorUtils.matchPath(pattern, simpleName);
- }
-
- if (pattern.contains("/")) {
- String pkgStylePattern = pattern.replace('/', '.');
- match = match || SelectorUtils.matchPath(pkgStylePattern, className);
- }
-
- boolean testMatch = match
- || className.equals(pattern)
- || className.endsWith("." + pattern)
- || SelectorUtils.matchPath(pattern, className);
- return reverse != testMatch;
- }
-
- // TODO this could be simplified/optimized
private Filter<?>[] newFilters() {
List<Filter<?>> filters = new ArrayList<>();
- // includeClassNamePatterns support only regex patterns
Optional<String> includesList =
- Optional.ofNullable(parameters.getProviderProperties().get(ProviderParameterNames.INCLUDES_SCAN_LIST));
- Set<String> enclosingClassNames = includesList.isPresent() ? getEnclosingClassNames() : Collections.emptySet();
- if (includesList.isPresent()) {
- String[] includesRegex = Stream.of(includesList.get().split(","))
- .filter(s -> s.startsWith("%regex["))
- .map(s -> StringUtils.replace(s, "%regex[", ""))
- .map(s -> s.substring(0, s.length() - 1))
- .toArray(String[]::new);
- if (includesRegex.length > 0) {
- filters.add(includeEnclosingClasses(
- ClassNameFilter.includeClassNamePatterns(includesRegex), enclosingClassNames));
- }
- }
-
- // excludeClassNamePatterns support only regex patterns
+ Optional.ofNullable(parameters.getProviderProperties().get(INCLUDES_SCAN_LIST));
Optional<String> excludesList =
- Optional.ofNullable(parameters.getProviderProperties().get(ProviderParameterNames.EXCLUDES_SCAN_LIST));
- if (excludesList.isPresent()) {
- String[] excludesRegex = Stream.of(excludesList.get().split(","))
- .filter(s -> s.startsWith("%regex["))
- .map(s -> StringUtils.replace(s, "%regex[", ""))
- .map(s -> s.substring(0, s.length() - 1))
- .toArray(String[]::new);
- if (excludesRegex.length > 0) {
- filters.add(ClassNameFilter.excludeClassNamePatterns(excludesRegex));
- }
- }
-
- if (includesList.isPresent()) {
- // usual include/exclude are scanner style patterns
- List<String> includes = Stream.of(includesList.get().split(","))
- .filter(s -> !s.startsWith("%regex["))
- .map(pattern -> StringUtils.replace(pattern, ".java", ""))
- // .map(pattern -> StringUtils.replace(pattern, "/", "."))
- .collect(toList());
- if (!includes.isEmpty()) {
- // use of CompositeFilter?
- ClassNameFilter classNameFilter = className -> {
- FilterResult result = includes.stream()
- .map(pattern -> FilterResult.includedIf(
- match(pattern, className) || matchClassName(className, pattern)))
- .filter(FilterResult::included)
- .findAny()
- .orElse(FilterResult.excluded("Not included by any pattern: " + includes));
- return result;
- };
- filters.add(includeEnclosingClasses(classNameFilter, enclosingClassNames));
- }
- }
-
- if (excludesList.isPresent()) {
-
- List<String> excludes = Stream.of(excludesList.get().split(","))
- .filter(s -> !s.startsWith("%regex["))
- .map(pattern -> StringUtils.replace(pattern, ".java", ""))
- // .map(pattern -> StringUtils.replace(pattern, "/", "."))
- .collect(toList());
- if (!excludes.isEmpty()) {
- // use of CompositeFilter?
- ClassNameFilter classNameFilter = className -> {
- FilterResult result = excludes.stream()
- .map(pattern -> {
- boolean inclusive = match(pattern, className);
- return !inclusive
- ? FilterResult.included("Not excluded by pattern: " + pattern)
- : FilterResult.excluded("Excluded by pattern: " + pattern);
- })
- .filter(FilterResult::excluded)
- .findAny()
- .orElse(FilterResult.included("Not excluded by any pattern: " + excludes));
- return result;
- };
- filters.add(classNameFilter);
- }
+ Optional.ofNullable(parameters.getProviderProperties().get(EXCLUDES_SCAN_LIST));
+ if (includesList.isPresent() || excludesList.isPresent()) {
+ // The plugin already applied includes/excludes (and -Dtest) when scanning the test classes directory.
+ // Engines may still discover classes on their own, so the same patterns are re-applied here, and they
+ // must be matched exactly like the scanner does: against the class file path (pkg/Name.class), which
+ // is also what the documentation describes for %regex[...] patterns.
+ TestListResolver scanListResolver = new TestListResolver(
+ includesList.map(Collections::singletonList).orElse(Collections.emptyList()),
+ excludesList.map(Collections::singletonList).orElse(Collections.emptyList()));
+ String includedReason = "Included by includes/excludes " + scanListResolver;
+ String excludedReason = "Not included by includes/excludes " + scanListResolver;
+ Set<String> enclosingClassNames = getEnclosingClassNames();
+ ClassNameFilter classNameFilter =
+ className -> scanListResolver.shouldRun(TestListResolver.toClassFileName(className), null)
+ ? FilterResult.included(includedReason)
+ : FilterResult.excluded(excludedReason);
+ filters.add(includeEnclosingClasses(classNameFilter, enclosingClassNames));
}
boolean useTestNG = parameters.getProviderProperties().get("testng.version") != null;
@@ -786,124 +691,4 @@ private static void cancelExecution(
+ "Please update to 6.0.0 or later!");
}
}
-
- private static boolean matchAntPathPattern(String pattern, String str, boolean isCaseSensitive) {
- if (str.startsWith("/") != pattern.startsWith("/")) {
- return false;
- }
-
- List<String> patDirs = tokenizePath(pattern, "/");
- List<String> strDirs = tokenizePath(str, "/");
-
- int patIdxStart = 0;
- int patIdxEnd = patDirs.size() - 1;
- int strIdxStart = 0;
- int strIdxEnd = strDirs.size() - 1;
-
- // up to first '**'
- while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
- String patDir = patDirs.get(patIdxStart);
- if ("**".equals(patDir)) {
- break;
- }
- if (!match(patDir, strDirs.get(strIdxStart), isCaseSensitive)) {
- return false;
- }
- patIdxStart++;
- strIdxStart++;
- }
- if (strIdxStart > strIdxEnd) {
- // String is exhausted
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (!"**".equals(patDirs.get(i))) {
- return false;
- }
- }
- return true;
- } else {
- if (patIdxStart > patIdxEnd) {
- // String not exhausted, but pattern is. Failure.
- return false;
- }
- }
-
- // up to last '**'
- while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
- String patDir = patDirs.get(patIdxEnd);
- if ("**".equals(patDir)) {
- break;
- }
- if (!match(patDir, strDirs.get(strIdxEnd), isCaseSensitive)) {
- return false;
- }
- patIdxEnd--;
- strIdxEnd--;
- }
- if (strIdxStart > strIdxEnd) {
- // String is exhausted
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (!"**".equals(patDirs.get(i))) {
- return false;
- }
- }
- return true;
- }
-
- while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
- int patIdxTmp = -1;
- for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
- if ("**".equals(patDirs.get(i))) {
- patIdxTmp = i;
- break;
- }
- }
- if (patIdxTmp == patIdxStart + 1) {
- // '**/**' situation, so skip one
- patIdxStart++;
- continue;
- }
- // Find the pattern between padIdxStart & padIdxTmp in str between
- // strIdxStart & strIdxEnd
- int patLength = (patIdxTmp - patIdxStart - 1);
- int strLength = (strIdxEnd - strIdxStart + 1);
- int foundIdx = -1;
- strLoop:
- for (int i = 0; i <= strLength - patLength; i++) {
- for (int j = 0; j < patLength; j++) {
- String subPat = patDirs.get(patIdxStart + j + 1);
- String subStr = strDirs.get(strIdxStart + i + j);
- if (!match(subPat, subStr, isCaseSensitive)) {
- continue strLoop;
- }
- }
-
- foundIdx = strIdxStart + i;
- break;
- }
-
- if (foundIdx == -1) {
- return false;
- }
-
- patIdxStart = patIdxTmp;
- strIdxStart = foundIdx + patLength;
- }
-
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (!"**".equals(patDirs.get(i))) {
- return false;
- }
- }
-
- return true;
- }
-
- private static List<String> tokenizePath(String path, String separator) {
- List<String> ret = new ArrayList<String>();
- StringTokenizer st = new StringTokenizer(path, separator);
- while (st.hasMoreTokens()) {
- ret.add(st.nextToken());
- }
- return ret;
- }
}
diff --git a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
index e5e242990..9106064b1 100644
--- a/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
+++ b/surefire-providers/surefire-junit-platform/src/test/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProviderTest.java
@@ -81,6 +81,7 @@
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDEDGROUPS_PROP;
+import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDES_SCAN_LIST;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDE_JUNIT5_ENGINES_PROP;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.GROUPS_PROP;
import static org.apache.maven.surefire.api.booter.ProviderParameterNames.INCLUDES_SCAN_LIST;
@@ -589,6 +590,168 @@ public void doesNotTreatDollarAsNestedClassSeparator() {
assertFalse(includeFilter.apply(LegalDollarClass.class.getName()).included());
}
+ @Test
+ public void includesClassMatchedByIncludePatternWithPackageDirectory() {
+ ClassNameFilter filter = scanListFilter(
+ singletonMap(INCLUDES_SCAN_LIST, "org/apache/maven/surefire/junitplatform/**/*AlphaTestClass.java"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void includesClassMatchedByIncludePatternWithMultipleWildcardDirectories() {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, "**/junitplatform/**/*.java"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertFalse(filter.apply(TestListResolver.class.getName()).included());
+ }
+
+ @Test
+ public void evaluatesRegexIncludeAgainstTheClassFilePath() {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, "%regex[.*AlphaTestClass\\.class]"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+
+ // a regex written for the dot separated class name does not fully match "<path>/AlphaTestClass.class"
+ ClassNameFilter classNameRegexFilter =
+ scanListFilter(singletonMap(INCLUDES_SCAN_LIST, "%regex[.*AlphaTestClass]"));
+
+ assertFalse(classNameRegexFilter.apply(AlphaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void excludesClassMatchedByExcludePatternWithPackageDirectory() {
+ ClassNameFilter filter =
+ scanListFilter(singletonMap(EXCLUDES_SCAN_LIST, "org/apache/maven/surefire/**/*BetaTestClass.java"));
+
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void excludesClassWhoseNameContainsDollarByTheDefaultExcludePattern() {
+ ClassNameFilter filter = scanListFilter(singletonMap(EXCLUDES_SCAN_LIST, "**/*$*"));
+
+ assertFalse(filter.apply(LegalDollarClass$Test.class.getName()).included());
+ assertTrue(filter.apply(LegalDollarClass.class.getName()).included());
+ }
+
+ @Test
+ public void includesClassSelectedByFullyQualifiedOrSimpleClassName() {
+ String fullyQualifiedName = AlphaTestClass.class.getName();
+
+ for (String pattern : Arrays.asList(fullyQualifiedName, AlphaTestClass.class.getSimpleName())) {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, pattern));
+
+ assertTrue(filter.apply(fullyQualifiedName).included(), pattern);
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included(), pattern);
+ }
+ }
+
+ @Test
+ public void includesClassSelectedTogetherWithItsTestMethods() {
+ String fullyQualifiedName = AlphaTestClass.class.getName();
+
+ for (String pattern : Arrays.asList(fullyQualifiedName + "#someMethod", fullyQualifiedName + "#m1+m2")) {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, pattern));
+
+ assertTrue(filter.apply(fullyQualifiedName).included(), pattern);
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included(), pattern);
+ }
+ }
+
+ @Test
+ public void includesEveryClassOfACommaSeparatedSelection() {
+ ClassNameFilter filter = scanListFilter(singletonMap(
+ INCLUDES_SCAN_LIST, AlphaTestClass.class.getName() + "," + GammaTestClass.class.getSimpleName()));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertTrue(filter.apply(GammaTestClass.class.getName()).included());
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void excludesClassSelectedWithALeadingExclamationMark() {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, "!" + BetaTestClass.class.getName()));
+
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void includesEveryClassWhenOnlyATestMethodIsSelected() {
+ ClassNameFilter filter = scanListFilter(singletonMap(INCLUDES_SCAN_LIST, "#someMethod"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertTrue(filter.apply(BetaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void includesEnclosingClassOfAScannedNestedClassRejectedByAnExcludePattern() {
+ ClassNameFilter filter =
+ scanListFilter(singletonMap(EXCLUDES_SCAN_LIST, "**/*$*"), NestingTest.Level1NestedTest.class);
+
+ assertTrue(filter.apply(NestingTest.class.getName()).included());
+ assertFalse(filter.apply(LegalDollarClass$Test.class.getName()).included());
+ }
+
+ @Test
+ public void appliesExcludePatternsOnTopOfIncludePatterns() {
+ Map<String, String> scanList = new HashMap<>();
+ scanList.put(INCLUDES_SCAN_LIST, "**/AlphaTestClass.java,**/BetaTestClass.java");
+ scanList.put(EXCLUDES_SCAN_LIST, "**/BetaTestClass.java,**/GammaTestClass.java");
+
+ ClassNameFilter filter = scanListFilter(scanList);
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertFalse(filter.apply(BetaTestClass.class.getName()).included());
+ assertFalse(filter.apply(GammaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void evaluatesAntAndRegexIncludePatternsAsAlternatives() {
+ ClassNameFilter filter = scanListFilter(
+ singletonMap(INCLUDES_SCAN_LIST, "**/AlphaTestClass.java,%regex[.*BetaTestClass\\.class]"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertTrue(filter.apply(BetaTestClass.class.getName()).included());
+ assertFalse(filter.apply(GammaTestClass.class.getName()).included());
+ }
+
+ @Test
+ public void includesClassWhenOnlyOneOfItsTestMethodsIsExcluded() {
+ ClassNameFilter filter = scanListFilter(
+ singletonMap(INCLUDES_SCAN_LIST, "!" + AlphaTestClass.class.getSimpleName() + "#someMethod"));
+
+ assertTrue(filter.apply(AlphaTestClass.class.getName()).included());
+ assertTrue(filter.apply(BetaTestClass.class.getName()).included());
+ }
+
+ private static ClassNameFilter scanListFilter(Map<String, String> scanListProperties) {
+ return scanListFilter(scanListProperties, new Class<?>[0]);
+ }
+
+ /**
+ * Creates the provider for the given {@code junit.includes.scan.list} and {@code junit.excludes.scan.list}
+ * provider properties and returns the single class name filter it derives from them.
+ *
+ * @param scanListProperties the provider properties holding the include and exclude patterns
+ * @param scannedClasses the classes the plugin has scanned, used only to compute the enclosing
+ * classes which are included regardless of the patterns
+ * @return the class name filter of the provider
+ */
+ private static ClassNameFilter scanListFilter(Map<String, String> scanListProperties, Class<?>... scannedClasses) {
+ ProviderParameters parameters = providerParametersMock(new TestListResolver(""), scannedClasses);
+ when(parameters.getProviderProperties()).thenReturn(scanListProperties);
+
+ JUnitPlatformProvider provider = new JUnitPlatformProvider(parameters);
+
+ assertThat(provider.getFilters()).hasSize(1);
+ return (ClassNameFilter) provider.getFilters()[0];
+ }
+
private static void assertDirectNestedClassSelection(String includeScanPattern) throws Exception {
Class<?> selectedClass = NestingTest.Level1NestedTest.Level2NestedTest.class;
TestListResolver testListResolver = new TestListResolver(selectedClass.getName());
@@ -1650,3 +1813,9 @@ class LegalDollarClass {}
@SuppressWarnings("checkstyle:typename")
class LegalDollarClass$Test {}
+
+class AlphaTestClass {}
+
+class BetaTestClass {}
+
+class GammaTestClass {}