diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/ActualRunner.java b/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/ActualRunner.java index f32dc277..3deea331 100644 --- a/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/ActualRunner.java +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/ActualRunner.java @@ -10,12 +10,16 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.Launcher; @@ -47,11 +51,28 @@ public boolean run(String testClassName) { .addPostDiscoveryFilters(TestSharding.makeShardFilter()) .build(); - DiscoverySelector classSelector = DiscoverySelectors.selectClass(testClassName); + final Class testClass; + try { + testClass = Class.forName(testClassName); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to find testClass", e); + } + + // We only allow for one level of nesting at the moment + boolean enclosed = isRunWithEnclosed(testClass); + List classSelectors = + enclosed + ? new ArrayList<>() + : Arrays.stream(testClass.getDeclaredClasses()) + .filter(clazz -> Modifier.isStatic(clazz.getModifiers())) + .map(clazz -> DiscoverySelectors.selectClass(clazz)) + .collect(Collectors.toList()); + + classSelectors.add(DiscoverySelectors.selectClass(testClassName)); LauncherDiscoveryRequestBuilder request = LauncherDiscoveryRequestBuilder.request() - .selectors(Collections.singletonList(classSelector)) + .selectors(classSelectors) .configurationParameter(LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME, "true") .configurationParameter(LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME, "true"); @@ -98,6 +119,27 @@ public boolean run(String testClassName) { } } + /** + * Checks if the test class is annotation with `@RunWith(Enclosed.class)`. We deliberately avoid + * using types here to avoid polluting the classpath with junit4 deps. + */ + private boolean isRunWithEnclosed(Class clazz) { + for (Annotation annotation : clazz.getAnnotations()) { + Class type = annotation.annotationType(); + if (type.getName().equals("org.junit.runner.RunWith")) { + try { + Class runner = (Class) type.getMethod("value").invoke(annotation, (Object[]) null); + if (runner.getName().equals("org.junit.experimental.runners.Enclosed")) { + return true; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return false; + } + } + } + return false; + } + private File getExitFile() { String exitFileName = System.getenv("TEST_PREMATURE_EXIT_FILE"); if (exitFileName == null) { diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/BUILD.bazel b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/BUILD.bazel index 1b5cd67f..68e5a924 100644 --- a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/BUILD.bazel +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/BUILD.bazel @@ -16,12 +16,16 @@ SHARDING_TEST = [ "ShardingTest.java", ] +VINTAGE_NESTED_TEST = [ + "NestedClassesVintageTest.java", +] + java_test_suite( name = "small-tests", size = "small", srcs = glob( ["*.java"], - exclude = PACKAGE_NAME_TEST + PARALLEL_TEST + SHARDING_TEST, + exclude = PACKAGE_NAME_TEST + PARALLEL_TEST + SHARDING_TEST + VINTAGE_NESTED_TEST, ), exclude_engines = ["junit-vintage"], include_engines = ["junit-jupiter"], @@ -36,6 +40,20 @@ java_test_suite( ] + junit5_vintage_deps("contrib_rules_jvm_tests"), ) +java_test_suite( + name = "vintage-nested-test", + size = "small", + srcs = [ + "NestedClassesVintageTest.java", + ], + exclude_engines = ["junit-jupiter"], + include_engines = ["junit-vintage"], + runner = "junit5", + deps = [ + "//java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5", + ] + junit5_vintage_deps("contrib_rules_jvm_tests"), +) + # Test that we can set the package name properly. We do this by # setting the `package` attribute deliberately rather than letting # the value be guessed at by the runner. diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesTest.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesTest.java new file mode 100644 index 00000000..d0364e4e --- /dev/null +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesTest.java @@ -0,0 +1,28 @@ +package com.github.bazel_contrib.contrib_rules_jvm.junit5; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class NestedClassesTest { + static class First { + @Test + public void shouldBeExecuted() { + System.out.println(">>>> Executed test in NestedClassesTest$First"); + } + } + + @Nested + @SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC") + class Second { + @Test + public void shouldBeExecuted() { + System.out.println(">>>> Executed test in NestedClassesTest$Second"); + } + } + + @Test + public void shouldBeExecuted() { + System.out.println(">>>> Executed test in NestedClassesTest"); + } +} diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesVintageTest.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesVintageTest.java new file mode 100644 index 00000000..1507da00 --- /dev/null +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/NestedClassesVintageTest.java @@ -0,0 +1,15 @@ +package com.github.bazel_contrib.contrib_rules_jvm.junit5; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +@RunWith(Enclosed.class) +public class NestedClassesVintageTest { + public static class First { + @Test + public void shouldBeExecuted() { + System.out.println(">>>> Executed test in NestedClassesVintageTest$First"); + } + } +}