Skip to content

Commit

Permalink
Introduce method filter support in the ConsoleLauncher
Browse files Browse the repository at this point in the history
Issue: #3107
  • Loading branch information
yhkuo41 committed May 28, 2023
1 parent e205ccd commit bfc798b
Show file tree
Hide file tree
Showing 12 changed files with 581 additions and 0 deletions.
Expand Up @@ -83,6 +83,10 @@ repository on GitHub.
`java`, and `jdk` packages by default. This feature can be disabled or configured to
prune other calls via configurations parameters. Please refer to the
<<../user-guide/index.adoc#stacktrace-pruning, User Guide>> for details.
* New `--exclude-methodname` and `--include-methodname` options added to the
`ConsoleLauncher` to include or exclude methods based on fully qualified method names
without parameters. For example, `--exclude-methodname=^org\.example\..+#methodname`
will exclude all methods called `methodName` under package `org.example`.


[[release-notes-5.10.0-M1-junit-jupiter]]
Expand Down
Expand Up @@ -61,6 +61,8 @@ public class TestDiscoveryOptions {
private List<String> excludedClassNamePatterns = emptyList();
private List<String> includedPackages = emptyList();
private List<String> excludedPackages = emptyList();
private List<String> includedMethodNamePatterns = emptyList();
private List<String> excludedMethodNamePatterns = emptyList();
private List<String> includedEngines = emptyList();
private List<String> excludedEngines = emptyList();
private List<String> includedTagExpressions = emptyList();
Expand Down Expand Up @@ -222,6 +224,22 @@ public void setExcludedPackages(List<String> excludedPackages) {
this.excludedPackages = excludedPackages;
}

public List<String> getIncludedMethodNamePatterns() {
return includedMethodNamePatterns;
}

public void setIncludedMethodNamePatterns(List<String> includedMethodNamePatterns) {
this.includedMethodNamePatterns = includedMethodNamePatterns;
}

public List<String> getExcludedMethodNamePatterns() {
return excludedMethodNamePatterns;
}

public void setExcludedMethodNamePatterns(List<String> excludedMethodNamePatterns) {
this.excludedMethodNamePatterns = excludedMethodNamePatterns;
}

public List<String> getIncludedEngines() {
return this.includedEngines;
}
Expand Down
Expand Up @@ -190,6 +190,16 @@ static class FilterOptions {
@Option(names = { "-exclude-package" }, arity = "1", hidden = true)
private List<String> excludePackages2 = new ArrayList<>();

@Option(names = {
"--include-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to include only methods whose fully qualified names without parameters match. " //
+ "When this option is repeated, all patterns will be combined using OR semantics.")
private List<String> includeMethodNamePatterns = new ArrayList<>();

@Option(names = {
"--exclude-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those methods whose fully qualified names without parameters match. " //
+ "When this option is repeated, all patterns will be combined using OR semantics.")
private List<String> excludeMethodNamePatterns = new ArrayList<>();

@Option(names = { "-t",
"--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. "
+ //
Expand Down Expand Up @@ -227,6 +237,8 @@ private void applyTo(TestDiscoveryOptions result) {
result.setExcludedClassNamePatterns(merge(this.excludeClassNamePatterns, this.excludeClassNamePatterns2));
result.setIncludedPackages(merge(this.includePackages, this.includePackages2));
result.setExcludedPackages(merge(this.excludePackages, this.excludePackages2));
result.setIncludedMethodNamePatterns(new ArrayList<>(this.includeMethodNamePatterns));
result.setExcludedMethodNamePatterns(new ArrayList<>(this.excludeMethodNamePatterns));
result.setIncludedTagExpressions(merge(this.includedTags, this.includedTags2));
result.setExcludedTagExpressions(merge(this.excludedTags, this.excludedTags2));
result.setIncludedEngines(merge(this.includedEngines, this.includedEngines2));
Expand Down
Expand Up @@ -18,6 +18,8 @@
import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames;
import static org.junit.platform.launcher.EngineFilter.excludeEngines;
import static org.junit.platform.launcher.EngineFilter.includeEngines;
import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns;
import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns;
import static org.junit.platform.launcher.TagFilter.excludeTags;
import static org.junit.platform.launcher.TagFilter.includeTags;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
Expand Down Expand Up @@ -104,6 +106,14 @@ private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDisc
requestBuilder.filters(excludePackageNames(options.getExcludedPackages()));
}

if (!options.getIncludedMethodNamePatterns().isEmpty()) {
requestBuilder.filters(includeMethodNamePatterns(options.getIncludedMethodNamePatterns()));
}

if (!options.getExcludedMethodNamePatterns().isEmpty()) {
requestBuilder.filters(excludeMethodNamePatterns(options.getExcludedMethodNamePatterns()));
}

if (!options.getIncludedTagExpressions().isEmpty()) {
requestBuilder.filters(includeTags(options.getIncludedTagExpressions()));
}
Expand Down
@@ -0,0 +1,64 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.launcher;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.support.descriptor.MethodSource;

/**
* Abstract {@link MethodFilter} that servers as a superclass
* for filters including or excluding fully qualified method names
* without parameters based on pattern-matching.
*
* @since 1.10
*/
abstract class AbstractMethodFilter implements MethodFilter {

protected final List<Pattern> patterns;
protected final String patternDescription;

AbstractMethodFilter(String... patterns) {
Preconditions.notEmpty(patterns, "patterns array must not be null or empty");
Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements");
this.patterns = Arrays.stream(patterns).map(Pattern::compile).collect(toList());
this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'"));
}

protected Optional<Pattern> findMatchingPattern(String methodName) {
if (methodName == null) {
return Optional.empty();
}
return this.patterns.stream().filter(pattern -> pattern.matcher(methodName).matches()).findAny();
}

protected String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) {
return descriptor.getSource() //
.filter(source -> source instanceof MethodSource) //
.map(methodSource -> getFullyQualifiedMethodNameWithoutParameters(((MethodSource) methodSource))) //
.orElse(null);
}

private String getFullyQualifiedMethodNameWithoutParameters(MethodSource methodSource) {
String methodNameWithParentheses = ReflectionUtils.getFullyQualifiedMethodName(methodSource.getJavaClass(),
methodSource.getMethodName(), (Class<?>[]) null);
return methodNameWithParentheses.substring(0, methodNameWithParentheses.length() - 2);
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.launcher;

import static org.junit.platform.engine.FilterResult.excluded;
import static org.junit.platform.engine.FilterResult.included;

import java.util.regex.Pattern;

import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;

/**
* {@link MethodFilter} that matches fully qualified method names against
* patterns in the form of regular expressions.
*
* <p>If the fully qualified name of a method matches against at least one
* pattern, the class will be excluded.
*
* @since 1.10
*/
class ExcludeMethodFilter extends AbstractMethodFilter {

ExcludeMethodFilter(String... patterns) {
super(patterns);
}

@Override
public FilterResult apply(TestDescriptor descriptor) {
String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor);
return findMatchingPattern(methodName) //
.map(pattern -> excluded(formatExclusionReason(methodName, pattern))) //
.orElseGet(() -> included(formatInclusionReason(methodName)));
}

private String formatInclusionReason(String methodName) {
return String.format("Method name [%s] does not match any excluded pattern: %s", methodName,
patternDescription);
}

private String formatExclusionReason(String methodName, Pattern pattern) {
return String.format("Method name [%s] matches excluded pattern: '%s'", methodName, pattern);
}

@Override
public String toString() {
return String.format("%s that excludes method names that match one of the following regular expressions: %s",
getClass().getSimpleName(), patternDescription);
}

}
@@ -0,0 +1,58 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.launcher;

import static org.junit.platform.engine.FilterResult.excluded;
import static org.junit.platform.engine.FilterResult.included;

import java.util.regex.Pattern;

import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;

/**
* {@link MethodFilter} that matches fully qualified method names against
* patterns in the form of regular expressions.
*
* <p>If the fully qualified name of a method matches against at least one
* pattern, the method will be included.
*
* @since 1.10
*/
class IncludeMethodFilter extends AbstractMethodFilter {

IncludeMethodFilter(String... patterns) {
super(patterns);
}

@Override
public FilterResult apply(TestDescriptor descriptor) {
String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor);
return findMatchingPattern(methodName) //
.map(pattern -> included(formatInclusionReason(methodName, pattern))) //
.orElseGet(() -> excluded(formatExclusionReason(methodName)));
}

private String formatInclusionReason(String methodName, Pattern pattern) {
return String.format("Method name [%s] matches included pattern: '%s'", methodName, pattern);
}

private String formatExclusionReason(String methodName) {
return String.format("Method name [%s] does not match any included pattern: %s", methodName,
patternDescription);
}

@Override
public String toString() {
return String.format("%s that includes method names that match one of the following regular expressions: %s",
getClass().getSimpleName(), patternDescription);
}
}
@@ -0,0 +1,107 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.launcher;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.reflect.Method;
import java.util.List;

import org.apiguardian.api.API;

/**
* {@link PostDiscoveryFilter} that is applied to the fully qualified
* {@link Method} name without parameters.
*
* @since 1.10
* @see #includeMethodNamePatterns(String...)
* @see #excludeMethodNamePatterns(String...)
*/
@API(status = EXPERIMENTAL, since = "1.10")
public interface MethodFilter extends PostDiscoveryFilter {

/**
* Create a new <em>include</em> {@link MethodFilter} based on the
* supplied patterns.
*
* <p>The patterns are combined using OR semantics, i.e. if the fully
* qualified name of a method matches against at least one of the patterns,
* the method will be included in the result set.
*
* @param patterns regular expressions to match against fully qualified
* method names; never {@code null}, empty, or containing {@code null}
* @see Class#getName()
* @see Method#getName()
* @see #includeMethodNamePatterns(List)
* @see #excludeMethodNamePatterns(String...)
*/
static MethodFilter includeMethodNamePatterns(String... patterns) {
return new IncludeMethodFilter(patterns);
}

/**
* Create a new <em>include</em> {@link MethodFilter} based on the
* supplied patterns.
*
* <p>The patterns are combined using OR semantics, i.e. if the fully
* qualified name of a method matches against at least one of the patterns,
* the method will be included in the result set.
*
* @param patterns regular expressions to match against fully qualified
* method names; never {@code null}, empty, or containing {@code null}
* @see Class#getName()
* @see Method#getName()
* @see #includeMethodNamePatterns(String...)
* @see #excludeMethodNamePatterns(String...)
*/
static MethodFilter includeMethodNamePatterns(List<String> patterns) {
return includeMethodNamePatterns(patterns.toArray(new String[0]));
}

/**
* Create a new <em>exclude</em> {@link MethodFilter} based on the
* supplied patterns.
*
* <p>The patterns are combined using OR semantics, i.e. if the fully
* qualified name of a method matches against at least one of the patterns,
* the method will be excluded from the result set.
*
* @param patterns regular expressions to match against fully qualified
* method names; never {@code null}, empty, or containing {@code null}
* @see Class#getName()
* @see Method#getName()
* @see #excludeMethodNamePatterns(List)
* @see #includeMethodNamePatterns(String...)
*/
static MethodFilter excludeMethodNamePatterns(String... patterns) {
return new ExcludeMethodFilter(patterns);
}

/**
* Create a new <em>exclude</em> {@link MethodFilter} based on the
* supplied patterns.
*
* <p>The patterns are combined using OR semantics, i.e. if the fully
* qualified name of a method matches against at least one of the patterns,
* the method will be excluded from the result set.
*
* @param patterns regular expressions to match against fully qualified
* method names; never {@code null}, empty, or containing {@code null}
* @see Class#getName()
* @see Method#getName()
* @see #excludeMethodNamePatterns(String...)
* @see #includeMethodNamePatterns(String...)
*/
static MethodFilter excludeMethodNamePatterns(List<String> patterns) {
return excludeMethodNamePatterns(patterns.toArray(new String[0]));
}

}

0 comments on commit bfc798b

Please sign in to comment.