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.Runner; 010 011 /** 012 * A RunnerBuilder is a strategy for constructing runners for classes. 013 * 014 * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking 015 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself. 016 * For example, 017 * imagine a custom runner that builds suites based on a list of classes in a text file: 018 * 019 * <pre> 020 * \@RunWith(TextFileSuite.class) 021 * \@SuiteSpecFile("mysuite.txt") 022 * class MySuite {} 023 * </pre> 024 * 025 * The implementation of TextFileSuite might include: 026 * 027 * <pre> 028 * public TextFileSuite(Class testClass, RunnerBuilder builder) { 029 * // ... 030 * for (String className : readClassNames()) 031 * addRunner(builder.runnerForClass(Class.forName(className))); 032 * // ... 033 * } 034 * </pre> 035 * 036 * @see org.junit.runners.Suite 037 * @since 4.5 038 */ 039 public abstract class RunnerBuilder { 040 private final Set<Class<?>> parents = new HashSet<Class<?>>(); 041 042 /** 043 * Override to calculate the correct runner for a test class at runtime. 044 * 045 * @param testClass class to be run 046 * @return a Runner 047 * @throws Throwable if a runner cannot be constructed 048 */ 049 public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; 050 051 /** 052 * Always returns a runner, even if it is just one that prints an error instead of running tests. 053 * 054 * @param testClass class to be run 055 * @return a Runner 056 */ 057 public Runner safeRunnerForClass(Class<?> testClass) { 058 try { 059 return runnerForClass(testClass); 060 } catch (Throwable e) { 061 return new ErrorReportingRunner(testClass, e); 062 } 063 } 064 065 Class<?> addParent(Class<?> parent) throws InitializationError { 066 if (!parents.add(parent)) { 067 throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); 068 } 069 return parent; 070 } 071 072 void removeParent(Class<?> klass) { 073 parents.remove(klass); 074 } 075 076 /** 077 * Constructs and returns a list of Runners, one for each child class in 078 * {@code children}. Care is taken to avoid infinite recursion: 079 * this builder will throw an exception if it is requested for another 080 * runner for {@code parent} before this call completes. 081 */ 082 public List<Runner> runners(Class<?> parent, Class<?>[] children) 083 throws InitializationError { 084 addParent(parent); 085 086 try { 087 return runners(children); 088 } finally { 089 removeParent(parent); 090 } 091 } 092 093 public List<Runner> runners(Class<?> parent, List<Class<?>> children) 094 throws InitializationError { 095 return runners(parent, children.toArray(new Class<?>[0])); 096 } 097 098 private List<Runner> runners(Class<?>[] children) { 099 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 }