    001    package org.junit.runners.model;
    002
    003    import java.util.ArrayList;
    004    import java.util.HashSet;
    005    import java.util.List;
    006    import java.util.Set;
    007
    008    import org.junit.internal.runners.ErrorReportingRunner;
    009    import org.junit.runner.Description;
    010    import org.junit.runner.OrderWith;
    011    import org.junit.runner.Runner;
    012    import org.junit.runner.manipulation.InvalidOrderingException;
    013    import 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     */
    043    public 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    }