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 */ runnerForClass(Class<?> testClass)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 */ safeRunnerForClass(Class<?> testClass)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 addParent(Class<?> parent)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 removeParent(Class<?> klass)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 */ runners(Class<?> parent, Class<?>[] children)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 runners(Class<?> parent, List<Class<?>> children)93 public List<Runner> runners(Class<?> parent, List<Class<?>> children) 94 throws InitializationError { 95 return runners(parent, children.toArray(new Class<?>[0])); 96 } 97 runners(Class<?>[] children)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 } 109