From cb6e52fb12d5dd52023f4ec696dd357a237b2ec6 Mon Sep 17 00:00:00 2001 From: AndroidX Test Team Date: Fri, 25 Sep 2026 00:35:55 -0700 Subject: [PATCH] Support filtering parameterized tests with arbitrary parameter names. Previously, TestRequestBuilder only stripped numeric suffixes (e.g. [0]) when matching test method names. Update the parameterized suffix pattern to support arbitrary parameter names, allowing custom-named parameterized tests to be filtered by their root method name. PiperOrigin-RevId: 988075477 --- runner/android_junit_runner/CHANGELOG.md | 2 + .../internal/runner/TestRequestBuilder.java | 21 +++++-- .../runner/TestRequestBuilderTest.java | 60 +++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/runner/android_junit_runner/CHANGELOG.md b/runner/android_junit_runner/CHANGELOG.md index 74db23aa9..629af6902 100644 --- a/runner/android_junit_runner/CHANGELOG.md +++ b/runner/android_junit_runner/CHANGELOG.md @@ -8,6 +8,8 @@ * Catch LinkageError when loading annotation classes (b/536117227) +* Support filtering parameterized tests with arbitrary (non-numeric) parameter names by their root method name. (b/564761639) + **New Features** * Make perfetto trace sections for tests more identifiable by prefixing with "test:" and using fully qualified class name. (b/204992764) diff --git a/runner/android_junit_runner/java/androidx/test/internal/runner/TestRequestBuilder.java b/runner/android_junit_runner/java/androidx/test/internal/runner/TestRequestBuilder.java index 0ed9bac6f..1a9d7e308 100644 --- a/runner/android_junit_runner/java/androidx/test/internal/runner/TestRequestBuilder.java +++ b/runner/android_junit_runner/java/androidx/test/internal/runner/TestRequestBuilder.java @@ -46,6 +46,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.runner.Description; import org.junit.runner.Request; @@ -364,6 +365,17 @@ public void removeMethod(String className, String methodName) { /** A {@link Filter} used to filter out desired test methods from a given class */ private static class MethodFilter extends AbstractFilter { + /** + * Matches a parameterized test method name, capturing the root method name. + * + *

Parameterized runners name tests as {@code methodName[]}, where {@code } + * is arbitrary text, e.g. {@code [0]}, {@code [0: 1 + 2 = 3]}, {@code [dryRun=true,retries=2]} + * or {@code [[1, 2]]}. Since a JVM method name cannot contain {@code '['}, everything before + * the first {@code '['} is the root method name. + */ + private static final Pattern PARAMETERIZED_SUFFIX_PATTERN = + Pattern.compile("^([^\\[]+)\\[.*\\]$", Pattern.DOTALL); + private final String className; private Set includedMethods = new HashSet<>(); private Set excludedMethods = new HashSet<>(); @@ -408,12 +420,9 @@ public boolean evaluateTest(Description description) { } // Strips out the parameterized suffix if it exists - private String stripParameterizedSuffix(String name) { - Pattern suffixPattern = Pattern.compile(".+(\\[[0-9]+\\])$"); - if (suffixPattern.matcher(name).matches()) { - name = name.substring(0, name.lastIndexOf('[')); - } - return name; + private static String stripParameterizedSuffix(String name) { + Matcher matcher = PARAMETERIZED_SUFFIX_PATTERN.matcher(name); + return matcher.matches() ? matcher.group(1) : name; } public void addInclusionMethod(String methodName) { diff --git a/runner/android_junit_runner/javatests/androidx/test/internal/runner/TestRequestBuilderTest.java b/runner/android_junit_runner/javatests/androidx/test/internal/runner/TestRequestBuilderTest.java index a3d64d120..20bd1c222 100644 --- a/runner/android_junit_runner/javatests/androidx/test/internal/runner/TestRequestBuilderTest.java +++ b/runner/android_junit_runner/javatests/androidx/test/internal/runner/TestRequestBuilderTest.java @@ -338,6 +338,30 @@ public static Collection data() { public void testParameterized() {} } + /** + * Parameterized test fixture whose generated names have non-numeric parameter suffixes, e.g. + * {@code testFoo[0: a b]}, {@code testFoo[1: [1, 2]]}, {@code testFoo[2: key=value (Integer)]} + * and a multiline {@code testFoo[3: multi\nline]}. + */ + @RunWith(value = Parameterized.class) + public static class NamedParameterizedTest { + + public NamedParameterizedTest(String data) {} + + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection data() { + Object[][] data = + new Object[][] {{"a b"}, {"[1, 2]"}, {"key=value (Integer)"}, {"multi\nline"}}; + return Arrays.asList(data); + } + + @Test + public void testFoo() {} + + @Test + public void testBar() {} + } + /** Test fixture for verifying support for suite() methods */ public static class JUnit3Suite { public static junit.framework.Test suite() { @@ -1009,6 +1033,42 @@ public void testParameterizedMethods() throws Exception { Assert.assertEquals(3, result.getRunCount()); } + /** Test including a parameterized method by root name with non-numeric parameter names. */ + @Test + public void testNamedParameterizedMethods_include() { + Request request = + builder.addTestMethod(NamedParameterizedTest.class.getName(), "testFoo").build(); + JUnitCore testRunner = new JUnitCore(); + Result result = testRunner.run(request); + Assert.assertEquals(4, result.getRunCount()); + } + + /** Test excluding a parameterized method by root name with non-numeric parameter names. */ + @Test + public void testNamedParameterizedMethods_exclude() { + Request request = + builder + .addTestClass(NamedParameterizedTest.class.getName()) + .removeTestMethod(NamedParameterizedTest.class.getName(), "testFoo") + .build(); + JUnitCore testRunner = new JUnitCore(); + Result result = testRunner.run(request); + Assert.assertEquals(4, result.getRunCount()); + } + + /** Test including a single parameterized instance by its full name. */ + @Test + public void testNamedParameterizedMethods_includeFullName() { + Request request = + builder + .addTestMethod(NamedParameterizedTest.class.getName(), "testFoo[1: [1, 2]]") + .addTestMethod(NamedParameterizedTest.class.getName(), "testBar[3: multi\nline]") + .build(); + JUnitCore testRunner = new JUnitCore(); + Result result = testRunner.run(request); + Assert.assertEquals(2, result.getRunCount()); + } + /** Verify adding a class method and removing same class leaves no tests. */ @Test public void testFilterClassAddMethod() {