001package org.junit.runners;
002
003import java.lang.annotation.Annotation;
004import java.lang.annotation.ElementType;
005import java.lang.annotation.Inherited;
006import java.lang.annotation.Retention;
007import java.lang.annotation.RetentionPolicy;
008import java.lang.annotation.Target;
009import java.text.MessageFormat;
010import java.util.ArrayList;
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.List;
015
016import org.junit.internal.AssumptionViolatedException;
017import org.junit.runner.Description;
018import org.junit.runner.Result;
019import org.junit.runner.Runner;
020import org.junit.runner.notification.Failure;
021import org.junit.runner.notification.RunNotifier;
022import org.junit.runners.model.FrameworkMethod;
023import org.junit.runners.model.InvalidTestClassError;
024import org.junit.runners.model.TestClass;
025import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
026import org.junit.runners.parameterized.ParametersRunnerFactory;
027import org.junit.runners.parameterized.TestWithParameters;
028
029/**
030 * The custom runner <code>Parameterized</code> implements parameterized tests.
031 * When running a parameterized test class, instances are created for the
032 * cross-product of the test methods and the test data elements.
033 * <p>
034 * For example, to test the <code>+</code> operator, write:
035 * <pre>
036 * &#064;RunWith(Parameterized.class)
037 * public class AdditionTest {
038 *     &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
039 *     public static Iterable&lt;Object[]&gt; data() {
040 *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
041 *                 { 3, 2, 5 }, { 4, 3, 7 } });
042 *     }
043 *
044 *     private int firstSummand;
045 *
046 *     private int secondSummand;
047 *
048 *     private int sum;
049 *
050 *     public AdditionTest(int firstSummand, int secondSummand, int sum) {
051 *         this.firstSummand = firstSummand;
052 *         this.secondSummand = secondSummand;
053 *         this.sum = sum;
054 *     }
055 *
056 *     &#064;Test
057 *     public void test() {
058 *         assertEquals(sum, firstSummand + secondSummand);
059 *     }
060 * }
061 * </pre>
062 * <p>
063 * Each instance of <code>AdditionTest</code> will be constructed using the
064 * three-argument constructor and the data values in the
065 * <code>&#064;Parameters</code> method.
066 * <p>
067 * In order that you can easily identify the individual tests, you may provide a
068 * name for the <code>&#064;Parameters</code> annotation. This name is allowed
069 * to contain placeholders, which are replaced at runtime. The placeholders are
070 * <dl>
071 * <dt>{index}</dt>
072 * <dd>the current parameter index</dd>
073 * <dt>{0}</dt>
074 * <dd>the first parameter value</dd>
075 * <dt>{1}</dt>
076 * <dd>the second parameter value</dd>
077 * <dt>...</dt>
078 * <dd>...</dd>
079 * </dl>
080 * <p>
081 * In the example given above, the <code>Parameterized</code> runner creates
082 * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter,
083 * then the current parameter index is used as name.
084 * <p>
085 * You can also write:
086 * <pre>
087 * &#064;RunWith(Parameterized.class)
088 * public class AdditionTest {
089 *     &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
090 *     public static Iterable&lt;Object[]&gt; data() {
091 *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
092 *                 { 3, 2, 5 }, { 4, 3, 7 } });
093 *     }
094 *
095 *     &#064;Parameter(0)
096 *     public int firstSummand;
097 *
098 *     &#064;Parameter(1)
099 *     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 */
205public 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         */
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         */
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         */
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     */
302    public Parameterized(Class<?> klass) throws Throwable {
303        this(klass, new RunnersFactory(klass));
304    }
305
306    private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception {
307        super(klass, runnersFactory.createRunners());
308        validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount);
309    }
310
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
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
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
350        public Description getDescription() {
351            return description;
352        }
353
354        @Override
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
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
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
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
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
416        private static Object[] normalizeParameters(Object parametersOrSingleParameter) {
417            return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
418                    : new Object[] { parametersOrSingleParameter };
419        }
420
421        @SuppressWarnings("unchecked")
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
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
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
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
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
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}