001    package org.junit.runners;
002    
003    import java.lang.annotation.ElementType;
004    import java.lang.annotation.Inherited;
005    import java.lang.annotation.Retention;
006    import java.lang.annotation.RetentionPolicy;
007    import java.lang.annotation.Target;
008    import java.text.MessageFormat;
009    import java.util.ArrayList;
010    import java.util.Arrays;
011    import java.util.Collections;
012    import java.util.List;
013    
014    import org.junit.runner.Runner;
015    import org.junit.runners.model.FrameworkMethod;
016    import org.junit.runners.model.InitializationError;
017    import org.junit.runners.model.TestClass;
018    import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
019    import org.junit.runners.parameterized.ParametersRunnerFactory;
020    import org.junit.runners.parameterized.TestWithParameters;
021    
022    /**
023     * The custom runner <code>Parameterized</code> implements parameterized tests.
024     * When running a parameterized test class, instances are created for the
025     * cross-product of the test methods and the test data elements.
026     * <p>
027     * For example, to test a Fibonacci function, write:
028     * <pre>
029     * &#064;RunWith(Parameterized.class)
030     * public class FibonacciTest {
031     *     &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
032     *     public static Iterable&lt;Object[]&gt; data() {
033     *         return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
034     *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
035     *     }
036     *
037     *     private int fInput;
038     *
039     *     private int fExpected;
040     *
041     *     public FibonacciTest(int input, int expected) {
042     *         fInput= input;
043     *         fExpected= expected;
044     *     }
045     *
046     *     &#064;Test
047     *     public void test() {
048     *         assertEquals(fExpected, Fibonacci.compute(fInput));
049     *     }
050     * }
051     * </pre>
052     * <p>
053     * Each instance of <code>FibonacciTest</code> will be constructed using the
054     * two-argument constructor and the data values in the
055     * <code>&#064;Parameters</code> method.
056     * <p>
057     * In order that you can easily identify the individual tests, you may provide a
058     * name for the <code>&#064;Parameters</code> annotation. This name is allowed
059     * to contain placeholders, which are replaced at runtime. The placeholders are
060     * <dl>
061     * <dt>{index}</dt>
062     * <dd>the current parameter index</dd>
063     * <dt>{0}</dt>
064     * <dd>the first parameter value</dd>
065     * <dt>{1}</dt>
066     * <dd>the second parameter value</dd>
067     * <dt>...</dt>
068     * <dd>...</dd>
069     * </dl>
070     * <p>
071     * In the example given above, the <code>Parameterized</code> runner creates
072     * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter,
073     * then the current parameter index is used as name.
074     * <p>
075     * You can also write:
076     * <pre>
077     * &#064;RunWith(Parameterized.class)
078     * public class FibonacciTest {
079     *  &#064;Parameters
080     *  public static Iterable&lt;Object[]&gt; data() {
081     *      return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
082     *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
083     *  }
084     *  
085     *  &#064;Parameter(0)
086     *  public int fInput;
087     *
088     *  &#064;Parameter(1)
089     *  public int fExpected;
090     *
091     *  &#064;Test
092     *  public void test() {
093     *      assertEquals(fExpected, Fibonacci.compute(fInput));
094     *  }
095     * }
096     * </pre>
097     * <p>
098     * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
099     * 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             */
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             */
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             */
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         */
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    
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
266        protected List<Runner> getChildren() {
267            return runners;
268        }
269    
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")
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    
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    
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    
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    
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    
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    }