001package org.junit.runners.model; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.junit.internal.runners.ErrorReportingRunner; 009import org.junit.runner.Description; 010import org.junit.runner.OrderWith; 011import org.junit.runner.Runner; 012import org.junit.runner.manipulation.InvalidOrderingException; 013import 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 */ 043public 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}