Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring Suite, Parameterized and class runners to allow reuse in custom Runners #1348

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 74 additions & 46 deletions src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
Expand Up @@ -30,6 +30,7 @@
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;

/**
* Implements the JUnit 4 standard test case class model, as defined by the
Expand All @@ -49,7 +50,7 @@
* JUnit4ClassRunner} was in an internal package, and is now deprecated.
* </ul>
* <p>
* In turn, in 2009 we introduced {@link Rule}s. In many cases where extending
* In turn, in 2009 we introduced {@link Rule}s. In many cases where extending
* BlockJUnit4ClassRunner was necessary to add new behavior, {@link Rule}s can
* be used, which makes the extension more reusable and composable.
*
Expand All @@ -62,9 +63,11 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
/**
* Creates a BlockJUnit4ClassRunner to run {@code testClass}
*
* @throws InitializationError if the test class is malformed.
* @throws InitializationError
* if the test class is malformed.
*/
public BlockJUnit4ClassRunner(Class<?> testClass) throws InitializationError {
public BlockJUnit4ClassRunner(Class<?> testClass)
throws InitializationError {
super(testClass);
}

Expand All @@ -73,16 +76,16 @@ public BlockJUnit4ClassRunner(Class<?> testClass) throws InitializationError {
//

@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
protected void runChild(final FrameworkMethod method,
RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
Statement statement;
try {
statement = methodBlock(method);
}
catch (Throwable ex) {
} catch (Throwable ex) {
statement = new Fail(ex);
}
runLeaf(statement, description, notifier);
Expand All @@ -103,8 +106,9 @@ protected Description describeChild(FrameworkMethod method) {
Description description = methodDescriptions.get(method);

if (description == null) {
description = Description.createTestDescription(getTestClass().getJavaClass(),
testName(method), method.getAnnotations());
description = Description.createTestDescription(
getTestClass().getJavaClass(), testName(method),
method.getAnnotations());
methodDescriptions.putIfAbsent(method, description);
}

Expand Down Expand Up @@ -174,9 +178,9 @@ protected void validateOnlyOneConstructor(List<Throwable> errors) {
* parameters (do not override)
*/
protected void validateZeroArgConstructor(List<Throwable> errors) {
if (!getTestClass().isANonStaticInnerClass()
&& hasOneConstructor()
&& (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) {
if (!getTestClass().isANonStaticInnerClass() && hasOneConstructor()
&& (getTestClass().getOnlyConstructor()
.getParameterTypes().length != 0)) {
String gripe = "Test class should have exactly one public zero-argument constructor";
errors.add(new Exception(gripe));
}
Expand All @@ -190,6 +194,7 @@ private boolean hasOneConstructor() {
* Adds to {@code errors} for each method annotated with {@code @Test},
* {@code @Before}, or {@code @After} that is not a public, void instance
* method with no arguments.
*
* @deprecated
*/
@Deprecated
Expand Down Expand Up @@ -230,7 +235,8 @@ protected Object createTest() throws Exception {

/**
* Returns a new fixture to run a particular test {@code method} against.
* Default implementation executes the no-argument {@link #createTest()} method.
* Default implementation executes the no-argument {@link #createTest()}
* method.
*
* @since 4.13
*/
Expand All @@ -253,8 +259,9 @@ protected String testName(FrameworkMethod method) {
* Here is an outline of the default implementation:
*
* <ul>
* <li>Invoke {@code method} on the result of {@link #createTest(org.junit.runners.model.FrameworkMethod)}, and
* throw any exceptions thrown by either operation.
* <li>Invoke {@code method} on the result of
* {@link #createTest(org.junit.runners.model.FrameworkMethod)}, and throw
* any exceptions thrown by either operation.
* <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code
* expecting} attribute, return normally only if the previous step threw an
* exception of the correct type, and throw an exception otherwise.
Expand Down Expand Up @@ -320,14 +327,16 @@ protected Statement methodInvoker(FrameworkMethod method, Object test) {
protected Statement possiblyExpectingExceptions(FrameworkMethod method,
Object test, Statement next) {
Test annotation = method.getAnnotation(Test.class);
return expectsException(annotation) ? new ExpectException(next,
getExpectedException(annotation)) : next;
return expectsException(annotation)
? new ExpectException(next, getExpectedException(annotation))
: next;
}

/**
* Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation
* has the {@code timeout} attribute, throw an exception if {@code next}
* takes more than the specified number of milliseconds.
*
* @deprecated
*/
@Deprecated
Expand All @@ -338,8 +347,7 @@ protected Statement withPotentialTimeout(FrameworkMethod method,
return next;
}
return FailOnTimeout.builder()
.withTimeout(timeout, TimeUnit.MILLISECONDS)
.build(next);
.withTimeout(timeout, TimeUnit.MILLISECONDS).build(next);
}

/**
Expand All @@ -349,10 +357,10 @@ protected Statement withPotentialTimeout(FrameworkMethod method,
*/
protected Statement withBefores(FrameworkMethod method, Object target,
Statement statement) {
List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(
Before.class);
return befores.isEmpty() ? statement : new RunBefores(statement,
befores, target);
List<FrameworkMethod> befores = getTestClass()
.getAnnotatedMethods(Before.class);
return befores.isEmpty() ? statement
: new RunBefores(statement, befores, target);
}

/**
Expand All @@ -364,24 +372,24 @@ protected Statement withBefores(FrameworkMethod method, Object target,
*/
protected Statement withAfters(FrameworkMethod method, Object target,
Statement statement) {
List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(
After.class);
return afters.isEmpty() ? statement : new RunAfters(statement, afters,
target);
List<FrameworkMethod> afters = getTestClass()
.getAnnotatedMethods(After.class);
return afters.isEmpty() ? statement
: new RunAfters(statement, afters, target);
}

private Statement withRules(FrameworkMethod method, Object target,
Statement statement) {
List<TestRule> testRules = getTestRules(target);
Statement result = statement;
result = withMethodRules(method, testRules, target, result);
result = withTestRules(method, testRules, result);
result = withTestRules(testRules, describeChild(method), result);

return result;
}

private Statement withMethodRules(FrameworkMethod method, List<TestRule> testRules,
Object target, Statement result) {
private Statement withMethodRules(FrameworkMethod method,
List<TestRule> testRules, Object target, Statement result) {
Statement withMethodRules = result;
for (org.junit.rules.MethodRule each : getMethodRules(target)) {
if (!(each instanceof TestRule && testRules.contains(each))) {
Expand All @@ -396,45 +404,65 @@ private List<org.junit.rules.MethodRule> getMethodRules(Object target) {
}

/**
* @param target the test case instance
* @param target
* the test case instance
* @return a list of MethodRules that should be applied when executing this
* test
*/
protected List<MethodRule> rules(Object target) {
List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(target,
List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(target,
Rule.class, MethodRule.class);

rules.addAll(getTestClass().getAnnotatedFieldValues(target,
Rule.class, MethodRule.class));
rules.addAll(getTestClass().getAnnotatedFieldValues(target, Rule.class,
MethodRule.class));

return rules;
}

/**
* Returns a {@link Statement}: apply all non-static fields
* annotated with {@link Rule}.
* Returns a {@link Statement}: apply all non-static fields annotated with
* {@link Rule}.
*
* @param description
* The description passed to the {@link Rule}
* @param statement
* The base statement
*
* @param statement The base statement
* @return a RunRules statement if any class-level {@link Rule}s are
* found, or the base statement
* @return a RunRules statement if any class-level {@link Rule}s are found,
* or the base statement
*/
private Statement withTestRules(FrameworkMethod method, List<TestRule> testRules,
Statement statement) {
return testRules.isEmpty() ? statement :
new RunRules(statement, testRules, describeChild(method));
public static Statement withTestRules(List<TestRule> testRules,
Description description, Statement statement) {
return testRules.isEmpty() ? statement
: new RunRules(statement, testRules, description);
}

/**
* @param target the test case instance
* @param target
* the test case instance
* @return a list of TestRules that should be applied when executing this
* test
*/
protected List<TestRule> getTestRules(Object target) {
List<TestRule> result = getTestClass().getAnnotatedMethodValues(target,
return getTestRules(target, getTestClass());
}

/**
* @param target
* the test case instance
* @param testClass
* the {@link TestClass} where the {@link TestRule} annotations have
* been defined.
* @return a list of TestRules that should be applied when executing this
* test
*/
public static List<TestRule> getTestRules(Object target,
TestClass testClass) {
List<TestRule> result = testClass.getAnnotatedMethodValues(target,
Rule.class, TestRule.class);

result.addAll(getTestClass().getAnnotatedFieldValues(target,
Rule.class, TestRule.class));
result.addAll(testClass.getAnnotatedFieldValues(target, Rule.class,
TestRule.class));

return result;
}
Expand Down
55 changes: 8 additions & 47 deletions src/main/java/org/junit/runners/Parameterized.java
Expand Up @@ -12,7 +12,7 @@
import java.util.List;

import org.junit.runner.Runner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.ParameterizedTestClass;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
import org.junit.runners.parameterized.ParametersRunnerFactory;
Expand Down Expand Up @@ -244,22 +244,20 @@ public Parameterized(Class<?> klass) throws Throwable {
private static class RunnersFactory {
private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();

private final TestClass testClass;
private final ParameterizedTestClass testClass;

static List<Runner> createRunnersForClass(Class<?> klass)
throws Throwable {
return new RunnersFactory(klass).createRunners();
}

private RunnersFactory(Class<?> klass) {
testClass = new TestClass(klass);
testClass = new ParameterizedTestClass(klass);
}

private List<Runner> createRunners() throws Throwable {
Parameters parameters = getParametersMethod().getAnnotation(
Parameters.class);
return Collections.unmodifiableList(createRunnersForParameters(
allParameters(), parameters.name(),
testClass.allParameters(), testClass.getNamePatternForParameters(),
getParametersRunnerFactory()));
}

Expand All @@ -278,37 +276,11 @@ private ParametersRunnerFactory getParametersRunnerFactory()

private TestWithParameters createTestWithNotNormalizedParameters(
String pattern, int index, Object parametersOrSingleParameter) {
Object[] parameters = (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
: new Object[] { parametersOrSingleParameter };
Object[] parameters = ParameterizedTestClass.normalizeParameter(parametersOrSingleParameter);
return createTestWithParameters(testClass, pattern, index,
parameters);
}

@SuppressWarnings("unchecked")
private Iterable<Object> allParameters() throws Throwable {
Object parameters = getParametersMethod().invokeExplosively(null);
if (parameters instanceof Iterable) {
return (Iterable<Object>) parameters;
} else if (parameters instanceof Object[]) {
return Arrays.asList((Object[]) parameters);
} else {
throw parametersMethodReturnedWrongType();
}
}

private FrameworkMethod getParametersMethod() throws Exception {
List<FrameworkMethod> methods = testClass
.getAnnotatedMethods(Parameters.class);
for (FrameworkMethod each : methods) {
if (each.isStatic() && each.isPublic()) {
return each;
}
}

throw new Exception("No public static parameters method on class "
+ testClass.getName());
}

private List<Runner> createRunnersForParameters(
Iterable<Object> allParameters, String namePattern,
ParametersRunnerFactory runnerFactory) throws Exception {
Expand All @@ -322,7 +294,7 @@ private List<Runner> createRunnersForParameters(
}
return runners;
} catch (ClassCastException e) {
throw parametersMethodReturnedWrongType();
throw testClass.parametersMethodReturnedWrongType();
}
}

Expand All @@ -338,22 +310,11 @@ private List<TestWithParameters> createTestsForParameters(
return children;
}

private Exception parametersMethodReturnedWrongType() throws Exception {
String className = testClass.getName();
String methodName = getParametersMethod().getName();
String message = MessageFormat.format(
"{0}.{1}() must return an Iterable of arrays.", className,
methodName);
return new Exception(message);
}

private TestWithParameters createTestWithParameters(
TestClass testClass, String pattern, int index,
Object[] parameters) {
String finalPattern = pattern.replaceAll("\\{index\\}",
Integer.toString(index));
String name = MessageFormat.format(finalPattern, parameters);
return new TestWithParameters("[" + name + "]", testClass,
String testName = ParameterizedTestClass.produceTestName(pattern, index, parameters);
return new TestWithParameters(testName, testClass,
Arrays.asList(parameters));
}
}
Expand Down