aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/junitparams
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/junitparams')
-rw-r--r--src/main/java/junitparams/FileParameters.java43
-rw-r--r--src/main/java/junitparams/JUnitParamsRunner.java501
-rw-r--r--src/main/java/junitparams/Parameters.java49
-rw-r--r--src/main/java/junitparams/converters/ConversionFailedException.java7
-rw-r--r--src/main/java/junitparams/converters/ConvertParam.java25
-rw-r--r--src/main/java/junitparams/converters/Converter.java26
-rw-r--r--src/main/java/junitparams/converters/Nullable.java36
-rw-r--r--src/main/java/junitparams/converters/NullableConverter.java19
-rw-r--r--src/main/java/junitparams/converters/Param.java29
-rw-r--r--src/main/java/junitparams/converters/ParamAnnotation.java32
-rw-r--r--src/main/java/junitparams/converters/ParamConverter.java16
-rw-r--r--src/main/java/junitparams/custom/CustomParameters.java25
-rw-r--r--src/main/java/junitparams/custom/FileParametersProvider.java62
-rw-r--r--src/main/java/junitparams/custom/ParametersProvider.java26
-rw-r--r--src/main/java/junitparams/custom/combined/Cartesian.java46
-rw-r--r--src/main/java/junitparams/custom/combined/CombinedParameters.java24
-rw-r--r--src/main/java/junitparams/custom/combined/CombinedParametersProvider.java27
-rw-r--r--src/main/java/junitparams/internal/InvokeParameterisedMethod.java237
-rw-r--r--src/main/java/junitparams/internal/ParameterisedTestClassRunner.java177
-rw-r--r--src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java108
-rw-r--r--src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java37
-rw-r--r--src/main/java/junitparams/internal/TestMethod.java141
-rw-r--r--src/main/java/junitparams/internal/Utils.java164
-rw-r--r--src/main/java/junitparams/internal/annotation/CustomParametersDescriptor.java30
-rw-r--r--src/main/java/junitparams/internal/annotation/FrameworkMethodAnnotations.java53
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersFromCustomProvider.java38
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersFromExternalClassMethod.java29
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersFromExternalClassProvideMethod.java80
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersFromTestClassMethod.java31
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersFromValue.java23
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametersReader.java55
-rw-r--r--src/main/java/junitparams/internal/parameters/ParametrizationStrategy.java6
-rw-r--r--src/main/java/junitparams/internal/parameters/ParamsFromMethodCommon.java150
-rw-r--r--src/main/java/junitparams/mappers/BufferedReaderDataMapper.java40
-rw-r--r--src/main/java/junitparams/mappers/CsvWithHeaderMapper.java19
-rw-r--r--src/main/java/junitparams/mappers/DataMapper.java31
-rw-r--r--src/main/java/junitparams/mappers/IdentityMapper.java20
-rw-r--r--src/main/java/junitparams/naming/MacroSubstitutionNamingStrategy.java123
-rw-r--r--src/main/java/junitparams/naming/TestCaseName.java40
-rw-r--r--src/main/java/junitparams/naming/TestCaseNamingStrategy.java8
40 files changed, 2633 insertions, 0 deletions
diff --git a/src/main/java/junitparams/FileParameters.java b/src/main/java/junitparams/FileParameters.java
new file mode 100644
index 0000000..1649b58
--- /dev/null
+++ b/src/main/java/junitparams/FileParameters.java
@@ -0,0 +1,43 @@
+package junitparams;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import junitparams.custom.CustomParameters;
+import junitparams.custom.FileParametersProvider;
+import junitparams.mappers.DataMapper;
+import junitparams.mappers.IdentityMapper;
+
+/**
+ * Denotes that parameters for a annotated test method should be taken from an
+ * external resource.
+ *
+ * @author Pawel Lipinski
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@CustomParameters(provider = FileParametersProvider.class)
+public @interface FileParameters {
+
+ /**
+ * File name (with full path) of the file with data.
+ */
+ String value();
+
+ /**
+ * The mapper which knows how to get the data from the external resource and
+ * turn it into a valid set of parameters. By default it is an
+ * IdentityMapper, meaning the resource has exactly the same format as the
+ * <p/>
+ * &#064;Parameters annotation value (when passed as String), being CSV.
+ */
+ Class<? extends DataMapper> mapper() default IdentityMapper.class;
+
+ /**
+ * Encoding to use when reading file contents.
+ */
+ String encoding() default "UTF-8";
+
+}
diff --git a/src/main/java/junitparams/JUnitParamsRunner.java b/src/main/java/junitparams/JUnitParamsRunner.java
new file mode 100644
index 0000000..fa37257
--- /dev/null
+++ b/src/main/java/junitparams/JUnitParamsRunner.java
@@ -0,0 +1,501 @@
+package junitparams;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import junitparams.internal.ParameterisedTestClassRunner;
+import junitparams.internal.ParametrizedTestMethodsFilter;
+import junitparams.internal.TestMethod;
+
+/**
+ * <h1>JUnitParams</h1><br>
+ * <p>
+ * This is a JUnit runner for parameterised tests that don't suck. Annotate your test class with
+ * <code>&#064;RunWith(JUnitParamsRunner.class)</code> and place
+ * <code>&#064;Parameters</code> annotation on each test method which requires
+ * parameters. Nothing more needed - no special structure, no dirty tricks.
+ * </p>
+ * <br>
+ * <h2>Contents</h2> <b> <a href="#p1">1. Parameterising tests</a><br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#a">a. Parameterising tests via values
+ * in annotation</a><br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#b">b. Parameterising tests via a
+ * method that returns parameter values</a><br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#c">c. Parameterising tests via
+ * external classes</a><br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#d">d. Loading parameters from files</a><br>
+ * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#d">e. Converting parameter values</a><br>
+ * <a href="#p2">2. Usage with Spring</a><br>
+ * <a href="#p3">3. Other options</a><br>
+ * </b><br>
+ * <h3 id="p1">1. Parameterising tests</h3> Parameterised tests are a great way
+ * to limit the amount of test code when you need to test the same code under
+ * different conditions. Ever tried to do it with standard JUnit tools like
+ * Parameterized runner or Theories? I always thought they're so awkward to use,
+ * that I've written this library to help all those out there who'd like to have
+ * a handy tool.
+ *
+ * So here we go. There are a few different ways to use JUnitParams, I will try
+ * to show you all of them here.
+ *
+ * <h4 id="a">a. Parameterising tests via values in annotation</h4>
+ * <p>
+ * You can parameterise your test with values defined in annotations. Just pass
+ * sets of test method argument values as an array of Strings, where each string
+ * contains the argument values separated by a comma or a pipe "|".
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters({ "20, Tarzan", "0, Jane" })
+ * public void cartoonCharacters(int yearsInJungle, String person) {
+ * ...
+ * }
+ * </pre>
+ *
+ * Sometimes you may be interested in passing enum values as parameters, then
+ * you can just write them as Strings like this:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters({ &quot;FROM_JUNGLE&quot;, &quot;FROM_CITY&quot; })
+ * public void passEnumAsParam(PersonType person) {
+ * }
+ * </pre>
+ *
+ * <h4 id="b">b. Parameterising tests via a method that returns parameter values
+ * </h4>
+ * <p>
+ * Obviously passing parameters as strings is handy only for trivial situations,
+ * that's why for normal cases you have a method that gives you a collection of
+ * parameters:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters(method = "cartoonCharacters")
+ * public void cartoonCharacters(int yearsInJungle, String person) {
+ * ...
+ * }
+ * private Object[] cartoonCharacters() {
+ * return $(
+ * $(0, "Tarzan"),
+ * $(20, "Jane")
+ * );
+ * }
+ * </pre>
+ *
+ * Where <code>$(...)</code> is a static method defined in
+ * <code>JUnitParamsRunner</code> class, which returns its parameters as a
+ * <code>Object[]</code> array. Just a shortcut, so that you don't need to write the ugly <code>new Object[] {}</code> kind of stuff.
+ *
+ * <p>
+ * <code>method</code> can take more than one method name - you can pass as many
+ * of them as you want, separated by commas. This enables you to divide your
+ * test cases e.g. into categories.
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters(method = "menCharactes, womenCharacters")
+ * public void cartoonCharacters(int yearsInJungle, String person) {
+ * ...
+ * }
+ * private Object[] menCharacters() {
+ * return $(
+ * $(20, "Tarzan"),
+ * $(2, "Chip"),
+ * $(2, "Dale")
+ * );
+ * }
+ * private Object[] womenCharacters() {
+ * return $(
+ * $(0, "Jane"),
+ * $(18, "Pocahontas")
+ * );
+ * }
+ * </pre>
+ * <p>
+ * The <code>method</code> argument of a <code>@Parameters</code> annotation can
+ * be ommited if the method that provides parameters has a the same name as the
+ * test, but prefixed by <code>parametersFor</code>. So our example would look
+ * like this:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters
+ * public void cartoonCharacters(int yearsInJungle, String person) {
+ * ...
+ * }
+ * private Object[] parametersForCartoonCharacters() {
+ * return $(
+ * $(0, "Tarzan"),
+ * $(20, "Jane")
+ * );
+ * }
+ * </pre>
+ *
+ * <p>
+ * If you don't like returning untyped values and arrays, you can equally well
+ * return any Iterable of concrete objects:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters
+ * public void cartoonCharacters(Person character) {
+ * ...
+ * }
+ * private List&lt;Person&gt; parametersForCartoonCharacters() {
+ * return Arrays.asList(
+ * new Person(0, "Tarzan"),
+ * new Person(20, "Jane")
+ * );
+ * }
+ * </pre>
+ *
+ * If we had more than just two Person's to make, we would get redundant,
+ * so JUnitParams gives you a simplified way of creating objects to be passed as
+ * params. You can omit the creation of the objects and just return their constructor
+ * argument values like this:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters
+ * public void cartoonCharacters(Person character) {
+ * ...
+ * }
+ * private List&lt;?&gt; parametersForCartoonCharacters() {
+ * return Arrays.asList(
+ * $(0, "Tarzan"),
+ * $(20, "Jane")
+ * );
+ * }
+ * </pre>
+ * And JUnitParams will invoke the appropriate constructor (<code>new Person(int age, String name)</code> in this case.)
+ * <b>If you want to use it, watch out! Automatic refactoring of constructor
+ * arguments won't be working here!</b>
+ *
+ * <p>
+ * You can also define methods that provide parameters in subclasses and use
+ * them in test methods defined in superclasses, as well as redefine data
+ * providing methods in subclasses to be used by test method defined in a
+ * superclass. That you can doesn't mean you should. Inheritance in tests is
+ * usually a code smell (readability hurts), so make sure you know what you're
+ * doing.
+ *
+ * <h4 id="c">c. Parameterising tests via external classes</h4>
+ * <p>
+ * For more complex cases you may want to externalise the method that provides
+ * parameters or use more than one method to provide parameters to a single test
+ * method. You can easily do that like this:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters(source = CartoonCharactersProvider.class)
+ * public void testReadyToLiveInJungle(int yearsInJungle, String person) {
+ * ...
+ * }
+ * ...
+ * class CartoonCharactersProvider {
+ * public static Object[] provideCartoonCharactersManually() {
+ * return $(
+ * $(0, "Tarzan"),
+ * $(20, "Jane")
+ * );
+ * }
+ * public static Object[] provideCartoonCharactersFromDB() {
+ * return cartoonsRepository.loadCharacters();
+ * }
+ * }
+ * </pre>
+ *
+ * All methods starting with <code>provide</code> are used as parameter
+ * providers.
+ *
+ * <p>
+ * Sometimes though you may want to use just one or few methods of some class to
+ * provide you parameters. This can be done as well like this:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters(source = CartoonCharactersProvider.class, method = "cinderellaCharacters,snowwhiteCharacters")
+ * public void testPrincesses(boolean isAPrincess, String characterName) {
+ * ...
+ * }
+ * </pre>
+ *
+ *
+ * <h4 id="d">d. Loading parameters from files</h4> You may be interested in
+ * loading parameters from a file. This is very easy if it's a CSV file with
+ * columns in the same order as test method parameters:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;FileParameters("cartoon-characters.csv")
+ * public void shouldSurviveInJungle(int yearsInJungle, String person) {
+ * ...
+ * }
+ * </pre>
+ *
+ * But if you want to process the data from the CSV file a bit to use it in the
+ * test method arguments, you
+ * need to use an <code>IdentityMapper</code>. Look:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;FileParameters(value = "cartoon-characters.csv", mapper = CartoonMapper.class)
+ * public void shouldSurviveInJungle(Person person) {
+ * ...
+ * }
+ *
+ * public class CartoonMapper extends IdentityMapper {
+ * &#064;Override
+ * public Object[] map(Reader reader) {
+ * Object[] map = super.map(reader);
+ * List&lt;Object[]&gt; result = new LinkedList&lt;Object[]&gt;();
+ * for (Object lineObj : map) {
+ * String line = (String) lineObj; // line in a format just like in the file
+ * result.add(new Object[] { ..... }); // some format edible by the test method
+ * }
+ * return result.toArray();
+ * }
+ *
+ * }
+ * </pre>
+ *
+ * A CSV files with a header are also supported with the use of <code>CsvWithHeaderMapper</code> class.
+ *
+ * You may also want to use a completely different file format, like excel or
+ * something. Then just parse it yourself:
+ *
+ * <pre>
+ * &#064;Test
+ * &#064;FileParameters(value = "cartoon-characters.xsl", mapper = ExcelCartoonMapper.class)
+ * public void shouldSurviveInJungle(Person person) {
+ * ...
+ * }
+ *
+ * public class CartoonMapper implements DataMapper {
+ * &#064;Override
+ * public Object[] map(Reader fileReader) {
+ * ...
+ * }
+ * }
+ * </pre>
+ *
+ * As you see, you don't need to open or close the file. Just read it from the
+ * reader and parse it the way you wish.
+ *
+ * By default the file is loaded from the file system, relatively to where you start the tests from. But you can also use a resource from
+ * the classpath by prefixing the file name with <code>classpath:</code>
+ *
+ * <h4 id="e">e. Converting parameter values</h4>
+ * Sometimes you want to pass some parameter in one form, but use it in the test in another. Dates are a good example. It's handy to
+ * specify them in the parameters as a String like "2013.01.01", but you'd like to use a Jodatime's LocalDate or JDKs Date in the test
+ * without manually converting the value in the test. This is where the converters become handy. It's enough to annotate a parameter with
+ * a <code>&#064;ConvertParam</code> annotation, give it a converter class and possibly some options (like date format in this case) and
+ * you're done. Here's an example:
+ * <pre>
+ * &#064;Test
+ * &#064;Parameters({ "01.12.2012, A" })
+ * public void convertMultipleParams(
+ * &#064;ConvertParam(value = StringToDateConverter.class, options = "dd.MM.yyyy") Date date,
+ * &#064;ConvertParam(LetterToASCIIConverter.class) int num) {
+ *
+ * Calendar calendar = Calendar.getInstance();
+ * calendar.setTime(date);
+ *
+ * assertEquals(2012, calendar.get(Calendar.YEAR));
+ * assertEquals(11, calendar.get(Calendar.MONTH));
+ * assertEquals(1, calendar.get(Calendar.DAY_OF_MONTH));
+ *
+ * assertEquals(65, num);
+ * }
+ * </pre>
+ *
+ * <h3 id="p2">2. Usage with Spring</h3>
+ * <p>
+ * You can easily use JUnitParams together with Spring. The only problem is that
+ * Spring's test framework is based on JUnit runners, and JUnit allows only one
+ * runner to be run at once. Which would normally mean that you could use only
+ * one of Spring or JUnitParams. Luckily we can cheat Spring a little by adding
+ * this to your test class:
+ *
+ * <pre>
+ * private TestContextManager testContextManager;
+ *
+ * &#064;Before
+ * public void init() throws Exception {
+ * this.testContextManager = new TestContextManager(getClass());
+ * this.testContextManager.prepareTestInstance(this);
+ * }
+ * </pre>
+ *
+ * This lets you use in your tests anything that Spring provides in its test
+ * framework.
+ *
+ * <h3 id="p3">3. Other options</h3>
+ * <h4> Enhancing test case description</h4>
+ * You can use <code>TestCaseName</code> annotation to provide template of the individual test case name:
+ * <pre>
+ * &#064;TestCaseName("factorial({0}) = {1}")
+ * &#064;Parameters({ "1,1"})
+ * public void fractional_test(int argument, int result) { }
+ * </pre>
+ * Will be displayed as 'fractional(1)=1'
+ * <h4>Customizing how parameter objects are shown in IDE</h4>
+ * <p>
+ * Tests show up in your IDE as a tree with test class name being the root, test
+ * methods being nodes, and parameter sets being the leaves. If you want to
+ * customize the way an parameter object is shown, create a <b>toString</b>
+ * method for it.
+ * <h4>Empty parameter sets</h4>
+ * <p>
+ * If you create a parameterised test, but won't give it any parameter sets, it
+ * will be ignored and you'll be warned about it.
+ * <h4>Parameterised test with no parameters</h4>
+ * <p>
+ * If for some reason you want to have a normal non-parameterised method to be
+ * annotated with @Parameters, then fine, you can do it. But it will be ignored
+ * then, since there won't be any params for it, and parameterised tests need
+ * parameters to execute properly (parameters are a part of test setup, right?)
+ * <h4>JUnit Rules</h4>
+ * <p>
+ * The runner for parameterised test is trying to keep all the @Rule's running,
+ * but if something doesn't work - let me know. It's pretty tricky, since the
+ * rules in JUnit are chained, but the chain is kind of... unstructured, so
+ * sometimes I need to guess how to call the next element in chain. If you have
+ * your own rule, make sure it has a field of type Statement which is the next
+ * statement in chain to call.
+ * <h4>Test inheritance</h4>
+ * <p>
+ * Although usually a bad idea, since it makes tests less readable, sometimes
+ * inheritance is the best way to remove repetitions from tests. JUnitParams is
+ * fine with inheritance - you can define a common test in the superclass, and
+ * have separate parameters provider methods in the subclasses. Also the other
+ * way around is ok, you can define parameter providers in superclass and have
+ * tests in subclasses uses them as their input.
+ *
+ * @author Pawel Lipinski (lipinski.pawel@gmail.com)
+ */
+public class JUnitParamsRunner extends BlockJUnit4ClassRunner {
+
+ private ParametrizedTestMethodsFilter parametrizedTestMethodsFilter = new ParametrizedTestMethodsFilter(this);
+ private ParameterisedTestClassRunner parameterisedRunner;
+ private Description description;
+
+ public JUnitParamsRunner(Class<?> klass) throws InitializationError {
+ super(klass);
+ parameterisedRunner = new ParameterisedTestClassRunner(getTestClass());
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ super.filter(filter);
+ this.parametrizedTestMethodsFilter = new ParametrizedTestMethodsFilter(this,filter);
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ super.validateFields(errors);
+ for (Throwable throwable : errors)
+ throwable.printStackTrace();
+ }
+
+ @Override
+ protected void runChild(FrameworkMethod method, RunNotifier notifier) {
+ if (handleIgnored(method, notifier))
+ return;
+
+ TestMethod testMethod = parameterisedRunner.testMethodFor(method);
+ if (parameterisedRunner.shouldRun(testMethod)){
+ parameterisedRunner.runParameterisedTest(testMethod, methodBlock(method), notifier);
+ }
+ else{
+ verifyMethodCanBeRunByStandardRunner(testMethod);
+ super.runChild(method, notifier);
+ }
+ }
+
+ private void verifyMethodCanBeRunByStandardRunner(TestMethod testMethod) {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ testMethod.frameworkMethod().validatePublicVoidNoArg(false, errors);
+ if (!errors.isEmpty()) {
+ throw new RuntimeException(errors.get(0));
+ }
+ }
+
+ private boolean handleIgnored(FrameworkMethod method, RunNotifier notifier) {
+ TestMethod testMethod = parameterisedRunner.testMethodFor(method);
+ if (testMethod.isIgnored())
+ notifier.fireTestIgnored(describeMethod(method));
+
+ return testMethod.isIgnored();
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ return parameterisedRunner.computeFrameworkMethods();
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test) {
+ Statement methodInvoker = parameterisedRunner.parameterisedMethodInvoker(method, test);
+ if (methodInvoker == null)
+ methodInvoker = super.methodInvoker(method, test);
+
+ return methodInvoker;
+ }
+
+ @Override
+ public Description getDescription() {
+ if (description == null) {
+ description = Description.createSuiteDescription(getName(), getTestClass().getAnnotations());
+ List<FrameworkMethod> resultMethods = getListOfMethods();
+
+ for (FrameworkMethod method : resultMethods)
+ description.addChild(describeMethod(method));
+ }
+
+ return description;
+ }
+
+ private List<FrameworkMethod> getListOfMethods() {
+ List<FrameworkMethod> frameworkMethods = parameterisedRunner.returnListOfMethods();
+ return parametrizedTestMethodsFilter.filteredMethods(frameworkMethods);
+ }
+
+ public Description describeMethod(FrameworkMethod method) {
+ Description child = parameterisedRunner.describeParameterisedMethod(method);
+
+ if (child == null)
+ child = describeChild(method);
+
+ return child;
+ }
+
+ /**
+ * Shortcut for returning an array of objects. All parameters passed to this
+ * method are returned in an <code>Object[]</code> array.
+ *
+ * Should not be used to create var-args arrays, because of the way Java resolves
+ * var-args for objects and primitives.
+ *
+ * @deprecated This method is no longer supported. It might be removed in future
+ * as it does not support all cases (especially var-args). Create arrays using
+ * <code>new Object[]{}</code> instead.
+ *
+ * @param params
+ * Values to be returned in an <code>Object[]</code> array.
+ * @return Values passed to this method.
+ */
+ @Deprecated
+ public static Object[] $(Object... params) {
+ return params;
+ }
+}
diff --git a/src/main/java/junitparams/Parameters.java b/src/main/java/junitparams/Parameters.java
new file mode 100644
index 0000000..2a7b21a
--- /dev/null
+++ b/src/main/java/junitparams/Parameters.java
@@ -0,0 +1,49 @@
+package junitparams;
+
+import javax.lang.model.type.NullType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * THE annotation for the test parameters. Use it to say that a method takes
+ * some parameters and define how to obtain them.
+ *
+ * @author Pawel Lipinski
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Parameters {
+ /**
+ * Parameter values defined as a String array. Each element in the array is
+ * a full parameter set, comma-separated or pipe-separated ('|').
+ * The values must match the method parameters in order and type.
+ * Whitespace characters are trimmed (use source class or method if You need to provide such parameters)
+ *
+ * Example: <code>@Parameters({
+ * "1, joe, 26.4, true",
+ * "2, angie, 37.2, false"})</code>
+ */
+ String[] value() default {};
+
+ /**
+ * Parameter values defined externally. The specified class must have at
+ * least one public static method starting with <code>provide</code>
+ * returning <code>Object[]</code>. All such methods are used, so you can
+ * group your examples. The resulting array should contain parameter sets in
+ * its elements. Each parameter set must be another Object[] array, which
+ * contains parameter values in its elements.
+ * Example: <code>@Parameters(source = PeopleProvider.class)</code>
+ */
+ Class<?> source() default NullType.class;
+
+ /**
+ * Parameter values returned by a method within the test class. This way you
+ * don't need additional classes and the test code may be a bit cleaner. The
+ * format of the data returned by the method is the same as for the source
+ * annotation class.
+ * Example: <code>@Parameters(method = "examplaryPeople")</code>
+ *
+ * You can use multiple methods to provide parameters - use comma to do it:
+ * Example: <code>@Parameters(method = "womenParams, menParams")</code>
+ */
+ String method() default "";
+}
diff --git a/src/main/java/junitparams/converters/ConversionFailedException.java b/src/main/java/junitparams/converters/ConversionFailedException.java
new file mode 100644
index 0000000..2a96c0c
--- /dev/null
+++ b/src/main/java/junitparams/converters/ConversionFailedException.java
@@ -0,0 +1,7 @@
+package junitparams.converters;
+
+public class ConversionFailedException extends Exception {
+ public ConversionFailedException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/junitparams/converters/ConvertParam.java b/src/main/java/junitparams/converters/ConvertParam.java
new file mode 100644
index 0000000..f452943
--- /dev/null
+++ b/src/main/java/junitparams/converters/ConvertParam.java
@@ -0,0 +1,25 @@
+package junitparams.converters;
+
+import java.lang.annotation.*;
+
+/**
+ *
+ * Defines a converter which should be used to convert a parameter to expected
+ * type.
+ *
+ * @deprecated use {@link Param}
+ * @author Pawel Lipinski
+ */
+
+@Retention(RetentionPolicy.RUNTIME)
+@Deprecated
+public @interface ConvertParam {
+
+ Class<? extends ParamConverter<?>> value();
+
+ /**
+ * Options / settings to be used by the converter class
+ */
+ String options() default "";
+
+}
diff --git a/src/main/java/junitparams/converters/Converter.java b/src/main/java/junitparams/converters/Converter.java
new file mode 100644
index 0000000..1ef9c1b
--- /dev/null
+++ b/src/main/java/junitparams/converters/Converter.java
@@ -0,0 +1,26 @@
+package junitparams.converters;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Defines the logic to convert parameter annotated with A to type T. Converter must have a public no-args constructor. Configuration is
+ * done via {@link Converter#initialize(java.lang.annotation.Annotation)} method<br>
+ * Inspired by javax.validation.ConstraintValidator
+ *
+ * @param <A> type of annotation mentioning this converter
+ * @param <T> conversion target type
+ */
+public interface Converter<A extends Annotation, T> {
+
+ /**
+ * Initializes this converter - you can read your annotation config here.
+ */
+ void initialize(A annotation);
+
+ /**
+ * Converts param to desired type.
+ *
+ * @throws ConversionFailedException
+ */
+ T convert(Object param) throws ConversionFailedException;
+}
diff --git a/src/main/java/junitparams/converters/Nullable.java b/src/main/java/junitparams/converters/Nullable.java
new file mode 100644
index 0000000..c959e9a
--- /dev/null
+++ b/src/main/java/junitparams/converters/Nullable.java
@@ -0,0 +1,36 @@
+
+package junitparams.converters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import junitparams.Parameters;
+
+/**
+ * Allows test null values defined as a String array in {@link Parameters}
+ *
+ * @author Peter Jurkovic
+ *
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Test
+ * {@literal @}Parameters({" null "})
+ * public void shouldBeNull({@literal @}Nullable String value) {
+ * assertThat(value).isNull();
+ * }
+ * </pre>
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Param(converter = NullableConverter.class)
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+public @interface Nullable {
+
+ /**
+ * Defines parameter value which will be replaced by Java null
+ */
+ String nullIdentifier() default "null";
+}
diff --git a/src/main/java/junitparams/converters/NullableConverter.java b/src/main/java/junitparams/converters/NullableConverter.java
new file mode 100644
index 0000000..98e9038
--- /dev/null
+++ b/src/main/java/junitparams/converters/NullableConverter.java
@@ -0,0 +1,19 @@
+package junitparams.converters;
+
+
+public class NullableConverter implements Converter<Nullable, String>{
+
+ private String nullIdentifier;
+
+ public void initialize(Nullable annotation) {
+ nullIdentifier = annotation.nullIdentifier() == null ? "null" : annotation.nullIdentifier();
+ }
+
+ public String convert(Object param) throws ConversionFailedException {
+ if(param instanceof String && ((String)param).trim().equalsIgnoreCase(nullIdentifier)){
+ return null;
+ }
+ return (String)param;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/converters/Param.java b/src/main/java/junitparams/converters/Param.java
new file mode 100644
index 0000000..e8ab65d
--- /dev/null
+++ b/src/main/java/junitparams/converters/Param.java
@@ -0,0 +1,29 @@
+package junitparams.converters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates parametrized test parameter with information about {@link Converter} that should be used for parameter conversion.
+ * <p>
+ * Can also be used to create custom annotations.<br>
+ * example:
+ * <pre>
+ * &#064;Retention(RetentionPolicy.RUNTIME)
+ * &#064;Target(ElementType.PARAMETER)
+ * &#064;Param(converter = FormattedDateConverter.class)
+ * public &#064;interface DateParam {
+ *
+ * String format() default "dd.MM.yyyy";
+ * }
+ * </pre>
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+public @interface Param {
+
+ Class<? extends Converter> converter();
+}
diff --git a/src/main/java/junitparams/converters/ParamAnnotation.java b/src/main/java/junitparams/converters/ParamAnnotation.java
new file mode 100644
index 0000000..0aca51e
--- /dev/null
+++ b/src/main/java/junitparams/converters/ParamAnnotation.java
@@ -0,0 +1,32 @@
+package junitparams.converters;
+
+import java.lang.annotation.Annotation;
+
+public class ParamAnnotation {
+
+ public static boolean matches(Annotation annotation) {
+ return getParam(annotation) != null;
+ }
+
+ public static Object convert(Annotation annotation, Object param) throws ConversionFailedException {
+ return converter(annotation).convert(param);
+ }
+
+ private static Param getParam(Annotation annotation) {
+ if (annotation.annotationType().isAssignableFrom(Param.class)) {
+ return (Param) annotation;
+ }
+ return annotation.annotationType().getAnnotation(Param.class);
+ }
+
+ private static Converter converter(Annotation annotation) {
+ Converter converter = null;
+ try {
+ converter = getParam(annotation).converter().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Your Converter class must have a public no-arg constructor!", e);
+ }
+ converter.initialize(annotation);
+ return converter;
+ }
+}
diff --git a/src/main/java/junitparams/converters/ParamConverter.java b/src/main/java/junitparams/converters/ParamConverter.java
new file mode 100644
index 0000000..4080ff0
--- /dev/null
+++ b/src/main/java/junitparams/converters/ParamConverter.java
@@ -0,0 +1,16 @@
+package junitparams.converters;
+
+/**
+ *
+ * Implement this interface if you want to convert params from some
+ * representation to the type expected by your test method's parameter.
+ *
+ * &lt;T&gt; is the expected parameter type.
+ *
+ * @deprecated use {@link Converter}
+ * @author Pawel Lipinski
+ */
+@Deprecated
+public interface ParamConverter<T> {
+ T convert(Object param, String options) throws ConversionFailedException;
+}
diff --git a/src/main/java/junitparams/custom/CustomParameters.java b/src/main/java/junitparams/custom/CustomParameters.java
new file mode 100644
index 0000000..06fd4a4
--- /dev/null
+++ b/src/main/java/junitparams/custom/CustomParameters.java
@@ -0,0 +1,25 @@
+package junitparams.custom;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Tells JUnitParams which {@link ParametersProvider} to use for parameters generation.<br>
+ * Use instead of {@link junitparams.Parameters} annotation.
+ * <p>
+ * Can also be used to create custom annotations.<br>
+ * Check {@link junitparams.FileParameters}, {@link FileParametersProvider} and CustomParametersProviderTest for usage examples.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface CustomParameters {
+
+ /**
+ * @return Your custom parameters provider class.
+ */
+ Class<? extends ParametersProvider> provider();
+
+}
diff --git a/src/main/java/junitparams/custom/FileParametersProvider.java b/src/main/java/junitparams/custom/FileParametersProvider.java
new file mode 100644
index 0000000..746fe5c
--- /dev/null
+++ b/src/main/java/junitparams/custom/FileParametersProvider.java
@@ -0,0 +1,62 @@
+package junitparams.custom;
+
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import junitparams.FileParameters;
+import junitparams.mappers.DataMapper;
+
+public class FileParametersProvider implements ParametersProvider<FileParameters> {
+
+ private FileParameters fileParameters;
+
+ @Override
+ public void initialize(FileParameters fileParameters) {
+ this.fileParameters = fileParameters;
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return paramsFromFile();
+ }
+
+ private Object[] paramsFromFile() {
+ try {
+ Reader reader = createProperReader();
+ DataMapper mapper = fileParameters.mapper().newInstance();
+ try {
+ return mapper.map(reader);
+ } finally {
+ reader.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Could not successfully read parameters from file: " + fileParameters.value(), e);
+ }
+ }
+
+ private Reader createProperReader() throws IOException {
+ String filepath = fileParameters.value();
+ String encoding = fileParameters.encoding();
+
+ if (filepath.indexOf(':') < 0) {
+ return new FileReader(filepath);
+ }
+
+ String protocol = filepath.substring(0, filepath.indexOf(':'));
+ String filename = filepath.substring(filepath.indexOf(':') + 1);
+
+ if ("classpath".equals(protocol)) {
+ return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(filename), encoding);
+ } else if ("file".equals(protocol)) {
+ return new InputStreamReader(new FileInputStream(filename), encoding);
+ }
+
+ throw new IllegalArgumentException("Unknown file access protocol. Only 'file' and 'classpath' are supported!");
+ }
+
+}
diff --git a/src/main/java/junitparams/custom/ParametersProvider.java b/src/main/java/junitparams/custom/ParametersProvider.java
new file mode 100644
index 0000000..8cf3621
--- /dev/null
+++ b/src/main/java/junitparams/custom/ParametersProvider.java
@@ -0,0 +1,26 @@
+package junitparams.custom;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * An interface for custom parameters providers. To be used with {@link CustomParameters} annotation.
+ * Must have a default no-args constructor.
+ *
+ * @param <A> type of annotation mentioning this provider
+ */
+public interface ParametersProvider<A extends Annotation> {
+
+ /**
+ * Initializes this provider - you can read your custom annotation config here.
+ *
+ * @param parametersAnnotation parameters annotation on test method
+ */
+ void initialize(A parametersAnnotation);
+
+ /**
+ * Actual parameters generation
+ *
+ * @return parameters for test method calls
+ */
+ Object[] getParameters();
+}
diff --git a/src/main/java/junitparams/custom/combined/Cartesian.java b/src/main/java/junitparams/custom/combined/Cartesian.java
new file mode 100644
index 0000000..f98948f
--- /dev/null
+++ b/src/main/java/junitparams/custom/combined/Cartesian.java
@@ -0,0 +1,46 @@
+package junitparams.custom.combined;
+
+import java.util.Arrays;
+import java.util.List;
+
+class Cartesian {
+
+ static Object[] getCartesianProductOf(List<Object[]> array) {
+ if (array == null || array.size() == 0) {
+ return new Object[]{};
+ }
+
+ for (int i = 0; i < array.size() - 1; i++) {
+ Object[] arrayOne = array.get(i);
+ Object[] arrayTwo = array.get(i + 1);
+ array.set(i + 1, cartesianProduct(arrayOne, arrayTwo));
+ }
+
+ return array.get(array.size() - 1);
+ }
+
+ private static Object[] cartesianProduct(Object[] arrayOne, Object[] arrayTwo) {
+ int numberOfCombinations = arrayOne.length * arrayTwo.length;
+ Object[] resultArray = new Object[numberOfCombinations][2];
+
+ int i = 0;
+ for (Object firstElement : arrayOne) {
+ for (Object secondElement : arrayTwo) {
+ resultArray[i] = getCartesianOfTwoElements(firstElement, secondElement);
+ i++;
+ }
+ }
+
+ return resultArray;
+ }
+
+ private static Object getCartesianOfTwoElements(Object objectOne, Object objectTwo) {
+ if (!objectOne.getClass().isArray()) {
+ return new Object[]{objectOne, objectTwo};
+ }
+ Object[] initialArray = (Object[]) objectOne;
+ Object[] newArray = Arrays.copyOf(initialArray, initialArray.length + 1);
+ newArray[newArray.length - 1] = objectTwo;
+ return newArray;
+ }
+}
diff --git a/src/main/java/junitparams/custom/combined/CombinedParameters.java b/src/main/java/junitparams/custom/combined/CombinedParameters.java
new file mode 100644
index 0000000..95e4a5e
--- /dev/null
+++ b/src/main/java/junitparams/custom/combined/CombinedParameters.java
@@ -0,0 +1,24 @@
+package junitparams.custom.combined;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import junitparams.custom.CustomParameters;
+
+@Retention(RetentionPolicy.RUNTIME)
+@CustomParameters(provider = CombinedParametersProvider.class)
+public @interface CombinedParameters {
+ /**
+ * Parameter values defined as a String array.
+ * Each of the elements is a list of values that should be tested for parameters.
+ * Using this annotation will result in creating a n-fold cartesian product of parameter values effectively testing
+ * each possible combination.
+ * Values in the array must match the test method's parameters in order and type.
+ * <p>
+ * Example:<br>
+ * <code>@CombinedParameters({"han,chewie","33,204"})<br>
+ * public void shouldTestAllNameAgeCombinations(String name, Integer age)
+ * </code>
+ */
+ String[] value() default {};
+}
diff --git a/src/main/java/junitparams/custom/combined/CombinedParametersProvider.java b/src/main/java/junitparams/custom/combined/CombinedParametersProvider.java
new file mode 100644
index 0000000..382b6ce
--- /dev/null
+++ b/src/main/java/junitparams/custom/combined/CombinedParametersProvider.java
@@ -0,0 +1,27 @@
+package junitparams.custom.combined;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junitparams.custom.ParametersProvider;
+import junitparams.internal.Utils;
+
+public class CombinedParametersProvider implements ParametersProvider<CombinedParameters> {
+
+ private CombinedParameters combinedParameters;
+
+ @Override
+ public void initialize(CombinedParameters parametersAnnotation) {
+ this.combinedParameters = parametersAnnotation;
+ }
+
+ @Override
+ public Object[] getParameters() {
+ List<Object[]> list = new ArrayList<Object[]>();
+ for(String parameterArray : combinedParameters.value()) {
+ list.add(Utils.splitAtCommaOrPipe(parameterArray));
+ }
+
+ return Cartesian.getCartesianProductOf(list);
+ }
+}
diff --git a/src/main/java/junitparams/internal/InvokeParameterisedMethod.java b/src/main/java/junitparams/internal/InvokeParameterisedMethod.java
new file mode 100644
index 0000000..34024d5
--- /dev/null
+++ b/src/main/java/junitparams/internal/InvokeParameterisedMethod.java
@@ -0,0 +1,237 @@
+package junitparams.internal;
+
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import junitparams.converters.ConversionFailedException;
+import junitparams.converters.ConvertParam;
+import junitparams.converters.ParamAnnotation;
+import junitparams.converters.ParamConverter;
+
+/**
+ * JUnit invoker for parameterised test methods
+ *
+ * @author Pawel Lipinski
+ */
+public class InvokeParameterisedMethod extends Statement {
+
+ private final Object[] params;
+ private final FrameworkMethod testMethod;
+ private final Object testClass;
+ private final String uniqueMethodId;
+
+ public InvokeParameterisedMethod(FrameworkMethod testMethod, Object testClass, Object params, int paramSetIdx) {
+ this.testMethod = testMethod;
+ this.testClass = testClass;
+ this.uniqueMethodId = Utils.uniqueMethodId(paramSetIdx - 1, params, testMethod.getName());
+ try {
+ if (params instanceof String)
+ this.params = castParamsFromString((String) params);
+ else {
+ this.params = castParamsFromObjects(params);
+ }
+ } catch (ConversionFailedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Object[] castParamsFromString(String params) throws ConversionFailedException {
+ Object[] columns = null;
+ try {
+ columns = Utils.splitAtCommaOrPipe(params);
+ columns = castParamsUsingConverters(columns);
+ } catch (RuntimeException e) {
+ new IllegalArgumentException("Cannot parse parameters. Did you use ',' or '|' as column separator? "
+ + params, e).printStackTrace();
+ }
+
+ return columns;
+ }
+
+ private Object[] castParamsFromObjects(Object params) throws ConversionFailedException {
+ Object[] paramset = Utils.safelyCastParamsToArray(params);
+
+ try {
+ return castParamsUsingConverters(paramset);
+ } catch (ConversionFailedException e) {
+ throw e;
+ } catch (Exception e) {
+ Class<?>[] typesOfParameters = createArrayOfTypesOf(paramset);
+ Object resultParam = createObjectOfExpectedTypeBasedOnParams(paramset, typesOfParameters);
+ return new Object[]{resultParam};
+ }
+ }
+
+ private Object createObjectOfExpectedTypeBasedOnParams(Object[] paramset, Class<?>[] typesOfParameters) {
+ Object resultParam;
+
+ try {
+ if (testMethod.getMethod().getParameterTypes()[0].isArray()) {
+ resultParam = Array.newInstance(typesOfParameters[0], paramset.length);
+ for (int i = 0; i < paramset.length; i++) {
+ ((Object[]) resultParam)[i] = paramset[i];
+ }
+ } else {
+ resultParam = testMethod.getMethod().getParameterTypes()[0].getConstructor(typesOfParameters).newInstance(paramset);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("While trying to create object of class " + testMethod.getMethod().getParameterTypes()[0]
+ + " could not find constructor with arguments matching (type-wise) the ones given in parameters.", e);
+ }
+ return resultParam;
+ }
+
+ private Class<?>[] createArrayOfTypesOf(Object[] paramset) {
+ Class<?>[] parametersBasedOnValues = new Class<?>[paramset.length];
+ for (int i = 0; i < paramset.length; i++) {
+ parametersBasedOnValues[i] = paramset[i].getClass();
+ }
+ return parametersBasedOnValues;
+ }
+
+ private Object[] castParamsUsingConverters(Object[] columns) throws ConversionFailedException {
+ Class<?>[] expectedParameterTypes = testMethod.getMethod().getParameterTypes();
+
+ if (testMethodParamsHasVarargs(columns, expectedParameterTypes)) {
+ columns = columnsWithVarargs(columns, expectedParameterTypes);
+ }
+
+ Annotation[][] parameterAnnotations = testMethod.getMethod().getParameterAnnotations();
+ verifySameSizeOfArrays(columns, expectedParameterTypes);
+ columns = castAllParametersToProperTypes(columns, expectedParameterTypes, parameterAnnotations);
+ return columns;
+ }
+
+ private Object[] columnsWithVarargs(Object[] columns, Class<?>[] expectedParameterTypes) {
+ Object[] allParameters = standardParameters(columns, expectedParameterTypes);
+ allParameters[allParameters.length - 1] = varargsParameters(columns, expectedParameterTypes);
+ return allParameters;
+ }
+
+ private Object[] varargsParameters(Object[] columns, Class<?>[] expectedParameterTypes) {
+ Class<?> varArgType = expectedParameterTypes[expectedParameterTypes.length - 1].getComponentType();
+ Object[] varArgsParameters = (Object[]) Array.newInstance(varArgType, columns.length - expectedParameterTypes.length + 1);
+ for (int i = 0; i < varArgsParameters.length; i++) {
+ varArgsParameters[i] = columns[i + expectedParameterTypes.length - 1];
+ }
+ return varArgsParameters;
+ }
+
+ private Object[] standardParameters(Object[] columns, Class<?>[] expectedParameterTypes) {
+ Object[] standardParameters = new Object[expectedParameterTypes.length];
+ for (int i = 0; i < standardParameters.length - 1; i++) {
+ standardParameters[i] = columns[i];
+ }
+ return standardParameters;
+ }
+
+ private boolean testMethodParamsHasVarargs(Object[] columns, Class<?>[] expectedParameterTypes) {
+ int last = expectedParameterTypes.length - 1;
+ if (columns[last] == null) {
+ return false;
+ }
+ return expectedParameterTypes.length <= columns.length
+ && expectedParameterTypes[last].isArray()
+ && expectedParameterTypes[last].getComponentType().equals(columns[last].getClass());
+ }
+
+ private Object[] castAllParametersToProperTypes(Object[] columns, Class<?>[] expectedParameterTypes,
+ Annotation[][] parameterAnnotations) throws ConversionFailedException {
+ Object[] result = new Object[columns.length];
+
+ for (int i = 0; i < columns.length; i++) {
+ if (parameterAnnotations[i].length == 0)
+ result[i] = castParameterDirectly(columns[i], expectedParameterTypes[i]);
+ else
+ result[i] = castParameterUsingConverter(columns[i], parameterAnnotations[i]);
+ }
+
+ return result;
+ }
+
+ private Object castParameterUsingConverter(Object param, Annotation[] annotations) throws ConversionFailedException {
+ for (Annotation annotation : annotations) {
+ if (ParamAnnotation.matches(annotation)) {
+ return ParamAnnotation.convert(annotation, param);
+ }
+ if (annotation.annotationType().isAssignableFrom(ConvertParam.class)) {
+ Class<? extends ParamConverter<?>> converterClass = ((ConvertParam) annotation).value();
+ String options = ((ConvertParam) annotation).options();
+ try {
+ return converterClass.newInstance().convert(param, options);
+ } catch (ConversionFailedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("Your ParamConverter class must have a public no-arg constructor!", e);
+ }
+ }
+ }
+ return param;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object castParameterDirectly(Object object, Class clazz) {
+ if (object == null || clazz.isInstance(object) || (!(object instanceof String) && clazz.isPrimitive()))
+ return object;
+ if (clazz.isEnum())
+ return (Enum.valueOf(clazz, (String) object));
+ if (clazz.isAssignableFrom(String.class))
+ return object.toString();
+ if (clazz.isAssignableFrom(Class.class))
+ try {
+ return Class.forName((String) object);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Parameter class (" + object + ") not found", e);
+ }
+ if (clazz.isAssignableFrom(Integer.TYPE) || clazz.isAssignableFrom(Integer.class))
+ return Integer.parseInt((String) object);
+ if (clazz.isAssignableFrom(Short.TYPE) || clazz.isAssignableFrom(Short.class))
+ return Short.parseShort((String) object);
+ if (clazz.isAssignableFrom(Long.TYPE) || clazz.isAssignableFrom(Long.class))
+ return Long.parseLong((String) object);
+ if (clazz.isAssignableFrom(Float.TYPE) || clazz.isAssignableFrom(Float.class))
+ return Float.parseFloat((String) object);
+ if (clazz.isAssignableFrom(Double.TYPE) || clazz.isAssignableFrom(Double.class))
+ return Double.parseDouble((String) object);
+ if (clazz.isAssignableFrom(Boolean.TYPE) || clazz.isAssignableFrom(Boolean.class))
+ return Boolean.parseBoolean((String) object);
+ if (clazz.isAssignableFrom(Character.TYPE) || clazz.isAssignableFrom(Character.class))
+ return object.toString().charAt(0);
+ if (clazz.isAssignableFrom(Byte.TYPE) || clazz.isAssignableFrom(Byte.class))
+ return Byte.parseByte((String) object);
+ if (clazz.isAssignableFrom(BigDecimal.class))
+ return new BigDecimal((String) object);
+ PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
+ if (editor != null) {
+ editor.setAsText((String) object);
+ return editor.getValue();
+ }
+ throw new IllegalArgumentException("Parameter type (" + clazz.getName() + ") cannot be handled!" +
+ " Only primitive types, BigDecimals and Strings can be used.");
+ }
+
+ private void verifySameSizeOfArrays(Object[] columns, Class<?>[] parameterTypes) {
+ if (parameterTypes.length != columns.length)
+ throw new IllegalArgumentException(
+ "Number of parameters inside @Parameters annotation doesn't match the number of test method parameters.\nThere are "
+ + columns.length + " parameters in annotation, while there's " + parameterTypes.length + " parameters in the "
+ + testMethod.getName() + " method.");
+ }
+
+ boolean matchesDescription(Description description) {
+ return description.hashCode() == uniqueMethodId.hashCode();
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ testMethod.invokeExplosively(testClass, params == null ? new Object[]{params} : params);
+ }
+
+}
diff --git a/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java b/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java
new file mode 100644
index 0000000..23daf88
--- /dev/null
+++ b/src/main/java/junitparams/internal/ParameterisedTestClassRunner.java
@@ -0,0 +1,177 @@
+package junitparams.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Testclass-level functionalities to handle parameters from a JUnit runner
+ * class.
+ *
+ * @author Pawel Lipinski
+ */
+public class ParameterisedTestClassRunner {
+
+ protected Map<TestMethod, ParameterisedTestMethodRunner> parameterisedMethods = new HashMap<TestMethod, ParameterisedTestMethodRunner>();
+ protected Map<FrameworkMethod, TestMethod> testMethods = new HashMap<FrameworkMethod, TestMethod>();
+ protected List<TestMethod> testMethodsList;
+
+ /**
+ * Creates a runner for a given test class. Computes all the test methods
+ * that are annotated as tests. Retrieves and caches all parameter values.
+ *
+ * @param testClass
+ */
+ public ParameterisedTestClassRunner(TestClass testClass) {
+ computeTestMethods(testClass);
+ fillTestMethodsMap();
+ computeFrameworkMethods();
+ }
+
+ protected void computeTestMethods(TestClass testClass) {
+ testMethodsList = TestMethod.listFrom(testClass.getAnnotatedMethods(Test.class), testClass);
+ }
+
+ private void fillTestMethodsMap() {
+ for (TestMethod testMethod : testMethodsList)
+ testMethods.put(testMethod.frameworkMethod(), testMethod);
+ }
+
+ /**
+ * Returns a list of <code>FrameworkMethod</code>s. Handles both
+ * parameterised methods (counts them as many times as many paramsets they
+ * have) and nonparameterised methods (just counts them once).
+ *
+ * @return a list of FrameworkMethod objects
+ */
+ public List<FrameworkMethod> computeFrameworkMethods() {
+ List<FrameworkMethod> resultMethods = new ArrayList<FrameworkMethod>();
+
+ for (TestMethod testMethod : testMethodsList) {
+ if (testMethod.isParameterised())
+ addTestMethodForEachParamSet(resultMethods, testMethod);
+ else
+ addTestMethodOnce(resultMethods, testMethod);
+ }
+
+ return resultMethods;
+ }
+
+ /**
+ * Returns a list of <code>FrameworkMethod</code>s - once per method, like
+ * there were no parameters.
+ * For JUnit to build names for IDE.
+ */
+ public List<FrameworkMethod> returnListOfMethods() {
+ List<FrameworkMethod> resultMethods = new ArrayList<FrameworkMethod>();
+
+ for (TestMethod testMethod : testMethodsList) {
+ addTestMethodOnce(resultMethods, testMethod);
+ cacheMethodRunner(testMethod);
+ testMethod.warnIfNoParamsGiven();
+ }
+
+ return resultMethods;
+ }
+
+ private void addTestMethodForEachParamSet(List<FrameworkMethod> resultMethods, TestMethod testMethod) {
+ if (testMethod.isNotIgnored()) {
+ int paramSetSize = testMethod.parametersSets().length;
+ for (int i = 0; i < paramSetSize; i++)
+ addTestMethodOnce(resultMethods, testMethod);
+ } else {
+ addTestMethodOnce(resultMethods, testMethod);
+ }
+ }
+
+ private void addTestMethodOnce(List<FrameworkMethod> resultMethods, TestMethod testMethod) {
+ resultMethods.add(testMethod.frameworkMethod());
+ }
+
+ private void cacheMethodRunner(TestMethod testMethod) {
+ if (!parameterisedMethods.containsKey(testMethod))
+ parameterisedMethods.put(testMethod, new ParameterisedTestMethodRunner(testMethod));
+ }
+
+ /**
+ * Returns a InvokeParameterisedMethod for parameterised methods and null
+ * for nonparameterised
+ *
+ * @param method Test method
+ * @param testClass
+ * @return a Statement with the invoker for the parameterised method
+ */
+ public Statement parameterisedMethodInvoker(FrameworkMethod method, Object testClass) {
+ TestMethod testMethod = testMethods.get(method);
+
+ if (!testMethod.isParameterised())
+ return null;
+
+ return buildMethodInvoker(method, testClass, testMethod);
+ }
+
+ private Statement buildMethodInvoker(FrameworkMethod method, Object testClass, TestMethod testMethod) {
+ ParameterisedTestMethodRunner parameterisedMethod = parameterisedMethods.get(testMethod);
+
+ return new InvokeParameterisedMethod(
+ method, testClass, parameterisedMethod.currentParamsFromAnnotation(), parameterisedMethod.count());
+ }
+
+ /**
+ * Tells if method should be run by this runner.
+ *
+ * @param testMethod
+ * @return true, iff testMethod should be run by this runner.
+ */
+ public boolean shouldRun(TestMethod testMethod) {
+ return testMethod.isParameterised();
+ }
+
+ /**
+ * Executes parameterised method.
+ *
+ * @param method
+ * @param methodInvoker
+ * @param notifier
+ */
+ public void runParameterisedTest(TestMethod method, Statement methodInvoker, RunNotifier notifier) {
+ parameterisedMethods.get(method).runTestMethod(methodInvoker, notifier);
+ }
+
+ /**
+ * Returns description of a parameterised method.
+ *
+ * @param method TODO
+ * @return Description of a method or null if it's not parameterised.
+ */
+ public Description describeParameterisedMethod(FrameworkMethod method) {
+ TestMethod testMethod = testMethods.get(method);
+
+ if (!testMethod.isParameterised())
+ return null;
+
+ return testMethod.describe();
+ }
+
+ /**
+ * Returns a cached TestMethod object related to the given FrameworkMethod.
+ * This object has all the params already retrieved, so use this one and not
+ * TestMethod's constructor if you want to have everything retrieved once
+ * and cached.
+ *
+ * @param method
+ * @return a cached TestMethod instance
+ */
+ public TestMethod testMethodFor(FrameworkMethod method) {
+ return testMethods.get(method);
+ }
+
+}
diff --git a/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java b/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java
new file mode 100644
index 0000000..9573048
--- /dev/null
+++ b/src/main/java/junitparams/internal/ParameterisedTestMethodRunner.java
@@ -0,0 +1,108 @@
+package junitparams.internal;
+
+import java.lang.reflect.Field;
+
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.internal.runners.model.EachTestNotifier;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.Statement;
+
+/**
+ * Testmethod-level functionalities for parameterised tests
+ *
+ * @author Pawel Lipinski
+ */
+public class ParameterisedTestMethodRunner {
+
+ public final TestMethod method;
+ private int count;
+
+ public ParameterisedTestMethodRunner(TestMethod testMethod) {
+ this.method = testMethod;
+ }
+
+ public int nextCount() {
+ return count++;
+ }
+
+ public int count() {
+ return count;
+ }
+
+ Object currentParamsFromAnnotation() {
+ return method.parametersSets()[nextCount()];
+ }
+
+ void runTestMethod(Statement methodInvoker, RunNotifier notifier) {
+ Description methodWithParams = findChildForParams(methodInvoker, method.describe());
+
+ runMethodInvoker(notifier, methodInvoker, methodWithParams);
+ }
+
+ private void runMethodInvoker(RunNotifier notifier, Statement methodInvoker, Description methodWithParams) {
+ EachTestNotifier eachNotifier = new EachTestNotifier(notifier, methodWithParams);
+ eachNotifier.fireTestStarted();
+ try {
+ methodInvoker.evaluate();
+ } catch (AssumptionViolatedException e) {
+ eachNotifier.addFailedAssumption(e);
+ } catch (Throwable e) {
+ eachNotifier.addFailure(e);
+ } finally {
+ eachNotifier.fireTestFinished();
+ }
+ }
+
+ private Description findChildForParams(Statement methodInvoker, Description methodDescription) {
+ if (System.getProperty("JUnitParams.flat") != null)
+ return methodDescription;
+
+ InvokeParameterisedMethod parameterisedInvoker = findParameterisedMethodInvokerInChain(methodInvoker);
+
+ for (Description child : methodDescription.getChildren()) {
+ if (parameterisedInvoker.matchesDescription(child))
+ return child;
+ }
+ return null;
+ }
+
+ private InvokeParameterisedMethod findParameterisedMethodInvokerInChain(Statement methodInvoker) {
+ while (methodInvoker != null && !(methodInvoker instanceof InvokeParameterisedMethod))
+ methodInvoker = nextChainedInvoker(methodInvoker);
+
+ if (methodInvoker == null)
+ throw new RuntimeException("Cannot find invoker for the parameterised method. Using wrong JUnit version?");
+
+ return (InvokeParameterisedMethod) methodInvoker;
+ }
+
+ private Statement nextChainedInvoker(Statement methodInvoker) {
+ Field[] declaredFields = methodInvoker.getClass().getDeclaredFields();
+
+ for (Field field : declaredFields) {
+ Statement statement = statementOrNull(methodInvoker, field);
+ if (statement != null)
+ return statement;
+ }
+
+ return null;
+ }
+
+ private Statement statementOrNull(Statement methodInvoker, Field field) {
+ if (Statement.class.isAssignableFrom(field.getType()))
+ return getOriginalStatement(methodInvoker, field);
+
+ return null;
+ }
+
+ private Statement getOriginalStatement(Statement methodInvoker, Field field) {
+ field.setAccessible(true);
+ try {
+ return (Statement) field.get(methodInvoker);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java b/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java
new file mode 100644
index 0000000..905934c
--- /dev/null
+++ b/src/main/java/junitparams/internal/ParametrizedTestMethodsFilter.java
@@ -0,0 +1,37 @@
+package junitparams.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.runner.manipulation.Filter;
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.JUnitParamsRunner;
+
+public class ParametrizedTestMethodsFilter {
+ private final junitparams.JUnitParamsRunner jUnitParamsRunner;
+
+ private final Filter filter;
+
+ public ParametrizedTestMethodsFilter(junitparams.JUnitParamsRunner jUnitParamsRunner, Filter filter) {
+ this.jUnitParamsRunner = jUnitParamsRunner;
+ this.filter = filter;
+ }
+
+ public ParametrizedTestMethodsFilter(JUnitParamsRunner jUnitParamsRunner) {
+ this.jUnitParamsRunner = jUnitParamsRunner;
+ this.filter = Filter.ALL;
+ }
+
+ public List<FrameworkMethod> filteredMethods(List<FrameworkMethod> frameworkMethods) {
+ List<FrameworkMethod> filteredMethods = new ArrayList<FrameworkMethod>();
+
+ for (FrameworkMethod frameworkMethod : frameworkMethods) {
+ if (filter.shouldRun(jUnitParamsRunner.describeMethod(frameworkMethod))) {
+ filteredMethods.add(frameworkMethod);
+ }
+ }
+
+ return filteredMethods;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/internal/TestMethod.java b/src/main/java/junitparams/internal/TestMethod.java
new file mode 100644
index 0000000..6125803
--- /dev/null
+++ b/src/main/java/junitparams/internal/TestMethod.java
@@ -0,0 +1,141 @@
+package junitparams.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Ignore;
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+import junitparams.internal.annotation.FrameworkMethodAnnotations;
+import junitparams.internal.parameters.ParametersReader;
+import junitparams.naming.MacroSubstitutionNamingStrategy;
+import junitparams.naming.TestCaseNamingStrategy;
+
+/**
+ * A wrapper for a test method
+ *
+ * @author Pawel Lipinski
+ */
+public class TestMethod {
+ private FrameworkMethod frameworkMethod;
+ FrameworkMethodAnnotations frameworkMethodAnnotations;
+ private Class<?> testClass;
+ private ParametersReader parametersReader;
+ private Object[] cachedParameters;
+ private TestCaseNamingStrategy namingStrategy;
+
+ public TestMethod(FrameworkMethod method, TestClass testClass) {
+ this.frameworkMethod = method;
+ this.testClass = testClass.getJavaClass();
+ frameworkMethodAnnotations = new FrameworkMethodAnnotations(method);
+ parametersReader = new ParametersReader(testClass(), frameworkMethod);
+
+ namingStrategy = new MacroSubstitutionNamingStrategy(this);
+ }
+
+ public String name() {
+ return frameworkMethod.getName();
+ }
+
+ public static List<TestMethod> listFrom(List<FrameworkMethod> annotatedMethods, TestClass testClass) {
+ List<TestMethod> methods = new ArrayList<TestMethod>();
+
+ for (FrameworkMethod frameworkMethod : annotatedMethods)
+ methods.add(new TestMethod(frameworkMethod, testClass));
+
+ return methods;
+ }
+
+ @Override
+ public int hashCode() {
+ return frameworkMethod.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof TestMethod)
+ && hasTheSameNameAsFrameworkMethod((TestMethod) obj)
+ && hasTheSameParameterTypesAsFrameworkMethod((TestMethod) obj);
+ }
+
+ private boolean hasTheSameNameAsFrameworkMethod(TestMethod testMethod) {
+ return frameworkMethod.getName().equals(testMethod.frameworkMethod.getName());
+ }
+
+ private boolean hasTheSameParameterTypesAsFrameworkMethod(TestMethod testMethod) {
+ Class<?>[] frameworkMethodParameterTypes = frameworkMethod.getMethod().getParameterTypes();
+ Class<?>[] testMethodParameterTypes = testMethod.frameworkMethod.getMethod().getParameterTypes();
+ return Arrays.equals(frameworkMethodParameterTypes, testMethodParameterTypes);
+ }
+
+ Class<?> testClass() {
+ return testClass;
+ }
+
+ public boolean isIgnored() {
+ return hasIgnoredAnnotation() || hasNoParameters();
+ }
+
+ private boolean hasIgnoredAnnotation() {
+ return frameworkMethodAnnotations.hasAnnotation(Ignore.class);
+ }
+
+ private boolean hasNoParameters() {
+ return isParameterised() && parametersSets().length == 0;
+ }
+
+ public boolean isNotIgnored() {
+ return !isIgnored();
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return frameworkMethodAnnotations.getAnnotation(annotationType);
+ }
+
+ Description describe() {
+ if (isNotIgnored() && !describeFlat()) {
+ Description parametrised = Description.createSuiteDescription(name());
+ Object[] params = parametersSets();
+ for (int i = 0; i < params.length; i++) {
+ Object paramSet = params[i];
+ String name = namingStrategy.getTestCaseName(i, paramSet);
+ String uniqueMethodId = Utils.uniqueMethodId(i, paramSet, name());
+
+ parametrised.addChild(
+ Description.createTestDescription(testClass().getName(), name, uniqueMethodId)
+ );
+ }
+ return parametrised;
+ } else {
+ return Description.createTestDescription(testClass(), name(), frameworkMethodAnnotations.allAnnotations());
+ }
+ }
+
+ private boolean describeFlat() {
+ return System.getProperty("JUnitParams.flat") != null;
+ }
+
+ public Object[] parametersSets() {
+ if (cachedParameters == null) {
+ cachedParameters = parametersReader.read();
+ }
+ return cachedParameters;
+ }
+
+ void warnIfNoParamsGiven() {
+ if (isNotIgnored() && isParameterised() && parametersSets().length == 0)
+ System.err.println("Method " + name() + " gets empty list of parameters, so it's being ignored!");
+ }
+
+ public FrameworkMethod frameworkMethod() {
+ return frameworkMethod;
+ }
+
+ boolean isParameterised() {
+ return frameworkMethodAnnotations.isParametrised();
+ }
+}
diff --git a/src/main/java/junitparams/internal/Utils.java b/src/main/java/junitparams/internal/Utils.java
new file mode 100644
index 0000000..0e10adf
--- /dev/null
+++ b/src/main/java/junitparams/internal/Utils.java
@@ -0,0 +1,164 @@
+package junitparams.internal;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Some String utils to handle parameterised tests' results.
+ *
+ * @author Pawel Lipinski
+ */
+public class Utils {
+ public static final String REGEX_ALL_NEWLINES = "(\\r\\n|\\n|\\r)";
+
+ public static String stringify(Object paramSet, int paramIdx) {
+ String result = "[" + paramIdx + "] ";
+
+ return result + stringify(paramSet);
+ }
+
+ public static String stringify(Object paramSet) {
+ String result;
+ if (paramSet == null)
+ result = "null";
+ else if (paramSet instanceof String)
+ result = paramSet.toString();
+ else
+ result = asCsvString(safelyCastParamsToArray(paramSet));
+
+ return trimSpecialChars(result);
+ }
+
+ public static String getParameterStringByIndexOrEmpty(Object paramSet, int parameterIndex) {
+ Object[] params = safelyCastParamsToArray(paramSet);
+ if (paramSet instanceof String) {
+ params = splitAtCommaOrPipe((String) paramSet);
+ }
+ if (parameterIndex >= 0 && parameterIndex < params.length) {
+ return addParamToResult("", params[parameterIndex]);
+ }
+
+ return "";
+ }
+
+ public static String[] splitAtCommaOrPipe(String input) {
+ ArrayList<String> result = new ArrayList<String>();
+
+ char character = '\0';
+ char previousCharacter;
+
+ StringBuilder value = new StringBuilder();
+ for (int i = 0; i < input.length(); i++) {
+ previousCharacter = character;
+ character = input.charAt(i);
+
+ if (character == ',' || character == '|') {
+ if (previousCharacter == '\\') {
+ value.setCharAt(value.length() - 1, character);
+ continue;
+ }
+ result.add(value.toString().trim());
+ value = new StringBuilder();
+ continue;
+ }
+
+ value.append(character);
+ }
+ result.add(value.toString().trim());
+
+ return result.toArray(new String[]{});
+ }
+
+ private static String trimSpecialChars(String result) {
+ return result.replace('(', '[').replace(')', ']').replaceAll(REGEX_ALL_NEWLINES, " ");
+ }
+
+ static Object[] safelyCastParamsToArray(Object paramSet) {
+ final Object[] params;
+ if (paramSet instanceof Object[]) {
+ params = (Object[]) paramSet;
+ } else {
+ params = new Object[]{paramSet};
+ }
+ return params;
+ }
+
+ private static String asCsvString(Object[] params) {
+ if (params == null)
+ return "null";
+
+ if (params.length == 0)
+ return "";
+
+ String result = "";
+
+ for (int i = 0; i < params.length - 1; i++) {
+ Object param = params[i];
+ result = addParamToResult(result, param) + ", ";
+ }
+ result = addParamToResult(result, params[params.length - 1]);
+
+ return result;
+ }
+
+ private static String addParamToResult(String result, Object param) {
+ if (param == null)
+ result += "null";
+ else if (param.getClass().isArray())
+ result += convertAnyArrayToString(param);
+ else if (hasOverridenToStringMethod(param))
+ result += param.toString();
+ else
+ result += param.getClass().getSimpleName();
+
+ return result;
+ }
+
+ private static boolean hasOverridenToStringMethod(Object param) {
+ Method[] methods = param.getClass().getMethods();
+ for (Method method : methods) {
+ if (method.getName().equals("toString") && overridesMethod(method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean overridesMethod(Method method) {
+ return !method.getDeclaringClass().equals(Object.class);
+ }
+
+ static String uniqueMethodId(int index, Object paramSet, String methodName) {
+ return stringify(paramSet, index) + " (" + methodName + ")";
+ }
+
+ private static String convertAnyArrayToString(Object arrayAsObject) {
+ if (arrayAsObject.getClass().getComponentType().isPrimitive()) {
+ return convertFromArrayOfPrimitives(arrayAsObject);
+ } else {
+ return Arrays.toString((Object[]) arrayAsObject);
+ }
+ }
+
+ private static final String convertFromArrayOfPrimitives(Object arrayOfPrimitives) {
+ String componentType = arrayOfPrimitives.getClass().getComponentType().getName();
+ if ("byte".equals(componentType)) {
+ return Arrays.toString((byte[]) arrayOfPrimitives);
+ } else if ("short".equals(componentType)) {
+ return Arrays.toString((short[]) arrayOfPrimitives);
+ } else if ("int".equals(componentType)) {
+ return Arrays.toString((int[]) arrayOfPrimitives);
+ } else if ("long".equals(componentType)) {
+ return Arrays.toString((long[]) arrayOfPrimitives);
+ } else if ("float".equals(componentType)) {
+ return Arrays.toString((float[]) arrayOfPrimitives);
+ } else if ("double".equals(componentType)) {
+ return Arrays.toString((double[]) arrayOfPrimitives);
+ } else if ("boolean".equals(componentType)) {
+ return Arrays.toString((boolean[]) arrayOfPrimitives);
+ } else {
+ return Arrays.toString((char[]) arrayOfPrimitives);
+ }
+ }
+}
diff --git a/src/main/java/junitparams/internal/annotation/CustomParametersDescriptor.java b/src/main/java/junitparams/internal/annotation/CustomParametersDescriptor.java
new file mode 100644
index 0000000..7f0729e
--- /dev/null
+++ b/src/main/java/junitparams/internal/annotation/CustomParametersDescriptor.java
@@ -0,0 +1,30 @@
+package junitparams.internal.annotation;
+
+import java.lang.annotation.Annotation;
+
+import junitparams.custom.CustomParameters;
+import junitparams.custom.ParametersProvider;
+
+public class CustomParametersDescriptor {
+
+ private final Annotation customAnnotation;
+
+ private final Class<? extends ParametersProvider> provider;
+
+ public CustomParametersDescriptor(CustomParameters customParameters) {
+ this(customParameters, customParameters);
+ }
+
+ public CustomParametersDescriptor(CustomParameters customParameters, Annotation customAnnotation) {
+ this.provider = customParameters.provider();
+ this.customAnnotation = customAnnotation;
+ }
+
+ public Class<? extends ParametersProvider> provider() {
+ return provider;
+ }
+
+ public Annotation annotation() {
+ return customAnnotation;
+ }
+}
diff --git a/src/main/java/junitparams/internal/annotation/FrameworkMethodAnnotations.java b/src/main/java/junitparams/internal/annotation/FrameworkMethodAnnotations.java
new file mode 100644
index 0000000..326bb21
--- /dev/null
+++ b/src/main/java/junitparams/internal/annotation/FrameworkMethodAnnotations.java
@@ -0,0 +1,53 @@
+package junitparams.internal.annotation;
+
+import java.lang.annotation.Annotation;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.Parameters;
+import junitparams.custom.CustomParameters;
+
+public class FrameworkMethodAnnotations {
+
+ private final FrameworkMethod frameworkMethod;
+
+ public FrameworkMethodAnnotations(FrameworkMethod frameworkMethod) {
+ this.frameworkMethod = frameworkMethod;
+ }
+
+ public boolean isParametrised() {
+ return hasAnnotation(Parameters.class)
+ || hasCustomParameters();
+ }
+
+ public Annotation[] allAnnotations() {
+ return frameworkMethod.getAnnotations();
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return frameworkMethod.getAnnotation(annotationType);
+ }
+
+ public boolean hasAnnotation(Class<? extends Annotation> annotation) {
+ return getAnnotation(annotation) != null;
+ }
+
+ public boolean hasCustomParameters() {
+ return getCustomParameters() != null;
+ }
+
+ public CustomParametersDescriptor getCustomParameters() {
+ CustomParameters customParameters = frameworkMethod.getAnnotation(CustomParameters.class);
+ if (customParameters != null) {
+ return new CustomParametersDescriptor(customParameters);
+ }
+
+ for (Annotation annotation : frameworkMethod.getAnnotations()) {
+ customParameters = annotation.annotationType().getAnnotation(CustomParameters.class);
+ if (customParameters != null) {
+ return new CustomParametersDescriptor(customParameters, annotation);
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/junitparams/internal/parameters/ParametersFromCustomProvider.java b/src/main/java/junitparams/internal/parameters/ParametersFromCustomProvider.java
new file mode 100644
index 0000000..09dbf99
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersFromCustomProvider.java
@@ -0,0 +1,38 @@
+package junitparams.internal.parameters;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.custom.ParametersProvider;
+import junitparams.internal.annotation.CustomParametersDescriptor;
+import junitparams.internal.annotation.FrameworkMethodAnnotations;
+
+public class ParametersFromCustomProvider implements ParametrizationStrategy {
+
+ private final FrameworkMethodAnnotations frameworkMethodAnnotations;
+
+ public ParametersFromCustomProvider(FrameworkMethod frameworkMethod) {
+ frameworkMethodAnnotations = new FrameworkMethodAnnotations(frameworkMethod);
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return frameworkMethodAnnotations.hasCustomParameters();
+ }
+
+ @Override
+ public Object[] getParameters() {
+ CustomParametersDescriptor parameters = frameworkMethodAnnotations.getCustomParameters();
+ ParametersProvider provider = instantiate(parameters.provider());
+ provider.initialize(parameters.annotation());
+ return provider.getParameters();
+ }
+
+ private ParametersProvider instantiate(Class<? extends ParametersProvider> providerClass) {
+ try {
+ return providerClass.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Your Provider class must have a public no-arg constructor!", e);
+ }
+ }
+
+}
diff --git a/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassMethod.java b/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassMethod.java
new file mode 100644
index 0000000..051fde0
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassMethod.java
@@ -0,0 +1,29 @@
+package junitparams.internal.parameters;
+
+import junitparams.Parameters;
+import org.junit.runners.model.FrameworkMethod;
+
+import javax.lang.model.type.NullType;
+
+class ParametersFromExternalClassMethod implements ParametrizationStrategy {
+ private ParamsFromMethodCommon paramsFromMethodCommon;
+ private Parameters annotation;
+
+ ParametersFromExternalClassMethod(FrameworkMethod frameworkMethod) {
+ this.paramsFromMethodCommon = new ParamsFromMethodCommon(frameworkMethod);
+ annotation = frameworkMethod.getAnnotation(Parameters.class);
+ }
+
+ @Override
+ public Object[] getParameters() {
+ Class<?> sourceClass = annotation.source();
+ return paramsFromMethodCommon.paramsFromMethod(sourceClass);
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return annotation != null
+ && !annotation.source().isAssignableFrom(NullType.class)
+ && !annotation.method().isEmpty();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassProvideMethod.java b/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassProvideMethod.java
new file mode 100644
index 0000000..7bded6d
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersFromExternalClassProvideMethod.java
@@ -0,0 +1,80 @@
+package junitparams.internal.parameters;
+
+import junitparams.Parameters;
+import org.junit.runners.model.FrameworkMethod;
+
+import javax.lang.model.type.NullType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+class ParametersFromExternalClassProvideMethod implements ParametrizationStrategy {
+ private final ParamsFromMethodCommon paramsFromMethodCommon;
+ private Parameters annotation;
+
+ ParametersFromExternalClassProvideMethod(FrameworkMethod frameworkMethod) {
+ this.paramsFromMethodCommon = new ParamsFromMethodCommon(frameworkMethod);
+ annotation = frameworkMethod.getAnnotation(Parameters.class);
+ }
+
+ @Override
+ public Object[] getParameters() {
+ Class<?> sourceClass = annotation.source();
+ return fillResultWithAllParamProviderMethods(sourceClass);
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return annotation != null
+ && !annotation.source().isAssignableFrom(NullType.class)
+ && annotation.method().isEmpty();
+ }
+
+ private Object[] fillResultWithAllParamProviderMethods(Class<?> sourceClass) {
+ if (sourceClass.isEnum()) {
+ return sourceClass.getEnumConstants();
+ }
+
+ List<Object> result = getParamsFromSourceHierarchy(sourceClass);
+ if (result.isEmpty())
+ throw new RuntimeException(
+ "No methods starting with provide or they return no result in the parameters source class: "
+ + sourceClass.getName());
+
+ return result.toArray();
+ }
+
+ private List<Object> getParamsFromSourceHierarchy(Class<?> sourceClass) {
+ List<Object> result = new ArrayList<Object>();
+ while (sourceClass.getSuperclass() != null) {
+ result.addAll(gatherParamsFromAllMethodsFrom(sourceClass));
+ sourceClass = sourceClass.getSuperclass();
+ }
+
+ return result;
+ }
+
+ private List<Object> gatherParamsFromAllMethodsFrom(Class<?> sourceClass) {
+ List<Object> result = new ArrayList<Object>();
+ Method[] methods = sourceClass.getDeclaredMethods();
+ for (Method prividerMethod : methods) {
+ if (prividerMethod.getName().startsWith("provide")) {
+ if (!Modifier.isStatic(prividerMethod.getModifiers())) {
+ throw new RuntimeException("Parameters source method " +
+ prividerMethod.getName() +
+ " is not declared as static. Change it to a static method.");
+ }
+ try {
+ result.addAll(
+ Arrays.asList(paramsFromMethodCommon.getDataFromMethod(prividerMethod)));
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot invoke parameters source method: " + prividerMethod.getName(),
+ e);
+ }
+ }
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/internal/parameters/ParametersFromTestClassMethod.java b/src/main/java/junitparams/internal/parameters/ParametersFromTestClassMethod.java
new file mode 100644
index 0000000..9d1ab7a
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersFromTestClassMethod.java
@@ -0,0 +1,31 @@
+package junitparams.internal.parameters;
+
+import javax.lang.model.type.NullType;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.Parameters;
+
+class ParametersFromTestClassMethod implements ParametrizationStrategy {
+ private ParamsFromMethodCommon paramsFromMethodCommon;
+ private Class<?> testClass;
+ private Parameters annotation;
+
+ ParametersFromTestClassMethod(FrameworkMethod frameworkMethod, Class<?> testClass) {
+ paramsFromMethodCommon = new ParamsFromMethodCommon(frameworkMethod);
+ this.testClass = testClass;
+ annotation = frameworkMethod.getAnnotation(Parameters.class);
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return paramsFromMethodCommon.paramsFromMethod(testClass);
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return annotation != null
+ && annotation.source().isAssignableFrom(NullType.class)
+ && (!annotation.method().isEmpty() || paramsFromMethodCommon.containsDefaultParametersProvidingMethod(testClass));
+ }
+}
diff --git a/src/main/java/junitparams/internal/parameters/ParametersFromValue.java b/src/main/java/junitparams/internal/parameters/ParametersFromValue.java
new file mode 100644
index 0000000..943794d
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersFromValue.java
@@ -0,0 +1,23 @@
+package junitparams.internal.parameters;
+
+import junitparams.Parameters;
+import org.junit.runners.model.FrameworkMethod;
+
+class ParametersFromValue implements ParametrizationStrategy {
+
+ private final Parameters parametersAnnotation;
+
+ ParametersFromValue(FrameworkMethod frameworkMethod) {
+ parametersAnnotation = frameworkMethod.getAnnotation(Parameters.class);
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return parametersAnnotation.value();
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return parametersAnnotation != null && parametersAnnotation.value().length > 0;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/internal/parameters/ParametersReader.java b/src/main/java/junitparams/internal/parameters/ParametersReader.java
new file mode 100644
index 0000000..b0f5ec3
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametersReader.java
@@ -0,0 +1,55 @@
+package junitparams.internal.parameters;
+
+import java.util.List;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.FileParameters;
+import junitparams.Parameters;
+
+import static java.lang.String.*;
+import static java.util.Arrays.*;
+
+public class ParametersReader {
+
+ public static final String ILLEGAL_STATE_EXCEPTION_MESSAGE
+ = format("Illegal usage of JUnitParams in method %s. " +
+ "Check that you have only used one supported parameters evaluation strategy. " +
+ "Common case is to use both %s and %s annotations.",
+ "%s", Parameters.class, FileParameters.class);
+
+ private final FrameworkMethod frameworkMethod;
+ private final List<ParametrizationStrategy> strategies;
+
+ public ParametersReader(Class<?> testClass, FrameworkMethod frameworkMethod) {
+ this.frameworkMethod = frameworkMethod;
+
+ strategies = asList(
+ new ParametersFromCustomProvider(frameworkMethod),
+ new ParametersFromValue(frameworkMethod),
+ new ParametersFromExternalClassProvideMethod(frameworkMethod),
+ new ParametersFromExternalClassMethod(frameworkMethod),
+ new ParametersFromTestClassMethod(frameworkMethod, testClass)
+ );
+ }
+
+ public Object[] read() {
+ boolean strategyAlreadyFound = false;
+ Object[] parameters = new Object[]{};
+
+ for (ParametrizationStrategy strategy : strategies) {
+ if (strategy.isApplicable()) {
+ if (strategyAlreadyFound) {
+ illegalState();
+ }
+ parameters = strategy.getParameters();
+ strategyAlreadyFound = true;
+ }
+ }
+ return parameters;
+ }
+
+ private void illegalState() {
+ throw new IllegalStateException(format(ILLEGAL_STATE_EXCEPTION_MESSAGE, frameworkMethod.getName()));
+ }
+}
diff --git a/src/main/java/junitparams/internal/parameters/ParametrizationStrategy.java b/src/main/java/junitparams/internal/parameters/ParametrizationStrategy.java
new file mode 100644
index 0000000..b140edd
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParametrizationStrategy.java
@@ -0,0 +1,6 @@
+package junitparams.internal.parameters;
+
+interface ParametrizationStrategy {
+ Object[] getParameters();
+ boolean isApplicable();
+}
diff --git a/src/main/java/junitparams/internal/parameters/ParamsFromMethodCommon.java b/src/main/java/junitparams/internal/parameters/ParamsFromMethodCommon.java
new file mode 100644
index 0000000..d2ec124
--- /dev/null
+++ b/src/main/java/junitparams/internal/parameters/ParamsFromMethodCommon.java
@@ -0,0 +1,150 @@
+package junitparams.internal.parameters;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import junitparams.Parameters;
+
+class ParamsFromMethodCommon {
+ private FrameworkMethod frameworkMethod;
+
+ ParamsFromMethodCommon(FrameworkMethod frameworkMethod) {
+ this.frameworkMethod = frameworkMethod;
+ }
+
+ Object[] paramsFromMethod(Class<?> sourceClass) {
+ String methodAnnotation = frameworkMethod.getAnnotation(Parameters.class).method();
+
+ if (methodAnnotation.isEmpty()) {
+ return invokeMethodWithParams(defaultMethodName(), sourceClass);
+ }
+
+ List<Object> result = new ArrayList<Object>();
+ for (String methodName : methodAnnotation.split(",")) {
+ for (Object param : invokeMethodWithParams(methodName.trim(), sourceClass))
+ result.add(param);
+ }
+
+ return result.toArray();
+ }
+
+ Object[] getDataFromMethod(Method providerMethod) throws IllegalAccessException, InvocationTargetException {
+ return encapsulateParamsIntoArrayIfSingleParamsetPassed((Object[]) providerMethod.invoke(null));
+ }
+
+ boolean containsDefaultParametersProvidingMethod(Class<?> sourceClass) {
+ return findMethodInTestClassHierarchy(defaultMethodName(), sourceClass) != null;
+ }
+
+ private String defaultMethodName() {
+ return "parametersFor" + frameworkMethod.getName().substring(0, 1).toUpperCase()
+ + this.frameworkMethod.getName().substring(1);
+ }
+
+ private Object[] invokeMethodWithParams(String methodName, Class<?> sourceClass) {
+ Method providerMethod = findMethodInTestClassHierarchy(methodName, sourceClass);
+ if (providerMethod == null) {
+ throw new RuntimeException("Could not find method: " + methodName + " so no params were used.");
+ }
+
+ return invokeParamsProvidingMethod(providerMethod, sourceClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object[] invokeParamsProvidingMethod(Method provideMethod, Class<?> sourceClass) {
+ try {
+ Object testObject = sourceClass.newInstance();
+ provideMethod.setAccessible(true);
+ Object result = provideMethod.invoke(testObject);
+
+ if (Object[].class.isAssignableFrom(result.getClass())) {
+ Object[] params = (Object[]) result;
+ return encapsulateParamsIntoArrayIfSingleParamsetPassed(params);
+ }
+
+ if (Iterable.class.isAssignableFrom(result.getClass())) {
+ try {
+ ArrayList<Object[]> res = new ArrayList<Object[]>();
+ for (Object[] paramSet : (Iterable<Object[]>) result)
+ res.add(paramSet);
+ return res.toArray();
+ } catch (ClassCastException e1) {
+ // Iterable with consecutive paramsets, each of one param
+ ArrayList<Object> res = new ArrayList<Object>();
+ for (Object param : (Iterable<?>) result)
+ res.add(new Object[]{param});
+ return res.toArray();
+ }
+ }
+
+ if (Iterator.class.isAssignableFrom(result.getClass())) {
+ Object iteratedElement = null;
+ try {
+ ArrayList<Object[]> res = new ArrayList<Object[]>();
+ Iterator<Object[]> iterator = (Iterator<Object[]>) result;
+ while (iterator.hasNext()) {
+ iteratedElement = iterator.next();
+ // ClassCastException will occur in the following line
+ // if the iterator is actually Iterator<Object> in Java 7
+ res.add((Object[]) iteratedElement);
+ }
+ return res.toArray();
+ } catch (ClassCastException e1) {
+ // Iterator with consecutive paramsets, each of one param
+ ArrayList<Object> res = new ArrayList<Object>();
+ Iterator<?> iterator = (Iterator<?>) result;
+ // The first element is already stored in iteratedElement
+ res.add(iteratedElement);
+ while (iterator.hasNext()) {
+ res.add(new Object[]{iterator.next()});
+ }
+ return res.toArray();
+ }
+ }
+
+ throw new ClassCastException();
+
+ } catch (ClassCastException e) {
+ throw new RuntimeException("The return type of: " + provideMethod.getName() + " defined in class " +
+ sourceClass + " is not Object[][] nor Iterable<Object[]>. Fix it!", e);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not invoke method: " + provideMethod.getName() + " defined in class " +
+ sourceClass + " so no params were used.", e);
+ }
+ }
+
+ private Method findMethodInTestClassHierarchy(String methodName, Class<?> sourceClass) {
+ Class<?> declaringClass = sourceClass;
+ while (declaringClass.getSuperclass() != null) {
+ try {
+ return declaringClass.getDeclaredMethod(methodName);
+ } catch (Exception ignore) {
+ }
+ declaringClass = declaringClass.getSuperclass();
+ }
+ return null;
+ }
+
+ private Object[] encapsulateParamsIntoArrayIfSingleParamsetPassed(Object[] params) {
+ if (frameworkMethod.getMethod().getParameterTypes().length != params.length) {
+ return params;
+ }
+
+ if (params.length == 0) {
+ return params;
+ }
+
+ Object param = params[0];
+ if (param == null || !param.getClass().isArray()) {
+ return new Object[]{params};
+ }
+
+ return params;
+ }
+
+}
diff --git a/src/main/java/junitparams/mappers/BufferedReaderDataMapper.java b/src/main/java/junitparams/mappers/BufferedReaderDataMapper.java
new file mode 100644
index 0000000..a7b0fd2
--- /dev/null
+++ b/src/main/java/junitparams/mappers/BufferedReaderDataMapper.java
@@ -0,0 +1,40 @@
+package junitparams.mappers;
+
+import java.io.BufferedReader;
+import java.io.Reader;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A Data Mapper based on Buffered Reader.
+ */
+class BufferedReaderDataMapper implements DataMapper {
+
+ private final int linesToSkip;
+
+ BufferedReaderDataMapper() {
+ this(0);
+ }
+
+ BufferedReaderDataMapper(int linesToSkip) {
+ this.linesToSkip = linesToSkip;
+ }
+
+ @Override
+ public Object[] map(Reader reader) {
+ BufferedReader br = new BufferedReader(reader);
+ String line;
+ List<String> result = new LinkedList<String>();
+ int lineNo = 0;
+ try {
+ while ((line = br.readLine()) != null) {
+ if (++lineNo > linesToSkip) {
+ result.add(line);
+ }
+ }
+ return result.toArray();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/junitparams/mappers/CsvWithHeaderMapper.java b/src/main/java/junitparams/mappers/CsvWithHeaderMapper.java
new file mode 100644
index 0000000..ea4ab59
--- /dev/null
+++ b/src/main/java/junitparams/mappers/CsvWithHeaderMapper.java
@@ -0,0 +1,19 @@
+package junitparams.mappers;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Reads a CSV file starting from the second line - the first one is supposed to
+ * be a header. If you don't want to skip the first line, use &#064;FilePatameters
+ * without any mapper.
+ *
+ * @author Pawel Lipinski
+ *
+ */
+public class CsvWithHeaderMapper extends BufferedReaderDataMapper {
+
+ public CsvWithHeaderMapper() {
+ super(1);
+ }
+}
diff --git a/src/main/java/junitparams/mappers/DataMapper.java b/src/main/java/junitparams/mappers/DataMapper.java
new file mode 100644
index 0000000..200b03d
--- /dev/null
+++ b/src/main/java/junitparams/mappers/DataMapper.java
@@ -0,0 +1,31 @@
+package junitparams.mappers;
+
+import java.io.*;
+
+/**
+ * Interface to be used by FileParameters'ized test methods. If you want to read
+ * your own format of data from file, implement the map method appropriately.
+ * For CSV files, just skip it.
+ *
+ * @author Pawel Lipinski
+ *
+ */
+public interface DataMapper {
+ /**
+ * Maps file contents to parameters. In your implementation read the data
+ * from the reader. The reader is closed in the framework, so just read it
+ * :)
+ *
+ * While reading transform the data into Object[][], where external
+ * dimension are different parameter sets, and internal dimension is the set
+ * of params per single test call
+ *
+ * You can optionally return Object[] with Strings inside, but each String
+ * must be a string in the same format as what you would normally pass to
+ * &#064;Parameters({})
+ *
+ * @param reader
+ * @return an array with all parameter sets
+ */
+ Object[] map(Reader reader);
+}
diff --git a/src/main/java/junitparams/mappers/IdentityMapper.java b/src/main/java/junitparams/mappers/IdentityMapper.java
new file mode 100644
index 0000000..14bbf59
--- /dev/null
+++ b/src/main/java/junitparams/mappers/IdentityMapper.java
@@ -0,0 +1,20 @@
+package junitparams.mappers;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A mapper, that maps contents of a file to a set of parameters for test
+ * methods. Basically a CSV with no header and ordering of columns exactly like
+ * the one in the test methods.
+ *
+ * It uses the logic from &#064;Parameters({}) for parsing lines of file, so be sure
+ * the columns in the file match exactly the ordering of arguments in the test
+ * method.
+ *
+ * @author Pawel Lipinski
+ *
+ */
+public class IdentityMapper extends BufferedReaderDataMapper{
+
+}
diff --git a/src/main/java/junitparams/naming/MacroSubstitutionNamingStrategy.java b/src/main/java/junitparams/naming/MacroSubstitutionNamingStrategy.java
new file mode 100644
index 0000000..ce56847
--- /dev/null
+++ b/src/main/java/junitparams/naming/MacroSubstitutionNamingStrategy.java
@@ -0,0 +1,123 @@
+package junitparams.naming;
+
+import junitparams.internal.TestMethod;
+import junitparams.internal.Utils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+public class MacroSubstitutionNamingStrategy implements TestCaseNamingStrategy {
+ private static final String MACRO_PATTERN = "\\{[^\\}]{0,50}\\}";
+ // Pattern that keeps delimiters in split result
+ private static final Pattern MACRO_SPLIT_PATTERN = Pattern.compile(String.format("(?=%s)|(?<=%s)", MACRO_PATTERN, MACRO_PATTERN));
+ private static final String MACRO_START = "{";
+ private static final String MACRO_END = "}";
+ static final String DEFAULT_TEMPLATE = "[{index}] {params} ({method})";
+ private TestMethod method;
+
+ public MacroSubstitutionNamingStrategy(TestMethod testMethod) {
+ this.method = testMethod;
+ }
+
+ @Override
+ public String getTestCaseName(int parametersIndex, Object parameters) {
+ TestCaseName testCaseName = method.getAnnotation(TestCaseName.class);
+
+ String template = getTemplate(testCaseName);
+ String builtName = buildNameByTemplate(template, parametersIndex, parameters);
+
+ if (builtName.trim().isEmpty()) {
+ return buildNameByTemplate(DEFAULT_TEMPLATE, parametersIndex, parameters);
+ } else {
+ return builtName;
+ }
+ }
+
+ private String getTemplate(TestCaseName testCaseName) {
+ if (testCaseName != null) {
+ return testCaseName.value();
+ }
+
+ return DEFAULT_TEMPLATE;
+ }
+
+ private String buildNameByTemplate(String template, int parametersIndex, Object parameters) {
+ StringBuilder nameBuilder = new StringBuilder();
+
+ String[] parts = MACRO_SPLIT_PATTERN.split(template);
+
+ for (String part : parts) {
+ String transformedPart = transformPart(part, parametersIndex, parameters);
+ nameBuilder.append(transformedPart);
+ }
+
+ return nameBuilder.toString();
+ }
+
+ private String transformPart(String part, int parametersIndex, Object parameters) {
+ if (isMacro(part)) {
+ return lookupMacroValue(part, parametersIndex, parameters);
+ }
+
+ return part;
+ }
+
+ private String lookupMacroValue(String macro, int parametersIndex, Object parameters) {
+ String macroKey = getMacroKey(macro);
+
+ switch (Macro.parse(macroKey)) {
+ case INDEX: return String.valueOf(parametersIndex);
+ case PARAMS: return Utils.stringify(parameters);
+ case METHOD: return method.name();
+ default: return substituteDynamicMacro(macro, macroKey, parameters);
+ }
+ }
+
+ private String substituteDynamicMacro(String macro, String macroKey, Object parameters) {
+ if (isMethodParameterIndex(macroKey)) {
+ int index = parseIndex(macroKey);
+ return Utils.getParameterStringByIndexOrEmpty(parameters, index);
+ }
+
+ return macro;
+ }
+
+ private boolean isMethodParameterIndex(String macroKey) {
+ return macroKey.matches("\\d+");
+ }
+
+ private int parseIndex(String macroKey) {
+ return Integer.parseInt(macroKey);
+ }
+
+ private String getMacroKey(String macro) {
+ return macro
+ .substring(MACRO_START.length(), macro.length() - MACRO_END.length())
+ .toUpperCase(Locale.ENGLISH);
+ }
+
+ private boolean isMacro(String part) {
+ return part.startsWith(MACRO_START) && part.endsWith(MACRO_END);
+ }
+
+ private enum Macro {
+ INDEX,
+ PARAMS,
+ METHOD,
+ NONE;
+
+ public static Macro parse(String value) {
+ if (macros.contains(value)) {
+ return Macro.valueOf(value);
+ } else {
+ return Macro.NONE;
+ }
+ }
+
+ private static final HashSet<String> macros = new HashSet<String>(Arrays.asList(
+ Macro.INDEX.toString(), Macro.PARAMS.toString(), Macro.METHOD.toString())
+ );
+ }
+}
diff --git a/src/main/java/junitparams/naming/TestCaseName.java b/src/main/java/junitparams/naming/TestCaseName.java
new file mode 100644
index 0000000..4063f1a
--- /dev/null
+++ b/src/main/java/junitparams/naming/TestCaseName.java
@@ -0,0 +1,40 @@
+package junitparams.naming;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Use this annotation to specify the name for individual test case.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestCaseName {
+
+ /**
+ * A template of the individual test case name.
+ * This template can contain macros, which will be substituted by their actual values at runtime.
+ * <p>
+ * Supported macros are:
+ * <ul>
+ * <li><b>{index}</b> - index of parameters set (starts from zero). Hint: use it to avoid names duplication.</li>
+ * <li><b>{params}</b> - parameters set joined by comma.</li>
+ * <li><b>{method}</b> - testing method name.</li>
+ * <li>
+ * <b>{0}</b>, <b>{1}</b>, <b>{2}</b> - single parameter by index in current parameters set.
+ * If there is no parameter with such index, it will use empty string.
+ * </li>
+ * </ul>
+ * Lets assume, that we are testing Fibonacci sequence generator. We have a test with the following signature
+ * <pre><code>
+ * {@literal @}Parameters({ "0,1", "8,34" })
+ * public void testFibonacci(int indexInSequence, int expectedNumber) { ... }
+ * </code></pre>
+ * Here are some examples, that can be used as a test name template:
+ * <ul>
+ * <li>{method}({params}) => testFibonacci(0, 1), testFibonacci(8, 34)</li>
+ * <li>fibonacci({0}) = {1} => fibonacci(0) = 1, fibonacci(8) = 34</li>
+ * <li>{0} element should be {1} => 0 element should be 1, 8 element should be 34</li>
+ * <li>Fibonacci sequence test #{index} => Fibonacci sequence test #0, Fibonacci sequence test #1</li>
+ * </ul>
+ */
+ String value() default MacroSubstitutionNamingStrategy.DEFAULT_TEMPLATE;
+} \ No newline at end of file
diff --git a/src/main/java/junitparams/naming/TestCaseNamingStrategy.java b/src/main/java/junitparams/naming/TestCaseNamingStrategy.java
new file mode 100644
index 0000000..478b96c
--- /dev/null
+++ b/src/main/java/junitparams/naming/TestCaseNamingStrategy.java
@@ -0,0 +1,8 @@
+package junitparams.naming;
+
+/**
+ * A strategy that can resolve a test case method name by it's parameters.
+ */
+public interface TestCaseNamingStrategy {
+ String getTestCaseName(int parametersIndex, Object parameters);
+}