View Javadoc
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 }