1 package org.junit.runners.model;
2
3 import java.util.ArrayList;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Set;
7
8 import org.junit.internal.runners.ErrorReportingRunner;
9 import org.junit.runner.Runner;
10
11 /**
12 * A RunnerBuilder is a strategy for constructing runners for classes.
13 *
14 * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking
15 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself.
16 * For example,
17 * imagine a custom runner that builds suites based on a list of classes in a text file:
18 *
19 * <pre>
20 * \@RunWith(TextFileSuite.class)
21 * \@SuiteSpecFile("mysuite.txt")
22 * class MySuite {}
23 * </pre>
24 *
25 * The implementation of TextFileSuite might include:
26 *
27 * <pre>
28 * public TextFileSuite(Class testClass, RunnerBuilder builder) {
29 * // ...
30 * for (String className : readClassNames())
31 * addRunner(builder.runnerForClass(Class.forName(className)));
32 * // ...
33 * }
34 * </pre>
35 *
36 * @see org.junit.runners.Suite
37 * @since 4.5
38 */
39 public abstract class RunnerBuilder {
40 private final Set<Class<?>> parents = new HashSet<Class<?>>();
41
42 /**
43 * Override to calculate the correct runner for a test class at runtime.
44 *
45 * @param testClass class to be run
46 * @return a Runner
47 * @throws Throwable if a runner cannot be constructed
48 */
49 public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
50
51 /**
52 * Always returns a runner, even if it is just one that prints an error instead of running tests.
53 *
54 * @param testClass class to be run
55 * @return a Runner
56 */
57 public Runner safeRunnerForClass(Class<?> testClass) {
58 try {
59 return runnerForClass(testClass);
60 } catch (Throwable e) {
61 return new ErrorReportingRunner(testClass, e);
62 }
63 }
64
65 Class<?> addParent(Class<?> parent) throws InitializationError {
66 if (!parents.add(parent)) {
67 throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
68 }
69 return parent;
70 }
71
72 void removeParent(Class<?> klass) {
73 parents.remove(klass);
74 }
75
76 /**
77 * Constructs and returns a list of Runners, one for each child class in
78 * {@code children}. Care is taken to avoid infinite recursion:
79 * this builder will throw an exception if it is requested for another
80 * runner for {@code parent} before this call completes.
81 */
82 public List<Runner> runners(Class<?> parent, Class<?>[] children)
83 throws InitializationError {
84 addParent(parent);
85
86 try {
87 return runners(children);
88 } finally {
89 removeParent(parent);
90 }
91 }
92
93 public List<Runner> runners(Class<?> parent, List<Class<?>> children)
94 throws InitializationError {
95 return runners(parent, children.toArray(new Class<?>[0]));
96 }
97
98 private List<Runner> runners(Class<?>[] children) {
99 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 }