From ffadd1a959dc046a196346617f8c15043b0d1a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Desgroppes?= Date: Fri, 21 Oct 2022 15:43:20 +0200 Subject: [PATCH] Support multiple classes and/or methods in filter (#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to allow the selection of: 1. a test class holding nested test class(es), either `static` or explicitly `@Nested`, in which case IntelliJ IDEA's Blaze plugin turns out to pass something like: ``` --test_filter=path.to.SomeTest,path.to.SomeTest.NestedTest ``` ... where the `,` only needs to be replaced by a `¦`. 2. multiple test methods specified through the "Rerun Failed Tests" feature of IntelliJ IDEA, in which case Blaze passes something like: ``` --test_filter=path.to.SomeTest#testFailed1,testFailed2,path.to.SomeTest.NestedTest#testFailed3 ``` ... where, in addition to replacing `,`s by `¦`s, `path.to.SomeTest#` (the preceding class name) needs to be propagated to `testFailed2`. Prior to this change, in both cases, IntelliJ IDEA would report: > No tests were found --- .../junit5/PatternFilter.java | 22 ++++ .../junit5/FilteringTest.java | 121 +++++++++++++++++- 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/PatternFilter.java b/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/PatternFilter.java index 2b73c552..0bc8fecd 100644 --- a/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/PatternFilter.java +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/PatternFilter.java @@ -22,6 +22,8 @@ public class PatternFilter implements PostDiscoveryFilter { public PatternFilter(String pattern) { if (pattern == null || pattern.isEmpty()) { pattern = ".*"; + } else { + pattern = convertCommaSeparatedSelections(pattern); } this.rawPattern = pattern; @@ -56,4 +58,24 @@ public FilterResult apply(TestDescriptor object) { return FilterResult.excluded("Did not match " + rawPattern); } + + private static String convertCommaSeparatedSelections(String pattern) { + var selections = pattern.split(","); + if (selections.length == 1) { + return pattern; + } + var precedingClassSelection = selections[0]; + var precedingHashIndex = precedingClassSelection.indexOf('#'); + for (int i = 1; i < selections.length; i++) { + var selection = selections[i]; + var hashIndex = selection.indexOf('#'); + if (hashIndex > -1) { // `class#` or `class#method` + precedingClassSelection = selection; + precedingHashIndex = hashIndex; + } else if (precedingHashIndex > -1) { // prepend preceding `class#` + selections[i] = precedingClassSelection.substring(0, precedingHashIndex + 1) + selection; + } + } + return String.join("|", selections); + } } diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/FilteringTest.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/FilteringTest.java index 5fade7b3..06300dfb 100644 --- a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/FilteringTest.java +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/FilteringTest.java @@ -6,6 +6,7 @@ import java.lang.reflect.Method; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; import org.junit.jupiter.engine.config.JupiterConfiguration; @@ -20,7 +21,10 @@ public class FilteringTest { private JupiterEngineDescriptor engineDescriptor; private ClassTestDescriptor classTestDescriptor; + private ClassTestDescriptor nestedClassTestDescriptor; private TestMethodTestDescriptor testMethodTestDescriptor; + private TestMethodTestDescriptor siblingTestMethodTestDescriptor; + private TestMethodTestDescriptor nestedTestMethodTestDescriptor; @BeforeEach public void setup() throws NoSuchMethodException { @@ -34,6 +38,19 @@ public void setup() throws NoSuchMethodException { testMethodTestDescriptor = new TestMethodTestDescriptor( classId.append("method", "bar"), JUnit5StyleTest.class, method, config); + Method siblingMethod = JUnit5StyleTest.class.getMethod("alwaysPassesToo"); + siblingTestMethodTestDescriptor = + new TestMethodTestDescriptor( + classId.append("method", "baz"), JUnit5StyleTest.class, siblingMethod, config); + nestedClassTestDescriptor = + new ClassTestDescriptor(classId, JUnit5StyleTest.NestedTest.class, config); + Method nestedMethod = JUnit5StyleTest.NestedTest.class.getMethod("alwaysPassesToo"); + nestedTestMethodTestDescriptor = + new TestMethodTestDescriptor( + classId.append("method", "qux"), + JUnit5StyleTest.NestedTest.class, + nestedMethod, + config); } @Test @@ -48,6 +65,15 @@ public void ifFilterIsNotSetAllTestsShouldBeAccepted() { FilterResult testResult = filter.apply(testMethodTestDescriptor); assertTrue(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedClassResult = filter.apply(nestedClassTestDescriptor); + assertTrue(nestedClassResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertTrue(nestedTestResult.included()); } @Test @@ -62,6 +88,15 @@ public void ifFilterIsSetButEmptyAllTestsShouldBeAccepted() { FilterResult testResult = filter.apply(testMethodTestDescriptor); assertTrue(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedClassResult = filter.apply(nestedClassTestDescriptor); + assertTrue(nestedClassResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertTrue(nestedTestResult.included()); } @Test @@ -76,6 +111,15 @@ public void ifFilterIsSetButNoTestsMatchTheContainersAreIncluded() { FilterResult testResult = filter.apply(testMethodTestDescriptor); assertFalse(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertFalse(siblingTestResult.included()); + + FilterResult nestedClassResult = filter.apply(nestedClassTestDescriptor); + assertTrue(nestedClassResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertFalse(nestedTestResult.included()); } @Test @@ -85,6 +129,46 @@ public void shouldIncludeATestMethodIfTheFilterIsJustTheClassName() { FilterResult testResult = filter.apply(testMethodTestDescriptor); assertTrue(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertFalse(nestedTestResult.included(), "nested class should not be matched"); + } + + @Test + public void shouldIncludeANestedTestMethodIfTheFilterIsJustTheNestedClassName() { + PatternFilter filter = + new PatternFilter(JUnit5StyleTest.NestedTest.class.getName().replace("$", "\\$") + "#"); + + FilterResult testResult = filter.apply(testMethodTestDescriptor); + assertFalse(testResult.included(), "enclosing class should not be matched"); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertFalse(siblingTestResult.included(), "enclosing class should not be matched"); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertTrue(nestedTestResult.included()); + } + + @Test + public void shouldIncludeMultipleTestMethodsIfTheFilterComprisesMultipleClassNames() { + PatternFilter filter = + new PatternFilter( + String.join( + ",", + JUnit5StyleTest.class.getName().replace("$", "\\$"), + JUnit5StyleTest.NestedTest.class.getName().replace("$", "\\$"))); + + FilterResult testResult = filter.apply(testMethodTestDescriptor); + assertTrue(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertTrue(nestedTestResult.included()); } @Test @@ -93,14 +177,40 @@ public void shouldNotIncludeATestMethodIfTheFilterDoesNotMatchTheMethodName() { FilterResult testResult = filter.apply(testMethodTestDescriptor); assertFalse(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertFalse(siblingTestResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertFalse(nestedTestResult.included()); } @Test public void shouldIncludeATestMethodIfTheFilterMatchesTheMethodName() { - PatternFilter filter = new PatternFilter("#alwaysPasses"); + PatternFilter filter = new PatternFilter("#alwaysPassesToo"); + + FilterResult testResult = filter.apply(testMethodTestDescriptor); + assertFalse(testResult.included(), "different method name should not be matched"); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertTrue(nestedTestResult.included()); + } + + @Test + public void shouldIncludeMultipleTestMethodsIfTheFilterComprisesMultipleMethodNames() { + PatternFilter filter = new PatternFilter("JUnit5StyleTest#alwaysPasses,alwaysPassesToo"); FilterResult testResult = filter.apply(testMethodTestDescriptor); assertTrue(testResult.included()); + + FilterResult siblingTestResult = filter.apply(siblingTestMethodTestDescriptor); + assertTrue(siblingTestResult.included()); + + FilterResult nestedTestResult = filter.apply(nestedTestMethodTestDescriptor); + assertFalse(nestedTestResult.included(), "nested class should not be matched"); } private static class EmptyConfigParameters implements ConfigurationParameters { @@ -123,5 +233,14 @@ public int size() { private static class JUnit5StyleTest { @Test public void alwaysPasses() {} + + @Test + public void alwaysPassesToo() {} + + @Nested + private class NestedTest { + @Test + public void alwaysPassesToo() {} + } } }