Skip to content

Commit

Permalink
Introduce ComplexRule as a superclass to TestRule.
Browse files Browse the repository at this point in the history
Allows a single rule implementation to support @ClassRule and/or
@rule, thus allowing features of complex runners to be migrated to
something like:
@rule @ClassRule
public static SpringContextManagementRule rule =
new MySpringContextManagementRule();
  • Loading branch information
neale.upstone committed Feb 22, 2011
1 parent b4180bf commit da6c5e5
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 36 deletions.
18 changes: 18 additions & 0 deletions src/main/java/org/junit/rules/ComplexRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.junit.rules;

import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
* A rule that can apply behaviour to at both class and method level.
*
* @author Neale Upstone
*/
public abstract class ComplexRule {
protected abstract Statement applyClassRule(Statement base,
Description description, Class<?> clazz);

protected abstract Statement applyMethodRule(Statement base,
Description description, Object target);

}
33 changes: 33 additions & 0 deletions src/main/java/org/junit/rules/RunClassRules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.junit.rules;

import java.util.List;

import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;

/**
* Run the class-specific part of each Rule on a {@link Statement}
*
* @author Neale Upstone
*/
public class RunClassRules extends Statement {

private final Statement statement;

public RunClassRules(Statement base, List<ComplexRule> rules, Description description, TestClass testClass) {
statement = applyAll(base, rules, description, testClass);
}

@Override
public void evaluate() throws Throwable {
statement.evaluate();
}

private static Statement applyAll(Statement result, Iterable<ComplexRule> rules, Description description, TestClass testClass) {
for (ComplexRule each : rules) {
result = each.applyClassRule(result, description, testClass.getJavaClass());
}
return result;
}
}
32 changes: 32 additions & 0 deletions src/main/java/org/junit/rules/RunMethodRules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.junit.rules;

import java.util.List;

import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
* Run the method-specific part of each Rule on a {@link Statement}
*
* @author Neale Upstone
*/
public class RunMethodRules extends Statement {

private final Statement statement;

public RunMethodRules(Statement base, List<ComplexRule> rules, Description description, Object target) {
statement = applyAll(base, rules, description, target);
}

@Override
public void evaluate() throws Throwable {
statement.evaluate();
}

private static Statement applyAll(Statement result, Iterable<ComplexRule> rules, Description description, Object target) {
for (ComplexRule each : rules) {
result = each.applyMethodRule(result, description, target);
}
return result;
}
}
12 changes: 11 additions & 1 deletion src/main/java/org/junit/rules/TestRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* <li>{@link Verifier}: fail test if object state ends up incorrect</li>
* </ul>
*/
public abstract class TestRule {
public abstract class TestRule extends ComplexRule {
/**
* Modifies the method-running {@link Statement} to implement this
* test-running rule.
Expand All @@ -51,4 +51,14 @@ public abstract class TestRule {
* a wrapper around {@code base}, or a completely new Statement.
*/
protected abstract Statement apply(Statement base, Description description);

@Override
protected Statement applyMethodRule(Statement base, Description description, Object target) {
return apply(base, description);
}

@Override
protected Statement applyClassRule(Statement base, Description description, Class<?> clazz) {
return apply(base, description);
}
}
41 changes: 25 additions & 16 deletions src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
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.RunRules;
import org.junit.rules.ComplexRule;
import org.junit.rules.RunMethodRules;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
Expand Down Expand Up @@ -154,28 +155,32 @@ protected void validateInstanceMethods(List<Throwable> errors) {
validatePublicVoidNoArgMethods(Before.class, false, errors);
validateTestMethods(errors);

if (computeTestMethods().size() == 0)
if (computeTestMethods().size() == 0) {
errors.add(new Exception("No runnable methods"));
}
}

private void validateFields(List<Throwable> errors) {
for (FrameworkField each : getTestClass()
.getAnnotatedFields(Rule.class))
.getAnnotatedFields(Rule.class)) {
validateRuleField(each.getField(), errors);
}
}

private void validateRuleField(Field field, List<Throwable> errors) {
Class<?> type= field.getType();
if (!isMethodRule(type) && !isTestRule(type))
if (!isMethodRule(type) && !isTestRule(type)) {
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()
+ " must be public"));
}
}

private boolean isTestRule(Class<?> type) {
return TestRule.class.isAssignableFrom(type);
return ComplexRule.class.isAssignableFrom(type);
}

@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -348,10 +353,12 @@ private Statement withRules(FrameworkMethod method, Object target,
@SuppressWarnings("deprecation")
private Statement withMethodRules(FrameworkMethod method, Object target,
Statement result) {
List<TestRule> testRules= getTestRules(target);
for (org.junit.rules.MethodRule each : getMethodRules(target))
if (! testRules.contains(each))
List<ComplexRule> testRules= getTestRules(target);
for (org.junit.rules.MethodRule each : getMethodRules(target)) {
if (! testRules.contains(each)) {
result= each.apply(result, method, target);
}
}
return result;
}

Expand All @@ -371,30 +378,32 @@ private List<org.junit.rules.MethodRule> getMethodRules(Object target) {
*/
private Statement withTestRules(FrameworkMethod method, Object target,
Statement statement) {
List<TestRule> testRules= getTestRules(target);
List<ComplexRule> testRules= getTestRules(target);
return testRules.isEmpty() ? statement :
new RunRules(statement, testRules, describeChild(method));
new RunMethodRules(statement, testRules, describeChild(method), target);
}

private List<TestRule> getTestRules(Object target) {
private List<ComplexRule> getTestRules(Object target) {
return getTestClass().getAnnotatedFieldValues(target,
Rule.class, TestRule.class);
Rule.class, ComplexRule.class);
}

private Class<? extends Throwable> getExpectedException(Test annotation) {
if (annotation == null || annotation.expected() == None.class)
if (annotation == null || annotation.expected() == None.class) {
return null;
else
} else {
return annotation.expected();
}
}

private boolean expectsException(Test annotation) {
return getExpectedException(annotation) != null;
}

private long getTimeout(Test annotation) {
if (annotation == null)
if (annotation == null) {
return 0;
}
return annotation.timeout();
}
}
48 changes: 29 additions & 19 deletions src/main/java/org/junit/runners/ParentRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.rules.ComplexRule;
import org.junit.rules.RunClassRules;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
Expand Down Expand Up @@ -124,8 +124,9 @@ protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annota
boolean isStatic, List<Throwable> errors) {
List<FrameworkMethod> methods= getTestClass().getAnnotatedMethods(annotation);

for (FrameworkMethod eachTestMethod : methods)
for (FrameworkMethod eachTestMethod : methods) {
eachTestMethod.validatePublicVoidNoArg(isStatic, errors);
}
}

/**
Expand Down Expand Up @@ -189,25 +190,26 @@ protected Statement withAfterClasses(Statement statement) {
* found, or the base statement
*/
private Statement withClassRules(Statement statement) {
List<TestRule> classRules= classRules();
List<ComplexRule> classRules= classRules();
return classRules.isEmpty() ? statement :
new RunRules(statement, classRules, getDescription());
new RunClassRules(statement, classRules, getDescription(), fTestClass);
}

/**
* @return the {@code ClassRule}s that can transform the block that runs
* each method in the tested class.
*/
protected List<TestRule> classRules() {
List<TestRule> results= new ArrayList<TestRule>();
for (FrameworkField field : classRuleFields())
protected List<ComplexRule> classRules() {
List<ComplexRule> results= new ArrayList<ComplexRule>();
for (FrameworkField field : classRuleFields()) {
results.add(getClassRule(field));
}
return results;
}

private TestRule getClassRule(final FrameworkField field) {
private ComplexRule getClassRule(final FrameworkField field) {
try {
return (TestRule) field.get(null);
return (ComplexRule) field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"How did getAnnotatedFields return a field we couldn't access?");
Expand Down Expand Up @@ -236,12 +238,13 @@ public void evaluate() {
}

private void runChildren(final RunNotifier notifier) {
for (final T each : getFilteredChildren())
fScheduler.schedule(new Runnable() {
for (final T each : getFilteredChildren()) {
fScheduler.schedule(new Runnable() {
public void run() {
ParentRunner.this.runChild(each, notifier);
}
});
}
fScheduler.finished();
}

Expand Down Expand Up @@ -289,8 +292,9 @@ protected final void runLeaf(Statement statement, Description description,
public Description getDescription() {
Description description= Description.createSuiteDescription(getName(),
fTestClass.getAnnotations());
for (T child : getFilteredChildren())
for (T child : getFilteredChildren()) {
description.addChild(describeChild(child));
}
return description;
}

Expand All @@ -317,9 +321,11 @@ public void run(final RunNotifier notifier) {
public void filter(Filter filter) throws NoTestsRemainException {
fFilter= filter;

for (T each : getChildren())
if (shouldRun(each))
for (T each : getChildren()) {
if (shouldRun(each)) {
return;
}
}
throw new NoTestsRemainException();
}

Expand All @@ -334,21 +340,24 @@ public void sort(Sorter sorter) {
private void validate() throws InitializationError {
List<Throwable> errors= new ArrayList<Throwable>();
collectInitializationErrors(errors);
if (!errors.isEmpty())
if (!errors.isEmpty()) {
throw new InitializationError(errors);
}
}

private List<T> getFilteredChildren() {
ArrayList<T> filtered= new ArrayList<T>();
for (T each : getChildren())
if (shouldRun(each))
for (T each : getChildren()) {
if (shouldRun(each)) {
try {
filterChild(each);
sortChild(each);
filtered.add(each);
} catch (NoTestsRemainException e) {
// don't add it
}
}
}
Collections.sort(filtered, comparator());
return filtered;
}
Expand All @@ -358,8 +367,9 @@ private void sortChild(T child) {
}

private void filterChild(T child) throws NoTestsRemainException {
if (fFilter != null)
if (fFilter != null) {
fFilter.apply(child);
}
}

private boolean shouldRun(T each) {
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/org/junit/tests/AllTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.tests.experimental.parallel.ParallelClassTest;
import org.junit.tests.experimental.parallel.ParallelMethodTest;
import org.junit.tests.experimental.rules.ClassRulesTest;
import org.junit.tests.experimental.rules.ComplexRulesTest;
import org.junit.tests.experimental.rules.ExpectedExceptionRuleTest;
import org.junit.tests.experimental.rules.ExternalResourceRuleTest;
import org.junit.tests.experimental.rules.MethodRulesTest;
Expand Down Expand Up @@ -138,6 +139,7 @@
ParentRunnerTest.class,
NameRulesTest.class,
ClassRulesTest.class,
ComplexRulesTest.class,
ExpectedExceptionRuleTest.class,
TempFolderRuleTest.class,
ExternalResourceRuleTest.class,
Expand Down
Loading

0 comments on commit da6c5e5

Please sign in to comment.