1package org.junit.runners.model; 2 3import java.util.ArrayList; 4import java.util.HashSet; 5import java.util.List; 6import java.util.Set; 7 8import org.junit.internal.runners.ErrorReportingRunner; 9import 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 */ 38public abstract class RunnerBuilder { 39 private final Set<Class<?>> parents= new HashSet<Class<?>>(); 40 41 /** 42 * Override to calculate the correct runner for a test class at runtime. 43 * 44 * @param testClass class to be run 45 * @return a Runner 46 * @throws Throwable if a runner cannot be constructed 47 */ 48 public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; 49 50 /** 51 * Always returns a runner, even if it is just one that prints an error instead of running tests. 52 * @param testClass class to be run 53 * @return a Runner 54 */ 55 public Runner safeRunnerForClass(Class<?> testClass) { 56 try { 57 return runnerForClass(testClass); 58 } catch (Throwable e) { 59 return new ErrorReportingRunner(testClass, e); 60 } 61 } 62 63 Class<?> addParent(Class<?> parent) throws InitializationError { 64 if (!parents.add(parent)) 65 throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); 66 return parent; 67 } 68 69 void removeParent(Class<?> klass) { 70 parents.remove(klass); 71 } 72 73 /** 74 * Constructs and returns a list of Runners, one for each child class in 75 * {@code children}. Care is taken to avoid infinite recursion: 76 * this builder will throw an exception if it is requested for another 77 * runner for {@code parent} before this call completes. 78 */ 79 public List<Runner> runners(Class<?> parent, Class<?>[] children) 80 throws InitializationError { 81 addParent(parent); 82 83 try { 84 return runners(children); 85 } finally { 86 removeParent(parent); 87 } 88 } 89 90 public List<Runner> runners(Class<?> parent, List<Class<?>> children) 91 throws InitializationError { 92 return runners(parent, children.toArray(new Class<?>[0])); 93 } 94 95 private List<Runner> runners(Class<?>[] children) { 96 ArrayList<Runner> runners= new ArrayList<Runner>(); 97 for (Class<?> each : children) { 98 Runner childRunner= safeRunnerForClass(each); 99 if (childRunner != null) 100 runners.add(childRunner); 101 } 102 return runners; 103 } 104} 105