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() {
return "4.7";
return "4.7.1-SNAPSHOT";
}

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.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test.None;
import org.junit.internal.AssumptionViolatedException;
Expand All @@ -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;
Expand Down Expand Up @@ -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"));
}
}
}

/**
Expand Down Expand Up @@ -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 {
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.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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand All @@ -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);
}
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 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}
*/
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.