From fdf5ae720dbdd00b7e4c3c7d62b54951f1b497b1 Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Thu, 25 May 2017 23:36:39 +0700 Subject: [PATCH 1/2] Declarative ordering of @Rule/@ClassRule rules --- src/main/java/org/junit/ClassRule.java | 8 ++ src/main/java/org/junit/Rule.java | 9 ++ .../java/org/junit/internal/Throwables.java | 2 +- .../junit/runners/BlockJUnit4ClassRunner.java | 96 +++++++-------- .../java/org/junit/runners/ParentRunner.java | 31 ++++- .../java/org/junit/runners/RuleContainer.java | 113 ++++++++++++++++++ .../runners/model/MemberValueConsumer.java | 18 +++ .../org/junit/runners/model/TestClass.java | 46 ++++++- .../org/junit/internal/StackTracesTest.java | 26 ++++ .../java/org/junit/rules/ClassRulesTest.java | 68 ++++++++++- .../org/junit/rules/LoggingMethodRule.java | 18 +++ .../org/junit/rules/LoggingStatement.java | 24 ++++ .../java/org/junit/rules/LoggingTestRule.java | 18 +++ .../java/org/junit/rules/TestRuleTest.java | 67 +++++++++++ .../org/junit/runners/AllRunnersTests.java | 1 + .../org/junit/runners/RuleContainerTest.java | 70 +++++++++++ 16 files changed, 554 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/junit/runners/RuleContainer.java create mode 100644 src/main/java/org/junit/runners/model/MemberValueConsumer.java create mode 100644 src/test/java/org/junit/rules/LoggingMethodRule.java create mode 100644 src/test/java/org/junit/rules/LoggingStatement.java create mode 100644 src/test/java/org/junit/rules/LoggingTestRule.java create mode 100644 src/test/java/org/junit/runners/RuleContainerTest.java diff --git a/src/main/java/org/junit/ClassRule.java b/src/main/java/org/junit/ClassRule.java index aa7988541fc5..75c9c8d94e8f 100644 --- a/src/main/java/org/junit/ClassRule.java +++ b/src/main/java/org/junit/ClassRule.java @@ -85,4 +85,12 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface ClassRule { + + /** + * Specifies the order in which rules are applied. The rules with a higher value are inner. + * + * @since 4.13 + */ + int order() default Rule.DEFAULT_ORDER; + } diff --git a/src/main/java/org/junit/Rule.java b/src/main/java/org/junit/Rule.java index f275aaf2fc35..bae47a256996 100644 --- a/src/main/java/org/junit/Rule.java +++ b/src/main/java/org/junit/Rule.java @@ -69,4 +69,13 @@ @Target({ElementType.FIELD, ElementType.METHOD}) public @interface Rule { + int DEFAULT_ORDER = -1; + + /** + * Specifies the order in which rules are applied. The rules with a higher value are inner. + * + * @since 4.13 + */ + int order() default DEFAULT_ORDER; + } diff --git a/src/main/java/org/junit/internal/Throwables.java b/src/main/java/org/junit/internal/Throwables.java index 6fc61afeacc5..ca7864f7b4d3 100644 --- a/src/main/java/org/junit/internal/Throwables.java +++ b/src/main/java/org/junit/internal/Throwables.java @@ -249,7 +249,7 @@ private static boolean isTestFrameworkMethod(String methodName) { "java.lang.reflect.", "org.junit.rules.RunRules.(", "org.junit.rules.RunRules.applyAll(", // calls TestRules - "org.junit.runners.BlockJUnit4ClassRunner.withMethodRules(", // calls MethodRules + "org.junit.runners.RuleContainer.apply(", // calls MethodRules & TestRules "junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown() }; diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java index 4514f47604e7..7285b4f0889e 100644 --- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -3,6 +3,7 @@ import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR; import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -22,12 +23,13 @@ import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.rules.MethodRule; -import org.junit.rules.RunRules; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; +import org.junit.runners.model.MemberValueConsumer; import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; @@ -391,29 +393,23 @@ protected Statement withAfters(FrameworkMethod method, Object target, target); } - private Statement withRules(FrameworkMethod method, Object target, - Statement statement) { - List testRules = getTestRules(target); - Statement result = statement; - result = withMethodRules(method, testRules, target, result); - result = withTestRules(method, testRules, result); - - return result; - } - - private Statement withMethodRules(FrameworkMethod method, List testRules, - Object target, Statement result) { - Statement withMethodRules = result; - for (org.junit.rules.MethodRule each : getMethodRules(target)) { - if (!(each instanceof TestRule && testRules.contains(each))) { - withMethodRules = each.apply(withMethodRules, method, target); + private Statement withRules(FrameworkMethod method, Object target, Statement statement) { + RuleContainer ruleContainer = new RuleContainer(); + CURRENT_RULE_CONTAINER.set(ruleContainer); + try { + List testRules = getTestRules(target); + for (MethodRule each : rules(target)) { + if (!(each instanceof TestRule && testRules.contains(each))) { + ruleContainer.add(each); + } + } + for (TestRule rule : testRules) { + ruleContainer.add(rule); } + } finally { + CURRENT_RULE_CONTAINER.remove(); } - return withMethodRules; - } - - private List getMethodRules(Object target) { - return rules(target); + return ruleContainer.apply(method, describeChild(method), target, statement); } /** @@ -422,27 +418,12 @@ private List getMethodRules(Object target) { * test */ 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; - } - - /** - * Returns a {@link Statement}: apply all non-static fields - * annotated with {@link Rule}. - * - * @param statement The base statement - * @return a RunRules statement if any class-level {@link Rule}s are - * found, or the base statement - */ - private Statement withTestRules(FrameworkMethod method, List testRules, - Statement statement) { - return testRules.isEmpty() ? statement : - new RunRules(statement, testRules, describeChild(method)); + RuleCollector collector = new RuleCollector(); + getTestClass().collectAnnotatedMethodValues(target, Rule.class, MethodRule.class, + collector); + getTestClass().collectAnnotatedFieldValues(target, Rule.class, MethodRule.class, + collector); + return collector.result; } /** @@ -451,13 +432,10 @@ private Statement withTestRules(FrameworkMethod method, List testRules * test */ protected List getTestRules(Object target) { - List result = getTestClass().getAnnotatedMethodValues(target, - Rule.class, TestRule.class); - - result.addAll(getTestClass().getAnnotatedFieldValues(target, - Rule.class, TestRule.class)); - - return result; + RuleCollector collector = new RuleCollector(); + getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector); + getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector); + return collector.result; } private Class getExpectedException(Test annotation) { @@ -474,4 +452,22 @@ private long getTimeout(Test annotation) { } return annotation.timeout(); } + + private static final ThreadLocal CURRENT_RULE_CONTAINER = + new ThreadLocal(); + + private static class RuleCollector implements MemberValueConsumer { + final List result = new ArrayList(); + + public void accept(FrameworkMember member, T value) { + Rule rule = member.getAnnotation(Rule.class); + if (rule != null) { + RuleContainer container = CURRENT_RULE_CONTAINER.get(); + if (container != null) { + container.setOrder(value, rule.order()); + } + } + result.add(value); + } + } } diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java index cfeaccd950ab..4949c242e5b2 100755 --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -36,9 +36,11 @@ import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.InvalidTestClassError; +import org.junit.runners.model.MemberValueConsumer; import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; @@ -269,9 +271,10 @@ private Statement withClassRules(Statement statement) { * each method in the tested class. */ protected List classRules() { - List result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class); - result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class)); - return result; + ClassRuleCollector collector = new ClassRuleCollector(); + testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector); + testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector); + return collector.getOrderedRules(); } /** @@ -487,4 +490,26 @@ public int compare(T o1, T o2) { public void setScheduler(RunnerScheduler scheduler) { this.scheduler = scheduler; } + + private static class ClassRuleCollector implements MemberValueConsumer { + final List entries = new ArrayList(); + + public void accept(FrameworkMember member, TestRule value) { + ClassRule rule = member.getAnnotation(ClassRule.class); + entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE, + rule != null ? rule.order() : null)); + } + + public List getOrderedRules() { + if (entries.isEmpty()) { + return Collections.emptyList(); + } + Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR); + List result = new ArrayList(entries.size()); + for (RuleContainer.RuleEntry entry : entries) { + result.add((TestRule) entry.rule); + } + return result; + } + } } diff --git a/src/main/java/org/junit/runners/RuleContainer.java b/src/main/java/org/junit/runners/RuleContainer.java new file mode 100644 index 000000000000..30ddd8d38a03 --- /dev/null +++ b/src/main/java/org/junit/runners/RuleContainer.java @@ -0,0 +1,113 @@ +package org.junit.runners; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.List; + +import org.junit.Rule; +import org.junit.rules.MethodRule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * Data structure for ordering of {@link TestRule}/{@link MethodRule} instances. + * + * @since 4.13 + */ +class RuleContainer { + private final IdentityHashMap orderValues = new IdentityHashMap(); + private final List testRules = new ArrayList(); + private final List methodRules = new ArrayList(); + + /** + * Sets order value for the specified rule. + */ + public void setOrder(Object rule, int order) { + orderValues.put(rule, order); + } + + public void add(MethodRule methodRule) { + methodRules.add(methodRule); + } + + public void add(TestRule testRule) { + testRules.add(testRule); + } + + static final Comparator ENTRY_COMPARATOR = new Comparator() { + public int compare(RuleEntry o1, RuleEntry o2) { + int result = compareInt(o1.order, o2.order); + return result != 0 ? result : o1.type - o2.type; + } + + private int compareInt(int a, int b) { + return (a < b) ? 1 : (a == b ? 0 : -1); + } + }; + + /** + * Returns entries in the order how they should be applied, i.e. inner-to-outer. + */ + private List getSortedEntries() { + List ruleEntries = new ArrayList( + methodRules.size() + testRules.size()); + for (MethodRule rule : methodRules) { + ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_METHOD_RULE, orderValues.get(rule))); + } + for (TestRule rule : testRules) { + ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_TEST_RULE, orderValues.get(rule))); + } + Collections.sort(ruleEntries, ENTRY_COMPARATOR); + return ruleEntries; + } + + /** + * Applies all the rules ordered accordingly to the specified {@code statement}. + */ + public Statement apply(FrameworkMethod method, Description description, Object target, + Statement statement) { + if (methodRules.isEmpty() && testRules.isEmpty()) { + return statement; + } + Statement result = statement; + for (RuleEntry ruleEntry : getSortedEntries()) { + if (ruleEntry.type == RuleEntry.TYPE_TEST_RULE) { + result = ((TestRule) ruleEntry.rule).apply(result, description); + } else { + result = ((MethodRule) ruleEntry.rule).apply(result, method, target); + } + } + return result; + } + + /** + * Returns rule instances in the order how they should be applied, i.e. inner-to-outer. + * VisibleForTesting + */ + List getSortedRules() { + List result = new ArrayList(); + for (RuleEntry entry : getSortedEntries()) { + result.add(entry.rule); + } + return result; + } + + static class RuleEntry { + static final int TYPE_TEST_RULE = 1; + static final int TYPE_METHOD_RULE = 0; + + final Object rule; + final int type; + final int order; + + RuleEntry(Object rule, int type, Integer order) { + this.rule = rule; + this.type = type; + this.order = order != null ? order.intValue() : Rule.DEFAULT_ORDER; + } + } +} diff --git a/src/main/java/org/junit/runners/model/MemberValueConsumer.java b/src/main/java/org/junit/runners/model/MemberValueConsumer.java new file mode 100644 index 000000000000..351b23ef7889 --- /dev/null +++ b/src/main/java/org/junit/runners/model/MemberValueConsumer.java @@ -0,0 +1,18 @@ +package org.junit.runners.model; + +/** + * Represents a receiver for values of annotated fields/methods together with the declaring member. + * + * @see TestClass#collectAnnotatedFieldValues(Object, Class, Class, MemberValueConsumer) + * @see TestClass#collectAnnotatedMethodValues(Object, Class, Class, MemberValueConsumer) + * @since 4.13 + */ +public interface MemberValueConsumer { + /** + * Receives the next value and its declaring member. + * + * @param member declaring member ({@link FrameworkMethod or {@link FrameworkField}} + * @param value the value of the next member + */ + void accept(FrameworkMember member, T value); +} diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java index d30bcc789baf..ed0816533b39 100755 --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -225,24 +225,59 @@ public T getAnnotation(Class annotationType) { public List getAnnotatedFieldValues(Object test, Class annotationClass, Class valueClass) { - List results = new ArrayList(); + final List results = new ArrayList(); + collectAnnotatedFieldValues(test, annotationClass, valueClass, + new MemberValueConsumer() { + public void accept(FrameworkMember member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the fields annotated with the specified annotation and having the specified type, + * retrieves the values and passes those to the specified consumer. + * + * @since 4.13 + */ + public void collectAnnotatedFieldValues(Object test, + Class annotationClass, Class valueClass, + MemberValueConsumer consumer) { for (FrameworkField each : getAnnotatedFields(annotationClass)) { try { Object fieldValue = each.get(test); if (valueClass.isInstance(fieldValue)) { - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (IllegalAccessException e) { throw new RuntimeException( "How did getFields return a field we couldn't access?", e); } } - return results; } public List getAnnotatedMethodValues(Object test, Class annotationClass, Class valueClass) { - List results = new ArrayList(); + final List results = new ArrayList(); + collectAnnotatedMethodValues(test, annotationClass, valueClass, + new MemberValueConsumer() { + public void accept(FrameworkMember member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the methods annotated with the specified annotation and returning the specified type, + * invokes it and pass the return value to the specified consumer. + * + * @since 4.13 + */ + public void collectAnnotatedMethodValues(Object test, + Class annotationClass, Class valueClass, + MemberValueConsumer consumer) { for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) { try { /* @@ -255,14 +290,13 @@ public List getAnnotatedMethodValues(Object test, */ if (valueClass.isAssignableFrom(each.getReturnType())) { Object fieldValue = each.invokeExplosively(test); - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (Throwable e) { throw new RuntimeException( "Exception in " + each.getName(), e); } } - return results; } public boolean isPublic() { diff --git a/src/test/java/org/junit/internal/StackTracesTest.java b/src/test/java/org/junit/internal/StackTracesTest.java index 6ec0768942fa..f9e7078bc95f 100644 --- a/src/test/java/org/junit/internal/StackTracesTest.java +++ b/src/test/java/org/junit/internal/StackTracesTest.java @@ -23,6 +23,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; @@ -144,6 +145,21 @@ public void getTrimmedStackForJUnit4TestFailingInTestRule() { assertNotEquals(failure.getTrace(), failure.getTrimmedTrace()); } + @Test + public void getTrimmedStackForJUnit4TestFailingInClassRule() { + Result result = runTest(TestWithThrowingClassRule.class); + assertEquals("No tests were executed", 0, result.getRunCount()); + assertEquals("One failure", 1, result.getFailureCount()); + Failure failure = result.getFailures().get(0); + + assertHasTrimmedTrace(failure, + message("java.lang.RuntimeException: cause"), + at("org.junit.internal.StackTracesTest$FakeClassUnderTest.doThrowExceptionWithoutCause"), + at("org.junit.internal.StackTracesTest$FakeClassUnderTest.throwsExceptionWithoutCause"), + at("org.junit.internal.StackTracesTest$ThrowingTestRule.apply")); + assertNotEquals(failure.getTrace(), failure.getTrimmedTrace()); + } + @Test public void getTrimmedStackForJUnit4TestFailingInMethodRule() { Result result = runTest(TestWithThrowingMethodRule.class); @@ -387,6 +403,16 @@ public void alwaysPasses() { } } + public static class TestWithThrowingClassRule { + + @ClassRule + public static final TestRule rule = new ThrowingTestRule(); + + @Test + public void alwaysPasses() { + } + } + public static class ThrowingMethodRule implements MethodRule { public Statement apply( diff --git a/src/test/java/org/junit/rules/ClassRulesTest.java b/src/test/java/org/junit/rules/ClassRulesTest.java index 04d1134f7946..e3100a2cba65 100644 --- a/src/test/java/org/junit/rules/ClassRulesTest.java +++ b/src/test/java/org/junit/rules/ClassRulesTest.java @@ -1,4 +1,4 @@ -/** +/* * Created Oct 19, 2009 */ package org.junit.rules; @@ -13,10 +13,12 @@ import java.util.List; import org.junit.ClassRule; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import org.junit.runners.MethodSorters; import org.junit.runners.model.Statement; /** @@ -248,4 +250,68 @@ public void testCallMethodOnlyOnceRule() { CallMethodOnlyOnceRule.countOfMethodCalls = 0; assertTrue(JUnitCore.runClasses(CallMethodOnlyOnceRule.class).wasSuccessful()); } + + private static final StringBuilder log = new StringBuilder(); + + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class ClassRuleOrdering { + @ClassRule(order = 1) + public static TestRule a() { + return new LoggingTestRule(log, "outer"); + } + + @ClassRule(order = 2) + public static TestRule z() { + return new LoggingTestRule(log, "inner"); + } + + @Test + public void foo() { + log.append(" foo"); + } + + @Test + public void bar() { + log.append(" bar"); + } + } + + @Test + public void classRuleOrdering() { + log.setLength(0); + Result result = JUnitCore.runClasses(ClassRuleOrdering.class); + assertTrue(result.wasSuccessful()); + assertEquals(" outer.begin inner.begin bar foo inner.end outer.end", log.toString()); + } + + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class ClassRuleOrderingDefault { + @ClassRule + public static TestRule a() { + return new LoggingTestRule(log, "outer"); + } + + @ClassRule + public static TestRule b() { + return new LoggingTestRule(log, "inner"); + } + + @Test + public void foo() { + log.append(" foo"); + } + + @Test + public void bar() { + log.append(" bar"); + } + } + + @Test + public void classRuleOrderingDefault() { + log.setLength(0); + Result result = JUnitCore.runClasses(ClassRuleOrderingDefault.class); + assertTrue(result.wasSuccessful()); + assertEquals(" inner.begin outer.begin bar foo outer.end inner.end", log.toString()); + } } diff --git a/src/test/java/org/junit/rules/LoggingMethodRule.java b/src/test/java/org/junit/rules/LoggingMethodRule.java new file mode 100644 index 000000000000..9df2d2378612 --- /dev/null +++ b/src/test/java/org/junit/rules/LoggingMethodRule.java @@ -0,0 +1,18 @@ +package org.junit.rules; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +class LoggingMethodRule implements MethodRule { + private final StringBuilder log; + private final String name; + + LoggingMethodRule(StringBuilder log, String name) { + this.name = name; + this.log = log; + } + + public Statement apply(Statement base, FrameworkMethod method, Object target) { + return new LoggingStatement(base, log, name); + } +} diff --git a/src/test/java/org/junit/rules/LoggingStatement.java b/src/test/java/org/junit/rules/LoggingStatement.java new file mode 100644 index 000000000000..af36239b5214 --- /dev/null +++ b/src/test/java/org/junit/rules/LoggingStatement.java @@ -0,0 +1,24 @@ +package org.junit.rules; + +import org.junit.runners.model.Statement; + +class LoggingStatement extends Statement { + private final Statement base; + private final StringBuilder log; + private final String name; + + LoggingStatement(Statement base, StringBuilder log, String name) { + this.base = base; + this.log = log; + this.name = name; + } + + public void evaluate() throws Throwable { + log.append(" ").append(name).append(".begin"); + try { + base.evaluate(); + } finally { + log.append(" ").append(name).append(".end"); + } + } +} diff --git a/src/test/java/org/junit/rules/LoggingTestRule.java b/src/test/java/org/junit/rules/LoggingTestRule.java new file mode 100644 index 000000000000..08792f202978 --- /dev/null +++ b/src/test/java/org/junit/rules/LoggingTestRule.java @@ -0,0 +1,18 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +class LoggingTestRule implements TestRule { + private final StringBuilder log; + private final String name; + + LoggingTestRule(StringBuilder log, String name) { + this.name = name; + this.log = log; + } + + public Statement apply(Statement base, Description description) { + return new LoggingStatement(base, log, name); + } +} diff --git a/src/test/java/org/junit/rules/TestRuleTest.java b/src/test/java/org/junit/rules/TestRuleTest.java index 0562fa610219..a75c0c6f2672 100644 --- a/src/test/java/org/junit/rules/TestRuleTest.java +++ b/src/test/java/org/junit/rules/TestRuleTest.java @@ -725,4 +725,71 @@ public void onlyOnce() { public void testCallMethodOnlyOnceRule() { assertTrue(JUnitCore.runClasses(CallMethodOnlyOnceRule.class).wasSuccessful()); } + + private static final StringBuilder ruleLog = new StringBuilder(); + + public static class TestRuleIsAroundMethodRule { + @Rule + public final MethodRule z = new LoggingMethodRule(ruleLog, "methodRule"); + + @Rule + public final TestRule a = new LoggingTestRule(ruleLog, "testRule"); + + @Test + public void foo() { + ruleLog.append(" foo"); + } + } + + @Test + public void testRuleIsAroundMethodRule() { + ruleLog.setLength(0); + Result result = JUnitCore.runClasses(TestRuleIsAroundMethodRule.class); + assertTrue(result.wasSuccessful()); + assertEquals(" testRule.begin methodRule.begin foo methodRule.end testRule.end", + ruleLog.toString()); + } + + public static class TestRuleOrdering { + @Rule(order = 1) + public final TestRule a = new LoggingTestRule(ruleLog, "outer"); + + @Rule(order = 2) + public final TestRule z = new LoggingTestRule(ruleLog, "inner"); + + @Test + public void foo() { + ruleLog.append(" foo"); + } + } + + @Test + public void testRuleOrdering() { + ruleLog.setLength(0); + Result result = JUnitCore.runClasses(TestRuleOrdering.class); + assertTrue(result.wasSuccessful()); + assertEquals(" outer.begin inner.begin foo inner.end outer.end", ruleLog.toString()); + } + + public static class TestRuleOrderingWithMethodRule { + @Rule(order = 1) + public final MethodRule z = new LoggingMethodRule(ruleLog, "methodRule"); + + @Rule(order = 2) + public final TestRule a = new LoggingTestRule(ruleLog, "testRule"); + + @Test + public void foo() { + ruleLog.append(" foo"); + } + } + + @Test + public void testRuleOrderingWithMethodRule() { + ruleLog.setLength(0); + Result result = JUnitCore.runClasses(TestRuleOrderingWithMethodRule.class); + assertTrue(result.wasSuccessful()); + assertEquals(" methodRule.begin testRule.begin foo testRule.end methodRule.end", + ruleLog.toString()); + } } diff --git a/src/test/java/org/junit/runners/AllRunnersTests.java b/src/test/java/org/junit/runners/AllRunnersTests.java index 1e92cece59a1..a51072c054a6 100644 --- a/src/test/java/org/junit/runners/AllRunnersTests.java +++ b/src/test/java/org/junit/runners/AllRunnersTests.java @@ -10,6 +10,7 @@ @SuiteClasses({ AllModelTests.class, AllParameterizedTests.class, + RuleContainerTest.class, CustomBlockJUnit4ClassRunnerTest.class }) public class AllRunnersTests { diff --git a/src/test/java/org/junit/runners/RuleContainerTest.java b/src/test/java/org/junit/runners/RuleContainerTest.java new file mode 100644 index 000000000000..714d0dfdf660 --- /dev/null +++ b/src/test/java/org/junit/runners/RuleContainerTest.java @@ -0,0 +1,70 @@ +package org.junit.runners; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.rules.MethodRule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +public class RuleContainerTest { + private final RuleContainer container = new RuleContainer(); + + @Test + public void methodRulesOnly() { + container.add(MRule.M1); + container.add(MRule.M2); + assertEquals("[M1, M2]", container.getSortedRules().toString()); + container.setOrder(MRule.M2, 1); + assertEquals("[M2, M1]", container.getSortedRules().toString()); + } + + @Test + public void testRuleAroundMethodRule() { + container.add(MRule.M1); + container.add(Rule.A); + assertEquals("[M1, A]", container.getSortedRules().toString()); + } + + @Test + public void ordering1() { + container.add(MRule.M1); + container.add(Rule.A); + container.setOrder(Rule.A, 1); + assertEquals("[A, M1]", container.getSortedRules().toString()); + } + + @Test + public void ordering2() { + container.add(Rule.A); + container.add(Rule.B); + container.add(Rule.C); + assertEquals("[A, B, C]", container.getSortedRules().toString()); + container.setOrder(Rule.B, 1); + container.setOrder(Rule.C, 2); + assertEquals("[C, B, A]", container.getSortedRules().toString()); + } + + private enum Rule implements TestRule { + A, + B, + C; + + public Statement apply(Statement base, Description description) { + return base; + } + } + + private enum MRule implements MethodRule { + M1, + M2; + + public Statement apply(Statement base, FrameworkMethod method, Object target) { + return base; + } + } +} From 0bb88ace9b1e2929cc58a60c7adb0aab605a7130 Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Thu, 25 May 2017 23:36:45 +0700 Subject: [PATCH 2/2] deprecate RuleChain --- src/main/java/org/junit/rules/RuleChain.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/junit/rules/RuleChain.java b/src/main/java/org/junit/rules/RuleChain.java index fe710eb2eb02..0ed497339ddd 100644 --- a/src/main/java/org/junit/rules/RuleChain.java +++ b/src/main/java/org/junit/rules/RuleChain.java @@ -53,8 +53,12 @@ * .around(new LoggingRule("inner rule")); * * + * @deprecated Since 4.13 ordering of rules can be naturally specified with an annotation attribute. + * @see org.junit.Rule#order() + * @see org.junit.ClassRule#order() * @since 4.10 */ +@Deprecated public class RuleChain implements TestRule { private static final RuleChain EMPTY_CHAIN = new RuleChain( Collections.emptyList());