diff options
Diffstat (limited to 'src/main/java/org/junit/runners/ParentRunner.java')
-rw-r--r--[-rwxr-xr-x] | src/main/java/org/junit/runners/ParentRunner.java | 176 |
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; + } + } } |