diff --git a/src/main/java/junit/runner/Version.java b/src/main/java/junit/runner/Version.java index 401be434d95f..63584233685c 100644 --- a/src/main/java/junit/runner/Version.java +++ b/src/main/java/junit/runner/Version.java @@ -9,7 +9,7 @@ private Version() { } public static String id() { - return "4.7"; + return "4.7.1-SNAPSHOT"; } public static void main(String[] args) { diff --git a/src/main/java/org/junit/rules/ClassRule.java b/src/main/java/org/junit/rules/ClassRule.java new file mode 100644 index 000000000000..bb27a922e67a --- /dev/null +++ b/src/main/java/org/junit/rules/ClassRule.java @@ -0,0 +1,30 @@ +/** + * Created Oct 19, 2009 + */ +package org.junit.rules; + +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +/** + * A ClassRule is the class-level analogue to a + * {@link org.junit.rules.MethodRule}. + * + * @author Alistair A. Israel + */ +public interface ClassRule { + + /** + * Modifies the class-running {@link Statement} to implement an additional, + * class-level test-running rule. + * + * @param base + * The {@link Statement} to be modified + * @param method + * The {@link TestClass} to be run + * @return a new statement, which may be the same as {@code base}, a wrapper + * around {@code base}, or a completely new Statement. + */ + Statement apply(Statement base, TestClass testClass); + +} diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java index 583cf57ad41a..9898a7abe067 100644 --- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -8,7 +8,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; import org.junit.Test.None; import org.junit.internal.AssumptionViolatedException; @@ -21,6 +20,7 @@ import org.junit.internal.runners.statements.InvokeMethod; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.ClassRule; import org.junit.rules.MethodRule; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; @@ -177,12 +177,20 @@ private void validateFields(List errors) { } private void validateRuleField(Field field, List errors) { - if (!MethodRule.class.isAssignableFrom(field.getType())) - errors.add(new Exception("Field " + field.getName() - + " must implement MethodRule")); if (!Modifier.isPublic(field.getModifiers())) errors.add(new Exception("Field " + field.getName() + " must be public")); + if (!MethodRule.class.isAssignableFrom(field.getType())) { + if (ClassRule.class.isAssignableFrom(field.getType())) { + if (!Modifier.isStatic(field.getModifiers())) { + errors.add(new Exception("Field " + field.getName() + + " must be static")); + } + } else { + errors.add(new Exception("Field " + field.getName() + + " must implement MethodRule or ClassRule")); + } + } } /** @@ -351,15 +359,14 @@ private Statement withRules(FrameworkMethod method, Object target, */ protected List rules(Object test) { List results= new ArrayList(); - for (FrameworkField each : ruleFields()) - results.add(createRule(test, each)); + for (FrameworkField each : ruleFields()) { + if (MethodRule.class.isAssignableFrom(each.getField().getType())) { + results.add(createRule(test, each)); + } + } return results; } - private List ruleFields() { - return getTestClass().getAnnotatedFields(Rule.class); - } - private MethodRule createRule(Object test, FrameworkField each) { try { diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java index eb30933681cc..79ebf337a3f6 100644 --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -9,11 +9,13 @@ import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.model.MultipleFailureException; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.ClassRule; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; @@ -23,6 +25,7 @@ import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerScheduler; @@ -143,6 +146,7 @@ protected Statement classBlock(final RunNotifier notifier) { Statement statement= childrenInvoker(notifier); statement= withBeforeClasses(statement); statement= withAfterClasses(statement); + statement= withClassRules(statement); return statement; } @@ -172,6 +176,57 @@ protected Statement withAfterClasses(Statement statement) { new RunAfters(statement, afters, null); } + /** + * Returns a {@link Statement}: apply all static {@link ClassRule} fields + * annotated with {@link Rule}. + * + * @param statement + * the base statement + * @return a WithClassRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withClassRules(Statement statement) { + final List classRules= classRules(); + if (classRules.isEmpty()) { + return statement; + } + Statement next = statement; + for (final ClassRule classRule : classRules) { + next = classRule.apply(next, fTestClass); + } + return next; + } + + /** + * @return the MethodRules that can transform the block + * that runs each method in the tested class. + */ + protected List classRules() { + final List results= new ArrayList(); + for (FrameworkField field : ruleFields()) { + if (ClassRule.class.isAssignableFrom(field.getType())) { + results.add(getClassRule(field)); + } + } + return results; + } + + private ClassRule getClassRule(FrameworkField field) { + try { + return (ClassRule) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "How did getFields return a field we couldn't access?"); + } + } + + /** + * @return list of {@link FrameworkField}s annotated with {@link Rule} + */ + protected List ruleFields() { + return fTestClass.getAnnotatedFields(Rule.class); + } + /** * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} * on each object returned by {@link #getChildren()} (subject to any imposed @@ -188,7 +243,7 @@ public void evaluate() { private void runChildren(final RunNotifier notifier) { for (final T each : getFilteredChildren()) - fScheduler.schedule(new Runnable() { + fScheduler.schedule(new Runnable() { public void run() { ParentRunner.this.runChild(each, notifier); } diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java index 170cd93168b0..ed56e4c760cc 100644 --- a/src/main/java/org/junit/runners/model/FrameworkField.java +++ b/src/main/java/org/junit/runners/model/FrameworkField.java @@ -33,6 +33,14 @@ public Field getField() { return fField; } + /** + * @return the underlying Java Field type + * @see java.lang.reflect.Field#getType() + */ + public Class getType() { + return fField.getType(); + } + /** * Attempts to retrieve the value of this field on {@code target} */