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--src/main/java/org/junit/runners/ParentRunner.java378
1 files changed, 378 insertions, 0 deletions
diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java
new file mode 100644
index 0000000..a41aad3
--- /dev/null
+++ b/src/main/java/org/junit/runners/ParentRunner.java
@@ -0,0 +1,378 @@
+package org.junit.runners;
+
+import static org.junit.internal.runners.rules.RuleFieldValidator.CLASS_RULE_VALIDATOR;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.internal.runners.model.EachTestNotifier;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.rules.RunRules;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runner.notification.StoppedByUserException;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.RunnerScheduler;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provides most of the functionality specific to a Runner that implements a
+ * "parent node" in the test tree, with children defined by objects of some data
+ * type {@code T}. (For {@link BlockJUnit4ClassRunner}, {@code T} is
+ * {@link Method} . For {@link Suite}, {@code T} is {@link Class}.) Subclasses
+ * must implement finding the children of the node, describing each child, and
+ * running each child. ParentRunner will filter and sort children, handle
+ * {@code @BeforeClass} and {@code @AfterClass} methods,
+ * handle annotated {@link ClassRule}s, create a composite
+ * {@link Description}, and run children sequentially.
+ */
+public abstract class ParentRunner<T> extends Runner implements Filterable,
+ Sortable {
+ private final TestClass fTestClass;
+
+ private Sorter fSorter= Sorter.NULL;
+
+ private List<T> fFilteredChildren= null;
+
+ private RunnerScheduler fScheduler= new RunnerScheduler() {
+ public void schedule(Runnable childStatement) {
+ childStatement.run();
+ }
+
+ public void finished() {
+ // do nothing
+ }
+ };
+
+ /**
+ * Constructs a new {@code ParentRunner} that will run {@code @TestClass}
+ * @throws InitializationError
+ */
+ protected ParentRunner(Class<?> testClass) throws InitializationError {
+ fTestClass= new TestClass(testClass);
+ validate();
+ }
+
+ //
+ // Must be overridden
+ //
+
+ /**
+ * Returns a list of objects that define the children of this Runner.
+ */
+ protected abstract List<T> getChildren();
+
+ /**
+ * Returns a {@link Description} for {@code child}, which can be assumed to
+ * be an element of the list returned by {@link ParentRunner#getChildren()}
+ */
+ protected abstract Description describeChild(T child);
+
+ /**
+ * Runs the test corresponding to {@code child}, which can be assumed to be
+ * an element of the list returned by {@link ParentRunner#getChildren()}.
+ * Subclasses are responsible for making sure that relevant test events are
+ * reported through {@code notifier}
+ */
+ protected abstract void runChild(T child, RunNotifier notifier);
+
+ //
+ // May be overridden
+ //
+
+ /**
+ * Adds to {@code errors} a throwable for each problem noted with the test class (available from {@link #getTestClass()}).
+ * Default implementation adds an error for each method annotated with
+ * {@code @BeforeClass} or {@code @AfterClass} that is not
+ * {@code public static void} with no arguments.
+ */
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ validatePublicVoidNoArgMethods(BeforeClass.class, true, errors);
+ validatePublicVoidNoArgMethods(AfterClass.class, true, errors);
+ validateClassRules(errors);
+ }
+
+ /**
+ * Adds to {@code errors} if any method in this class is annotated with
+ * {@code annotation}, but:
+ * <ul>
+ * <li>is not public, or
+ * <li>takes parameters, or
+ * <li>returns something other than void, or
+ * <li>is static (given {@code isStatic is false}), or
+ * <li>is not static (given {@code isStatic is true}).
+ */
+ protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation,
+ boolean isStatic, List<Throwable> errors) {
+ List<FrameworkMethod> methods= getTestClass().getAnnotatedMethods(annotation);
+
+ for (FrameworkMethod eachTestMethod : methods)
+ eachTestMethod.validatePublicVoidNoArg(isStatic, errors);
+ }
+
+ private void validateClassRules(List<Throwable> errors) {
+ CLASS_RULE_VALIDATOR.validate(getTestClass(), errors);
+ }
+
+ /**
+ * Constructs a {@code Statement} to run all of the tests in the test class. Override to add pre-/post-processing.
+ * Here is an outline of the implementation:
+ * <ul>
+ * <li>Call {@link #runChild(Object, RunNotifier)} on each object returned by {@link #getChildren()} (subject to any imposed filter and sort).</li>
+ * <li>ALWAYS run all non-overridden {@code @BeforeClass} methods on this class
+ * and superclasses before the previous step; if any throws an
+ * Exception, stop execution and pass the exception on.
+ * <li>ALWAYS run all non-overridden {@code @AfterClass} methods on this class
+ * and superclasses before any of the previous steps; all AfterClass methods are
+ * always executed: exceptions thrown by previous steps are combined, if
+ * necessary, with exceptions from AfterClass methods into a
+ * {@link MultipleFailureException}.
+ * </ul>
+ * @param notifier
+ * @return {@code Statement}
+ */
+ protected Statement classBlock(final RunNotifier notifier) {
+ Statement statement= childrenInvoker(notifier);
+ statement= withBeforeClasses(statement);
+ statement= withAfterClasses(statement);
+ statement= withClassRules(statement);
+ return statement;
+ }
+
+ /**
+ * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class
+ * and superclasses before executing {@code statement}; if any throws an
+ * Exception, stop execution and pass the exception on.
+ */
+ protected Statement withBeforeClasses(Statement statement) {
+ List<FrameworkMethod> befores= fTestClass
+ .getAnnotatedMethods(BeforeClass.class);
+ return befores.isEmpty() ? statement :
+ new RunBefores(statement, befores, null);
+ }
+
+ /**
+ * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class
+ * and superclasses before 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 MultipleFailureException}.
+ */
+ protected Statement withAfterClasses(Statement statement) {
+ List<FrameworkMethod> afters= fTestClass
+ .getAnnotatedMethods(AfterClass.class);
+ return afters.isEmpty() ? statement :
+ new RunAfters(statement, afters, null);
+ }
+
+ /**
+ * Returns a {@link Statement}: apply all
+ * static fields assignable to {@link TestRule}
+ * annotated with {@link ClassRule}.
+ *
+ * @param statement
+ * the base statement
+ * @return a RunRules statement if any class-level {@link Rule}s are
+ * found, or the base statement
+ */
+ private Statement withClassRules(Statement statement) {
+ List<TestRule> classRules= classRules();
+ return classRules.isEmpty() ? statement :
+ new RunRules(statement, classRules, getDescription());
+ }
+
+ /**
+ * @return the {@code ClassRule}s that can transform the block that runs
+ * each method in the tested class.
+ */
+ protected List<TestRule> classRules() {
+ return fTestClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class);
+ }
+
+ /**
+ * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)}
+ * on each object returned by {@link #getChildren()} (subject to any imposed
+ * filter and sort)
+ */
+ protected Statement childrenInvoker(final RunNotifier notifier) {
+ return new Statement() {
+ @Override
+ public void evaluate() {
+ runChildren(notifier);
+ }
+ };
+ }
+
+ private void runChildren(final RunNotifier notifier) {
+ for (final T each : getFilteredChildren())
+ fScheduler.schedule(new Runnable() {
+ public void run() {
+ ParentRunner.this.runChild(each, notifier);
+ }
+ });
+ fScheduler.finished();
+ }
+
+ /**
+ * Returns a name used to describe this Runner
+ */
+ protected String getName() {
+ return fTestClass.getName();
+ }
+
+ //
+ // Available for subclasses
+ //
+
+ /**
+ * Returns a {@link TestClass} object wrapping the class to be executed.
+ */
+ public final TestClass getTestClass() {
+ return fTestClass;
+ }
+
+ /**
+ * Runs a {@link Statement} that represents a leaf (aka atomic) test.
+ */
+ protected final void runLeaf(Statement statement, Description description,
+ RunNotifier notifier) {
+ EachTestNotifier eachNotifier= new EachTestNotifier(notifier, description);
+ eachNotifier.fireTestStarted();
+ try {
+ statement.evaluate();
+ } catch (AssumptionViolatedException e) {
+ eachNotifier.addFailedAssumption(e);
+ } catch (Throwable e) {
+ eachNotifier.addFailure(e);
+ } finally {
+ eachNotifier.fireTestFinished();
+ }
+ }
+
+ /**
+ * @return the annotations that should be attached to this runner's
+ * description.
+ */
+ protected Annotation[] getRunnerAnnotations() {
+ return fTestClass.getAnnotations();
+ }
+
+ //
+ // Implementation of Runner
+ //
+
+ @Override
+ public Description getDescription() {
+ Description description= Description.createSuiteDescription(getName(),
+ getRunnerAnnotations());
+ for (T child : getFilteredChildren())
+ description.addChild(describeChild(child));
+ return description;
+ }
+
+ @Override
+ public void run(final RunNotifier notifier) {
+ EachTestNotifier testNotifier= new EachTestNotifier(notifier,
+ getDescription());
+ try {
+ Statement statement= classBlock(notifier);
+ statement.evaluate();
+ } catch (AssumptionViolatedException e) {
+ testNotifier.fireTestIgnored();
+ } catch (StoppedByUserException e) {
+ throw e;
+ } catch (Throwable e) {
+ testNotifier.addFailure(e);
+ }
+ }
+
+ //
+ // Implementation of Filterable and Sortable
+ //
+
+ public void filter(Filter filter) throws NoTestsRemainException {
+ for (Iterator<T> iter = getFilteredChildren().iterator(); iter.hasNext(); ) {
+ T each = iter.next();
+ if (shouldRun(filter, each))
+ try {
+ filter.apply(each);
+ } catch (NoTestsRemainException e) {
+ iter.remove();
+ }
+ else
+ iter.remove();
+ }
+ if (getFilteredChildren().isEmpty()) {
+ throw new NoTestsRemainException();
+ }
+ }
+
+ public void sort(Sorter sorter) {
+ fSorter= sorter;
+ for (T each : getFilteredChildren())
+ sortChild(each);
+ Collections.sort(getFilteredChildren(), comparator());
+ }
+
+ //
+ // Private implementation
+ //
+
+ private void validate() throws InitializationError {
+ List<Throwable> errors= new ArrayList<Throwable>();
+ collectInitializationErrors(errors);
+ if (!errors.isEmpty())
+ throw new InitializationError(errors);
+ }
+
+ private List<T> getFilteredChildren() {
+ if (fFilteredChildren == null)
+ fFilteredChildren = new ArrayList<T>(getChildren());
+ return fFilteredChildren;
+ }
+
+ private void sortChild(T child) {
+ fSorter.apply(child);
+ }
+
+ private boolean shouldRun(Filter filter, T each) {
+ return filter.shouldRun(describeChild(each));
+ }
+
+ private Comparator<? super T> comparator() {
+ return new Comparator<T>() {
+ public int compare(T o1, T o2) {
+ return fSorter.compare(describeChild(o1), describeChild(o2));
+ }
+ };
+ }
+
+ /**
+ * Sets a scheduler that determines the order and parallelization
+ * of children. Highly experimental feature that may change.
+ */
+ public void setScheduler(RunnerScheduler scheduler) {
+ this.fScheduler = scheduler;
+ }
+}