• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.junit.runners;
2 
3 import java.lang.annotation.Annotation;
4 import java.lang.annotation.ElementType;
5 import java.lang.annotation.Inherited;
6 import java.lang.annotation.Retention;
7 import java.lang.annotation.RetentionPolicy;
8 import java.lang.annotation.Target;
9 import java.text.MessageFormat;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.List;
15 
16 import org.junit.internal.AssumptionViolatedException;
17 import org.junit.runner.Description;
18 import org.junit.runner.Result;
19 import org.junit.runner.Runner;
20 import org.junit.runner.notification.Failure;
21 import org.junit.runner.notification.RunNotifier;
22 import org.junit.runners.model.FrameworkMethod;
23 import org.junit.runners.model.InvalidTestClassError;
24 import org.junit.runners.model.TestClass;
25 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
26 import org.junit.runners.parameterized.ParametersRunnerFactory;
27 import org.junit.runners.parameterized.TestWithParameters;
28 
29 /**
30  * The custom runner <code>Parameterized</code> implements parameterized tests.
31  * When running a parameterized test class, instances are created for the
32  * cross-product of the test methods and the test data elements.
33  * <p>
34  * For example, to test the <code>+</code> operator, write:
35  * <pre>
36  * &#064;RunWith(Parameterized.class)
37  * public class AdditionTest {
38  *     &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
39  *     public static Iterable&lt;Object[]&gt; data() {
40  *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
41  *                 { 3, 2, 5 }, { 4, 3, 7 } });
42  *     }
43  *
44  *     private int firstSummand;
45  *
46  *     private int secondSummand;
47  *
48  *     private int sum;
49  *
50  *     public AdditionTest(int firstSummand, int secondSummand, int sum) {
51  *         this.firstSummand = firstSummand;
52  *         this.secondSummand = secondSummand;
53  *         this.sum = sum;
54  *     }
55  *
56  *     &#064;Test
57  *     public void test() {
58  *         assertEquals(sum, firstSummand + secondSummand);
59  *     }
60  * }
61  * </pre>
62  * <p>
63  * Each instance of <code>AdditionTest</code> will be constructed using the
64  * three-argument constructor and the data values in the
65  * <code>&#064;Parameters</code> method.
66  * <p>
67  * In order that you can easily identify the individual tests, you may provide a
68  * name for the <code>&#064;Parameters</code> annotation. This name is allowed
69  * to contain placeholders, which are replaced at runtime. The placeholders are
70  * <dl>
71  * <dt>{index}</dt>
72  * <dd>the current parameter index</dd>
73  * <dt>{0}</dt>
74  * <dd>the first parameter value</dd>
75  * <dt>{1}</dt>
76  * <dd>the second parameter value</dd>
77  * <dt>...</dt>
78  * <dd>...</dd>
79  * </dl>
80  * <p>
81  * In the example given above, the <code>Parameterized</code> runner creates
82  * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter,
83  * then the current parameter index is used as name.
84  * <p>
85  * You can also write:
86  * <pre>
87  * &#064;RunWith(Parameterized.class)
88  * public class AdditionTest {
89  *     &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
90  *     public static Iterable&lt;Object[]&gt; data() {
91  *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
92  *                 { 3, 2, 5 }, { 4, 3, 7 } });
93  *     }
94  *
95  *     &#064;Parameter(0)
96  *     public int firstSummand;
97  *
98  *     &#064;Parameter(1)
99  *     public int secondSummand;
100  *
101  *     &#064;Parameter(2)
102  *     public int sum;
103  *
104  *     &#064;Test
105  *     public void test() {
106  *         assertEquals(sum, firstSummand + secondSummand);
107  *     }
108  * }
109  * </pre>
110  * <p>
111  * Each instance of <code>AdditionTest</code> will be constructed with the default constructor
112  * and fields annotated by <code>&#064;Parameter</code>  will be initialized
113  * with the data values in the <code>&#064;Parameters</code> method.
114  *
115  * <p>
116  * The parameters can be provided as an array, too:
117  *
118  * <pre>
119  * &#064;Parameters
120  * public static Object[][] data() {
121  * 	return new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } };
122  * }
123  * </pre>
124  *
125  * <h3>Tests with single parameter</h3>
126  * <p>
127  * If your test needs a single parameter only, you don't have to wrap it with an
128  * array. Instead you can provide an <code>Iterable</code> or an array of
129  * objects.
130  * <pre>
131  * &#064;Parameters
132  * public static Iterable&lt;? extends Object&gt; data() {
133  * 	return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
134  * }
135  * </pre>
136  * <p>
137  * or
138  * <pre>
139  * &#064;Parameters
140  * public static Object[] data() {
141  * 	return new Object[] { &quot;first test&quot;, &quot;second test&quot; };
142  * }
143  * </pre>
144  *
145  * <h3>Executing code before/after executing tests for specific parameters</h3>
146  * <p>
147  * If your test needs to perform some preparation or cleanup based on the
148  * parameters, this can be done by adding public static methods annotated with
149  * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no
150  * parameters or the same parameters as the test.
151  * <pre>
152  * &#064;BeforeParam
153  * public static void beforeTestsForParameter(String onlyParameter) {
154  *     System.out.println("Testing " + onlyParameter);
155  * }
156  * </pre>
157  *
158  * <h3>Create different runners</h3>
159  * <p>
160  * By default the {@code Parameterized} runner creates a slightly modified
161  * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an
162  * own {@code Parameterized} runner that creates another runner for each set of
163  * parameters. Therefore you have to build a {@link ParametersRunnerFactory}
164  * that creates a runner for each {@link TestWithParameters}. (
165  * {@code TestWithParameters} are bundling the parameters and the test name.)
166  * The factory must have a public zero-arg constructor.
167  *
168  * <pre>
169  * public class YourRunnerFactory implements ParametersRunnerFactory {
170  *     public Runner createRunnerForTestWithParameters(TestWithParameters test)
171  *             throws InitializationError {
172  *         return YourRunner(test);
173  *     }
174  * }
175  * </pre>
176  * <p>
177  * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized}
178  * runner that it should use your factory.
179  *
180  * <pre>
181  * &#064;RunWith(Parameterized.class)
182  * &#064;UseParametersRunnerFactory(YourRunnerFactory.class)
183  * public class YourTest {
184  *     ...
185  * }
186  * </pre>
187  *
188  * <h3>Avoid creating parameters</h3>
189  * <p>With {@link org.junit.Assume assumptions} you can dynamically skip tests.
190  * Assumptions are also supported by the <code>&#064;Parameters</code> method.
191  * Creating parameters is stopped when the assumption fails and none of the
192  * tests in the test class is executed. JUnit reports a
193  * {@link Result#getAssumptionFailureCount() single assumption failure} for the
194  * whole test class in this case.
195  * <pre>
196  * &#064;Parameters
197  * public static Iterable&lt;? extends Object&gt; data() {
198  * 	String os = System.getProperty("os.name").toLowerCase()
199  * 	Assume.assumeTrue(os.contains("win"));
200  * 	return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
201  * }
202  * </pre>
203  * @since 4.0
204  */
205 public class Parameterized extends Suite {
206     /**
207      * Annotation for a method which provides parameters to be injected into the
208      * test class constructor by <code>Parameterized</code>. The method has to
209      * be public and static.
210      */
211     @Retention(RetentionPolicy.RUNTIME)
212     @Target(ElementType.METHOD)
213     public @interface Parameters {
214         /**
215          * Optional pattern to derive the test's name from the parameters. Use
216          * numbers in braces to refer to the parameters or the additional data
217          * as follows:
218          * <pre>
219          * {index} - the current parameter index
220          * {0} - the first parameter value
221          * {1} - the second parameter value
222          * etc...
223          * </pre>
224          * <p>
225          * Default value is "{index}" for compatibility with previous JUnit
226          * versions.
227          *
228          * @return {@link MessageFormat} pattern string, except the index
229          *         placeholder.
230          * @see MessageFormat
231          */
name()232         String name() default "{index}";
233     }
234 
235     /**
236      * Annotation for fields of the test class which will be initialized by the
237      * method annotated by <code>Parameters</code>.
238      * By using directly this annotation, the test class constructor isn't needed.
239      * Index range must start at 0.
240      * Default value is 0.
241      */
242     @Retention(RetentionPolicy.RUNTIME)
243     @Target(ElementType.FIELD)
244     public @interface Parameter {
245         /**
246          * Method that returns the index of the parameter in the array
247          * returned by the method annotated by <code>Parameters</code>.
248          * Index range must start at 0.
249          * Default value is 0.
250          *
251          * @return the index of the parameter.
252          */
value()253         int value() default 0;
254     }
255 
256     /**
257      * Add this annotation to your test class if you want to generate a special
258      * runner. You have to specify a {@link ParametersRunnerFactory} class that
259      * creates such runners. The factory must have a public zero-arg
260      * constructor.
261      */
262     @Retention(RetentionPolicy.RUNTIME)
263     @Inherited
264     @Target(ElementType.TYPE)
265     public @interface UseParametersRunnerFactory {
266         /**
267          * @return a {@link ParametersRunnerFactory} class (must have a default
268          *         constructor)
269          */
value()270         Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class;
271     }
272 
273     /**
274      * Annotation for {@code public static void} methods which should be executed before
275      * evaluating tests with particular parameters.
276      *
277      * @see org.junit.BeforeClass
278      * @see org.junit.Before
279      * @since 4.13
280      */
281     @Retention(RetentionPolicy.RUNTIME)
282     @Target(ElementType.METHOD)
283     public @interface BeforeParam {
284     }
285 
286     /**
287      * Annotation for {@code public static void} methods which should be executed after
288      * evaluating tests with particular parameters.
289      *
290      * @see org.junit.AfterClass
291      * @see org.junit.After
292      * @since 4.13
293      */
294     @Retention(RetentionPolicy.RUNTIME)
295     @Target(ElementType.METHOD)
296     public @interface AfterParam {
297     }
298 
299     /**
300      * Only called reflectively. Do not use programmatically.
301      */
Parameterized(Class<?> klass)302     public Parameterized(Class<?> klass) throws Throwable {
303         this(klass, new RunnersFactory(klass));
304     }
305 
Parameterized(Class<?> klass, RunnersFactory runnersFactory)306     private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception {
307         super(klass, runnersFactory.createRunners());
308         validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount);
309     }
310 
validateBeforeParamAndAfterParamMethods(Integer parameterCount)311     private void validateBeforeParamAndAfterParamMethods(Integer parameterCount)
312             throws InvalidTestClassError {
313         List<Throwable> errors = new ArrayList<Throwable>();
314         validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors);
315         validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors);
316         if (!errors.isEmpty()) {
317             throw new InvalidTestClassError(getTestClass().getJavaClass(), errors);
318         }
319     }
320 
validatePublicStaticVoidMethods( Class<? extends Annotation> annotation, Integer parameterCount, List<Throwable> errors)321     private void validatePublicStaticVoidMethods(
322             Class<? extends Annotation> annotation, Integer parameterCount,
323             List<Throwable> errors) {
324         List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation);
325         for (FrameworkMethod fm : methods) {
326             fm.validatePublicVoid(true, errors);
327             if (parameterCount != null) {
328                 int methodParameterCount = fm.getMethod().getParameterTypes().length;
329                 if (methodParameterCount != 0 && methodParameterCount != parameterCount) {
330                     errors.add(new Exception("Method " + fm.getName()
331                             + "() should have 0 or " + parameterCount + " parameter(s)"));
332                 }
333             }
334         }
335     }
336 
337     private static class AssumptionViolationRunner extends Runner {
338         private final Description description;
339         private final AssumptionViolatedException exception;
340 
AssumptionViolationRunner(TestClass testClass, String methodName, AssumptionViolatedException exception)341         AssumptionViolationRunner(TestClass testClass, String methodName,
342                 AssumptionViolatedException exception) {
343             this.description = Description
344                     .createTestDescription(testClass.getJavaClass(),
345                             methodName + "() assumption violation");
346             this.exception = exception;
347         }
348 
349         @Override
getDescription()350         public Description getDescription() {
351             return description;
352         }
353 
354         @Override
run(RunNotifier notifier)355         public void run(RunNotifier notifier) {
356             notifier.fireTestAssumptionFailed(new Failure(description, exception));
357         }
358     }
359 
360     private static class RunnersFactory {
361         private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
362 
363         private final TestClass testClass;
364         private final FrameworkMethod parametersMethod;
365         private final List<Object> allParameters;
366         private final int parameterCount;
367         private final Runner runnerOverride;
368 
RunnersFactory(Class<?> klass)369         private RunnersFactory(Class<?> klass) throws Throwable {
370             testClass = new TestClass(klass);
371             parametersMethod = getParametersMethod(testClass);
372             List<Object> allParametersResult;
373             AssumptionViolationRunner assumptionViolationRunner = null;
374             try {
375                 allParametersResult = allParameters(testClass, parametersMethod);
376             } catch (AssumptionViolatedException e) {
377                 allParametersResult = Collections.emptyList();
378                 assumptionViolationRunner = new AssumptionViolationRunner(testClass,
379                         parametersMethod.getName(), e);
380             }
381             allParameters = allParametersResult;
382             runnerOverride = assumptionViolationRunner;
383             parameterCount =
384                     allParameters.isEmpty() ? 0 : normalizeParameters(allParameters.get(0)).length;
385         }
386 
createRunners()387         private List<Runner> createRunners() throws Exception {
388             if (runnerOverride != null) {
389                 return Collections.singletonList(runnerOverride);
390             }
391             Parameters parameters = parametersMethod.getAnnotation(Parameters.class);
392             return Collections.unmodifiableList(createRunnersForParameters(
393                     allParameters, parameters.name(),
394                     getParametersRunnerFactory()));
395         }
396 
getParametersRunnerFactory()397         private ParametersRunnerFactory getParametersRunnerFactory()
398                 throws InstantiationException, IllegalAccessException {
399             UseParametersRunnerFactory annotation = testClass
400                     .getAnnotation(UseParametersRunnerFactory.class);
401             if (annotation == null) {
402                 return DEFAULT_FACTORY;
403             } else {
404                 Class<? extends ParametersRunnerFactory> factoryClass = annotation
405                         .value();
406                 return factoryClass.newInstance();
407             }
408         }
409 
createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter)410         private TestWithParameters createTestWithNotNormalizedParameters(
411                 String pattern, int index, Object parametersOrSingleParameter) {
412             Object[] parameters = normalizeParameters(parametersOrSingleParameter);
413             return createTestWithParameters(testClass, pattern, index, parameters);
414         }
415 
normalizeParameters(Object parametersOrSingleParameter)416         private static Object[] normalizeParameters(Object parametersOrSingleParameter) {
417             return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
418                     : new Object[] { parametersOrSingleParameter };
419         }
420 
421         @SuppressWarnings("unchecked")
allParameters( TestClass testClass, FrameworkMethod parametersMethod)422         private static List<Object> allParameters(
423                 TestClass testClass, FrameworkMethod parametersMethod) throws Throwable {
424             Object parameters = parametersMethod.invokeExplosively(null);
425             if (parameters instanceof List) {
426                 return (List<Object>) parameters;
427             } else if (parameters instanceof Collection) {
428                 return new ArrayList<Object>((Collection<Object>) parameters);
429             } else if (parameters instanceof Iterable) {
430                 List<Object> result = new ArrayList<Object>();
431                 for (Object entry : ((Iterable<Object>) parameters)) {
432                     result.add(entry);
433                 }
434                 return result;
435             } else if (parameters instanceof Object[]) {
436                 return Arrays.asList((Object[]) parameters);
437             } else {
438                 throw parametersMethodReturnedWrongType(testClass, parametersMethod);
439             }
440         }
441 
getParametersMethod(TestClass testClass)442         private static FrameworkMethod getParametersMethod(TestClass testClass) throws Exception {
443             List<FrameworkMethod> methods = testClass
444                     .getAnnotatedMethods(Parameters.class);
445             for (FrameworkMethod each : methods) {
446                 if (each.isStatic() && each.isPublic()) {
447                     return each;
448                 }
449             }
450 
451             throw new Exception("No public static parameters method on class "
452                     + testClass.getName());
453         }
454 
createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory)455         private List<Runner> createRunnersForParameters(
456                 Iterable<Object> allParameters, String namePattern,
457                 ParametersRunnerFactory runnerFactory) throws Exception {
458             try {
459                 List<TestWithParameters> tests = createTestsForParameters(
460                         allParameters, namePattern);
461                 List<Runner> runners = new ArrayList<Runner>();
462                 for (TestWithParameters test : tests) {
463                     runners.add(runnerFactory
464                             .createRunnerForTestWithParameters(test));
465                 }
466                 return runners;
467             } catch (ClassCastException e) {
468                 throw parametersMethodReturnedWrongType(testClass, parametersMethod);
469             }
470         }
471 
createTestsForParameters( Iterable<Object> allParameters, String namePattern)472         private List<TestWithParameters> createTestsForParameters(
473                 Iterable<Object> allParameters, String namePattern)
474                 throws Exception {
475             int i = 0;
476             List<TestWithParameters> children = new ArrayList<TestWithParameters>();
477             for (Object parametersOfSingleTest : allParameters) {
478                 children.add(createTestWithNotNormalizedParameters(namePattern,
479                         i++, parametersOfSingleTest));
480             }
481             return children;
482         }
483 
parametersMethodReturnedWrongType( TestClass testClass, FrameworkMethod parametersMethod)484         private static Exception parametersMethodReturnedWrongType(
485                 TestClass testClass, FrameworkMethod parametersMethod) throws Exception {
486             String className = testClass.getName();
487             String methodName = parametersMethod.getName();
488             String message = MessageFormat.format(
489                     "{0}.{1}() must return an Iterable of arrays.", className,
490                     methodName);
491             return new Exception(message);
492         }
493 
createTestWithParameters( TestClass testClass, String pattern, int index, Object[] parameters)494         private TestWithParameters createTestWithParameters(
495                 TestClass testClass, String pattern, int index,
496                 Object[] parameters) {
497             String finalPattern = pattern.replaceAll("\\{index\\}",
498                     Integer.toString(index));
499             String name = MessageFormat.format(finalPattern, parameters);
500             return new TestWithParameters("[" + name + "]", testClass,
501                     Arrays.asList(parameters));
502         }
503     }
504 }
505