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 }