Permalink
Browse files

Proof-of-concept for class-level @Rule support

  • Loading branch information...
aisrael committed Oct 19, 2009
1 parent 334bf58 commit 79ef5a7e1d7fa144cc81c9414f4791aaea8b3d75
@@ -9,7 +9,7 @@ private Version() {
}
public static String id() {
- return "4.7";
+ return "4.7.1-SNAPSHOT";
}
public static void main(String[] args) {
@@ -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);
+
+}
@@ -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<Throwable> errors) {
}
private void validateRuleField(Field field, List<Throwable> 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<MethodRule> rules(Object test) {
List<MethodRule> results= new ArrayList<MethodRule>();
- 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<FrameworkField> ruleFields() {
- return getTestClass().getAnnotatedFields(Rule.class);
- }
-
private MethodRule createRule(Object test,
FrameworkField each) {
try {
@@ -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<ClassRule> 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<ClassRule> classRules() {
+ final List<ClassRule> results= new ArrayList<ClassRule>();
+ 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<FrameworkField> 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);
}
@@ -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}
*/

1 comment on commit 79ef5a7

@dsaff

This comment has been minimized.

Show comment Hide comment
@dsaff

dsaff Dec 8, 2009

This is for issue 29, right?

dsaff commented on 79ef5a7 Dec 8, 2009

This is for issue 29, right?

Please sign in to comment.