Skip to content

Commit bfc798b

Browse files
committed
Introduce method filter support in the ConsoleLauncher
Issue: #3107
1 parent e205ccd commit bfc798b

File tree

12 files changed

+581
-0
lines changed

12 files changed

+581
-0
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc

+4
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ repository on GitHub.
8383
`java`, and `jdk` packages by default. This feature can be disabled or configured to
8484
prune other calls via configurations parameters. Please refer to the
8585
<<../user-guide/index.adoc#stacktrace-pruning, User Guide>> for details.
86+
* New `--exclude-methodname` and `--include-methodname` options added to the
87+
`ConsoleLauncher` to include or exclude methods based on fully qualified method names
88+
without parameters. For example, `--exclude-methodname=^org\.example\..+#methodname`
89+
will exclude all methods called `methodName` under package `org.example`.
8690

8791

8892
[[release-notes-5.10.0-M1-junit-jupiter]]

junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java

+18
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public class TestDiscoveryOptions {
6161
private List<String> excludedClassNamePatterns = emptyList();
6262
private List<String> includedPackages = emptyList();
6363
private List<String> excludedPackages = emptyList();
64+
private List<String> includedMethodNamePatterns = emptyList();
65+
private List<String> excludedMethodNamePatterns = emptyList();
6466
private List<String> includedEngines = emptyList();
6567
private List<String> excludedEngines = emptyList();
6668
private List<String> includedTagExpressions = emptyList();
@@ -222,6 +224,22 @@ public void setExcludedPackages(List<String> excludedPackages) {
222224
this.excludedPackages = excludedPackages;
223225
}
224226

227+
public List<String> getIncludedMethodNamePatterns() {
228+
return includedMethodNamePatterns;
229+
}
230+
231+
public void setIncludedMethodNamePatterns(List<String> includedMethodNamePatterns) {
232+
this.includedMethodNamePatterns = includedMethodNamePatterns;
233+
}
234+
235+
public List<String> getExcludedMethodNamePatterns() {
236+
return excludedMethodNamePatterns;
237+
}
238+
239+
public void setExcludedMethodNamePatterns(List<String> excludedMethodNamePatterns) {
240+
this.excludedMethodNamePatterns = excludedMethodNamePatterns;
241+
}
242+
225243
public List<String> getIncludedEngines() {
226244
return this.includedEngines;
227245
}

junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java

+12
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ static class FilterOptions {
190190
@Option(names = { "-exclude-package" }, arity = "1", hidden = true)
191191
private List<String> excludePackages2 = new ArrayList<>();
192192

193+
@Option(names = {
194+
"--include-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to include only methods whose fully qualified names without parameters match. " //
195+
+ "When this option is repeated, all patterns will be combined using OR semantics.")
196+
private List<String> includeMethodNamePatterns = new ArrayList<>();
197+
198+
@Option(names = {
199+
"--exclude-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those methods whose fully qualified names without parameters match. " //
200+
+ "When this option is repeated, all patterns will be combined using OR semantics.")
201+
private List<String> excludeMethodNamePatterns = new ArrayList<>();
202+
193203
@Option(names = { "-t",
194204
"--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. "
195205
+ //
@@ -227,6 +237,8 @@ private void applyTo(TestDiscoveryOptions result) {
227237
result.setExcludedClassNamePatterns(merge(this.excludeClassNamePatterns, this.excludeClassNamePatterns2));
228238
result.setIncludedPackages(merge(this.includePackages, this.includePackages2));
229239
result.setExcludedPackages(merge(this.excludePackages, this.excludePackages2));
240+
result.setIncludedMethodNamePatterns(new ArrayList<>(this.includeMethodNamePatterns));
241+
result.setExcludedMethodNamePatterns(new ArrayList<>(this.excludeMethodNamePatterns));
230242
result.setIncludedTagExpressions(merge(this.includedTags, this.includedTags2));
231243
result.setExcludedTagExpressions(merge(this.excludedTags, this.excludedTags2));
232244
result.setIncludedEngines(merge(this.includedEngines, this.includedEngines2));

junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames;
1919
import static org.junit.platform.launcher.EngineFilter.excludeEngines;
2020
import static org.junit.platform.launcher.EngineFilter.includeEngines;
21+
import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns;
22+
import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns;
2123
import static org.junit.platform.launcher.TagFilter.excludeTags;
2224
import static org.junit.platform.launcher.TagFilter.includeTags;
2325
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
@@ -104,6 +106,14 @@ private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDisc
104106
requestBuilder.filters(excludePackageNames(options.getExcludedPackages()));
105107
}
106108

109+
if (!options.getIncludedMethodNamePatterns().isEmpty()) {
110+
requestBuilder.filters(includeMethodNamePatterns(options.getIncludedMethodNamePatterns()));
111+
}
112+
113+
if (!options.getExcludedMethodNamePatterns().isEmpty()) {
114+
requestBuilder.filters(excludeMethodNamePatterns(options.getExcludedMethodNamePatterns()));
115+
}
116+
107117
if (!options.getIncludedTagExpressions().isEmpty()) {
108118
requestBuilder.filters(includeTags(options.getIncludedTagExpressions()));
109119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.launcher;
12+
13+
import static java.util.stream.Collectors.joining;
14+
import static java.util.stream.Collectors.toList;
15+
16+
import java.util.Arrays;
17+
import java.util.List;
18+
import java.util.Optional;
19+
import java.util.regex.Pattern;
20+
21+
import org.junit.platform.commons.util.Preconditions;
22+
import org.junit.platform.commons.util.ReflectionUtils;
23+
import org.junit.platform.engine.TestDescriptor;
24+
import org.junit.platform.engine.support.descriptor.MethodSource;
25+
26+
/**
27+
* Abstract {@link MethodFilter} that servers as a superclass
28+
* for filters including or excluding fully qualified method names
29+
* without parameters based on pattern-matching.
30+
*
31+
* @since 1.10
32+
*/
33+
abstract class AbstractMethodFilter implements MethodFilter {
34+
35+
protected final List<Pattern> patterns;
36+
protected final String patternDescription;
37+
38+
AbstractMethodFilter(String... patterns) {
39+
Preconditions.notEmpty(patterns, "patterns array must not be null or empty");
40+
Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements");
41+
this.patterns = Arrays.stream(patterns).map(Pattern::compile).collect(toList());
42+
this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'"));
43+
}
44+
45+
protected Optional<Pattern> findMatchingPattern(String methodName) {
46+
if (methodName == null) {
47+
return Optional.empty();
48+
}
49+
return this.patterns.stream().filter(pattern -> pattern.matcher(methodName).matches()).findAny();
50+
}
51+
52+
protected String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) {
53+
return descriptor.getSource() //
54+
.filter(source -> source instanceof MethodSource) //
55+
.map(methodSource -> getFullyQualifiedMethodNameWithoutParameters(((MethodSource) methodSource))) //
56+
.orElse(null);
57+
}
58+
59+
private String getFullyQualifiedMethodNameWithoutParameters(MethodSource methodSource) {
60+
String methodNameWithParentheses = ReflectionUtils.getFullyQualifiedMethodName(methodSource.getJavaClass(),
61+
methodSource.getMethodName(), (Class<?>[]) null);
62+
return methodNameWithParentheses.substring(0, methodNameWithParentheses.length() - 2);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.launcher;
12+
13+
import static org.junit.platform.engine.FilterResult.excluded;
14+
import static org.junit.platform.engine.FilterResult.included;
15+
16+
import java.util.regex.Pattern;
17+
18+
import org.junit.platform.engine.FilterResult;
19+
import org.junit.platform.engine.TestDescriptor;
20+
21+
/**
22+
* {@link MethodFilter} that matches fully qualified method names against
23+
* patterns in the form of regular expressions.
24+
*
25+
* <p>If the fully qualified name of a method matches against at least one
26+
* pattern, the class will be excluded.
27+
*
28+
* @since 1.10
29+
*/
30+
class ExcludeMethodFilter extends AbstractMethodFilter {
31+
32+
ExcludeMethodFilter(String... patterns) {
33+
super(patterns);
34+
}
35+
36+
@Override
37+
public FilterResult apply(TestDescriptor descriptor) {
38+
String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor);
39+
return findMatchingPattern(methodName) //
40+
.map(pattern -> excluded(formatExclusionReason(methodName, pattern))) //
41+
.orElseGet(() -> included(formatInclusionReason(methodName)));
42+
}
43+
44+
private String formatInclusionReason(String methodName) {
45+
return String.format("Method name [%s] does not match any excluded pattern: %s", methodName,
46+
patternDescription);
47+
}
48+
49+
private String formatExclusionReason(String methodName, Pattern pattern) {
50+
return String.format("Method name [%s] matches excluded pattern: '%s'", methodName, pattern);
51+
}
52+
53+
@Override
54+
public String toString() {
55+
return String.format("%s that excludes method names that match one of the following regular expressions: %s",
56+
getClass().getSimpleName(), patternDescription);
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.launcher;
12+
13+
import static org.junit.platform.engine.FilterResult.excluded;
14+
import static org.junit.platform.engine.FilterResult.included;
15+
16+
import java.util.regex.Pattern;
17+
18+
import org.junit.platform.engine.FilterResult;
19+
import org.junit.platform.engine.TestDescriptor;
20+
21+
/**
22+
* {@link MethodFilter} that matches fully qualified method names against
23+
* patterns in the form of regular expressions.
24+
*
25+
* <p>If the fully qualified name of a method matches against at least one
26+
* pattern, the method will be included.
27+
*
28+
* @since 1.10
29+
*/
30+
class IncludeMethodFilter extends AbstractMethodFilter {
31+
32+
IncludeMethodFilter(String... patterns) {
33+
super(patterns);
34+
}
35+
36+
@Override
37+
public FilterResult apply(TestDescriptor descriptor) {
38+
String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor);
39+
return findMatchingPattern(methodName) //
40+
.map(pattern -> included(formatInclusionReason(methodName, pattern))) //
41+
.orElseGet(() -> excluded(formatExclusionReason(methodName)));
42+
}
43+
44+
private String formatInclusionReason(String methodName, Pattern pattern) {
45+
return String.format("Method name [%s] matches included pattern: '%s'", methodName, pattern);
46+
}
47+
48+
private String formatExclusionReason(String methodName) {
49+
return String.format("Method name [%s] does not match any included pattern: %s", methodName,
50+
patternDescription);
51+
}
52+
53+
@Override
54+
public String toString() {
55+
return String.format("%s that includes method names that match one of the following regular expressions: %s",
56+
getClass().getSimpleName(), patternDescription);
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.launcher;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import java.lang.reflect.Method;
16+
import java.util.List;
17+
18+
import org.apiguardian.api.API;
19+
20+
/**
21+
* {@link PostDiscoveryFilter} that is applied to the fully qualified
22+
* {@link Method} name without parameters.
23+
*
24+
* @since 1.10
25+
* @see #includeMethodNamePatterns(String...)
26+
* @see #excludeMethodNamePatterns(String...)
27+
*/
28+
@API(status = EXPERIMENTAL, since = "1.10")
29+
public interface MethodFilter extends PostDiscoveryFilter {
30+
31+
/**
32+
* Create a new <em>include</em> {@link MethodFilter} based on the
33+
* supplied patterns.
34+
*
35+
* <p>The patterns are combined using OR semantics, i.e. if the fully
36+
* qualified name of a method matches against at least one of the patterns,
37+
* the method will be included in the result set.
38+
*
39+
* @param patterns regular expressions to match against fully qualified
40+
* method names; never {@code null}, empty, or containing {@code null}
41+
* @see Class#getName()
42+
* @see Method#getName()
43+
* @see #includeMethodNamePatterns(List)
44+
* @see #excludeMethodNamePatterns(String...)
45+
*/
46+
static MethodFilter includeMethodNamePatterns(String... patterns) {
47+
return new IncludeMethodFilter(patterns);
48+
}
49+
50+
/**
51+
* Create a new <em>include</em> {@link MethodFilter} based on the
52+
* supplied patterns.
53+
*
54+
* <p>The patterns are combined using OR semantics, i.e. if the fully
55+
* qualified name of a method matches against at least one of the patterns,
56+
* the method will be included in the result set.
57+
*
58+
* @param patterns regular expressions to match against fully qualified
59+
* method names; never {@code null}, empty, or containing {@code null}
60+
* @see Class#getName()
61+
* @see Method#getName()
62+
* @see #includeMethodNamePatterns(String...)
63+
* @see #excludeMethodNamePatterns(String...)
64+
*/
65+
static MethodFilter includeMethodNamePatterns(List<String> patterns) {
66+
return includeMethodNamePatterns(patterns.toArray(new String[0]));
67+
}
68+
69+
/**
70+
* Create a new <em>exclude</em> {@link MethodFilter} based on the
71+
* supplied patterns.
72+
*
73+
* <p>The patterns are combined using OR semantics, i.e. if the fully
74+
* qualified name of a method matches against at least one of the patterns,
75+
* the method will be excluded from the result set.
76+
*
77+
* @param patterns regular expressions to match against fully qualified
78+
* method names; never {@code null}, empty, or containing {@code null}
79+
* @see Class#getName()
80+
* @see Method#getName()
81+
* @see #excludeMethodNamePatterns(List)
82+
* @see #includeMethodNamePatterns(String...)
83+
*/
84+
static MethodFilter excludeMethodNamePatterns(String... patterns) {
85+
return new ExcludeMethodFilter(patterns);
86+
}
87+
88+
/**
89+
* Create a new <em>exclude</em> {@link MethodFilter} based on the
90+
* supplied patterns.
91+
*
92+
* <p>The patterns are combined using OR semantics, i.e. if the fully
93+
* qualified name of a method matches against at least one of the patterns,
94+
* the method will be excluded from the result set.
95+
*
96+
* @param patterns regular expressions to match against fully qualified
97+
* method names; never {@code null}, empty, or containing {@code null}
98+
* @see Class#getName()
99+
* @see Method#getName()
100+
* @see #excludeMethodNamePatterns(String...)
101+
* @see #includeMethodNamePatterns(String...)
102+
*/
103+
static MethodFilter excludeMethodNamePatterns(List<String> patterns) {
104+
return excludeMethodNamePatterns(patterns.toArray(new String[0]));
105+
}
106+
107+
}

0 commit comments

Comments
 (0)