Skip to content

Commit

Permalink
Proof-of-concept for class-level @rule support
Browse files Browse the repository at this point in the history
  • Loading branch information
aisrael committed Oct 19, 2009
1 parent 334bf58 commit 79ef5a7
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/main/java/junit/runner/Version.java
Expand Up @@ -9,7 +9,7 @@ private Version() {
} }


public static String id() { public static String id() {
return "4.7"; return "4.7.1-SNAPSHOT";
} }


public static void main(String[] args) { public static void main(String[] args) {
Expand Down
30 changes: 30 additions & 0 deletions 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);

}
27 changes: 17 additions & 10 deletions src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
Expand Up @@ -8,7 +8,6 @@
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.Test.None; import org.junit.Test.None;
import org.junit.internal.AssumptionViolatedException; import org.junit.internal.AssumptionViolatedException;
Expand All @@ -21,6 +20,7 @@
import org.junit.internal.runners.statements.InvokeMethod; import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores; import org.junit.internal.runners.statements.RunBefores;
import org.junit.rules.ClassRule;
import org.junit.rules.MethodRule; import org.junit.rules.MethodRule;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.RunNotifier;
Expand Down Expand Up @@ -177,12 +177,20 @@ private void validateFields(List<Throwable> errors) {
} }


private void validateRuleField(Field field, 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())) if (!Modifier.isPublic(field.getModifiers()))
errors.add(new Exception("Field " + field.getName() errors.add(new Exception("Field " + field.getName()
+ " must be public")); + " 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"));
}
}
} }


/** /**
Expand Down Expand Up @@ -351,15 +359,14 @@ private Statement withRules(FrameworkMethod method, Object target,
*/ */
protected List<MethodRule> rules(Object test) { protected List<MethodRule> rules(Object test) {
List<MethodRule> results= new ArrayList<MethodRule>(); List<MethodRule> results= new ArrayList<MethodRule>();
for (FrameworkField each : ruleFields()) for (FrameworkField each : ruleFields()) {
results.add(createRule(test, each)); if (MethodRule.class.isAssignableFrom(each.getField().getType())) {
results.add(createRule(test, each));
}
}
return results; return results;
} }


private List<FrameworkField> ruleFields() {
return getTestClass().getAnnotatedFields(Rule.class);
}

private MethodRule createRule(Object test, private MethodRule createRule(Object test,
FrameworkField each) { FrameworkField each) {
try { try {
Expand Down
57 changes: 56 additions & 1 deletion src/main/java/org/junit/runners/ParentRunner.java
Expand Up @@ -9,11 +9,13 @@


import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.internal.AssumptionViolatedException; import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.model.MultipleFailureException; import org.junit.internal.runners.model.MultipleFailureException;
import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores; import org.junit.internal.runners.statements.RunBefores;
import org.junit.rules.ClassRule;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.Runner; import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filter;
Expand All @@ -23,6 +25,7 @@
import org.junit.runner.manipulation.Sorter; import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException; import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError; import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.RunnerScheduler;
Expand Down Expand Up @@ -143,6 +146,7 @@ protected Statement classBlock(final RunNotifier notifier) {
Statement statement= childrenInvoker(notifier); Statement statement= childrenInvoker(notifier);
statement= withBeforeClasses(statement); statement= withBeforeClasses(statement);
statement= withAfterClasses(statement); statement= withAfterClasses(statement);
statement= withClassRules(statement);
return statement; return statement;
} }


Expand Down Expand Up @@ -172,6 +176,57 @@ protected Statement withAfterClasses(Statement statement) {
new RunAfters(statement, afters, null); 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)} * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)}
* on each object returned by {@link #getChildren()} (subject to any imposed * on each object returned by {@link #getChildren()} (subject to any imposed
Expand All @@ -188,7 +243,7 @@ public void evaluate() {


private void runChildren(final RunNotifier notifier) { private void runChildren(final RunNotifier notifier) {
for (final T each : getFilteredChildren()) for (final T each : getFilteredChildren())
fScheduler.schedule(new Runnable() { fScheduler.schedule(new Runnable() {
public void run() { public void run() {
ParentRunner.this.runChild(each, notifier); ParentRunner.this.runChild(each, notifier);
} }
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/junit/runners/model/FrameworkField.java
Expand Up @@ -33,6 +33,14 @@ public Field getField() {
return fField; 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} * Attempts to retrieve the value of this field on {@code target}
*/ */
Expand Down

1 comment on commit 79ef5a7

@dsaff
Copy link

@dsaff dsaff commented on 79ef5a7 Dec 8, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for issue 29, right?

Please sign in to comment.