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.Runner;
010
011 /**
012 * A RunnerBuilder is a strategy for constructing runners for classes.
013 *
014 * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking
015 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself.
016 * For example,
017 * imagine a custom runner that builds suites based on a list of classes in a text file:
018 *
019 * <pre>
020 * \@RunWith(TextFileSuite.class)
021 * \@SuiteSpecFile("mysuite.txt")
022 * class MySuite {}
023 * </pre>
024 *
025 * The implementation of TextFileSuite might include:
026 *
027 * <pre>
028 * public TextFileSuite(Class testClass, RunnerBuilder builder) {
029 * // ...
030 * for (String className : readClassNames())
031 * addRunner(builder.runnerForClass(Class.forName(className)));
032 * // ...
033 * }
034 * </pre>
035 *
036 * @see org.junit.runners.Suite
037 * @since 4.5
038 */
039 public abstract class RunnerBuilder {
040 private final Set<Class<?>> parents = new HashSet<Class<?>>();
041
042 /**
043 * Override to calculate the correct runner for a test class at runtime.
044 *
045 * @param testClass class to be run
046 * @return a Runner
047 * @throws Throwable if a runner cannot be constructed
048 */
049 public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
050
051 /**
052 * Always returns a runner, even if it is just one that prints an error instead of running tests.
053 *
054 * @param testClass class to be run
055 * @return a Runner
056 */
057 public Runner safeRunnerForClass(Class<?> testClass) {
058 try {
059 return runnerForClass(testClass);
060 } catch (Throwable e) {
061 return new ErrorReportingRunner(testClass, e);
062 }
063 }
064
065 Class<?> addParent(Class<?> parent) throws InitializationError {
066 if (!parents.add(parent)) {
067 throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
068 }
069 return parent;
070 }
071
072 void removeParent(Class<?> klass) {
073 parents.remove(klass);
074 }
075
076 /**
077 * Constructs and returns a list of Runners, one for each child class in
078 * {@code children}. Care is taken to avoid infinite recursion:
079 * this builder will throw an exception if it is requested for another
080 * runner for {@code parent} before this call completes.
081 */
082 public List<Runner> runners(Class<?> parent, Class<?>[] children)
083 throws InitializationError {
084 addParent(parent);
085
086 try {
087 return runners(children);
088 } finally {
089 removeParent(parent);
090 }
091 }
092
093 public List<Runner> runners(Class<?> parent, List<Class<?>> children)
094 throws InitializationError {
095 return runners(parent, children.toArray(new Class<?>[0]));
096 }
097
098 private List<Runner> runners(Class<?>[] children) {
099 ArrayList<Runner> runners = new ArrayList<Runner>();
100 for (Class<?> each : children) {
101 Runner childRunner = safeRunnerForClass(each);
102 if (childRunner != null) {
103 runners.add(childRunner);
104 }
105 }
106 return runners;
107 }
108 }