1 package org.junit.internal.builders;
2
3 import org.junit.runner.RunWith;
4 import org.junit.runner.Runner;
5 import org.junit.runners.model.InitializationError;
6 import org.junit.runners.model.RunnerBuilder;
7
8 import java.lang.reflect.Modifier;
9
10
11 /**
12 * The {@code AnnotatedBuilder} is a strategy for constructing runners for test class that have been annotated with the
13 * {@code @RunWith} annotation. All tests within this class will be executed using the runner that was specified within
14 * the annotation.
15 * <p>
16 * If a runner supports inner member classes, the member classes will inherit the runner from the enclosing class, e.g.:
17 * <pre>
18 * @RunWith(MyRunner.class)
19 * public class MyTest {
20 * // some tests might go here
21 *
22 * public class MyMemberClass {
23 * @Test
24 * public void thisTestRunsWith_MyRunner() {
25 * // some test logic
26 * }
27 *
28 * // some more tests might go here
29 * }
30 *
31 * @RunWith(AnotherRunner.class)
32 * public class AnotherMemberClass {
33 * // some tests might go here
34 *
35 * public class DeepInnerClass {
36 * @Test
37 * public void thisTestRunsWith_AnotherRunner() {
38 * // some test logic
39 * }
40 * }
41 *
42 * public class DeepInheritedClass extends SuperTest {
43 * @Test
44 * public void thisTestRunsWith_SuperRunner() {
45 * // some test logic
46 * }
47 * }
48 * }
49 * }
50 *
51 * @RunWith(SuperRunner.class)
52 * public class SuperTest {
53 * // some tests might go here
54 * }
55 * </pre>
56 * The key points to note here are:
57 * <ul>
58 * <li>If there is no RunWith annotation, no runner will be created.</li>
59 * <li>The resolve step is inside-out, e.g. the closest RunWith annotation wins</li>
60 * <li>RunWith annotations are inherited and work as if the class was annotated itself.</li>
61 * <li>The default JUnit runner does not support inner member classes,
62 * so this is only valid for custom runners that support inner member classes.</li>
63 * <li>Custom runners with support for inner classes may or may not support RunWith annotations for member
64 * classes. Please refer to the custom runner documentation.</li>
65 * </ul>
66 *
67 * @see org.junit.runners.model.RunnerBuilder
68 * @see org.junit.runner.RunWith
69 * @since 4.0
70 */
71 public class AnnotatedBuilder extends RunnerBuilder {
72 private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
73
74 private final RunnerBuilder suiteBuilder;
75
76 public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
77 this.suiteBuilder = suiteBuilder;
78 }
79
80 @Override
81 public Runner runnerForClass(Class<?> testClass) throws Exception {
82 for (Class<?> currentTestClass = testClass; currentTestClass != null;
83 currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
84 RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
85 if (annotation != null) {
86 return buildRunner(annotation.value(), testClass);
87 }
88 }
89
90 return null;
91 }
92
93 private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) {
94 if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) {
95 return currentTestClass.getEnclosingClass();
96 } else {
97 return null;
98 }
99 }
100
101 public Runner buildRunner(Class<? extends Runner> runnerClass,
102 Class<?> testClass) throws Exception {
103 try {
104 return runnerClass.getConstructor(Class.class).newInstance(testClass);
105 } catch (NoSuchMethodException e) {
106 try {
107 return runnerClass.getConstructor(Class.class,
108 RunnerBuilder.class).newInstance(testClass, suiteBuilder);
109 } catch (NoSuchMethodException e2) {
110 String simpleName = runnerClass.getSimpleName();
111 throw new InitializationError(String.format(
112 CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
113 }
114 }
115 }
116 }