diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java index 98f86bd5eb0f..91d37777f044 100644 --- a/src/main/java/org/junit/runners/Parameterized.java +++ b/src/main/java/org/junit/runners/Parameterized.java @@ -1,354 +1,349 @@ -package org.junit.runners; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Field; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.model.FrameworkField; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; - -/** - *

- * The custom runner Parameterized implements parameterized tests. - * When running a parameterized test class, instances are created for the - * cross-product of the test methods and the test data elements. - *

- * - * For example, to test a Fibonacci function, write: - * - *
- * @RunWith(Parameterized.class)
- * public class FibonacciTest {
- *     @Parameters(name= "{index}: fib[{0}]={1}")
- *     public static Iterable<Object[]> data() {
- *         return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
- *     }
- *
- *     private int fInput;
- *
- *     private int fExpected;
- *
- *     public FibonacciTest(int input, int expected) {
- *         fInput= input;
- *         fExpected= expected;
- *     }
- *
- *     @Test
- *     public void test() {
- *         assertEquals(fExpected, Fibonacci.compute(fInput));
- *     }
- * }
- * 
- * - *

- * Each instance of FibonacciTest will be constructed using the - * two-argument constructor and the data values in the - * @Parameters method. - * - *

- * In order that you can easily identify the individual tests, you may provide a - * name for the @Parameters annotation. This name is allowed - * to contain placeholders, which are replaced at runtime. The placeholders are - *

- *
{index}
- *
the current parameter index
- *
{0}
- *
the first parameter value
- *
{1}
- *
the second parameter value
- *
...
- *
- *
- * In the example given above, the Parameterized runner creates - * names like [1: fib(3)=2]. If you don't use the name parameter, - * then the current parameter index is used as name. - *

- * - * You can also write: - * - *
- * @RunWith(Parameterized.class)
- * public class FibonacciTest {
- *  @Parameters
- *  public static Iterable<Object[]> data() {
- *      return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
- *  }
- *  
- *  @Parameter(0)
- *  public int fInput;
- *
- *  @Parameter(1)
- *  public int fExpected;
- *
- *  @Test
- *  public void test() {
- *      assertEquals(fExpected, Fibonacci.compute(fInput));
- *  }
- * }
- * 
- * - *

- * Each instance of FibonacciTest will be constructed with the default constructor - * and fields annotated by @Parameter will be initialized - * with the data values in the @Parameters method. - *

- * - * @since 4.0 - */ -public class Parameterized extends Suite { - /** - * Annotation for a method which provides parameters to be injected into the - * test class constructor by Parameterized - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public static @interface Parameters { - /** - *

- * Optional pattern to derive the test's name from the parameters. Use - * numbers in braces to refer to the parameters or the additional data - * as follows: - *

- * - *
-         * {index} - the current parameter index
-         * {0} - the first parameter value
-         * {1} - the second parameter value
-         * etc...
-         * 
- *

- * Default value is "{index}" for compatibility with previous JUnit - * versions. - *

- * - * @return {@link MessageFormat} pattern string, except the index - * placeholder. - * @see MessageFormat - */ - String name() default "{index}"; - } - - /** - * Annotation for fields of the test class which will be initialized by the - * method annotated by Parameters
- * By using directly this annotation, the test class constructor isn't needed.
- * Index range must start at 0. - * Default value is 0. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public static @interface Parameter { - /** - * Method that returns the index of the parameter in the array - * returned by the method annotated by Parameters.
- * Index range must start at 0. - * Default value is 0. - * - * @return the index of the parameter. - */ - int value() default 0; - } - - private class TestClassRunnerForParameters extends BlockJUnit4ClassRunner { - private final Object[] fParameters; - - private final String fName; - - TestClassRunnerForParameters(Class type, Object[] parameters, - String name) throws InitializationError { - super(type); - fParameters = parameters; - fName = name; - } - - @Override - public Object createTest() throws Exception { - if (fieldsAreAnnotated()) { - return createTestUsingFieldInjection(); - } else { - return createTestUsingConstructorInjection(); - } - } - - private Object createTestUsingConstructorInjection() throws Exception { - return getTestClass().getOnlyConstructor().newInstance(fParameters); - } - - private Object createTestUsingFieldInjection() throws Exception { - List annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); - if (annotatedFieldsByParameter.size() != fParameters.length) { - throw new Exception("Wrong number of parameters and @Parameter fields." + - " @Parameter fields counted: " + annotatedFieldsByParameter.size() + ", available parameters: " + fParameters.length + "."); - } - Object testClassInstance = getTestClass().getJavaClass().newInstance(); - for (FrameworkField each : annotatedFieldsByParameter) { - Field field = each.getField(); - Parameter annotation = field.getAnnotation(Parameter.class); - int index = annotation.value(); - try { - field.set(testClassInstance, fParameters[index]); - } catch (IllegalArgumentException iare) { - throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() + - " with the value " + fParameters[index] + - " that is not the right type (" + fParameters[index].getClass().getSimpleName() + " instead of " + - field.getType().getSimpleName() + ").", iare); - } - } - return testClassInstance; - } - - @Override - protected String getName() { - return fName; - } - - @Override - protected String testName(FrameworkMethod method) { - return method.getName() + getName(); - } - - @Override - protected void validateConstructor(List errors) { - validateOnlyOneConstructor(errors); - if (fieldsAreAnnotated()) { - validateZeroArgConstructor(errors); - } - } - - @Override - protected void validateFields(List errors) { - super.validateFields(errors); - if (fieldsAreAnnotated()) { - List annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); - int[] usedIndices = new int[annotatedFieldsByParameter.size()]; - for (FrameworkField each : annotatedFieldsByParameter) { - int index = each.getField().getAnnotation(Parameter.class).value(); - if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { - errors.add( - new Exception("Invalid @Parameter value: " + index + ". @Parameter fields counted: " + - annotatedFieldsByParameter.size() + ". Please use an index between 0 and " + - (annotatedFieldsByParameter.size() - 1) + ".") - ); - } else { - usedIndices[index]++; - } - } - for (int index = 0; index < usedIndices.length; index++) { - int numberOfUse = usedIndices[index]; - if (numberOfUse == 0) { - errors.add(new Exception("@Parameter(" + index + ") is never used.")); - } else if (numberOfUse > 1) { - errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ").")); - } - } - } - } - - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); - } - - @Override - protected Annotation[] getRunnerAnnotations() { - return new Annotation[0]; - } - } - - private static final List NO_RUNNERS = Collections - .emptyList(); - - private final ArrayList runners = new ArrayList(); - - /** - * Only called reflectively. Do not use programmatically. - */ - public Parameterized(Class klass) throws Throwable { - super(klass, NO_RUNNERS); - Parameters parameters = getParametersMethod().getAnnotation( - Parameters.class); - createRunnersForParameters(allParameters(), parameters.name()); - } - - @Override - protected List getChildren() { - return runners; - } - - @SuppressWarnings("unchecked") - private Iterable allParameters() throws Throwable { - Object parameters = getParametersMethod().invokeExplosively(null); - if (parameters instanceof Iterable) { - return (Iterable) parameters; - } else { - throw parametersMethodReturnedWrongType(); - } - } - - private FrameworkMethod getParametersMethod() throws Exception { - List methods = getTestClass().getAnnotatedMethods( - Parameters.class); - for (FrameworkMethod each : methods) { - if (each.isStatic() && each.isPublic()) { - return each; - } - } - - throw new Exception("No public static parameters method on class " - + getTestClass().getName()); - } - - private void createRunnersForParameters(Iterable allParameters, - String namePattern) throws InitializationError, Exception { - try { - int i = 0; - for (Object[] parametersOfSingleTest : allParameters) { - String name = nameFor(namePattern, i, parametersOfSingleTest); - TestClassRunnerForParameters runner = new TestClassRunnerForParameters( - getTestClass().getJavaClass(), parametersOfSingleTest, - name); - runners.add(runner); - ++i; - } - } catch (ClassCastException e) { - throw parametersMethodReturnedWrongType(); - } - } - - private String nameFor(String namePattern, int index, Object[] parameters) { - String finalPattern = namePattern.replaceAll("\\{index\\}", - Integer.toString(index)); - String name = MessageFormat.format(finalPattern, parameters); - return "[" + name + "]"; - } - - private Exception parametersMethodReturnedWrongType() throws Exception { - String className = getTestClass().getName(); - String methodName = getParametersMethod().getName(); - String message = MessageFormat.format( - "{0}.{1}() must return an Iterable of arrays.", - className, methodName); - return new Exception(message); - } - - private List getAnnotatedFieldsByParameter() { - return getTestClass().getAnnotatedFields(Parameter.class); - } - - private boolean fieldsAreAnnotated() { - return !getAnnotatedFieldsByParameter().isEmpty(); - } +package org.junit.runners; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +/** + *

+ * The custom runner Parameterized implements parameterized tests. + * When running a parameterized test class, instances are created for the + * cross-product of the test methods and the test data elements. + *

+ * + * For example, to test a Fibonacci function, write: + * + *
+ * @RunWith(Parameterized.class)
+ * public class FibonacciTest {
+ *     @Parameters(name= "{index}: fib[{0}]={1}")
+ *     public static Iterable<Object[]> data() {
+ *         return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
+ *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
+ *     }
+ *
+ *     private int fInput;
+ *
+ *     private int fExpected;
+ *
+ *     public FibonacciTest(int input, int expected) {
+ *         fInput= input;
+ *         fExpected= expected;
+ *     }
+ *
+ *     @Test
+ *     public void test() {
+ *         assertEquals(fExpected, Fibonacci.compute(fInput));
+ *     }
+ * }
+ * 
+ * + *

+ * Each instance of FibonacciTest will be constructed using the + * two-argument constructor and the data values in the + * @Parameters method. + * + *

+ * In order that you can easily identify the individual tests, you may provide a + * name for the @Parameters annotation. This name is allowed + * to contain placeholders, which are replaced at runtime. The placeholders are + *

+ *
{index}
+ *
the current parameter index
+ *
{0}
+ *
the first parameter value
+ *
{1}
+ *
the second parameter value
+ *
...
+ *
+ *
+ * In the example given above, the Parameterized runner creates + * names like [1: fib(3)=2]. If you don't use the name parameter, + * then the current parameter index is used as name. + *

+ * + * You can also write: + * + *
+ * @RunWith(Parameterized.class)
+ * public class FibonacciTest {
+ *  @Parameters
+ *  public static Iterable<Object[]> data() {
+ *      return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
+ *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
+ *  }
+ *  
+ *  @Parameter(0)
+ *  public int fInput;
+ *
+ *  @Parameter(1)
+ *  public int fExpected;
+ *
+ *  @Test
+ *  public void test() {
+ *      assertEquals(fExpected, Fibonacci.compute(fInput));
+ *  }
+ * }
+ * 
+ * + *

+ * Each instance of FibonacciTest will be constructed with the default constructor + * and fields annotated by @Parameter will be initialized + * with the data values in the @Parameters method. + *

+ * + * @since 4.0 + */ +public class Parameterized extends Suite { + /** + * Annotation for a method which provides parameters to be injected into the + * test class constructor by Parameterized + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface Parameters { + /** + *

+ * Optional pattern to derive the test's name from the parameters. Use + * numbers in braces to refer to the parameters or the additional data + * as follows: + *

+ * + *
+         * {index} - the current parameter index
+         * {0} - the first parameter value
+         * {1} - the second parameter value
+         * etc...
+         * 
+ *

+ * Default value is "{index}" for compatibility with previous JUnit + * versions. + *

+ * + * @return {@link MessageFormat} pattern string, except the index + * placeholder. + * @see MessageFormat + */ + String name() default "{index}"; + } + + /** + * Annotation for fields of the test class which will be initialized by the + * method annotated by Parameters
+ * By using directly this annotation, the test class constructor isn't needed.
+ * Index range must start at 0. + * Default value is 0. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public static @interface Parameter { + /** + * Method that returns the index of the parameter in the array + * returned by the method annotated by Parameters.
+ * Index range must start at 0. + * Default value is 0. + * + * @return the index of the parameter. + */ + int value() default 0; + } + + protected class TestClassRunnerForParameters extends BlockJUnit4ClassRunner { + private final Object[] fParameters; + + private final String fPattern; + + private final int fIndex; + + protected TestClassRunnerForParameters(Class type, String pattern, int index, Object[] parameters) throws InitializationError { + super(type); + + fPattern = pattern; + fIndex = index; + fParameters = parameters; + } + + @Override + public Object createTest() throws Exception { + if (fieldsAreAnnotated()) { + return createTestUsingFieldInjection(); + } else { + return createTestUsingConstructorInjection(); + } + } + + private Object createTestUsingConstructorInjection() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(fParameters); + } + + private Object createTestUsingFieldInjection() throws Exception { + List annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); + if (annotatedFieldsByParameter.size() != fParameters.length) { + throw new Exception("Wrong number of parameters and @Parameter fields." + + " @Parameter fields counted: " + annotatedFieldsByParameter.size() + ", available parameters: " + fParameters.length + "."); + } + Object testClassInstance = getTestClass().getJavaClass().newInstance(); + for (FrameworkField each : annotatedFieldsByParameter) { + Field field = each.getField(); + Parameter annotation = field.getAnnotation(Parameter.class); + int index = annotation.value(); + try { + field.set(testClassInstance, fParameters[index]); + } catch (IllegalArgumentException iare) { + throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() + + " with the value " + fParameters[index] + + " that is not the right type (" + fParameters[index].getClass().getSimpleName() + " instead of " + + field.getType().getSimpleName() + ").", iare); + } + } + return testClassInstance; + } + + @Override + protected String getName() { + String finalPattern = fPattern.replaceAll("\\{index\\}", Integer.toString(fIndex)); + String name = MessageFormat.format(finalPattern, fParameters); + return "[" + name + "]"; + } + + @Override + protected String testName(FrameworkMethod method) { + return method.getName() + getName(); + } + + @Override + protected void validateConstructor(List errors) { + validateOnlyOneConstructor(errors); + if (fieldsAreAnnotated()) { + validateZeroArgConstructor(errors); + } + } + + @Override + protected void validateFields(List errors) { + super.validateFields(errors); + if (fieldsAreAnnotated()) { + List annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); + int[] usedIndices = new int[annotatedFieldsByParameter.size()]; + for (FrameworkField each : annotatedFieldsByParameter) { + int index = each.getField().getAnnotation(Parameter.class).value(); + if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { + errors.add( + new Exception("Invalid @Parameter value: " + index + ". @Parameter fields counted: " + + annotatedFieldsByParameter.size() + ". Please use an index between 0 and " + + (annotatedFieldsByParameter.size() - 1) + ".") + ); + } else { + usedIndices[index]++; + } + } + for (int index = 0; index < usedIndices.length; index++) { + int numberOfUse = usedIndices[index]; + if (numberOfUse == 0) { + errors.add(new Exception("@Parameter(" + index + ") is never used.")); + } else if (numberOfUse > 1) { + errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ").")); + } + } + } + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } + + @Override + protected Annotation[] getRunnerAnnotations() { + return new Annotation[0]; + } + } + + private static final List NO_RUNNERS = Collections.emptyList(); + + private final ArrayList runners = new ArrayList(); + + /** + * Only called reflectively. Do not use programmatically. + */ + public Parameterized(Class klass) throws Throwable { + super(klass, NO_RUNNERS); + Parameters parameters = getParametersMethod().getAnnotation( + Parameters.class); + createRunnersForParameters(allParameters(), parameters.name()); + } + + @Override + protected List getChildren() { + return runners; + } + + protected Runner createRunner(String pattern, int index, Object[] parameters) throws InitializationError { + return new TestClassRunnerForParameters(getTestClass().getJavaClass(), pattern, index, parameters); + } + + @SuppressWarnings("unchecked") + private Iterable allParameters() throws Throwable { + Object parameters = getParametersMethod().invokeExplosively(null); + if (parameters instanceof Iterable) { + return (Iterable) parameters; + } else { + throw parametersMethodReturnedWrongType(); + } + } + + private FrameworkMethod getParametersMethod() throws Exception { + List methods = getTestClass().getAnnotatedMethods( + Parameters.class); + for (FrameworkMethod each : methods) { + if (each.isStatic() && each.isPublic()) { + return each; + } + } + + throw new Exception("No public static parameters method on class " + + getTestClass().getName()); + } + + private void createRunnersForParameters(Iterable allParameters, String namePattern) throws Exception { + try { + int i = 0; + for (Object[] parametersOfSingleTest : allParameters) { + runners.add(createRunner(namePattern, i++, parametersOfSingleTest)); + } + } catch (ClassCastException e) { + throw parametersMethodReturnedWrongType(); + } + } + + private Exception parametersMethodReturnedWrongType() throws Exception { + String className = getTestClass().getName(); + String methodName = getParametersMethod().getName(); + String message = MessageFormat.format( + "{0}.{1}() must return an Iterable of arrays.", + className, methodName); + return new Exception(message); + } + + private List getAnnotatedFieldsByParameter() { + return getTestClass().getAnnotatedFields(Parameter.class); + } + + private boolean fieldsAreAnnotated() { + return !getAnnotatedFieldsByParameter().isEmpty(); + } } \ No newline at end of file