1 package org.junit.runners;
2 
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Inherited;
5 import java.lang.annotation.Retention;
6 import java.lang.annotation.RetentionPolicy;
7 import java.lang.annotation.Target;
8 import java.text.MessageFormat;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.List;
13 
14 import org.junit.runner.Runner;
15 import org.junit.runners.model.FrameworkMethod;
16 import org.junit.runners.model.InitializationError;
17 import org.junit.runners.model.TestClass;
18 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
19 import org.junit.runners.parameterized.ParametersRunnerFactory;
20 import org.junit.runners.parameterized.TestWithParameters;
21 
22 /**
23  * The custom runner <code>Parameterized</code> implements parameterized tests.
24  * When running a parameterized test class, instances are created for the
25  * cross-product of the test methods and the test data elements.
26  * <p>
27  * For example, to test a Fibonacci function, write:
28  * <pre>
29  * &#064;RunWith(Parameterized.class)
30  * public class FibonacciTest {
31  *     &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
32  *     public static Iterable&lt;Object[]&gt; data() {
33  *         return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
34  *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
35  *     }
36  *
37  *     private int fInput;
38  *
39  *     private int fExpected;
40  *
41  *     public FibonacciTest(int input, int expected) {
42  *         fInput= input;
43  *         fExpected= expected;
44  *     }
45  *
46  *     &#064;Test
47  *     public void test() {
48  *         assertEquals(fExpected, Fibonacci.compute(fInput));
49  *     }
50  * }
51  * </pre>
52  * <p>
53  * Each instance of <code>FibonacciTest</code> will be constructed using the
54  * two-argument constructor and the data values in the
55  * <code>&#064;Parameters</code> method.
56  * <p>
57  * In order that you can easily identify the individual tests, you may provide a
58  * name for the <code>&#064;Parameters</code> annotation. This name is allowed
59  * to contain placeholders, which are replaced at runtime. The placeholders are
60  * <dl>
61  * <dt>{index}</dt>
62  * <dd>the current parameter index</dd>
63  * <dt>{0}</dt>
64  * <dd>the first parameter value</dd>
65  * <dt>{1}</dt>
66  * <dd>the second parameter value</dd>
67  * <dt>...</dt>
68  * <dd>...</dd>
69  * </dl>
70  * <p>
71  * In the example given above, the <code>Parameterized</code> runner creates
72  * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter,
73  * then the current parameter index is used as name.
74  * <p>
75  * You can also write:
76  * <pre>
77  * &#064;RunWith(Parameterized.class)
78  * public class FibonacciTest {
79  *  &#064;Parameters
80  *  public static Iterable&lt;Object[]&gt; data() {
81  *      return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
82  *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
83  *  }
84  *
85  *  &#064;Parameter(0)
86  *  public int fInput;
87  *
88  *  &#064;Parameter(1)
89  *  public int fExpected;
90  *
91  *  &#064;Test
92  *  public void test() {
93  *      assertEquals(fExpected, Fibonacci.compute(fInput));
94  *  }
95  * }
96  * </pre>
97  * <p>
98  * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
99  * and fields annotated by <code>&#064;Parameter</code>  will be initialized
100  * with the data values in the <code>&#064;Parameters</code> method.
101  *
102  * <p>
103  * The parameters can be provided as an array, too:
104  *
105  * <pre>
106  * &#064;Parameters
107  * public static Object[][] data() {
108  * 	return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
109  * 			{ 5, 5 }, { 6, 8 } };
110  * }
111  * </pre>
112  *
113  * <h3>Tests with single parameter</h3>
114  * <p>
115  * If your test needs a single parameter only, you don't have to wrap it with an
116  * array. Instead you can provide an <code>Iterable</code> or an array of
117  * objects.
118  * <pre>
119  * &#064;Parameters
120  * public static Iterable&lt;? extends Object&gt; data() {
121  * 	return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
122  * }
123  * </pre>
124  * <p>
125  * or
126  * <pre>
127  * &#064;Parameters
128  * public static Object[] data() {
129  * 	return new Object[] { &quot;first test&quot;, &quot;second test&quot; };
130  * }
131  * </pre>
132  *
133  * <h3>Create different runners</h3>
134  * <p>
135  * By default the {@code Parameterized} runner creates a slightly modified
136  * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an
137  * own {@code Parameterized} runner that creates another runner for each set of
138  * parameters. Therefore you have to build a {@link ParametersRunnerFactory}
139  * that creates a runner for each {@link TestWithParameters}. (
140  * {@code TestWithParameters} are bundling the parameters and the test name.)
141  * The factory must have a public zero-arg constructor.
142  *
143  * <pre>
144  * public class YourRunnerFactory implements ParameterizedRunnerFactory {
145  *     public Runner createRunnerForTestWithParameters(TestWithParameters test)
146  *             throws InitializationError {
147  *         return YourRunner(test);
148  *     }
149  * }
150  * </pre>
151  * <p>
152  * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized}
153  * runner that it should use your factory.
154  *
155  * <pre>
156  * &#064;RunWith(Parameterized.class)
157  * &#064;UseParametersRunnerFactory(YourRunnerFactory.class)
158  * public class YourTest {
159  *     ...
160  * }
161  * </pre>
162  *
163  * @since 4.0
164  */
165 public class Parameterized extends Suite {
166     /**
167      * Annotation for a method which provides parameters to be injected into the
168      * test class constructor by <code>Parameterized</code>. The method has to
169      * be public and static.
170      */
171     @Retention(RetentionPolicy.RUNTIME)
172     @Target(ElementType.METHOD)
173     public static @interface Parameters {
174         /**
175          * Optional pattern to derive the test's name from the parameters. Use
176          * numbers in braces to refer to the parameters or the additional data
177          * as follows:
178          * <pre>
179          * {index} - the current parameter index
180          * {0} - the first parameter value
181          * {1} - the second parameter value
182          * etc...
183          * </pre>
184          * <p>
185          * Default value is "{index}" for compatibility with previous JUnit
186          * versions.
187          *
188          * @return {@link MessageFormat} pattern string, except the index
189          *         placeholder.
190          * @see MessageFormat
191          */
name()192         String name() default "{index}";
193     }
194 
195     /**
196      * Annotation for fields of the test class which will be initialized by the
197      * method annotated by <code>Parameters</code>.
198      * By using directly this annotation, the test class constructor isn't needed.
199      * Index range must start at 0.
200      * Default value is 0.
201      */
202     @Retention(RetentionPolicy.RUNTIME)
203     @Target(ElementType.FIELD)
204     public static @interface Parameter {
205         /**
206          * Method that returns the index of the parameter in the array
207          * returned by the method annotated by <code>Parameters</code>.
208          * Index range must start at 0.
209          * Default value is 0.
210          *
211          * @return the index of the parameter.
212          */
value()213         int value() default 0;
214     }
215 
216     /**
217      * Add this annotation to your test class if you want to generate a special
218      * runner. You have to specify a {@link ParametersRunnerFactory} class that
219      * creates such runners. The factory must have a public zero-arg
220      * constructor.
221      */
222     @Retention(RetentionPolicy.RUNTIME)
223     @Inherited
224     @Target(ElementType.TYPE)
225     public @interface UseParametersRunnerFactory {
226         /**
227          * @return a {@link ParametersRunnerFactory} class (must have a default
228          *         constructor)
229          */
value()230         Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class;
231     }
232 
233     private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
234 
235     private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList();
236 
237     private final List<Runner> runners;
238 
239     /**
240      * Only called reflectively. Do not use programmatically.
241      */
Parameterized(Class<?> klass)242     public Parameterized(Class<?> klass) throws Throwable {
243         super(klass, NO_RUNNERS);
244         ParametersRunnerFactory runnerFactory = getParametersRunnerFactory(
245                 klass);
246         Parameters parameters = getParametersMethod().getAnnotation(
247                 Parameters.class);
248         runners = Collections.unmodifiableList(createRunnersForParameters(
249                 allParameters(), parameters.name(), runnerFactory));
250     }
251 
getParametersRunnerFactory(Class<?> klass)252     private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass)
253             throws InstantiationException, IllegalAccessException {
254         UseParametersRunnerFactory annotation = klass
255                 .getAnnotation(UseParametersRunnerFactory.class);
256         if (annotation == null) {
257             return DEFAULT_FACTORY;
258         } else {
259             Class<? extends ParametersRunnerFactory> factoryClass = annotation
260                     .value();
261             return factoryClass.newInstance();
262         }
263     }
264 
265     @Override
getChildren()266     protected List<Runner> getChildren() {
267         return runners;
268     }
269 
createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter)270     private TestWithParameters createTestWithNotNormalizedParameters(
271             String pattern, int index, Object parametersOrSingleParameter) {
272         Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
273             : new Object[] { parametersOrSingleParameter };
274         return createTestWithParameters(getTestClass(), pattern, index,
275                 parameters);
276     }
277 
278     @SuppressWarnings("unchecked")
allParameters()279     private Iterable<Object> allParameters() throws Throwable {
280         Object parameters = getParametersMethod().invokeExplosively(null);
281         if (parameters instanceof Iterable) {
282             return (Iterable<Object>) parameters;
283         } else if (parameters instanceof Object[]) {
284             return Arrays.asList((Object[]) parameters);
285         } else {
286             throw parametersMethodReturnedWrongType();
287         }
288     }
289 
getParametersMethod()290     private FrameworkMethod getParametersMethod() throws Exception {
291         List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(
292                 Parameters.class);
293         for (FrameworkMethod each : methods) {
294             if (each.isStatic() && each.isPublic()) {
295                 return each;
296             }
297         }
298 
299         throw new Exception("No public static parameters method on class "
300                 + getTestClass().getName());
301     }
302 
createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory)303     private List<Runner> createRunnersForParameters(
304             Iterable<Object> allParameters, String namePattern,
305             ParametersRunnerFactory runnerFactory)
306             throws InitializationError,
307             Exception {
308         try {
309             List<TestWithParameters> tests = createTestsForParameters(
310                     allParameters, namePattern);
311             List<Runner> runners = new ArrayList<Runner>();
312             for (TestWithParameters test : tests) {
313                 runners.add(runnerFactory
314                         .createRunnerForTestWithParameters(test));
315             }
316             return runners;
317         } catch (ClassCastException e) {
318             throw parametersMethodReturnedWrongType();
319         }
320     }
321 
createTestsForParameters( Iterable<Object> allParameters, String namePattern)322     private List<TestWithParameters> createTestsForParameters(
323             Iterable<Object> allParameters, String namePattern)
324             throws Exception {
325         int i = 0;
326         List<TestWithParameters> children = new ArrayList<TestWithParameters>();
327         for (Object parametersOfSingleTest : allParameters) {
328             children.add(createTestWithNotNormalizedParameters(namePattern,
329                     i++, parametersOfSingleTest));
330         }
331         return children;
332     }
333 
parametersMethodReturnedWrongType()334     private Exception parametersMethodReturnedWrongType() throws Exception {
335         String className = getTestClass().getName();
336         String methodName = getParametersMethod().getName();
337         String message = MessageFormat.format(
338                 "{0}.{1}() must return an Iterable of arrays.",
339                 className, methodName);
340         return new Exception(message);
341     }
342 
createTestWithParameters( TestClass testClass, String pattern, int index, Object[] parameters)343     private static TestWithParameters createTestWithParameters(
344             TestClass testClass, String pattern, int index, Object[] parameters) {
345         String finalPattern = pattern.replaceAll("\\{index\\}",
346                 Integer.toString(index));
347         String name = MessageFormat.format(finalPattern, parameters);
348         return new TestWithParameters("[" + name + "]", testClass,
349                 Arrays.asList(parameters));
350     }
351 }
352