aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/runners/ParentRunner.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/runners/ParentRunner.java')
-rw-r--r--[-rwxr-xr-x]src/main/java/org/junit/runners/ParentRunner.java176
1 files changed, 153 insertions, 23 deletions
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;
+ }
+ }
}