diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java index bc0bf957e29e..8c3cef6c9d91 100644 --- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -55,7 +55,9 @@ * @since 4.5 */ public class BlockJUnit4ClassRunner extends ParentRunner { + private final ConcurrentHashMap methodDescriptions = new ConcurrentHashMap(); + /** * Creates a BlockJUnit4ClassRunner to run {@code testClass} * @@ -75,10 +77,17 @@ protected void runChild(final FrameworkMethod method, RunNotifier notifier) { if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { - runLeaf(methodBlock(method), description, notifier); + Statement statement; + try { + statement = methodBlock(method); + } + catch (Throwable ex) { + statement = new Fail(ex); + } + runLeaf(statement, description, notifier); } } - + /** * Evaluates whether {@link FrameworkMethod}s are ignored based on the * {@link Ignore} annotation. @@ -390,10 +399,10 @@ private List getMethodRules(Object target) { protected List rules(Object target) { List rules = getTestClass().getAnnotatedMethodValues(target, Rule.class, MethodRule.class); - + rules.addAll(getTestClass().getAnnotatedFieldValues(target, Rule.class, MethodRule.class)); - + return rules; } diff --git a/src/test/java/org/junit/runners/CustomBlockJUnit4ClassRunnerTest.java b/src/test/java/org/junit/runners/CustomBlockJUnit4ClassRunnerTest.java new file mode 100644 index 000000000000..d9f072baacb6 --- /dev/null +++ b/src/test/java/org/junit/runners/CustomBlockJUnit4ClassRunnerTest.java @@ -0,0 +1,90 @@ +package org.junit.runners; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +/** + * Tests that verify proper behavior for custom runners that extend + * {@link BlockJUnit4ClassRunner}. + * + * @author Sam Brannen + * @since 4.13 + */ +public class CustomBlockJUnit4ClassRunnerTest { + + @Test + public void exceptionsFromMethodBlockMustNotResultInUnrootedTests() throws Exception { + TrackingRunListener listener = new TrackingRunListener(); + RunNotifier notifier = new RunNotifier(); + notifier.addListener(listener); + + new CustomBlockJUnit4ClassRunner(CustomBlockJUnit4ClassRunnerTestCase.class).run(notifier); + assertEquals("tests started.", 2, listener.testStartedCount.get()); + assertEquals("tests failed.", 1, listener.testFailureCount.get()); + assertEquals("tests finished.", 2, listener.testFinishedCount.get()); + } + + + public static class CustomBlockJUnit4ClassRunnerTestCase { + @Test public void shouldPass() { /* no-op */ } + @Test public void throwException() { /* no-op */ } + } + + /** + * Custom extension of {@link BlockJUnit4ClassRunner} that always throws + * an exception from the {@code methodBlock()} if a test method is named + * exactly {@code "throwException"}. + */ + private static class CustomBlockJUnit4ClassRunner extends BlockJUnit4ClassRunner { + + CustomBlockJUnit4ClassRunner(Class testClass) throws InitializationError { + super(testClass); + } + + @Override + protected Statement methodBlock(FrameworkMethod method) { + if ("throwException".equals(method.getName())) { + throw new RuntimeException("throwException() test method invoked"); + } + return super.methodBlock(method); + } + } + + /** + * Simple {@link RunListener} that tracks the number of times that + * certain callbacks are invoked. + */ + private static class TrackingRunListener extends RunListener { + + final AtomicInteger testStartedCount = new AtomicInteger(); + final AtomicInteger testFailureCount = new AtomicInteger(); + final AtomicInteger testFinishedCount = new AtomicInteger(); + + + @Override + public void testStarted(Description description) throws Exception { + testStartedCount.incrementAndGet(); + } + + @Override + public void testFailure(Failure failure) throws Exception { + testFailureCount.incrementAndGet(); + } + + @Override + public void testFinished(Description description) throws Exception { + testFinishedCount.incrementAndGet(); + } + } + +} diff --git a/src/test/java/org/junit/tests/AllTests.java b/src/test/java/org/junit/tests/AllTests.java index 2b24b0e7f58b..b3d0d4527246 100644 --- a/src/test/java/org/junit/tests/AllTests.java +++ b/src/test/java/org/junit/tests/AllTests.java @@ -18,6 +18,7 @@ import org.junit.runner.notification.ConcurrentRunNotifierTest; import org.junit.runner.notification.RunNotifierTest; import org.junit.runner.notification.SynchronizedRunListenerTest; +import org.junit.runners.CustomBlockJUnit4ClassRunnerTest; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.model.FrameworkFieldTest; @@ -203,6 +204,7 @@ RuleMemberValidatorTest.class, RuleChainTest.class, BlockJUnit4ClassRunnerTest.class, + CustomBlockJUnit4ClassRunnerTest.class, MethodSorterTest.class, TestedOnSupplierTest.class, StacktracePrintingMatcherTest.class,