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 99212a2c6 Allow method filters in excludes (#3561)
99212a2c6 is described below

commit 99212a2c62d8e0a1bb56b8a867bed12d0b714055
Author: Olivier Lamy <[email protected]>
AuthorDate: Fri Oct 30 19:51:03 2026 +1000

    Allow method filters in excludes (#3561)
---
 .../maven/plugin/failsafe/IntegrationTestMojo.java | 15 +++++-
 .../plugin/surefire/AbstractSurefireMojo.java      | 32 +++++++++---
 .../plugin/surefire/AbstractSurefireMojoTest.java  | 59 +++++++++++++++++++++-
 .../apache/maven/plugin/surefire/SurefireMojo.java | 15 +++++-
 .../markdown/examples/inclusion-exclusion.md.vm    | 19 +++++++
 .../its/AbstractTestMultipleMethodPatterns.java    |  5 +-
 .../surefire/its/IncludesExcludesJunit5IT.java     |  8 +++
 .../resources/includes-excludes-junit-5/pom.xml    | 19 +++++++
 8 files changed, 160 insertions(+), 12 deletions(-)

diff --git 
a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
 
b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index c6df87d78..925eb28ec 100644
--- 
a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ 
b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -100,7 +100,7 @@ public class IntegrationTestMojo extends 
AbstractSurefireMojo {
      * <code>**{@literal /}${it.test}.java</code>, so you can just type {@code 
-Dit.test=MyIT} to run
      * a single test file called "foo/MyIT.java". The test patterns prefixed 
with a <em>!</em> will be excluded.
      * <br>
-     * This parameter overrides the parameter {@code includes} and {@code 
excludes}.
+     * This parameter overrides {@code includes}, {@code excludes}, {@code 
includesFile} and {@code excludesFile}.
      * <br>
      * Since 2.7.3 You can execute a limited number of methods in the test 
with adding <i>#myMethod</i> or
      * <i>#my*ethod</i>. E.g. type {@code -Dit.test=MyIT#myMethod} 
<b>supported for junit 4.x and TestNg.</b>
@@ -248,6 +248,9 @@ public class IntegrationTestMojo extends 
AbstractSurefireMojo {
      * {@literal <include>}%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], 
pkg{@literal /}**{@literal /}*Fast*.java{@literal </include>}
      * </code></pre>
      * <br>
+     * Method filters like <i>pkg{@literal /}SomeIT#testMethod</i> are not 
allowed here, use {@code includesFile}
+     * or {@code test} instead.
+     * <br>
      * <br>
      * <b>Notice that</b> these values are relative to the directory 
containing generated test classes of the project
      * being tested. This directory is declared by the parameter {@code 
testClassesDirectory} which defaults
@@ -275,6 +278,12 @@ public class IntegrationTestMojo extends 
AbstractSurefireMojo {
      * {@literal <exclude>}%regex[pkg.*Slow.*.class], Unstable*{@literal 
</exclude>}
      * </code></pre>
      * <br>
+     * Since 3.6.2, you can also exclude test methods, with the same syntax as 
the {@code test} parameter. The rest
+     * of the class still runs:
+     * <pre><code>
+     * {@literal <exclude>}pkg{@literal /}SomeIT#testMethod, **{@literal 
/}*IT#testSlow*{@literal </exclude>}
+     * </code></pre>
+     * <br>
      * <b>Notice that</b> these values are relative to the directory 
containing generated test classes of the project
      * being tested. This directory is declared by the parameter {@code 
testClassesDirectory} which defaults
      * to the POM property <code>${project.build.testOutputDirectory}</code>, 
typically
@@ -410,6 +419,8 @@ public class IntegrationTestMojo extends 
AbstractSurefireMojo {
      * <pre><code>
      * pkg.SomeIT#testMethod
      * </code></pre>
+     * <br>
+     * This file is ignored when the {@code test} parameter is set.
      *
      * @since 2.13
      */
@@ -430,6 +441,8 @@ public class IntegrationTestMojo extends 
AbstractSurefireMojo {
      * <pre><code>
      * pkg.SomeIT#testMethod
      * </code></pre>
+     * <br>
+     * This file is ignored when the {@code test} parameter is set.
      *
      * @since 2.13
      */
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 ac51631ea..2c13eff1f 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
@@ -2290,11 +2290,13 @@ private List<String> getExcludeList(boolean asScanList) 
throws MojoFailureExcept
             excludes = Collections.emptyList();
         } else {
             excludes = new ArrayList<>();
-            if (asScanList) {
-                if (getExcludes() != null) {
+            if (getExcludes() != null) {
+                if (asScanList) {
                     excludes.addAll(getExcludes());
+                } else {
+                    // excluded classes are already dropped by the scan, only 
method excludes matter here
+                    excludes.addAll(getMethodFilters(getExcludes()));
                 }
-                checkMethodFilterInIncludesExcludes(excludes);
             }
 
             if (getExcludesFile() != null) {
@@ -2336,7 +2338,7 @@ private List<String> getIncludeList(boolean asScanList) 
throws MojoFailureExcept
                 if (getIncludes() != null) {
                     includes.addAll(getIncludes());
                 }
-                checkMethodFilterInIncludesExcludes(includes);
+                checkMethodFilterInIncludes(includes);
             }
 
             if (getIncludesFile() != null) {
@@ -2351,12 +2353,30 @@ private List<String> getIncludeList(boolean asScanList) 
throws MojoFailureExcept
         return filterNulls(includes);
     }
 
-    private void checkMethodFilterInIncludesExcludes(Iterable<String> 
patterns) throws MojoFailureException {
+    private void checkMethodFilterInIncludes(Iterable<String> patterns) throws 
MojoFailureException {
         for (String pattern : patterns) {
             if (pattern != null && pattern.contains("#")) {
-                throw new MojoFailureException("Method filter prohibited in 
includes|excludes parameter: " + pattern);
+                throw new MojoFailureException("Method filter prohibited in 
includes parameter: " + pattern);
+            }
+        }
+    }
+
+    /**
+     * Keeps only the patterns naming a method, like {@code 
pkg/SomeTest#testMethod}.
+     */
+    @Nonnull
+    private static List<String> getMethodFilters(List<String> patterns) {
+        List<String> methodFilters = new ArrayList<>();
+        for (String pattern : patterns) {
+            if (pattern != null) {
+                for (String item : split(pattern, ",")) {
+                    if (item.contains("#")) {
+                        methodFilters.add(item.trim());
+                    }
+                }
             }
         }
+        return methodFilters;
     }
 
     private TestListResolver getIncludedAndExcludedTests() throws 
MojoFailureException {
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
index 5c4c32971..a5d0c3cf9 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
@@ -56,6 +56,7 @@
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.surefire.api.suite.RunResult;
 import org.apache.maven.surefire.api.testset.RunOrderParameters;
+import org.apache.maven.surefire.api.testset.TestListResolver;
 import org.apache.maven.surefire.api.util.DefaultScanResult;
 import org.apache.maven.surefire.api.util.RunOrder;
 import org.apache.maven.surefire.api.util.SureFireFileManager;
@@ -2459,8 +2460,7 @@ public void shouldNotPerformMethodFilteringOnIncludes() 
throws Exception {
         plugin.setDependenciesToScan(new String[] {"g:a"});
 
         Exception e = assertThrows(Exception.class, plugin::scanDependencies);
-        assertThat(e.getMessage())
-                .isEqualTo("Method filter prohibited in includes|excludes 
parameter: AnotherTest#method ");
+        assertThat(e.getMessage()).isEqualTo("Method filter prohibited in 
includes parameter: AnotherTest#method ");
     }
 
     @Test
@@ -2543,6 +2543,61 @@ public void shouldFilterTestsOnExcludes() throws 
Exception {
         assertThat(result.getClasses()).isEmpty();
     }
 
+    @Test
+    public void shouldFilterTestMethodsOnExcludes() throws Exception {
+        Mojo plugin = new Mojo();
+
+        plugin.setLogger(mock(Logger.class));
+
+        plugin.setExcludes(singletonList("AnotherTest#method"));
+
+        VersionRange version = VersionRange.createFromVersion("1.0");
+        ArtifactHandler handler = new DefaultArtifactHandler();
+        Artifact testDeps = new DefaultArtifact("g", "a", version, "compile", 
"test-jar", null, handler);
+        File artifactFile = SureFireFileManager.createTempFile("surefire", 
"-classes");
+        String classDir = artifactFile.getCanonicalPath();
+        assertThat(artifactFile.delete()).isTrue();
+        File classes = new File(classDir);
+        assertThat(classes.mkdir()).isTrue();
+        testDeps.setFile(classes);
+        assertThat(new File(classes, 
"AnotherTest.class").createNewFile()).isTrue();
+        plugin.setProjectTestArtifacts(singletonList(testDeps));
+        plugin.setDependenciesToScan(new String[] {"g:a"});
+
+        // the class is kept, only its method is skipped
+        DefaultScanResult result = plugin.scanDependencies();
+        assertThat(result.getClasses()).containsExactly("AnotherTest");
+
+        TestListResolver specificTests = plugin.getSpecificTests();
+        assertThat(specificTests.shouldRun("AnotherTest.class", 
"method")).isFalse();
+        assertThat(specificTests.shouldRun("AnotherTest.class", 
"otherMethod")).isTrue();
+    }
+
+    @Test
+    public void shouldPassOnlyMethodExcludesToMethodFilter() throws Exception {
+        Mojo plugin = new Mojo();
+
+        plugin.setExcludes(asList("**/SlowTest.java", "pkg/AnotherTest#method, 
**/UnstableTest.java", null));
+
+        TestListResolver specificTests = plugin.getSpecificTests();
+        assertThat(specificTests.getIncludedPatterns()).isEmpty();
+        assertThat(specificTests.getExcludedPatterns()).hasSize(1);
+        assertThat(specificTests.shouldRun("pkg/AnotherTest.class", 
"method")).isFalse();
+        assertThat(specificTests.shouldRun("pkg/SlowTest.class", 
"method")).isTrue();
+    }
+
+    @Test
+    public void shouldIgnoreMethodExcludesWhenTestIsSet() throws Exception {
+        Mojo plugin = new Mojo();
+
+        plugin.setExcludes(singletonList("AnotherTest#method"));
+        plugin.setTest("AnotherTest");
+
+        TestListResolver specificTests = plugin.getSpecificTests();
+        assertThat(specificTests.getExcludedPatterns()).isEmpty();
+        assertThat(specificTests.shouldRun("AnotherTest.class", 
"method")).isTrue();
+    }
+
     @Test
     public void shouldUseOnlySpecificTests() throws Exception {
         Mojo plugin = new Mojo();
diff --git 
a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefireMojo.java
 
b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefireMojo.java
index 78d4fcb36..6d50f7f05 100644
--- 
a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefireMojo.java
+++ 
b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefireMojo.java
@@ -72,7 +72,7 @@ public class SurefireMojo extends AbstractSurefireMojo 
implements SurefireReport
      * <code>**{@literal /}${test}.java</code>, so you can just type {@code 
-Dtest=MyTest} to run a single test called
      * "foo/MyTest.java". The test patterns prefixed with a <em>!</em> will be 
excluded.
      * <br>
-     * This parameter overrides the parameter {@code includes}, {@code 
excludes}.
+     * This parameter overrides {@code includes}, {@code excludes}, {@code 
includesFile} and {@code excludesFile}.
      * <br>
      * Since 2.7.3, you can execute a limited number of methods in the test by 
adding <i>#myMethod</i> or
      * <i>#my*ethod</i>. For example, {@code -Dtest=MyTest#myMethod}. This is 
supported for junit 4.x and TestNg.<br>
@@ -202,6 +202,9 @@ public class SurefireMojo extends AbstractSurefireMojo 
implements SurefireReport
      * {@literal <include>}%regex[.*[Cat|Dog].*], !%regex[pkg.*Slow.*.class], 
pkg{@literal /}**{@literal /}*Fast*.java{@literal </include>}
      * </code></pre>
      * <br>
+     * Method filters like <i>pkg{@literal /}SomeTest#testMethod</i> are not 
allowed here, use {@code includesFile}
+     * or {@code test} instead.
+     * <br>
      * <br>
      * <b>Notice that</b> these values are relative to the directory 
containing generated test classes of the project
      * being tested. This directory is declared by the parameter {@code 
testClassesDirectory} which defaults
@@ -229,6 +232,12 @@ public class SurefireMojo extends AbstractSurefireMojo 
implements SurefireReport
      * {@literal <exclude>}%regex[pkg.*Slow.*.class], Unstable*{@literal 
</exclude>}
      * </code></pre>
      * <br>
+     * Since 3.6.2, you can also exclude test methods, with the same syntax as 
the {@code test} parameter. The rest
+     * of the class still runs:
+     * <pre><code>
+     * {@literal <exclude>}pkg{@literal /}SomeTest#testMethod, **{@literal 
/}*Test#testSlow*{@literal </exclude>}
+     * </code></pre>
+     * <br>
      * <b>Notice that</b> these values are relative to the directory 
containing generated test classes of the project
      * being tested. This directory is declared by the parameter {@code 
testClassesDirectory} which defaults
      * to the POM property <code>${project.build.testOutputDirectory}</code>, 
typically
@@ -373,6 +382,8 @@ public class SurefireMojo extends AbstractSurefireMojo 
implements SurefireReport
      * <pre><code>
      * pkg.SomeTest#testMethod
      * </code></pre>
+     * <br>
+     * This file is ignored when the {@code test} parameter is set.
      *
      * @since 2.13
      */
@@ -392,6 +403,8 @@ public class SurefireMojo extends AbstractSurefireMojo 
implements SurefireReport
      * <pre><code>
      * pkg.SomeTest#testMethod
      * </code></pre>
+     * <br>
+     * This file is ignored when the {@code test} parameter is set.
      *
      * @since 2.13
      */
diff --git 
a/maven-surefire-plugin/src/site/markdown/examples/inclusion-exclusion.md.vm 
b/maven-surefire-plugin/src/site/markdown/examples/inclusion-exclusion.md.vm
index 8007b11a1..cb7e2fd42 100644
--- a/maven-surefire-plugin/src/site/markdown/examples/inclusion-exclusion.md.vm
+++ b/maven-surefire-plugin/src/site/markdown/examples/inclusion-exclusion.md.vm
@@ -159,6 +159,25 @@ As of ${thisPlugin} Plugin 2.19.1, the syntax with fully 
qualified class names o
 
 The character (?) replaces single character and (*) represents zero or more 
characters. Multiple formats can be additionally combined. This syntax can be 
used in parameters: `test`, `includes`, `excludes`, `includesFile`, 
`excludesFile`.
 
+Excluding test methods
+----------------------
+
+As of ${thisPlugin} Plugin 3.6.2, you can also exclude test methods, with the 
same syntax as the `test` parameter (see [Running a Single 
Test](single-test.html)):
+
+```xml
+  [...]
+#{if}(${project.artifactId}=="maven-surefire-plugin")
+          <exclude>pkg/SomeTest#testMethod, **/*Test#testSlow*</exclude>
+#{else}
+          <exclude>pkg/SomeIT#testMethod, **/*IT#testSlow*</exclude>
+#{end}
+  [...]
+```
+
+Only the matching methods are skipped, the rest of the class still runs. 
`includesFile` and `excludesFile` have accepted methods since 3.0.0-M6, but 
`includes` does not and fails the build if you try.
+
+When `test` is set, `includes`, `excludes`, `includesFile` and `excludesFile` 
are all ignored.
+
 Tests from dependencies
 -----------------------
 
diff --git 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractTestMultipleMethodPatterns.java
 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractTestMultipleMethodPatterns.java
index 0a8b9382b..c6c5208ed 100644
--- 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractTestMultipleMethodPatterns.java
+++ 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/AbstractTestMultipleMethodPatterns.java
@@ -397,7 +397,8 @@ public void shouldNotRunExcludedMethods() {
 
     @Test
     public void shouldNotRunExcludedClassesAndMethods() {
-        assumeTrue(getSettings().getConfiguration() == TEST);
+        // in includes/excludes mode the ! patterns go to <excludes>
+        assumeTrue(getSettings().getConfiguration() == TEST || 
getSettings().getConfiguration() == INCLUDES_EXCLUDES);
         prepare("!#*Fail*, !TestFour#testSuccessTwo")
                 .executeTest()
                 .verifyErrorFree(11)
@@ -414,6 +415,6 @@ public void negativeTest() {
         prepare(pattern)
                 .failNever()
                 .executeTest()
-                .verifyTextInLog("Method filter prohibited in 
includes|excludes parameter: " + pattern);
+                .verifyTextInLog("Method filter prohibited in includes 
parameter: " + pattern);
     }
 }
diff --git 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/IncludesExcludesJunit5IT.java
 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/IncludesExcludesJunit5IT.java
index 837da130b..7e2be9de5 100644
--- 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/IncludesExcludesJunit5IT.java
+++ 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/IncludesExcludesJunit5IT.java
@@ -51,6 +51,14 @@ public void testPathBasedIncludesExcludes() {
         testWithProfile("path");
     }
 
+    /**
+     * Excludes the failing method of DontRunTest instead of the whole class.
+     */
+    @Test
+    public void testMethodExcludes() {
+        testWithProfile("method");
+    }
+
     private void testWithProfile(String profile) {
         final OutputValidator outputValidator =
                 
unpack().activateProfile(profile).executeTest().verifyErrorFree(2);
diff --git a/surefire-its/src/test/resources/includes-excludes-junit-5/pom.xml 
b/surefire-its/src/test/resources/includes-excludes-junit-5/pom.xml
index a20ed25ef..9d9a1c995 100644
--- a/surefire-its/src/test/resources/includes-excludes-junit-5/pom.xml
+++ b/surefire-its/src/test/resources/includes-excludes-junit-5/pom.xml
@@ -110,6 +110,25 @@ under the License.
           </plugin>
         </plugins>
       </build>
+    </profile>
+    <profile>
+      <id>method</id>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <excludes>
+                <exclude>**/DontRunTest#testRun</exclude>
+              </excludes>
+              <includes>
+                <include>**/NotIncludedByDefault.java</include>
+                <include>**/*Test.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
     </profile>
       <profile>
         <id>withXmlFile</id>

Reply via email to