    001    package org.junit.runners;
    002
    003    import java.lang.annotation.Annotation;
    004    import java.lang.annotation.ElementType;
    005    import java.lang.annotation.Inherited;
    006    import java.lang.annotation.Retention;
    007    import java.lang.annotation.RetentionPolicy;
    008    import java.lang.annotation.Target;
    009    import java.text.MessageFormat;
    010    import java.util.ArrayList;
    011    import java.util.Arrays;
    012    import java.util.Collection;
    013    import java.util.Collections;
    014    import java.util.List;
    015
    016    import org.junit.internal.AssumptionViolatedException;
    017    import org.junit.runner.Description;
    018    import org.junit.runner.Result;
    019    import org.junit.runner.Runner;
    020    import org.junit.runner.notification.Failure;
    021    import org.junit.runner.notification.RunNotifier;
    022    import org.junit.runners.model.FrameworkMethod;
    023    import org.junit.runners.model.InvalidTestClassError;
    024    import org.junit.runners.model.TestClass;
    025    import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
    026    import org.junit.runners.parameterized.ParametersRunnerFactory;
    027    import 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     */
    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             */
    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    }