001package org.junit.runners.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.junit.internal.runners.ErrorReportingRunner;
009import org.junit.runner.Description;
010import org.junit.runner.OrderWith;
011import org.junit.runner.Runner;
012import org.junit.runner.manipulation.InvalidOrderingException;
013import org.junit.runner.manipulation.Ordering;
014
015/**
016 * A RunnerBuilder is a strategy for constructing runners for classes.
017 *
018 * Only writers of custom runners should use <code>RunnerBuilder</code>s.  A custom runner class with a constructor taking
019 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself.
020 * For example,
021 * imagine a custom runner that builds suites based on a list of classes in a text file:
022 *
023 * <pre>
024 * \@RunWith(TextFileSuite.class)
025 * \@SuiteSpecFile("mysuite.txt")
026 * class MySuite {}
027 * </pre>
028 *
029 * The implementation of TextFileSuite might include:
030 *
031 * <pre>
032 * public TextFileSuite(Class testClass, RunnerBuilder builder) {
033 *   // ...
034 *   for (String className : readClassNames())
035 *     addRunner(builder.runnerForClass(Class.forName(className)));
036 *   // ...
037 * }
038 * </pre>
039 *
040 * @see org.junit.runners.Suite
041 * @since 4.5
042 */
043public abstract class RunnerBuilder {
044    private final Set<Class<?>> parents = new HashSet<Class<?>>();
045
046    /**
047     * Override to calculate the correct runner for a test class at runtime.
048     *
049     * @param testClass class to be run
050     * @return a Runner
051     * @throws Throwable if a runner cannot be constructed
052     */
053    public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
054
055    /**
056     * Always returns a runner for the given test class.
057     *
058     * <p>In case of an exception a runner will be returned that prints an error instead of running
059     * tests.
060     *
061     * <p>Note that some of the internal JUnit implementations of RunnerBuilder will return
062     * {@code null} from this method, but no RunnerBuilder passed to a Runner constructor will
063     * return {@code null} from this method.
064     *
065     * @param testClass class to be run
066     * @return a Runner
067     */
068    public Runner safeRunnerForClass(Class<?> testClass) {
069        try {
070            Runner runner = runnerForClass(testClass);
071            if (runner != null) {
072                configureRunner(runner);
073            }
074            return runner;
075        } catch (Throwable e) {
076            return new ErrorReportingRunner(testClass, e);
077        }
078    }
079
080    private void configureRunner(Runner runner) throws InvalidOrderingException {
081        Description description = runner.getDescription();
082        OrderWith orderWith = description.getAnnotation(OrderWith.class);
083        if (orderWith != null) {
084            Ordering ordering = Ordering.definedBy(orderWith.value(), description);
085            ordering.apply(runner);
086        }
087    }
088
089    Class<?> addParent(Class<?> parent) throws InitializationError {
090        if (!parents.add(parent)) {
091            throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
092        }
093        return parent;
094    }
095
096    void removeParent(Class<?> klass) {
097        parents.remove(klass);
098    }
099
100    /**
101     * Constructs and returns a list of Runners, one for each child class in
102     * {@code children}.  Care is taken to avoid infinite recursion:
103     * this builder will throw an exception if it is requested for another
104     * runner for {@code parent} before this call completes.
105     */
106    public List<Runner> runners(Class<?> parent, Class<?>[] children)
107            throws InitializationError {
108        addParent(parent);
109
110        try {
111            return runners(children);
112        } finally {
113            removeParent(parent);
114        }
115    }
116
117    public List<Runner> runners(Class<?> parent, List<Class<?>> children)
118            throws InitializationError {
119        return runners(parent, children.toArray(new Class<?>[0]));
120    }
121
122    private List<Runner> runners(Class<?>[] children) {
123        List<Runner> runners = new ArrayList<Runner>();
124        for (Class<?> each : children) {
125            Runner childRunner = safeRunnerForClass(each);
126            if (childRunner != null) {
127                runners.add(childRunner);
128            }
129        }
130        return runners;
131    }
132}