aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/runners
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/runners')
-rw-r--r--src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java172
-rw-r--r--src/main/java/org/junit/runners/JUnit4.java3
-rw-r--r--src/main/java/org/junit/runners/Parameterized.java405
-rw-r--r--[-rwxr-xr-x]src/main/java/org/junit/runners/ParentRunner.java176
-rw-r--r--src/main/java/org/junit/runners/RuleContainer.java113
-rw-r--r--src/main/java/org/junit/runners/Suite.java4
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkField.java21
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkMember.java24
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkMethod.java14
-rw-r--r--src/main/java/org/junit/runners/model/InitializationError.java2
-rw-r--r--src/main/java/org/junit/runners/model/InvalidTestClassError.java39
-rw-r--r--src/main/java/org/junit/runners/model/MemberValueConsumer.java18
-rw-r--r--src/main/java/org/junit/runners/model/MultipleFailureException.java41
-rw-r--r--src/main/java/org/junit/runners/model/RunnerBuilder.java30
-rw-r--r--[-rwxr-xr-x]src/main/java/org/junit/runners/model/TestClass.java57
-rw-r--r--src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java94
-rw-r--r--src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java2
-rw-r--r--src/main/java/org/junit/runners/parameterized/TestWithParameters.java7
18 files changed, 962 insertions, 260 deletions
diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
index 4d06199..455341a 100644
--- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
+++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
@@ -3,8 +3,10 @@ package org.junit.runners;
import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR;
import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.junit.After;
@@ -21,14 +23,18 @@ 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.MethodRule;
-import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.FrameworkMember;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.MemberValueConsumer;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+import org.junit.validator.PublicClassValidator;
+import org.junit.validator.TestClassValidator;
/**
* Implements the JUnit 4 standard test case class model, as defined by the
@@ -55,14 +61,27 @@ import org.junit.runners.model.Statement;
* @since 4.5
*/
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
- private final ConcurrentHashMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>();
+ private static TestClassValidator PUBLIC_CLASS_VALIDATOR = new PublicClassValidator();
+
+ private final ConcurrentMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>();
+
+ /**
+ * Creates a BlockJUnit4ClassRunner to run {@code testClass}
+ *
+ * @throws InitializationError if the test class is malformed.
+ */
+ public BlockJUnit4ClassRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
/**
- * Creates a BlockJUnit4ClassRunner to run {@code klass}
+ * Creates a BlockJUnit4ClassRunner to run {@code testClass}.
*
* @throws InitializationError if the test class is malformed.
+ * @since 4.13
*/
- public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError {
- super(klass);
+ protected BlockJUnit4ClassRunner(TestClass testClass) throws InitializationError {
+ super(testClass);
}
//
@@ -75,10 +94,16 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
- runLeaf(methodBlock(method), description, notifier);
+ Statement statement = new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ methodBlock(method).evaluate();
+ }
+ };
+ runLeaf(statement, description, notifier);
}
}
-
+
/**
* Evaluates whether {@link FrameworkMethod}s are ignored based on the
* {@link Ignore} annotation.
@@ -123,6 +148,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
protected void collectInitializationErrors(List<Throwable> errors) {
super.collectInitializationErrors(errors);
+ validatePublicConstructor(errors);
validateNoNonStaticInnerClass(errors);
validateConstructor(errors);
validateInstanceMethods(errors);
@@ -130,6 +156,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
validateMethods(errors);
}
+ private void validatePublicConstructor(List<Throwable> errors) {
+ if (getTestClass().getJavaClass() != null) {
+ errors.addAll(PUBLIC_CLASS_VALIDATOR.validateTestClass(getTestClass()));
+ }
+ }
+
protected void validateNoNonStaticInnerClass(List<Throwable> errors) {
if (getTestClass().isANonStaticInnerClass()) {
String gripe = "The inner class " + getTestClass().getName()
@@ -180,6 +212,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
* 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
protected void validateInstanceMethods(List<Throwable> errors) {
@@ -187,7 +220,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
validatePublicVoidNoArgMethods(Before.class, false, errors);
validateTestMethods(errors);
- if (computeTestMethods().size() == 0) {
+ if (computeTestMethods().isEmpty()) {
errors.add(new Exception("No runnable methods"));
}
}
@@ -218,6 +251,16 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
}
/**
+ * Returns a new fixture to run a particular test {@code method} against.
+ * Default implementation executes the no-argument {@link #createTest()} method.
+ *
+ * @since 4.13
+ */
+ protected Object createTest(FrameworkMethod method) throws Exception {
+ return createTest();
+ }
+
+ /**
* Returns the name that describes {@code method} for {@link Description}s.
* Default implementation is the method's name
*/
@@ -232,10 +275,10 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
* Here is an outline of the default implementation:
*
* <ul>
- * <li>Invoke {@code method} on the result of {@code createTest()}, and
+ * <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
+ * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@link Test#expected()}
+ * attribute, return normally only if the previous step threw an
* exception of the correct type, and throw an exception otherwise.
* <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code
* timeout} attribute, throw an exception if the previous step takes more
@@ -257,13 +300,13 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
* This can be overridden in subclasses, either by overriding this method,
* or the implementations creating each sub-statement.
*/
- protected Statement methodBlock(FrameworkMethod method) {
+ protected Statement methodBlock(final FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
- return createTest();
+ return createTest(method);
}
}.run();
} catch (Throwable e) {
@@ -276,6 +319,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
+ statement = withInterruptIsolation(statement);
return statement;
}
@@ -292,21 +336,22 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
/**
* Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation
- * has the {@code expecting} attribute, return normally only if {@code next}
+ * has the {@link Test#expected()} attribute, return normally only if {@code next}
* throws an exception of the correct type, and throw an exception
* otherwise.
*/
protected Statement possiblyExpectingExceptions(FrameworkMethod method,
Object test, Statement next) {
Test annotation = method.getAnnotation(Test.class);
- return expectsException(annotation) ? new ExpectException(next,
- getExpectedException(annotation)) : next;
+ Class<? extends Throwable> expectedExceptionClass = getExpectedException(annotation);
+ return expectedExceptionClass != null ? new ExpectException(next, expectedExceptionClass) : 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
protected Statement withPotentialTimeout(FrameworkMethod method,
@@ -348,28 +393,23 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
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);
-
- return result;
- }
-
- private Statement withMethodRules(FrameworkMethod method, List<TestRule> testRules,
- Object target, Statement result) {
- for (org.junit.rules.MethodRule each : getMethodRules(target)) {
- if (!testRules.contains(each)) {
- result = each.apply(result, method, target);
+ private Statement withRules(FrameworkMethod method, Object target, Statement statement) {
+ RuleContainer ruleContainer = new RuleContainer();
+ CURRENT_RULE_CONTAINER.set(ruleContainer);
+ try {
+ List<TestRule> testRules = getTestRules(target);
+ for (MethodRule each : rules(target)) {
+ if (!(each instanceof TestRule && testRules.contains(each))) {
+ ruleContainer.add(each);
+ }
}
+ for (TestRule rule : testRules) {
+ ruleContainer.add(rule);
+ }
+ } finally {
+ CURRENT_RULE_CONTAINER.remove();
}
- return result;
- }
-
- private List<org.junit.rules.MethodRule> getMethodRules(Object target) {
- return rules(target);
+ return ruleContainer.apply(method, describeChild(method), target, statement);
}
/**
@@ -378,27 +418,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
* test
*/
protected List<MethodRule> rules(Object target) {
- List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(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}.
- *
- * @param statement 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));
+ RuleCollector<MethodRule> collector = new RuleCollector<MethodRule>();
+ getTestClass().collectAnnotatedMethodValues(target, Rule.class, MethodRule.class,
+ collector);
+ getTestClass().collectAnnotatedFieldValues(target, Rule.class, MethodRule.class,
+ collector);
+ return collector.result;
}
/**
@@ -407,13 +432,10 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
* test
*/
protected List<TestRule> getTestRules(Object target) {
- List<TestRule> result = getTestClass().getAnnotatedMethodValues(target,
- Rule.class, TestRule.class);
-
- result.addAll(getTestClass().getAnnotatedFieldValues(target,
- Rule.class, TestRule.class));
-
- return result;
+ RuleCollector<TestRule> collector = new RuleCollector<TestRule>();
+ getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector);
+ getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector);
+ return collector.result;
}
private Class<? extends Throwable> getExpectedException(Test annotation) {
@@ -424,14 +446,28 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
}
}
- private boolean expectsException(Test annotation) {
- return getExpectedException(annotation) != null;
- }
-
private long getTimeout(Test annotation) {
if (annotation == null) {
return 0;
}
return annotation.timeout();
}
+
+ private static final ThreadLocal<RuleContainer> CURRENT_RULE_CONTAINER =
+ new ThreadLocal<RuleContainer>();
+
+ private static class RuleCollector<T> implements MemberValueConsumer<T> {
+ final List<T> result = new ArrayList<T>();
+
+ public void accept(FrameworkMember<?> member, T value) {
+ Rule rule = member.getAnnotation(Rule.class);
+ if (rule != null) {
+ RuleContainer container = CURRENT_RULE_CONTAINER.get();
+ if (container != null) {
+ container.setOrder(value, rule.order());
+ }
+ }
+ result.add(value);
+ }
+ }
}
diff --git a/src/main/java/org/junit/runners/JUnit4.java b/src/main/java/org/junit/runners/JUnit4.java
index 6ba28c2..28eafb3 100644
--- a/src/main/java/org/junit/runners/JUnit4.java
+++ b/src/main/java/org/junit/runners/JUnit4.java
@@ -1,6 +1,7 @@
package org.junit.runners;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
/**
* Aliases the current default JUnit 4 class runner, for future-proofing. If
@@ -19,6 +20,6 @@ public final class JUnit4 extends BlockJUnit4ClassRunner {
* Constructs a new instance of the default runner
*/
public JUnit4(Class<?> klass) throws InitializationError {
- super(klass);
+ super(new TestClass(klass));
}
}
diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java
index 829c8f0..d11b66a 100644
--- a/src/main/java/org/junit/runners/Parameterized.java
+++ b/src/main/java/org/junit/runners/Parameterized.java
@@ -1,5 +1,6 @@
package org.junit.runners;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
@@ -8,12 +9,18 @@ import java.lang.annotation.Target;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.InvalidTestClassError;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
import org.junit.runners.parameterized.ParametersRunnerFactory;
@@ -24,34 +31,37 @@ import org.junit.runners.parameterized.TestWithParameters;
* When running a parameterized test class, instances are created for the
* cross-product of the test methods and the test data elements.
* <p>
- * For example, to test a Fibonacci function, write:
+ * For example, to test the <code>+</code> operator, write:
* <pre>
* &#064;RunWith(Parameterized.class)
- * public class FibonacciTest {
- * &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
+ * public class AdditionTest {
+ * &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
* public static Iterable&lt;Object[]&gt; data() {
- * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
+ * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
+ * { 3, 2, 5 }, { 4, 3, 7 } });
* }
*
- * private int fInput;
+ * private int firstSummand;
*
- * private int fExpected;
+ * private int secondSummand;
*
- * public FibonacciTest(int input, int expected) {
- * fInput= input;
- * fExpected= expected;
+ * private int sum;
+ *
+ * public AdditionTest(int firstSummand, int secondSummand, int sum) {
+ * this.firstSummand = firstSummand;
+ * this.secondSummand = secondSummand;
+ * this.sum = sum;
* }
*
* &#064;Test
* public void test() {
- * assertEquals(fExpected, Fibonacci.compute(fInput));
+ * assertEquals(sum, firstSummand + secondSummand);
* }
* }
* </pre>
* <p>
- * Each instance of <code>FibonacciTest</code> will be constructed using the
- * two-argument constructor and the data values in the
+ * Each instance of <code>AdditionTest</code> will be constructed using the
+ * three-argument constructor and the data values in the
* <code>&#064;Parameters</code> method.
* <p>
* In order that you can easily identify the individual tests, you may provide a
@@ -69,33 +79,36 @@ import org.junit.runners.parameterized.TestWithParameters;
* </dl>
* <p>
* In the example given above, the <code>Parameterized</code> runner creates
- * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter,
+ * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter,
* then the current parameter index is used as name.
* <p>
* You can also write:
* <pre>
* &#064;RunWith(Parameterized.class)
- * public class FibonacciTest {
- * &#064;Parameters
- * public static Iterable&lt;Object[]&gt; data() {
- * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
- * }
- *
- * &#064;Parameter(0)
- * public int fInput;
+ * public class AdditionTest {
+ * &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
+ * public static Iterable&lt;Object[]&gt; data() {
+ * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
+ * { 3, 2, 5 }, { 4, 3, 7 } });
+ * }
+ *
+ * &#064;Parameter(0)
+ * public int firstSummand;
*
- * &#064;Parameter(1)
- * public int fExpected;
+ * &#064;Parameter(1)
+ * public int secondSummand;
*
- * &#064;Test
- * public void test() {
- * assertEquals(fExpected, Fibonacci.compute(fInput));
- * }
+ * &#064;Parameter(2)
+ * public int sum;
+ *
+ * &#064;Test
+ * public void test() {
+ * assertEquals(sum, firstSummand + secondSummand);
+ * }
* }
* </pre>
* <p>
- * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
+ * Each instance of <code>AdditionTest</code> will be constructed with the default constructor
* and fields annotated by <code>&#064;Parameter</code> will be initialized
* with the data values in the <code>&#064;Parameters</code> method.
*
@@ -105,8 +118,7 @@ import org.junit.runners.parameterized.TestWithParameters;
* <pre>
* &#064;Parameters
* public static Object[][] data() {
- * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
- * { 5, 5 }, { 6, 8 } };
+ * return new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } };
* }
* </pre>
*
@@ -130,6 +142,19 @@ import org.junit.runners.parameterized.TestWithParameters;
* }
* </pre>
*
+ * <h3>Executing code before/after executing tests for specific parameters</h3>
+ * <p>
+ * If your test needs to perform some preparation or cleanup based on the
+ * parameters, this can be done by adding public static methods annotated with
+ * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no
+ * parameters or the same parameters as the test.
+ * <pre>
+ * &#064;BeforeParam
+ * public static void beforeTestsForParameter(String onlyParameter) {
+ * System.out.println("Testing " + onlyParameter);
+ * }
+ * </pre>
+ *
* <h3>Create different runners</h3>
* <p>
* By default the {@code Parameterized} runner creates a slightly modified
@@ -141,7 +166,7 @@ import org.junit.runners.parameterized.TestWithParameters;
* The factory must have a public zero-arg constructor.
*
* <pre>
- * public class YourRunnerFactory implements ParameterizedRunnerFactory {
+ * public class YourRunnerFactory implements ParametersRunnerFactory {
* public Runner createRunnerForTestWithParameters(TestWithParameters test)
* throws InitializationError {
* return YourRunner(test);
@@ -160,6 +185,21 @@ import org.junit.runners.parameterized.TestWithParameters;
* }
* </pre>
*
+ * <h3>Avoid creating parameters</h3>
+ * <p>With {@link org.junit.Assume assumptions} you can dynamically skip tests.
+ * Assumptions are also supported by the <code>&#064;Parameters</code> method.
+ * Creating parameters is stopped when the assumption fails and none of the
+ * tests in the test class is executed. JUnit reports a
+ * {@link Result#getAssumptionFailureCount() single assumption failure} for the
+ * whole test class in this case.
+ * <pre>
+ * &#064;Parameters
+ * public static Iterable&lt;? extends Object&gt; data() {
+ * String os = System.getProperty("os.name").toLowerCase()
+ * Assume.assumeTrue(os.contains("win"));
+ * return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
+ * }
+ * </pre>
* @since 4.0
*/
public class Parameterized extends Suite {
@@ -170,7 +210,7 @@ public class Parameterized extends Suite {
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
- public static @interface Parameters {
+ public @interface Parameters {
/**
* Optional pattern to derive the test's name from the parameters. Use
* numbers in braces to refer to the parameters or the additional data
@@ -201,7 +241,7 @@ public class Parameterized extends Suite {
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
- public static @interface Parameter {
+ public @interface Parameter {
/**
* Method that returns the index of the parameter in the array
* returned by the method annotated by <code>Parameters</code>.
@@ -230,122 +270,235 @@ public class Parameterized extends Suite {
Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class;
}
- private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
-
- private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList();
+ /**
+ * Annotation for {@code public static void} methods which should be executed before
+ * evaluating tests with particular parameters.
+ *
+ * @see org.junit.BeforeClass
+ * @see org.junit.Before
+ * @since 4.13
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface BeforeParam {
+ }
- private final List<Runner> runners;
+ /**
+ * Annotation for {@code public static void} methods which should be executed after
+ * evaluating tests with particular parameters.
+ *
+ * @see org.junit.AfterClass
+ * @see org.junit.After
+ * @since 4.13
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface AfterParam {
+ }
/**
* Only called reflectively. Do not use programmatically.
*/
public Parameterized(Class<?> klass) throws Throwable {
- super(klass, NO_RUNNERS);
- ParametersRunnerFactory runnerFactory = getParametersRunnerFactory(
- klass);
- Parameters parameters = getParametersMethod().getAnnotation(
- Parameters.class);
- runners = Collections.unmodifiableList(createRunnersForParameters(
- allParameters(), parameters.name(), runnerFactory));
+ this(klass, new RunnersFactory(klass));
}
- private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass)
- throws InstantiationException, IllegalAccessException {
- UseParametersRunnerFactory annotation = klass
- .getAnnotation(UseParametersRunnerFactory.class);
- if (annotation == null) {
- return DEFAULT_FACTORY;
- } else {
- Class<? extends ParametersRunnerFactory> factoryClass = annotation
- .value();
- return factoryClass.newInstance();
- }
+ private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception {
+ super(klass, runnersFactory.createRunners());
+ validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount);
}
- @Override
- protected List<Runner> getChildren() {
- return runners;
+ private void validateBeforeParamAndAfterParamMethods(Integer parameterCount)
+ throws InvalidTestClassError {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors);
+ validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors);
+ if (!errors.isEmpty()) {
+ throw new InvalidTestClassError(getTestClass().getJavaClass(), errors);
+ }
}
- private TestWithParameters createTestWithNotNormalizedParameters(
- String pattern, int index, Object parametersOrSingleParameter) {
- Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
- : new Object[] { parametersOrSingleParameter };
- return createTestWithParameters(getTestClass(), pattern, index,
- parameters);
+ private void validatePublicStaticVoidMethods(
+ Class<? extends Annotation> annotation, Integer parameterCount,
+ List<Throwable> errors) {
+ List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation);
+ for (FrameworkMethod fm : methods) {
+ fm.validatePublicVoid(true, errors);
+ if (parameterCount != null) {
+ int methodParameterCount = fm.getMethod().getParameterTypes().length;
+ if (methodParameterCount != 0 && methodParameterCount != parameterCount) {
+ errors.add(new Exception("Method " + fm.getName()
+ + "() should have 0 or " + parameterCount + " parameter(s)"));
+ }
+ }
+ }
}
- @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 static class AssumptionViolationRunner extends Runner {
+ private final Description description;
+ private final AssumptionViolatedException exception;
+
+ AssumptionViolationRunner(TestClass testClass, String methodName,
+ AssumptionViolatedException exception) {
+ this.description = Description
+ .createTestDescription(testClass.getJavaClass(),
+ methodName + "() assumption violation");
+ this.exception = exception;
+ }
+
+ @Override
+ public Description getDescription() {
+ return description;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ notifier.fireTestAssumptionFailed(new Failure(description, exception));
}
}
- private FrameworkMethod getParametersMethod() throws Exception {
- List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(
- Parameters.class);
- for (FrameworkMethod each : methods) {
- if (each.isStatic() && each.isPublic()) {
- return each;
+ private static class RunnersFactory {
+ private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
+
+ private final TestClass testClass;
+ private final FrameworkMethod parametersMethod;
+ private final List<Object> allParameters;
+ private final int parameterCount;
+ private final Runner runnerOverride;
+
+ private RunnersFactory(Class<?> klass) throws Throwable {
+ testClass = new TestClass(klass);
+ parametersMethod = getParametersMethod(testClass);
+ List<Object> allParametersResult;
+ AssumptionViolationRunner assumptionViolationRunner = null;
+ try {
+ allParametersResult = allParameters(testClass, parametersMethod);
+ } catch (AssumptionViolatedException e) {
+ allParametersResult = Collections.emptyList();
+ assumptionViolationRunner = new AssumptionViolationRunner(testClass,
+ parametersMethod.getName(), e);
}
+ allParameters = allParametersResult;
+ runnerOverride = assumptionViolationRunner;
+ parameterCount =
+ allParameters.isEmpty() ? 0 : normalizeParameters(allParameters.get(0)).length;
}
- throw new Exception("No public static parameters method on class "
- + getTestClass().getName());
- }
+ private List<Runner> createRunners() throws Exception {
+ if (runnerOverride != null) {
+ return Collections.singletonList(runnerOverride);
+ }
+ Parameters parameters = parametersMethod.getAnnotation(Parameters.class);
+ return Collections.unmodifiableList(createRunnersForParameters(
+ allParameters, parameters.name(),
+ getParametersRunnerFactory()));
+ }
- private List<Runner> createRunnersForParameters(
- Iterable<Object> allParameters, String namePattern,
- ParametersRunnerFactory runnerFactory)
- throws InitializationError,
- Exception {
- try {
- List<TestWithParameters> tests = createTestsForParameters(
- allParameters, namePattern);
- List<Runner> runners = new ArrayList<Runner>();
- for (TestWithParameters test : tests) {
- runners.add(runnerFactory
- .createRunnerForTestWithParameters(test));
+ private ParametersRunnerFactory getParametersRunnerFactory()
+ throws InstantiationException, IllegalAccessException {
+ UseParametersRunnerFactory annotation = testClass
+ .getAnnotation(UseParametersRunnerFactory.class);
+ if (annotation == null) {
+ return DEFAULT_FACTORY;
+ } else {
+ Class<? extends ParametersRunnerFactory> factoryClass = annotation
+ .value();
+ return factoryClass.newInstance();
}
- return runners;
- } catch (ClassCastException e) {
- throw parametersMethodReturnedWrongType();
}
- }
- private List<TestWithParameters> createTestsForParameters(
- Iterable<Object> allParameters, String namePattern)
- throws Exception {
- int i = 0;
- List<TestWithParameters> children = new ArrayList<TestWithParameters>();
- for (Object parametersOfSingleTest : allParameters) {
- children.add(createTestWithNotNormalizedParameters(namePattern,
- i++, parametersOfSingleTest));
+ private TestWithParameters createTestWithNotNormalizedParameters(
+ String pattern, int index, Object parametersOrSingleParameter) {
+ Object[] parameters = normalizeParameters(parametersOrSingleParameter);
+ return createTestWithParameters(testClass, pattern, index, parameters);
}
- return children;
- }
- private Exception parametersMethodReturnedWrongType() throws Exception {
- String className = getTestClass().getName();
- String methodName = getParametersMethod().getName();
- String message = MessageFormat.format(
- "{0}.{1}() must return an Iterable of arrays.",
- className, methodName);
- return new Exception(message);
- }
+ private static Object[] normalizeParameters(Object parametersOrSingleParameter) {
+ return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
+ : new Object[] { parametersOrSingleParameter };
+ }
- private static 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,
- Arrays.asList(parameters));
+ @SuppressWarnings("unchecked")
+ private static List<Object> allParameters(
+ TestClass testClass, FrameworkMethod parametersMethod) throws Throwable {
+ Object parameters = parametersMethod.invokeExplosively(null);
+ if (parameters instanceof List) {
+ return (List<Object>) parameters;
+ } else if (parameters instanceof Collection) {
+ return new ArrayList<Object>((Collection<Object>) parameters);
+ } else if (parameters instanceof Iterable) {
+ List<Object> result = new ArrayList<Object>();
+ for (Object entry : ((Iterable<Object>) parameters)) {
+ result.add(entry);
+ }
+ return result;
+ } else if (parameters instanceof Object[]) {
+ return Arrays.asList((Object[]) parameters);
+ } else {
+ throw parametersMethodReturnedWrongType(testClass, parametersMethod);
+ }
+ }
+
+ private static FrameworkMethod getParametersMethod(TestClass testClass) 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 {
+ try {
+ List<TestWithParameters> tests = createTestsForParameters(
+ allParameters, namePattern);
+ List<Runner> runners = new ArrayList<Runner>();
+ for (TestWithParameters test : tests) {
+ runners.add(runnerFactory
+ .createRunnerForTestWithParameters(test));
+ }
+ return runners;
+ } catch (ClassCastException e) {
+ throw parametersMethodReturnedWrongType(testClass, parametersMethod);
+ }
+ }
+
+ private List<TestWithParameters> createTestsForParameters(
+ Iterable<Object> allParameters, String namePattern)
+ throws Exception {
+ int i = 0;
+ List<TestWithParameters> children = new ArrayList<TestWithParameters>();
+ for (Object parametersOfSingleTest : allParameters) {
+ children.add(createTestWithNotNormalizedParameters(namePattern,
+ i++, parametersOfSingleTest));
+ }
+ return children;
+ }
+
+ private static Exception parametersMethodReturnedWrongType(
+ TestClass testClass, FrameworkMethod parametersMethod) throws Exception {
+ String className = testClass.getName();
+ String methodName = parametersMethod.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,
+ Arrays.asList(parameters));
+ }
}
}
diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java
index 92641bf..0a0e7cb 100755..100644
--- a/src/main/java/org/junit/runners/ParentRunner.java
+++ b/src/main/java/org/junit/runners/ParentRunner.java
@@ -1,21 +1,25 @@
package org.junit.runners;
+import static org.junit.internal.Checks.notNull;
import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_METHOD_VALIDATOR;
import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_VALIDATOR;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.internal.AssumptionViolatedException;
@@ -28,18 +32,22 @@ import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.InvalidOrderingException;
import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Orderable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
+import org.junit.runners.model.FrameworkMember;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.InvalidTestClassError;
+import org.junit.runners.model.MemberValueConsumer;
import org.junit.runners.model.RunnerScheduler;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.junit.validator.AnnotationsValidator;
-import org.junit.validator.PublicClassValidator;
import org.junit.validator.TestClassValidator;
/**
@@ -56,15 +64,15 @@ import org.junit.validator.TestClassValidator;
* @since 4.5
*/
public abstract class ParentRunner<T> extends Runner implements Filterable,
- Sortable {
- private static final List<TestClassValidator> VALIDATORS = Arrays.asList(
- new AnnotationsValidator(), new PublicClassValidator());
+ Orderable {
+ private static final List<TestClassValidator> VALIDATORS = Collections.<TestClassValidator>singletonList(
+ new AnnotationsValidator());
- private final Object childrenLock = new Object();
+ private final Lock childrenLock = new ReentrantLock();
private final TestClass testClass;
// Guarded by childrenLock
- private volatile Collection<T> filteredChildren = null;
+ private volatile List<T> filteredChildren = null;
private volatile RunnerScheduler scheduler = new RunnerScheduler() {
public void schedule(Runnable childStatement) {
@@ -84,6 +92,21 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
validate();
}
+ /**
+ * Constructs a new {@code ParentRunner} that will run the {@code TestClass}.
+ *
+ * @since 4.13
+ */
+ protected ParentRunner(TestClass testClass) throws InitializationError {
+ this.testClass = notNull(testClass);
+ validate();
+ }
+
+ /**
+ * @deprecated Please use {@link #ParentRunner(org.junit.runners.model.TestClass)}.
+ * @since 4.12
+ */
+ @Deprecated
protected TestClass createTestClass(Class<?> testClass) {
return new TestClass(testClass);
}
@@ -192,6 +215,7 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
+ statement = withInterruptIsolation(statement);
}
return statement;
}
@@ -219,7 +243,7 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
/**
* Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class
- * and superclasses before executing {@code statement}; all AfterClass methods are
+ * and superclasses after executing {@code statement}; all AfterClass methods are
* always executed: exceptions thrown by previous steps are combined, if
* necessary, with exceptions from AfterClass methods into a
* {@link org.junit.runners.model.MultipleFailureException}.
@@ -251,9 +275,10 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
* each method in the tested class.
*/
protected List<TestRule> classRules() {
- List<TestRule> result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class);
- result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class));
- return result;
+ ClassRuleCollector collector = new ClassRuleCollector();
+ testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector);
+ testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector);
+ return collector.getOrderedRules();
}
/**
@@ -271,6 +296,22 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
}
/**
+ * @return a {@link Statement}: clears interrupt status of current thread after execution of statement
+ */
+ protected final Statement withInterruptIsolation(final Statement statement) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ statement.evaluate();
+ } finally {
+ Thread.interrupted(); // clearing thread interrupted status for isolation
+ }
+ }
+ };
+ }
+
+ /**
* Evaluates whether a child is ignored. The default implementation always
* returns <code>false</code>.
*
@@ -346,8 +387,16 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
@Override
public Description getDescription() {
- Description description = Description.createSuiteDescription(getName(),
- getRunnerAnnotations());
+ Class<?> clazz = getTestClass().getJavaClass();
+ Description description;
+ // if subclass overrides `getName()` then we should use it
+ // to maintain backwards compatibility with JUnit 4.12
+ if (clazz == null || !clazz.getName().equals(getName())) {
+ description = Description.createSuiteDescription(getName(), getRunnerAnnotations());
+ } else {
+ description = Description.createSuiteDescription(clazz, getRunnerAnnotations());
+ }
+
for (T child : getFilteredChildren()) {
description.addChild(describeChild(child));
}
@@ -358,6 +407,7 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
+ testNotifier.fireTestSuiteStarted();
try {
Statement statement = classBlock(notifier);
statement.evaluate();
@@ -367,6 +417,8 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
+ } finally {
+ testNotifier.fireTestSuiteFinished();
}
}
@@ -375,7 +427,8 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
//
public void filter(Filter filter) throws NoTestsRemainException {
- synchronized (childrenLock) {
+ childrenLock.lock();
+ try {
List<T> children = new ArrayList<T>(getFilteredChildren());
for (Iterator<T> iter = children.iterator(); iter.hasNext(); ) {
T each = iter.next();
@@ -389,21 +442,70 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
iter.remove();
}
}
- filteredChildren = Collections.unmodifiableCollection(children);
+ filteredChildren = Collections.unmodifiableList(children);
if (filteredChildren.isEmpty()) {
throw new NoTestsRemainException();
}
+ } finally {
+ childrenLock.unlock();
}
}
public void sort(Sorter sorter) {
- synchronized (childrenLock) {
+ if (shouldNotReorder()) {
+ return;
+ }
+
+ childrenLock.lock();
+ try {
for (T each : getFilteredChildren()) {
sorter.apply(each);
}
List<T> sortedChildren = new ArrayList<T>(getFilteredChildren());
Collections.sort(sortedChildren, comparator(sorter));
- filteredChildren = Collections.unmodifiableCollection(sortedChildren);
+ filteredChildren = Collections.unmodifiableList(sortedChildren);
+ } finally {
+ childrenLock.unlock();
+ }
+ }
+
+ /**
+ * Implementation of {@link Orderable#order(Orderer)}.
+ *
+ * @since 4.13
+ */
+ public void order(Orderer orderer) throws InvalidOrderingException {
+ if (shouldNotReorder()) {
+ return;
+ }
+
+ childrenLock.lock();
+ try {
+ List<T> children = getFilteredChildren();
+ // In theory, we could have duplicate Descriptions. De-dup them before ordering,
+ // and add them back at the end.
+ Map<Description, List<T>> childMap = new LinkedHashMap<Description, List<T>>(
+ children.size());
+ for (T child : children) {
+ Description description = describeChild(child);
+ List<T> childrenWithDescription = childMap.get(description);
+ if (childrenWithDescription == null) {
+ childrenWithDescription = new ArrayList<T>(1);
+ childMap.put(description, childrenWithDescription);
+ }
+ childrenWithDescription.add(child);
+ orderer.apply(child);
+ }
+
+ List<Description> inOrder = orderer.order(childMap.keySet());
+
+ children = new ArrayList<T>(children.size());
+ for (Description description : inOrder) {
+ children.addAll(childMap.get(description));
+ }
+ filteredChildren = Collections.unmodifiableList(children);
+ } finally {
+ childrenLock.unlock();
}
}
@@ -411,20 +513,29 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
// Private implementation
//
+ private boolean shouldNotReorder() {
+ // If the test specifies a specific order, do not reorder.
+ return getDescription().getAnnotation(FixMethodOrder.class) != null;
+ }
+
private void validate() throws InitializationError {
List<Throwable> errors = new ArrayList<Throwable>();
collectInitializationErrors(errors);
if (!errors.isEmpty()) {
- throw new InitializationError(errors);
+ throw new InvalidTestClassError(testClass.getJavaClass(), errors);
}
}
- private Collection<T> getFilteredChildren() {
+ private List<T> getFilteredChildren() {
if (filteredChildren == null) {
- synchronized (childrenLock) {
+ childrenLock.lock();
+ try {
if (filteredChildren == null) {
- filteredChildren = Collections.unmodifiableCollection(getChildren());
+ filteredChildren = Collections.unmodifiableList(
+ new ArrayList<T>(getChildren()));
}
+ } finally {
+ childrenLock.unlock();
}
}
return filteredChildren;
@@ -449,4 +560,23 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
public void setScheduler(RunnerScheduler scheduler) {
this.scheduler = scheduler;
}
+
+ private static class ClassRuleCollector implements MemberValueConsumer<TestRule> {
+ final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>();
+
+ public void accept(FrameworkMember<?> member, TestRule value) {
+ ClassRule rule = member.getAnnotation(ClassRule.class);
+ entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE,
+ rule != null ? rule.order() : null));
+ }
+
+ public List<TestRule> getOrderedRules() {
+ Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR);
+ List<TestRule> result = new ArrayList<TestRule>(entries.size());
+ for (RuleContainer.RuleEntry entry : entries) {
+ result.add((TestRule) entry.rule);
+ }
+ return result;
+ }
+ }
}
diff --git a/src/main/java/org/junit/runners/RuleContainer.java b/src/main/java/org/junit/runners/RuleContainer.java
new file mode 100644
index 0000000..30ddd8d
--- /dev/null
+++ b/src/main/java/org/junit/runners/RuleContainer.java
@@ -0,0 +1,113 @@
+package org.junit.runners;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+/**
+ * Data structure for ordering of {@link TestRule}/{@link MethodRule} instances.
+ *
+ * @since 4.13
+ */
+class RuleContainer {
+ private final IdentityHashMap<Object, Integer> orderValues = new IdentityHashMap<Object, Integer>();
+ private final List<TestRule> testRules = new ArrayList<TestRule>();
+ private final List<MethodRule> methodRules = new ArrayList<MethodRule>();
+
+ /**
+ * Sets order value for the specified rule.
+ */
+ public void setOrder(Object rule, int order) {
+ orderValues.put(rule, order);
+ }
+
+ public void add(MethodRule methodRule) {
+ methodRules.add(methodRule);
+ }
+
+ public void add(TestRule testRule) {
+ testRules.add(testRule);
+ }
+
+ static final Comparator<RuleEntry> ENTRY_COMPARATOR = new Comparator<RuleEntry>() {
+ public int compare(RuleEntry o1, RuleEntry o2) {
+ int result = compareInt(o1.order, o2.order);
+ return result != 0 ? result : o1.type - o2.type;
+ }
+
+ private int compareInt(int a, int b) {
+ return (a < b) ? 1 : (a == b ? 0 : -1);
+ }
+ };
+
+ /**
+ * Returns entries in the order how they should be applied, i.e. inner-to-outer.
+ */
+ private List<RuleEntry> getSortedEntries() {
+ List<RuleEntry> ruleEntries = new ArrayList<RuleEntry>(
+ methodRules.size() + testRules.size());
+ for (MethodRule rule : methodRules) {
+ ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_METHOD_RULE, orderValues.get(rule)));
+ }
+ for (TestRule rule : testRules) {
+ ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_TEST_RULE, orderValues.get(rule)));
+ }
+ Collections.sort(ruleEntries, ENTRY_COMPARATOR);
+ return ruleEntries;
+ }
+
+ /**
+ * Applies all the rules ordered accordingly to the specified {@code statement}.
+ */
+ public Statement apply(FrameworkMethod method, Description description, Object target,
+ Statement statement) {
+ if (methodRules.isEmpty() && testRules.isEmpty()) {
+ return statement;
+ }
+ Statement result = statement;
+ for (RuleEntry ruleEntry : getSortedEntries()) {
+ if (ruleEntry.type == RuleEntry.TYPE_TEST_RULE) {
+ result = ((TestRule) ruleEntry.rule).apply(result, description);
+ } else {
+ result = ((MethodRule) ruleEntry.rule).apply(result, method, target);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns rule instances in the order how they should be applied, i.e. inner-to-outer.
+ * VisibleForTesting
+ */
+ List<Object> getSortedRules() {
+ List<Object> result = new ArrayList<Object>();
+ for (RuleEntry entry : getSortedEntries()) {
+ result.add(entry.rule);
+ }
+ return result;
+ }
+
+ static class RuleEntry {
+ static final int TYPE_TEST_RULE = 1;
+ static final int TYPE_METHOD_RULE = 0;
+
+ final Object rule;
+ final int type;
+ final int order;
+
+ RuleEntry(Object rule, int type, Integer order) {
+ this.rule = rule;
+ this.type = type;
+ this.order = order != null ? order.intValue() : Rule.DEFAULT_ORDER;
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java
index b37179f..c2c8e58 100644
--- a/src/main/java/org/junit/runners/Suite.java
+++ b/src/main/java/org/junit/runners/Suite.java
@@ -47,7 +47,7 @@ public class Suite extends ParentRunner<Runner> {
/**
* @return the classes to be run
*/
- public Class<?>[] value();
+ Class<?>[] value();
}
private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
@@ -88,7 +88,7 @@ public class Suite extends ParentRunner<Runner> {
* @param suiteClasses the classes in the suite
*/
protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
- this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses);
+ this(new AllDefaultPossibilitiesBuilder(), klass, suiteClasses);
}
/**
diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java
index 945e389..ea2b16f 100644
--- a/src/main/java/org/junit/runners/model/FrameworkField.java
+++ b/src/main/java/org/junit/runners/model/FrameworkField.java
@@ -14,12 +14,26 @@ import org.junit.runners.BlockJUnit4ClassRunner;
public class FrameworkField extends FrameworkMember<FrameworkField> {
private final Field field;
- FrameworkField(Field field) {
+ /**
+ * Returns a new {@code FrameworkField} for {@code field}.
+ *
+ * <p>Access relaxed to {@code public} since version 4.13.1.
+ */
+ public FrameworkField(Field field) {
if (field == null) {
throw new NullPointerException(
"FrameworkField cannot be created without an underlying field.");
}
this.field = field;
+
+ if (isPublic()) {
+ // This field could be a public field in a package-scope base class
+ try {
+ field.setAccessible(true);
+ } catch (SecurityException e) {
+ // We may get an IllegalAccessException when we try to access the field
+ }
+ }
}
@Override
@@ -41,6 +55,11 @@ public class FrameworkField extends FrameworkMember<FrameworkField> {
}
@Override
+ boolean isBridgeMethod() {
+ return false;
+ }
+
+ @Override
protected int getModifiers() {
return field.getModifiers();
}
diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java
index 724f096..5634b3f 100644
--- a/src/main/java/org/junit/runners/model/FrameworkMember.java
+++ b/src/main/java/org/junit/runners/model/FrameworkMember.java
@@ -12,15 +12,29 @@ public abstract class FrameworkMember<T extends FrameworkMember<T>> implements
Annotatable {
abstract boolean isShadowedBy(T otherMember);
- boolean isShadowedBy(List<T> members) {
- for (T each : members) {
- if (isShadowedBy(each)) {
- return true;
+ T handlePossibleBridgeMethod(List<T> members) {
+ for (int i = members.size() - 1; i >=0; i--) {
+ T otherMember = members.get(i);
+ if (isShadowedBy(otherMember)) {
+ if (otherMember.isBridgeMethod()) {
+ /*
+ * We need to return the previously-encountered bridge method
+ * because JUnit won't be able to call the parent method,
+ * because the parent class isn't public.
+ */
+ members.remove(i);
+ return otherMember;
+ }
+ // We found a shadowed member that isn't a bridge method. Ignore it.
+ return null;
}
}
- return false;
+ // No shadow or bridge method found. The caller should add *this* member.
+ return (T) this;
}
+ abstract boolean isBridgeMethod();
+
protected abstract int getModifiers();
/**
diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java
index 3580052..4471407 100644
--- a/src/main/java/org/junit/runners/model/FrameworkMethod.java
+++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java
@@ -28,6 +28,15 @@ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> {
"FrameworkMethod cannot be created without an underlying method.");
}
this.method = method;
+
+ if (isPublic()) {
+ // This method could be a public method in a package-scope base class
+ try {
+ method.setAccessible(true);
+ } catch (SecurityException e) {
+ // We may get an IllegalAccessException when we try to call the method
+ }
+ }
}
/**
@@ -149,6 +158,11 @@ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> {
}
@Override
+ boolean isBridgeMethod() {
+ return method.isBridge();
+ }
+
+ @Override
public boolean equals(Object obj) {
if (!FrameworkMethod.class.isInstance(obj)) {
return false;
diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java
index 841b565..dd9c8b3 100644
--- a/src/main/java/org/junit/runners/model/InitializationError.java
+++ b/src/main/java/org/junit/runners/model/InitializationError.java
@@ -14,7 +14,7 @@ public class InitializationError extends Exception {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final List<Throwable> fErrors;
diff --git a/src/main/java/org/junit/runners/model/InvalidTestClassError.java b/src/main/java/org/junit/runners/model/InvalidTestClassError.java
new file mode 100644
index 0000000..57be610
--- /dev/null
+++ b/src/main/java/org/junit/runners/model/InvalidTestClassError.java
@@ -0,0 +1,39 @@
+package org.junit.runners.model;
+
+import java.util.List;
+
+/**
+ * Thrown by {@link org.junit.runner.Runner}s in case the class under test is not valid.
+ * <p>
+ * Its message conveniently lists all of the validation errors.
+ *
+ * @since 4.13
+ */
+public class InvalidTestClassError extends InitializationError {
+ private static final long serialVersionUID = 1L;
+
+ private final String message;
+
+ public InvalidTestClassError(Class<?> offendingTestClass, List<Throwable> validationErrors) {
+ super(validationErrors);
+ this.message = createMessage(offendingTestClass, validationErrors);
+ }
+
+ private static String createMessage(Class<?> testClass, List<Throwable> validationErrors) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("Invalid test class '%s':", testClass.getName()));
+ int i = 1;
+ for (Throwable error : validationErrors) {
+ sb.append("\n " + (i++) + ". " + error.getMessage());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return a message with a list of all of the validation errors
+ */
+ @Override
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/org/junit/runners/model/MemberValueConsumer.java b/src/main/java/org/junit/runners/model/MemberValueConsumer.java
new file mode 100644
index 0000000..a6157bf
--- /dev/null
+++ b/src/main/java/org/junit/runners/model/MemberValueConsumer.java
@@ -0,0 +1,18 @@
+package org.junit.runners.model;
+
+/**
+ * Represents a receiver for values of annotated fields/methods together with the declaring member.
+ *
+ * @see TestClass#collectAnnotatedFieldValues(Object, Class, Class, MemberValueConsumer)
+ * @see TestClass#collectAnnotatedMethodValues(Object, Class, Class, MemberValueConsumer)
+ * @since 4.13
+ */
+public interface MemberValueConsumer<T> {
+ /**
+ * Receives the next value and its declaring member.
+ *
+ * @param member declaring member ({@link FrameworkMethod} or {@link FrameworkField})
+ * @param value the value of the next member
+ */
+ void accept(FrameworkMember<?> member, T value);
+}
diff --git a/src/main/java/org/junit/runners/model/MultipleFailureException.java b/src/main/java/org/junit/runners/model/MultipleFailureException.java
index 325c645..8e355a7 100644
--- a/src/main/java/org/junit/runners/model/MultipleFailureException.java
+++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java
@@ -1,9 +1,13 @@
package org.junit.runners.model;
+import java.io.PrintStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.junit.TestCouldNotBeSkippedException;
+import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.Throwables;
/**
@@ -17,12 +21,22 @@ public class MultipleFailureException extends Exception {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final List<Throwable> fErrors;
public MultipleFailureException(List<Throwable> errors) {
- this.fErrors = new ArrayList<Throwable>(errors);
+ if (errors.isEmpty()) {
+ throw new IllegalArgumentException(
+ "List of Throwables must not be empty");
+ }
+ this.fErrors = new ArrayList<Throwable>(errors.size());
+ for (Throwable error : errors) {
+ if (error instanceof AssumptionViolatedException) {
+ error = new TestCouldNotBeSkippedException((AssumptionViolatedException) error);
+ }
+ fErrors.add(error);
+ }
}
public List<Throwable> getFailures() {
@@ -34,11 +48,32 @@ public class MultipleFailureException extends Exception {
StringBuilder sb = new StringBuilder(
String.format("There were %d errors:", fErrors.size()));
for (Throwable e : fErrors) {
- sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage()));
+ sb.append(String.format("%n %s(%s)", e.getClass().getName(), e.getMessage()));
}
return sb.toString();
}
+ @Override
+ public void printStackTrace() {
+ for (Throwable e: fErrors) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void printStackTrace(PrintStream s) {
+ for (Throwable e: fErrors) {
+ e.printStackTrace(s);
+ }
+ }
+
+ @Override
+ public void printStackTrace(PrintWriter s) {
+ for (Throwable e: fErrors) {
+ e.printStackTrace(s);
+ }
+ }
+
/**
* Asserts that a list of throwables is empty. If it isn't empty,
* will throw {@link MultipleFailureException} (if there are
diff --git a/src/main/java/org/junit/runners/model/RunnerBuilder.java b/src/main/java/org/junit/runners/model/RunnerBuilder.java
index 7d3eee3..ba7c9e2 100644
--- a/src/main/java/org/junit/runners/model/RunnerBuilder.java
+++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java
@@ -6,7 +6,11 @@ import java.util.List;
import java.util.Set;
import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.runner.Description;
+import org.junit.runner.OrderWith;
import org.junit.runner.Runner;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.Ordering;
/**
* A RunnerBuilder is a strategy for constructing runners for classes.
@@ -49,19 +53,39 @@ public abstract class RunnerBuilder {
public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
/**
- * Always returns a runner, even if it is just one that prints an error instead of running tests.
+ * Always returns a runner for the given test class.
+ *
+ * <p>In case of an exception a runner will be returned that prints an error instead of running
+ * tests.
+ *
+ * <p>Note that some of the internal JUnit implementations of RunnerBuilder will return
+ * {@code null} from this method, but no RunnerBuilder passed to a Runner constructor will
+ * return {@code null} from this method.
*
* @param testClass class to be run
* @return a Runner
*/
public Runner safeRunnerForClass(Class<?> testClass) {
try {
- return runnerForClass(testClass);
+ Runner runner = runnerForClass(testClass);
+ if (runner != null) {
+ configureRunner(runner);
+ }
+ return runner;
} catch (Throwable e) {
return new ErrorReportingRunner(testClass, e);
}
}
+ private void configureRunner(Runner runner) throws InvalidOrderingException {
+ Description description = runner.getDescription();
+ OrderWith orderWith = description.getAnnotation(OrderWith.class);
+ if (orderWith != null) {
+ Ordering ordering = Ordering.definedBy(orderWith.value(), description);
+ ordering.apply(runner);
+ }
+ }
+
Class<?> addParent(Class<?> parent) throws InitializationError {
if (!parents.add(parent)) {
throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
@@ -96,7 +120,7 @@ public abstract class RunnerBuilder {
}
private List<Runner> runners(Class<?>[] children) {
- ArrayList<Runner> runners = new ArrayList<Runner>();
+ List<Runner> runners = new ArrayList<Runner>();
for (Class<?> each : children) {
Runner childRunner = safeRunnerForClass(each);
if (childRunner != null) {
diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java
index c8a544d..5962c2b 100755..100644
--- a/src/main/java/org/junit/runners/model/TestClass.java
+++ b/src/main/java/org/junit/runners/model/TestClass.java
@@ -84,20 +84,21 @@ public class TestClass implements Annotatable {
for (Annotation each : member.getAnnotations()) {
Class<? extends Annotation> type = each.annotationType();
List<T> members = getAnnotatedMembers(map, type, true);
- if (member.isShadowedBy(members)) {
+ T memberToAdd = member.handlePossibleBridgeMethod(members);
+ if (memberToAdd == null) {
return;
}
if (runsTopToBottom(type)) {
- members.add(0, member);
+ members.add(0, memberToAdd);
} else {
- members.add(member);
+ members.add(memberToAdd);
}
}
}
private static <T extends FrameworkMember<T>> Map<Class<? extends Annotation>, List<T>>
makeDeeplyUnmodifiable(Map<Class<? extends Annotation>, List<T>> source) {
- LinkedHashMap<Class<? extends Annotation>, List<T>> copy =
+ Map<Class<? extends Annotation>, List<T>> copy =
new LinkedHashMap<Class<? extends Annotation>, List<T>>();
for (Map.Entry<Class<? extends Annotation>, List<T>> entry : source.entrySet()) {
copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
@@ -168,7 +169,7 @@ public class TestClass implements Annotatable {
}
private static List<Class<?>> getSuperClasses(Class<?> testClass) {
- ArrayList<Class<?>> results = new ArrayList<Class<?>>();
+ List<Class<?>> results = new ArrayList<Class<?>>();
Class<?> current = testClass;
while (current != null) {
results.add(current);
@@ -224,24 +225,59 @@ public class TestClass implements Annotatable {
public <T> List<T> getAnnotatedFieldValues(Object test,
Class<? extends Annotation> annotationClass, Class<T> valueClass) {
- List<T> results = new ArrayList<T>();
+ final List<T> results = new ArrayList<T>();
+ collectAnnotatedFieldValues(test, annotationClass, valueClass,
+ new MemberValueConsumer<T>() {
+ public void accept(FrameworkMember<?> member, T value) {
+ results.add(value);
+ }
+ });
+ return results;
+ }
+
+ /**
+ * Finds the fields annotated with the specified annotation and having the specified type,
+ * retrieves the values and passes those to the specified consumer.
+ *
+ * @since 4.13
+ */
+ public <T> void collectAnnotatedFieldValues(Object test,
+ Class<? extends Annotation> annotationClass, Class<T> valueClass,
+ MemberValueConsumer<T> consumer) {
for (FrameworkField each : getAnnotatedFields(annotationClass)) {
try {
Object fieldValue = each.get(test);
if (valueClass.isInstance(fieldValue)) {
- results.add(valueClass.cast(fieldValue));
+ consumer.accept(each, valueClass.cast(fieldValue));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(
"How did getFields return a field we couldn't access?", e);
}
}
- return results;
}
public <T> List<T> getAnnotatedMethodValues(Object test,
Class<? extends Annotation> annotationClass, Class<T> valueClass) {
- List<T> results = new ArrayList<T>();
+ final List<T> results = new ArrayList<T>();
+ collectAnnotatedMethodValues(test, annotationClass, valueClass,
+ new MemberValueConsumer<T>() {
+ public void accept(FrameworkMember<?> member, T value) {
+ results.add(value);
+ }
+ });
+ return results;
+ }
+
+ /**
+ * Finds the methods annotated with the specified annotation and returning the specified type,
+ * invokes it and pass the return value to the specified consumer.
+ *
+ * @since 4.13
+ */
+ public <T> void collectAnnotatedMethodValues(Object test,
+ Class<? extends Annotation> annotationClass, Class<T> valueClass,
+ MemberValueConsumer<T> consumer) {
for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) {
try {
/*
@@ -254,14 +290,13 @@ public class TestClass implements Annotatable {
*/
if (valueClass.isAssignableFrom(each.getReturnType())) {
Object fieldValue = each.invokeExplosively(test);
- results.add(valueClass.cast(fieldValue));
+ consumer.accept(each, valueClass.cast(fieldValue));
}
} catch (Throwable e) {
throw new RuntimeException(
"Exception in " + each.getName(), e);
}
}
- return results;
}
public boolean isPublic() {
diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java
index 1c49f84..5c70a75 100644
--- a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java
+++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java
@@ -4,8 +4,12 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.runner.RunWith;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
@@ -18,13 +22,17 @@ import org.junit.runners.model.Statement;
*/
public class BlockJUnit4ClassRunnerWithParameters extends
BlockJUnit4ClassRunner {
+ private enum InjectionType {
+ CONSTRUCTOR, FIELD
+ }
+
private final Object[] parameters;
private final String name;
public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
throws InitializationError {
- super(test.getTestClass().getJavaClass());
+ super(test.getTestClass());
parameters = test.getParameters().toArray(
new Object[test.getParameters().size()]);
name = test.getName();
@@ -32,10 +40,15 @@ public class BlockJUnit4ClassRunnerWithParameters extends
@Override
public Object createTest() throws Exception {
- if (fieldsAreAnnotated()) {
- return createTestUsingFieldInjection();
- } else {
- return createTestUsingConstructorInjection();
+ InjectionType injectionType = getInjectionType();
+ switch (injectionType) {
+ case CONSTRUCTOR:
+ return createTestUsingConstructorInjection();
+ case FIELD:
+ return createTestUsingFieldInjection();
+ default:
+ throw new IllegalStateException("The injection type "
+ + injectionType + " is not supported.");
}
}
@@ -60,6 +73,13 @@ public class BlockJUnit4ClassRunnerWithParameters extends
int index = annotation.value();
try {
field.set(testClassInstance, parameters[index]);
+ } catch (IllegalAccessException e) {
+ IllegalAccessException wrappedException = new IllegalAccessException(
+ "Cannot set parameter '" + field.getName()
+ + "'. Ensure that the field '" + field.getName()
+ + "' is public.");
+ wrappedException.initCause(e);
+ throw wrappedException;
} catch (IllegalArgumentException iare) {
throw new Exception(getTestClass().getName()
+ ": Trying to set " + field.getName()
@@ -86,7 +106,7 @@ public class BlockJUnit4ClassRunnerWithParameters extends
@Override
protected void validateConstructor(List<Throwable> errors) {
validateOnlyOneConstructor(errors);
- if (fieldsAreAnnotated()) {
+ if (getInjectionType() != InjectionType.CONSTRUCTOR) {
validateZeroArgConstructor(errors);
}
}
@@ -94,7 +114,7 @@ public class BlockJUnit4ClassRunnerWithParameters extends
@Override
protected void validateFields(List<Throwable> errors) {
super.validateFields(errors);
- if (fieldsAreAnnotated()) {
+ if (getInjectionType() == InjectionType.FIELD) {
List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
int[] usedIndices = new int[annotatedFieldsByParameter.size()];
for (FrameworkField each : annotatedFieldsByParameter) {
@@ -125,18 +145,74 @@ public class BlockJUnit4ClassRunnerWithParameters extends
@Override
protected Statement classBlock(RunNotifier notifier) {
- return childrenInvoker(notifier);
+ Statement statement = childrenInvoker(notifier);
+ statement = withBeforeParams(statement);
+ statement = withAfterParams(statement);
+ return statement;
+ }
+
+ private Statement withBeforeParams(Statement statement) {
+ List<FrameworkMethod> befores = getTestClass()
+ .getAnnotatedMethods(Parameterized.BeforeParam.class);
+ return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores);
+ }
+
+ private class RunBeforeParams extends RunBefores {
+ RunBeforeParams(Statement next, List<FrameworkMethod> befores) {
+ super(next, befores, null);
+ }
+
+ @Override
+ protected void invokeMethod(FrameworkMethod method) throws Throwable {
+ int paramCount = method.getMethod().getParameterTypes().length;
+ method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
+ }
+ }
+
+ private Statement withAfterParams(Statement statement) {
+ List<FrameworkMethod> afters = getTestClass()
+ .getAnnotatedMethods(Parameterized.AfterParam.class);
+ return afters.isEmpty() ? statement : new RunAfterParams(statement, afters);
+ }
+
+ private class RunAfterParams extends RunAfters {
+ RunAfterParams(Statement next, List<FrameworkMethod> afters) {
+ super(next, afters, null);
+ }
+
+ @Override
+ protected void invokeMethod(FrameworkMethod method) throws Throwable {
+ int paramCount = method.getMethod().getParameterTypes().length;
+ method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
+ }
}
@Override
protected Annotation[] getRunnerAnnotations() {
- return new Annotation[0];
+ Annotation[] allAnnotations = super.getRunnerAnnotations();
+ Annotation[] annotationsWithoutRunWith = new Annotation[allAnnotations.length - 1];
+ int i = 0;
+ for (Annotation annotation: allAnnotations) {
+ if (!annotation.annotationType().equals(RunWith.class)) {
+ annotationsWithoutRunWith[i] = annotation;
+ ++i;
+ }
+ }
+ return annotationsWithoutRunWith;
}
private List<FrameworkField> getAnnotatedFieldsByParameter() {
return getTestClass().getAnnotatedFields(Parameter.class);
}
+ private InjectionType getInjectionType() {
+ if (fieldsAreAnnotated()) {
+ return InjectionType.FIELD;
+ } else {
+ return InjectionType.CONSTRUCTOR;
+ }
+ }
+
private boolean fieldsAreAnnotated() {
return !getAnnotatedFieldsByParameter().isEmpty();
}
diff --git a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java
index 16ea1f3..8123e83 100644
--- a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java
+++ b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java
@@ -4,7 +4,7 @@ import org.junit.runner.Runner;
import org.junit.runners.model.InitializationError;
/**
- * A {@code ParameterizedRunnerFactory} creates a runner for a single
+ * A {@code ParametersRunnerFactory} creates a runner for a single
* {@link TestWithParameters}.
*
* @since 4.12
diff --git a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java
index 1b86644..1c5abd9 100644
--- a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java
+++ b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java
@@ -1,6 +1,7 @@
package org.junit.runners.parameterized;
import static java.util.Collections.unmodifiableList;
+import static org.junit.internal.Checks.notNull;
import java.util.ArrayList;
import java.util.List;
@@ -73,10 +74,4 @@ public class TestWithParameters {
return testClass.getName() + " '" + name + "' with parameters "
+ parameters;
}
-
- private static void notNull(Object value, String message) {
- if (value == null) {
- throw new NullPointerException(message);
- }
- }
}