aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/experimental/theories
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/experimental/theories')
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoint.java51
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoints.java55
-rw-r--r--src/main/java/org/junit/experimental/theories/FromDataPoints.java54
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSignature.java202
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSupplier.java39
-rw-r--r--src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java40
-rw-r--r--src/main/java/org/junit/experimental/theories/PotentialAssignment.java77
-rw-r--r--src/main/java/org/junit/experimental/theories/Theories.java458
-rw-r--r--src/main/java/org/junit/experimental/theories/Theory.java14
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java291
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/Assignments.java249
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java18
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java30
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java85
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java90
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java22
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java26
17 files changed, 1231 insertions, 570 deletions
diff --git a/src/main/java/org/junit/experimental/theories/DataPoint.java b/src/main/java/org/junit/experimental/theories/DataPoint.java
index 2aaba6a..0a017bb 100644
--- a/src/main/java/org/junit/experimental/theories/DataPoint.java
+++ b/src/main/java/org/junit/experimental/theories/DataPoint.java
@@ -1,9 +1,56 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Annotating an field or method with @DataPoint will cause the field value
+ * or the value returned by the method to be used as a potential parameter for
+ * theories in that class, when run with the
+ * {@link org.junit.experimental.theories.Theories Theories} runner.
+ * <p>
+ * A DataPoint is only considered as a potential value for parameters for
+ * which its type is assignable. When multiple {@code DataPoint}s exist
+ * with overlapping types more control can be obtained by naming each DataPoint
+ * using the value of this annotation, e.g. with
+ * <code>&#064;DataPoint({"dataset1", "dataset2"})</code>, and then specifying
+ * which named set to consider as potential values for each parameter using the
+ * {@link org.junit.experimental.theories.FromDataPoints &#064;FromDataPoints}
+ * annotation.
+ * <p>
+ * Parameters with no specified source (i.e. without &#064;FromDataPoints or
+ * other {@link org.junit.experimental.theories.ParametersSuppliedBy
+ * &#064;ParameterSuppliedBy} annotations) will use all {@code DataPoint}s that are
+ * assignable to the parameter type as potential values, including named sets of
+ * {@code DataPoint}s.
+ *
+ * <pre>
+ * &#064;DataPoint
+ * public static String dataPoint = "value";
+ *
+ * &#064;DataPoint("generated")
+ * public static String generatedDataPoint() {
+ * return "generated value";
+ * }
+ *
+ * &#064;Theory
+ * public void theoryMethod(String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({FIELD, METHOD})
public @interface DataPoint {
-
-}
+ String[] value() default {};
+ Class<? extends Throwable>[] ignoredExceptions() default {};
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/DataPoints.java b/src/main/java/org/junit/experimental/theories/DataPoints.java
index 42145e3..b47461b 100644
--- a/src/main/java/org/junit/experimental/theories/DataPoints.java
+++ b/src/main/java/org/junit/experimental/theories/DataPoints.java
@@ -1,9 +1,64 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Annotating an array or iterable-typed field or method with &#064;DataPoints
+ * will cause the values in the array or iterable given to be used as potential
+ * parameters for theories in that class when run with the
+ * {@link org.junit.experimental.theories.Theories Theories} runner.
+ * <p>
+ * DataPoints will only be considered as potential values for parameters for
+ * which their types are assignable. When multiple sets of DataPoints exist with
+ * overlapping types more control can be obtained by naming the DataPoints using
+ * the value of this annotation, e.g. with
+ * <code>&#064;DataPoints({"dataset1", "dataset2"})</code>, and then specifying
+ * which named set to consider as potential values for each parameter using the
+ * {@link org.junit.experimental.theories.FromDataPoints &#064;FromDataPoints}
+ * annotation.
+ * <p>
+ * Parameters with no specified source (i.e. without &#064;FromDataPoints or
+ * other {@link org.junit.experimental.theories.ParametersSuppliedBy
+ * &#064;ParameterSuppliedBy} annotations) will use all DataPoints that are
+ * assignable to the parameter type as potential values, including named sets of
+ * DataPoints.
+ * <p>
+ * DataPoints methods whose array types aren't assignable from the target
+ * parameter type (and so can't possibly return relevant values) will not be
+ * called when generating values for that parameter. Iterable-typed datapoints
+ * methods must always be called though, as this information is not available
+ * here after generic type erasure, so expensive methods returning iterable
+ * datapoints are a bad idea.
+ *
+ * <pre>
+ * &#064;DataPoints
+ * public static String[] dataPoints = new String[] { ... };
+ *
+ * &#064;DataPoints
+ * public static String[] generatedDataPoints() {
+ * return new String[] { ... };
+ * }
+ *
+ * &#064;Theory
+ * public void theoryMethod(String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({ FIELD, METHOD })
public @interface DataPoints {
+ String[] value() default {};
+ Class<? extends Throwable>[] ignoredExceptions() default {};
}
diff --git a/src/main/java/org/junit/experimental/theories/FromDataPoints.java b/src/main/java/org/junit/experimental/theories/FromDataPoints.java
new file mode 100644
index 0000000..2b149ca
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/FromDataPoints.java
@@ -0,0 +1,54 @@
+package org.junit.experimental.theories;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.experimental.theories.internal.SpecificDataPointsSupplier;
+
+/**
+ * Annotating a parameter of a {@link org.junit.experimental.theories.Theory
+ * &#064;Theory} method with <code>&#064;FromDataPoints</code> will limit the
+ * datapoints considered as potential values for that parameter to just the
+ * {@link org.junit.experimental.theories.DataPoints DataPoints} with the given
+ * name. DataPoint names can be given as the value parameter of the
+ * &#064;DataPoints annotation.
+ * <p>
+ * DataPoints without names will not be considered as values for any parameters
+ * annotated with &#064;FromDataPoints.
+ * <pre>
+ * &#064;DataPoints
+ * public static String[] unnamed = new String[] { ... };
+ *
+ * &#064;DataPoints("regexes")
+ * public static String[] regexStrings = new String[] { ... };
+ *
+ * &#064;DataPoints({"forMatching", "alphanumeric"})
+ * public static String[] testStrings = new String[] { ... };
+ *
+ * &#064;Theory
+ * public void stringTheory(String param) {
+ * // This will be called with every value in 'regexStrings',
+ * // 'testStrings' and 'unnamed'.
+ * }
+ *
+ * &#064;Theory
+ * public void regexTheory(&#064;FromDataPoints("regexes") String regex,
+ * &#064;FromDataPoints("forMatching") String value) {
+ * // This will be called with only the values in 'regexStrings' as
+ * // regex, only the values in 'testStrings' as value, and none
+ * // of the values in 'unnamed'.
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.DataPoints
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@ParametersSuppliedBy(SpecificDataPointsSupplier.class)
+public @interface FromDataPoints {
+ String value();
+}
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSignature.java b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
index e7150fc..cf22583 100644
--- a/src/main/java/org/junit/experimental/theories/ParameterSignature.java
+++ b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.experimental.theories;
import java.lang.annotation.Annotation;
@@ -8,83 +5,130 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ParameterSignature {
- public static ArrayList<ParameterSignature> signatures(Method method) {
- return signatures(method.getParameterTypes(), method
- .getParameterAnnotations());
- }
-
- public static List<ParameterSignature> signatures(Constructor<?> constructor) {
- return signatures(constructor.getParameterTypes(), constructor
- .getParameterAnnotations());
- }
-
- private static ArrayList<ParameterSignature> signatures(
- Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
- ArrayList<ParameterSignature> sigs= new ArrayList<ParameterSignature>();
- for (int i= 0; i < parameterTypes.length; i++) {
- sigs.add(new ParameterSignature(parameterTypes[i],
- parameterAnnotations[i]));
- }
- return sigs;
- }
-
- private final Class<?> type;
-
- private final Annotation[] annotations;
-
- private ParameterSignature(Class<?> type, Annotation[] annotations) {
- this.type= type;
- this.annotations= annotations;
- }
-
- public boolean canAcceptType(Class<?> candidate) {
- return type.isAssignableFrom(candidate);
- }
-
- public Class<?> getType() {
- return type;
- }
-
- public List<Annotation> getAnnotations() {
- return Arrays.asList(annotations);
- }
-
- public boolean canAcceptArrayType(Class<?> type) {
- return type.isArray() && canAcceptType(type.getComponentType());
- }
-
- public boolean hasAnnotation(Class<? extends Annotation> type) {
- return getAnnotation(type) != null;
- }
-
- public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
- Annotation[] annotations2= annotations;
- return findDeepAnnotation(annotations2, annotationType, 3);
- }
-
- private <T extends Annotation> T findDeepAnnotation(
- Annotation[] annotations, Class<T> annotationType, int depth) {
- if (depth == 0)
- return null;
- for (Annotation each : annotations) {
- if (annotationType.isInstance(each))
- return annotationType.cast(each);
- Annotation candidate= findDeepAnnotation(each.annotationType()
- .getAnnotations(), annotationType, depth - 1);
- if (candidate != null)
- return annotationType.cast(candidate);
- }
-
- return null;
- }
-
- public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
- for (Annotation each : getAnnotations())
- if (annotationType.isInstance(each))
- return annotationType.cast(each);
- return null;
- }
+
+ private static final Map<Class<?>, Class<?>> CONVERTABLE_TYPES_MAP = buildConvertableTypesMap();
+
+ private static Map<Class<?>, Class<?>> buildConvertableTypesMap() {
+ Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
+
+ putSymmetrically(map, boolean.class, Boolean.class);
+ putSymmetrically(map, byte.class, Byte.class);
+ putSymmetrically(map, short.class, Short.class);
+ putSymmetrically(map, char.class, Character.class);
+ putSymmetrically(map, int.class, Integer.class);
+ putSymmetrically(map, long.class, Long.class);
+ putSymmetrically(map, float.class, Float.class);
+ putSymmetrically(map, double.class, Double.class);
+
+ return Collections.unmodifiableMap(map);
+ }
+
+ private static <T> void putSymmetrically(Map<T, T> map, T a, T b) {
+ map.put(a, b);
+ map.put(b, a);
+ }
+
+ public static ArrayList<ParameterSignature> signatures(Method method) {
+ return signatures(method.getParameterTypes(), method
+ .getParameterAnnotations());
+ }
+
+ public static List<ParameterSignature> signatures(Constructor<?> constructor) {
+ return signatures(constructor.getParameterTypes(), constructor
+ .getParameterAnnotations());
+ }
+
+ private static ArrayList<ParameterSignature> signatures(
+ Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
+ ArrayList<ParameterSignature> sigs = new ArrayList<ParameterSignature>();
+ for (int i = 0; i < parameterTypes.length; i++) {
+ sigs.add(new ParameterSignature(parameterTypes[i],
+ parameterAnnotations[i]));
+ }
+ return sigs;
+ }
+
+ private final Class<?> type;
+
+ private final Annotation[] annotations;
+
+ private ParameterSignature(Class<?> type, Annotation[] annotations) {
+ this.type = type;
+ this.annotations = annotations;
+ }
+
+ public boolean canAcceptValue(Object candidate) {
+ return (candidate == null) ? !type.isPrimitive() : canAcceptType(candidate.getClass());
+ }
+
+ public boolean canAcceptType(Class<?> candidate) {
+ return type.isAssignableFrom(candidate) ||
+ isAssignableViaTypeConversion(type, candidate);
+ }
+
+ public boolean canPotentiallyAcceptType(Class<?> candidate) {
+ return candidate.isAssignableFrom(type) ||
+ isAssignableViaTypeConversion(candidate, type) ||
+ canAcceptType(candidate);
+ }
+
+ private boolean isAssignableViaTypeConversion(Class<?> targetType, Class<?> candidate) {
+ if (CONVERTABLE_TYPES_MAP.containsKey(candidate)) {
+ Class<?> wrapperClass = CONVERTABLE_TYPES_MAP.get(candidate);
+ return targetType.isAssignableFrom(wrapperClass);
+ } else {
+ return false;
+ }
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+
+ public List<Annotation> getAnnotations() {
+ return Arrays.asList(annotations);
+ }
+
+ public boolean hasAnnotation(Class<? extends Annotation> type) {
+ return getAnnotation(type) != null;
+ }
+
+ public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
+ Annotation[] annotations2 = annotations;
+ return findDeepAnnotation(annotations2, annotationType, 3);
+ }
+
+ private <T extends Annotation> T findDeepAnnotation(
+ Annotation[] annotations, Class<T> annotationType, int depth) {
+ if (depth == 0) {
+ return null;
+ }
+ for (Annotation each : annotations) {
+ if (annotationType.isInstance(each)) {
+ return annotationType.cast(each);
+ }
+ Annotation candidate = findDeepAnnotation(each.annotationType()
+ .getAnnotations(), annotationType, depth - 1);
+ if (candidate != null) {
+ return annotationType.cast(candidate);
+ }
+ }
+
+ return null;
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ for (Annotation each : getAnnotations()) {
+ if (annotationType.isInstance(each)) {
+ return annotationType.cast(each);
+ }
+ }
+ return null;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
index 9016c91..bac8b34 100644
--- a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
@@ -2,7 +2,42 @@ package org.junit.experimental.theories;
import java.util.List;
-
+/**
+ * Abstract parent class for suppliers of input data points for theories. Extend this class to customize how {@link
+ * org.junit.experimental.theories.Theories Theories} runner
+ * finds accepted data points. Then use your class together with <b>&#064;ParametersSuppliedBy</b> on input
+ * parameters for theories.
+ *
+ * <p>
+ * For example, here is a supplier for values between two integers, and an annotation that references it:
+ *
+ * <pre>
+ * &#064;Retention(RetentionPolicy.RUNTIME)
+ * <b>&#064;ParametersSuppliedBy</b>(BetweenSupplier.class)
+ * public @interface Between {
+ * int first();
+ *
+ * int last();
+ * }
+ *
+ * public static class BetweenSupplier extends <b>ParameterSupplier</b> {
+ * &#064;Override
+ * public List&lt;<b>PotentialAssignment</b>&gt; getValueSources(<b>ParameterSignature</b> sig) {
+ * List&lt;<b>PotentialAssignment</b>&gt; list = new ArrayList&lt;PotentialAssignment&gt;();
+ * Between annotation = (Between) sig.getSupplierAnnotation();
+ *
+ * for (int i = annotation.first(); i &lt;= annotation.last(); i++)
+ * list.add(<b>PotentialAssignment</b>.forValue("ints", i));
+ * return list;
+ * }
+ * }
+ * </pre>
+ * </p>
+ *
+ * @see org.junit.experimental.theories.ParametersSuppliedBy
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
public abstract class ParameterSupplier {
- public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig);
+ public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable;
}
diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
index 8f090ef..15b5d95 100644
--- a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
+++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
@@ -1,12 +1,48 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.PARAMETER;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
-
+/**
+ * Annotating a {@link org.junit.experimental.theories.Theory Theory} method
+ * parameter with &#064;ParametersSuppliedBy causes it to be supplied with
+ * values from the named
+ * {@link org.junit.experimental.theories.ParameterSupplier ParameterSupplier}
+ * when run as a theory by the {@link org.junit.experimental.theories.Theories
+ * Theories} runner.
+ *
+ * In addition, annotations themselves can be annotated with
+ * &#064;ParametersSuppliedBy, and then used similarly. ParameterSuppliedBy
+ * annotations on parameters are detected by searching up this heirarchy such
+ * that these act as syntactic sugar, making:
+ *
+ * <pre>
+ * &#064;ParametersSuppliedBy(Supplier.class)
+ * public &#064;interface SpecialParameter { }
+ *
+ * &#064;Theory
+ * public void theoryMethod(&#064;SpecialParameter String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * equivalent to:
+ *
+ * <pre>
+ * &#064;Theory
+ * public void theoryMethod(&#064;ParametersSuppliedBy(Supplier.class) String param) {
+ * ...
+ * }
+ * </pre>
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({ ANNOTATION_TYPE, PARAMETER })
public @interface ParametersSuppliedBy {
- Class<? extends ParameterSupplier> value();
+ Class<? extends ParameterSupplier> value();
}
diff --git a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
index 0c008d0..18ca07a 100644
--- a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
+++ b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
@@ -1,31 +1,52 @@
package org.junit.experimental.theories;
+import static java.lang.String.format;
+
public abstract class PotentialAssignment {
- public static class CouldNotGenerateValueException extends Exception {
- private static final long serialVersionUID= 1L;
- }
-
- public static PotentialAssignment forValue(final String name, final Object value) {
- return new PotentialAssignment() {
- @Override
- public Object getValue() throws CouldNotGenerateValueException {
- return value;
- }
-
- @Override
- public String toString() {
- return String.format("[%s]", value);
- }
-
- @Override
- public String getDescription()
- throws CouldNotGenerateValueException {
- return name;
- }
- };
- }
-
- public abstract Object getValue() throws CouldNotGenerateValueException;
-
- public abstract String getDescription() throws CouldNotGenerateValueException;
-}
+ public static class CouldNotGenerateValueException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public CouldNotGenerateValueException() {
+ }
+
+ public CouldNotGenerateValueException(Throwable e) {
+ super(e);
+ }
+ }
+
+ public static PotentialAssignment forValue(final String name, final Object value) {
+ return new PotentialAssignment() {
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return format("[%s]", value);
+ }
+
+ @Override
+ public String getDescription() {
+ String valueString;
+
+ if (value == null) {
+ valueString = "null";
+ } else {
+ try {
+ valueString = format("\"%s\"", value);
+ } catch (Throwable e) {
+ valueString = format("[toString() threw %s: %s]",
+ e.getClass().getSimpleName(), e.getMessage());
+ }
+ }
+
+ return format("%s <from %s>", valueString, name);
+ }
+ };
+ }
+
+ public abstract Object getValue() throws CouldNotGenerateValueException;
+
+ public abstract String getDescription() throws CouldNotGenerateValueException;
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/Theories.java b/src/main/java/org/junit/experimental/theories/Theories.java
index 82ff98b..817f553 100644
--- a/src/main/java/org/junit/experimental/theories/Theories.java
+++ b/src/main/java/org/junit/experimental/theories/Theories.java
@@ -1,16 +1,14 @@
-/**
- *
- */
package org.junit.experimental.theories;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
-import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException;
+import org.junit.Assume;
import org.junit.experimental.theories.internal.Assignments;
import org.junit.experimental.theories.internal.ParameterizedAssertionError;
import org.junit.internal.AssumptionViolatedException;
@@ -20,180 +18,288 @@ import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
+/**
+ * The Theories runner allows to test a certain functionality against a subset of an infinite set of data points.
+ * <p>
+ * A Theory is a piece of functionality (a method) that is executed against several data inputs called data points.
+ * To make a test method a theory you mark it with <b>&#064;Theory</b>. To create a data point you create a public
+ * field in your test class and mark it with <b>&#064;DataPoint</b>. The Theories runner then executes your test
+ * method as many times as the number of data points declared, providing a different data point as
+ * the input argument on each invocation.
+ * </p>
+ * <p>
+ * A Theory differs from standard test method in that it captures some aspect of the intended behavior in possibly
+ * infinite numbers of scenarios which corresponds to the number of data points declared. Using assumptions and
+ * assertions properly together with covering multiple scenarios with different data points can make your tests more
+ * flexible and bring them closer to scientific theories (hence the name).
+ * </p>
+ * <p>
+ * For example:
+ * <pre>
+ *
+ * &#064;RunWith(<b>Theories.class</b>)
+ * public class UserTest {
+ * <b>&#064;DataPoint</b>
+ * public static String GOOD_USERNAME = "optimus";
+ * <b>&#064;DataPoint</b>
+ * public static String USERNAME_WITH_SLASH = "optimus/prime";
+ *
+ * <b>&#064;Theory</b>
+ * public void filenameIncludesUsername(String username) {
+ * assumeThat(username, not(containsString("/")));
+ * assertThat(new User(username).configFileName(), containsString(username));
+ * }
+ * }
+ * </pre>
+ * This makes it clear that the user's filename should be included in the config file name,
+ * only if it doesn't contain a slash. Another test or theory might define what happens when a username does contain
+ * a slash. <code>UserTest</code> will attempt to run <code>filenameIncludesUsername</code> on every compatible data
+ * point defined in the class. If any of the assumptions fail, the data point is silently ignored. If all of the
+ * assumptions pass, but an assertion fails, the test fails.
+ * <p>
+ * Defining general statements as theories allows data point reuse across a bunch of functionality tests and also
+ * allows automated tools to search for new, unexpected data points that expose bugs.
+ * </p>
+ * <p>
+ * The support for Theories has been absorbed from the Popper project, and more complete documentation can be found
+ * from that projects archived documentation.
+ * </p>
+ *
+ * @see <a href="http://web.archive.org/web/20071012143326/popper.tigris.org/tutorial.html">Archived Popper project documentation</a>
+ * @see <a href="http://web.archive.org/web/20110608210825/http://shareandenjoy.saff.net/tdd-specifications.pdf">Paper on Theories</a>
+ */
public class Theories extends BlockJUnit4ClassRunner {
- public Theories(Class<?> klass) throws InitializationError {
- super(klass);
- }
-
- @Override
- protected void collectInitializationErrors(List<Throwable> errors) {
- super.collectInitializationErrors(errors);
- validateDataPointFields(errors);
- }
-
- private void validateDataPointFields(List<Throwable> errors) {
- Field[] fields= getTestClass().getJavaClass().getDeclaredFields();
-
- for (Field each : fields)
- if (each.getAnnotation(DataPoint.class) != null && !Modifier.isStatic(each.getModifiers()))
- errors.add(new Error("DataPoint field " + each.getName() + " must be static"));
- }
-
- @Override
- protected void validateConstructor(List<Throwable> errors) {
- validateOnlyOneConstructor(errors);
- }
-
- @Override
- protected void validateTestMethods(List<Throwable> errors) {
- for (FrameworkMethod each : computeTestMethods())
- if(each.getAnnotation(Theory.class) != null)
- each.validatePublicVoid(false, errors);
- else
- each.validatePublicVoidNoArg(false, errors);
- }
-
- @Override
- protected List<FrameworkMethod> computeTestMethods() {
- List<FrameworkMethod> testMethods= super.computeTestMethods();
- List<FrameworkMethod> theoryMethods= getTestClass().getAnnotatedMethods(Theory.class);
- testMethods.removeAll(theoryMethods);
- testMethods.addAll(theoryMethods);
- return testMethods;
- }
-
- @Override
- public Statement methodBlock(final FrameworkMethod method) {
- return new TheoryAnchor(method, getTestClass());
- }
-
- public static class TheoryAnchor extends Statement {
- private int successes= 0;
-
- private FrameworkMethod fTestMethod;
- private TestClass fTestClass;
-
- private List<AssumptionViolatedException> fInvalidParameters= new ArrayList<AssumptionViolatedException>();
-
- public TheoryAnchor(FrameworkMethod method, TestClass testClass) {
- fTestMethod= method;
- fTestClass= testClass;
- }
+ public Theories(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ super.collectInitializationErrors(errors);
+ validateDataPointFields(errors);
+ validateDataPointMethods(errors);
+ }
+
+ private void validateDataPointFields(List<Throwable> errors) {
+ Field[] fields = getTestClass().getJavaClass().getDeclaredFields();
+
+ for (Field field : fields) {
+ if (field.getAnnotation(DataPoint.class) == null && field.getAnnotation(DataPoints.class) == null) {
+ continue;
+ }
+ if (!Modifier.isStatic(field.getModifiers())) {
+ errors.add(new Error("DataPoint field " + field.getName() + " must be static"));
+ }
+ if (!Modifier.isPublic(field.getModifiers())) {
+ errors.add(new Error("DataPoint field " + field.getName() + " must be public"));
+ }
+ }
+ }
+
+ private void validateDataPointMethods(List<Throwable> errors) {
+ Method[] methods = getTestClass().getJavaClass().getDeclaredMethods();
+
+ for (Method method : methods) {
+ if (method.getAnnotation(DataPoint.class) == null && method.getAnnotation(DataPoints.class) == null) {
+ continue;
+ }
+ if (!Modifier.isStatic(method.getModifiers())) {
+ errors.add(new Error("DataPoint method " + method.getName() + " must be static"));
+ }
+ if (!Modifier.isPublic(method.getModifiers())) {
+ errors.add(new Error("DataPoint method " + method.getName() + " must be public"));
+ }
+ }
+ }
+
+ @Override
+ protected void validateConstructor(List<Throwable> errors) {
+ validateOnlyOneConstructor(errors);
+ }
+
+ @Override
+ protected void validateTestMethods(List<Throwable> errors) {
+ for (FrameworkMethod each : computeTestMethods()) {
+ if (each.getAnnotation(Theory.class) != null) {
+ each.validatePublicVoid(false, errors);
+ each.validateNoTypeParametersOnArgs(errors);
+ } else {
+ each.validatePublicVoidNoArg(false, errors);
+ }
+
+ for (ParameterSignature signature : ParameterSignature.signatures(each.getMethod())) {
+ ParametersSuppliedBy annotation = signature.findDeepAnnotation(ParametersSuppliedBy.class);
+ if (annotation != null) {
+ validateParameterSupplier(annotation.value(), errors);
+ }
+ }
+ }
+ }
+
+ private void validateParameterSupplier(Class<? extends ParameterSupplier> supplierClass, List<Throwable> errors) {
+ Constructor<?>[] constructors = supplierClass.getConstructors();
+
+ if (constructors.length != 1) {
+ errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
+ " must have only one constructor (either empty or taking only a TestClass)"));
+ } else {
+ Class<?>[] paramTypes = constructors[0].getParameterTypes();
+ if (!(paramTypes.length == 0) && !paramTypes[0].equals(TestClass.class)) {
+ errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
+ " constructor must take either nothing or a single TestClass instance"));
+ }
+ }
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> testMethods = new ArrayList<FrameworkMethod>(super.computeTestMethods());
+ List<FrameworkMethod> theoryMethods = getTestClass().getAnnotatedMethods(Theory.class);
+ testMethods.removeAll(theoryMethods);
+ testMethods.addAll(theoryMethods);
+ return testMethods;
+ }
+
+ @Override
+ public Statement methodBlock(final FrameworkMethod method) {
+ return new TheoryAnchor(method, getTestClass());
+ }
+
+ public static class TheoryAnchor extends Statement {
+ private int successes = 0;
+
+ private final FrameworkMethod testMethod;
+ private final TestClass testClass;
+
+ private List<AssumptionViolatedException> fInvalidParameters = new ArrayList<AssumptionViolatedException>();
+
+ public TheoryAnchor(FrameworkMethod testMethod, TestClass testClass) {
+ this.testMethod = testMethod;
+ this.testClass = testClass;
+ }
private TestClass getTestClass() {
- return fTestClass;
+ return testClass;
}
- @Override
- public void evaluate() throws Throwable {
- runWithAssignment(Assignments.allUnassigned(
- fTestMethod.getMethod(), getTestClass()));
-
- if (successes == 0)
- Assert
- .fail("Never found parameters that satisfied method assumptions. Violated assumptions: "
- + fInvalidParameters);
- }
-
- protected void runWithAssignment(Assignments parameterAssignment)
- throws Throwable {
- if (!parameterAssignment.isComplete()) {
- runWithIncompleteAssignment(parameterAssignment);
- } else {
- runWithCompleteAssignment(parameterAssignment);
- }
- }
-
- protected void runWithIncompleteAssignment(Assignments incomplete)
- throws InstantiationException, IllegalAccessException,
- Throwable {
- for (PotentialAssignment source : incomplete
- .potentialsForNextUnassigned()) {
- runWithAssignment(incomplete.assignNext(source));
- }
- }
-
- protected void runWithCompleteAssignment(final Assignments complete)
- throws InstantiationException, IllegalAccessException,
- InvocationTargetException, NoSuchMethodException, Throwable {
- new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
- @Override
- protected void collectInitializationErrors(
- List<Throwable> errors) {
- // do nothing
- }
-
- @Override
- public Statement methodBlock(FrameworkMethod method) {
- final Statement statement= super.methodBlock(method);
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- try {
- statement.evaluate();
- handleDataPointSuccess();
- } catch (AssumptionViolatedException e) {
- handleAssumptionViolation(e);
- } catch (Throwable e) {
- reportParameterizedError(e, complete
- .getArgumentStrings(nullsOk()));
- }
- }
-
- };
- }
-
- @Override
- protected Statement methodInvoker(FrameworkMethod method, Object test) {
- return methodCompletesWithParameters(method, complete, test);
- }
-
- @Override
- public Object createTest() throws Exception {
- return getTestClass().getOnlyConstructor().newInstance(
- complete.getConstructorArguments(nullsOk()));
- }
- }.methodBlock(fTestMethod).evaluate();
- }
-
- private Statement methodCompletesWithParameters(
- final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- try {
- final Object[] values= complete.getMethodArguments(
- nullsOk());
- method.invokeExplosively(freshInstance, values);
- } catch (CouldNotGenerateValueException e) {
- // ignore
- }
- }
- };
- }
-
- protected void handleAssumptionViolation(AssumptionViolatedException e) {
- fInvalidParameters.add(e);
- }
-
- protected void reportParameterizedError(Throwable e, Object... params)
- throws Throwable {
- if (params.length == 0)
- throw e;
- throw new ParameterizedAssertionError(e, fTestMethod.getName(),
- params);
- }
-
- private boolean nullsOk() {
- Theory annotation= fTestMethod.getMethod().getAnnotation(
- Theory.class);
- if (annotation == null)
- return false;
- return annotation.nullsAccepted();
- }
-
- protected void handleDataPointSuccess() {
- successes++;
- }
- }
+ @Override
+ public void evaluate() throws Throwable {
+ runWithAssignment(Assignments.allUnassigned(
+ testMethod.getMethod(), getTestClass()));
+
+ //if this test method is not annotated with Theory, then no successes is a valid case
+ boolean hasTheoryAnnotation = testMethod.getAnnotation(Theory.class) != null;
+ if (successes == 0 && hasTheoryAnnotation) {
+ Assert
+ .fail("Never found parameters that satisfied method assumptions. Violated assumptions: "
+ + fInvalidParameters);
+ }
+ }
+
+ protected void runWithAssignment(Assignments parameterAssignment)
+ throws Throwable {
+ if (!parameterAssignment.isComplete()) {
+ runWithIncompleteAssignment(parameterAssignment);
+ } else {
+ runWithCompleteAssignment(parameterAssignment);
+ }
+ }
+
+ protected void runWithIncompleteAssignment(Assignments incomplete)
+ throws Throwable {
+ for (PotentialAssignment source : incomplete
+ .potentialsForNextUnassigned()) {
+ runWithAssignment(incomplete.assignNext(source));
+ }
+ }
+
+ protected void runWithCompleteAssignment(final Assignments complete)
+ throws Throwable {
+ new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
+ @Override
+ protected void collectInitializationErrors(
+ List<Throwable> errors) {
+ // do nothing
+ }
+
+ @Override
+ public Statement methodBlock(FrameworkMethod method) {
+ final Statement statement = super.methodBlock(method);
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ statement.evaluate();
+ handleDataPointSuccess();
+ } catch (AssumptionViolatedException e) {
+ handleAssumptionViolation(e);
+ } catch (Throwable e) {
+ reportParameterizedError(e, complete
+ .getArgumentStrings(nullsOk()));
+ }
+ }
+
+ };
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test) {
+ return methodCompletesWithParameters(method, complete, test);
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ Object[] params = complete.getConstructorArguments();
+
+ if (!nullsOk()) {
+ Assume.assumeNotNull(params);
+ }
+
+ return getTestClass().getOnlyConstructor().newInstance(params);
+ }
+ }.methodBlock(testMethod).evaluate();
+ }
+
+ private Statement methodCompletesWithParameters(
+ final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ final Object[] values = complete.getMethodArguments();
+
+ if (!nullsOk()) {
+ Assume.assumeNotNull(values);
+ }
+
+ method.invokeExplosively(freshInstance, values);
+ }
+ };
+ }
+
+ protected void handleAssumptionViolation(AssumptionViolatedException e) {
+ fInvalidParameters.add(e);
+ }
+
+ protected void reportParameterizedError(Throwable e, Object... params)
+ throws Throwable {
+ if (params.length == 0) {
+ throw e;
+ }
+ throw new ParameterizedAssertionError(e, testMethod.getName(),
+ params);
+ }
+
+ private boolean nullsOk() {
+ Theory annotation = testMethod.getMethod().getAnnotation(
+ Theory.class);
+ if (annotation == null) {
+ return false;
+ }
+ return annotation.nullsAccepted();
+ }
+
+ protected void handleDataPointSuccess() {
+ successes++;
+ }
+ }
}
diff --git a/src/main/java/org/junit/experimental/theories/Theory.java b/src/main/java/org/junit/experimental/theories/Theory.java
index 134fe9d..0b9f2c4 100644
--- a/src/main/java/org/junit/experimental/theories/Theory.java
+++ b/src/main/java/org/junit/experimental/theories/Theory.java
@@ -1,12 +1,18 @@
-/**
- *
- */
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Marks test methods that should be read as theories by the {@link org.junit.experimental.theories.Theories Theories} runner.
+ *
+ * @see org.junit.experimental.theories.Theories
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target(METHOD)
public @interface Theory {
- boolean nullsAccepted() default true;
+ boolean nullsAccepted() default true;
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
index 615cc3e..f15fb28 100644
--- a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
@@ -1,19 +1,19 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
+import org.junit.Assume;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.ParameterSignature;
import org.junit.experimental.theories.ParameterSupplier;
import org.junit.experimental.theories.PotentialAssignment;
+import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;
@@ -21,107 +21,184 @@ import org.junit.runners.model.TestClass;
* Supplies Theory parameters based on all public members of the target class.
*/
public class AllMembersSupplier extends ParameterSupplier {
- static class MethodParameterValue extends PotentialAssignment {
- private final FrameworkMethod fMethod;
-
- private MethodParameterValue(FrameworkMethod dataPointMethod) {
- fMethod= dataPointMethod;
- }
-
- @Override
- public Object getValue() throws CouldNotGenerateValueException {
- try {
- return fMethod.invokeExplosively(null);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(
- "unexpected: argument length is checked");
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "unexpected: getMethods returned an inaccessible method");
- } catch (Throwable e) {
- throw new CouldNotGenerateValueException();
- // do nothing, just look for more values
- }
- }
-
- @Override
- public String getDescription() throws CouldNotGenerateValueException {
- return fMethod.getName();
- }
- }
-
- private final TestClass fClass;
-
- /**
- * Constructs a new supplier for {@code type}
- */
- public AllMembersSupplier(TestClass type) {
- fClass= type;
- }
-
- @Override
- public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
- List<PotentialAssignment> list= new ArrayList<PotentialAssignment>();
-
- addFields(sig, list);
- addSinglePointMethods(sig, list);
- addMultiPointMethods(list);
-
- return list;
- }
-
- private void addMultiPointMethods(List<PotentialAssignment> list) {
- for (FrameworkMethod dataPointsMethod : fClass
- .getAnnotatedMethods(DataPoints.class))
- try {
- addArrayValues(dataPointsMethod.getName(), list, dataPointsMethod.invokeExplosively(null));
- } catch (Throwable e) {
- // ignore and move on
- }
- }
-
- @SuppressWarnings("deprecation")
- private void addSinglePointMethods(ParameterSignature sig,
- List<PotentialAssignment> list) {
- for (FrameworkMethod dataPointMethod : fClass
- .getAnnotatedMethods(DataPoint.class)) {
- Class<?> type= sig.getType();
- if ((dataPointMethod.producesType(type)))
- list.add(new MethodParameterValue(dataPointMethod));
- }
- }
-
- private void addFields(ParameterSignature sig,
- List<PotentialAssignment> list) {
- for (final Field field : fClass.getJavaClass().getFields()) {
- if (Modifier.isStatic(field.getModifiers())) {
- Class<?> type= field.getType();
- if (sig.canAcceptArrayType(type)
- && field.getAnnotation(DataPoints.class) != null) {
- addArrayValues(field.getName(), list, getStaticFieldValue(field));
- } else if (sig.canAcceptType(type)
- && field.getAnnotation(DataPoint.class) != null) {
- list.add(PotentialAssignment
- .forValue(field.getName(), getStaticFieldValue(field)));
- }
- }
- }
- }
-
- private void addArrayValues(String name, List<PotentialAssignment> list, Object array) {
- for (int i= 0; i < Array.getLength(array); i++)
- list.add(PotentialAssignment.forValue(name + "[" + i + "]", Array.get(array, i)));
- }
-
- private Object getStaticFieldValue(final Field field) {
- try {
- return field.get(null);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(
- "unexpected: field from getClass doesn't exist on object");
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "unexpected: getFields returned an inaccessible field");
- }
- }
+ static class MethodParameterValue extends PotentialAssignment {
+ private final FrameworkMethod method;
+
+ private MethodParameterValue(FrameworkMethod dataPointMethod) {
+ method = dataPointMethod;
+ }
+
+ @Override
+ public Object getValue() throws CouldNotGenerateValueException {
+ try {
+ return method.invokeExplosively(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: argument length is checked");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getMethods returned an inaccessible method");
+ } catch (Throwable throwable) {
+ DataPoint annotation = method.getAnnotation(DataPoint.class);
+ Assume.assumeTrue(annotation == null || !isAssignableToAnyOf(annotation.ignoredExceptions(), throwable));
+
+ throw new CouldNotGenerateValueException(throwable);
+ }
+ }
+
+ @Override
+ public String getDescription() throws CouldNotGenerateValueException {
+ return method.getName();
+ }
+ }
+
+ private final TestClass clazz;
+
+ /**
+ * Constructs a new supplier for {@code type}
+ */
+ public AllMembersSupplier(TestClass type) {
+ clazz = type;
+ }
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable {
+ List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
+
+ addSinglePointFields(sig, list);
+ addMultiPointFields(sig, list);
+ addSinglePointMethods(sig, list);
+ addMultiPointMethods(sig, list);
+
+ return list;
+ }
+
+ private void addMultiPointMethods(ParameterSignature sig, List<PotentialAssignment> list) throws Throwable {
+ for (FrameworkMethod dataPointsMethod : getDataPointsMethods(sig)) {
+ Class<?> returnType = dataPointsMethod.getReturnType();
+
+ if ((returnType.isArray() && sig.canPotentiallyAcceptType(returnType.getComponentType())) ||
+ Iterable.class.isAssignableFrom(returnType)) {
+ try {
+ addDataPointsValues(returnType, sig, dataPointsMethod.getName(), list,
+ dataPointsMethod.invokeExplosively(null));
+ } catch (Throwable throwable) {
+ DataPoints annotation = dataPointsMethod.getAnnotation(DataPoints.class);
+ if (annotation != null && isAssignableToAnyOf(annotation.ignoredExceptions(), throwable)) {
+ return;
+ } else {
+ throw throwable;
+ }
+ }
+ }
+ }
+ }
+
+ private void addSinglePointMethods(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (FrameworkMethod dataPointMethod : getSingleDataPointMethods(sig)) {
+ if (sig.canAcceptType(dataPointMethod.getType())) {
+ list.add(new MethodParameterValue(dataPointMethod));
+ }
+ }
+ }
+
+ private void addMultiPointFields(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (final Field field : getDataPointsFields(sig)) {
+ Class<?> type = field.getType();
+ addDataPointsValues(type, sig, field.getName(), list, getStaticFieldValue(field));
+ }
+ }
+
+ private void addSinglePointFields(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (final Field field : getSingleDataPointFields(sig)) {
+ Object value = getStaticFieldValue(field);
+
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(field.getName(), value));
+ }
+ }
+ }
+
+ private void addDataPointsValues(Class<?> type, ParameterSignature sig, String name,
+ List<PotentialAssignment> list, Object value) {
+ if (type.isArray()) {
+ addArrayValues(sig, name, list, value);
+ }
+ else if (Iterable.class.isAssignableFrom(type)) {
+ addIterableValues(sig, name, list, (Iterable<?>) value);
+ }
+ }
+
+ private void addArrayValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Object array) {
+ for (int i = 0; i < Array.getLength(array); i++) {
+ Object value = Array.get(array, i);
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(name + "[" + i + "]", value));
+ }
+ }
+ }
+
+ private void addIterableValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Iterable<?> iterable) {
+ Iterator<?> iterator = iterable.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Object value = iterator.next();
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(name + "[" + i + "]", value));
+ }
+ i += 1;
+ }
+ }
+
+ private Object getStaticFieldValue(final Field field) {
+ try {
+ return field.get(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: field from getClass doesn't exist on object");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getFields returned an inaccessible field");
+ }
+ }
+
+ private static boolean isAssignableToAnyOf(Class<?>[] typeArray, Object target) {
+ for (Class<?> type : typeArray) {
+ if (type.isAssignableFrom(target.getClass())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) {
+ return clazz.getAnnotatedMethods(DataPoints.class);
+ }
+
+ protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) {
+ List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoint.class);
+ Collection<Field> validFields = new ArrayList<Field>();
+
+ for (FrameworkField frameworkField : fields) {
+ validFields.add(frameworkField.getField());
+ }
+
+ return validFields;
+ }
+
+ protected Collection<Field> getDataPointsFields(ParameterSignature sig) {
+ List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoints.class);
+ Collection<Field> validFields = new ArrayList<Field>();
+
+ for (FrameworkField frameworkField : fields) {
+ validFields.add(frameworkField.getField());
+ }
+
+ return validFields;
+ }
+
+ protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) {
+ return clazz.getAnnotatedMethods(DataPoint.class);
+ }
+
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/Assignments.java b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
index bd94f00..a94c8a5 100644
--- a/src/main/java/org/junit/experimental/theories/internal/Assignments.java
+++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
@@ -1,8 +1,8 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
+import static java.util.Collections.emptyList;
+
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -19,115 +19,136 @@ import org.junit.runners.model.TestClass;
* parameters
*/
public class Assignments {
- private List<PotentialAssignment> fAssigned;
-
- private final List<ParameterSignature> fUnassigned;
-
- private final TestClass fClass;
-
- private Assignments(List<PotentialAssignment> assigned,
- List<ParameterSignature> unassigned, TestClass testClass) {
- fUnassigned= unassigned;
- fAssigned= assigned;
- fClass= testClass;
- }
-
- /**
- * Returns a new assignment list for {@code testMethod}, with no params
- * assigned.
- */
- public static Assignments allUnassigned(Method testMethod,
- TestClass testClass) throws Exception {
- List<ParameterSignature> signatures;
- signatures= ParameterSignature.signatures(testClass
- .getOnlyConstructor());
- signatures.addAll(ParameterSignature.signatures(testMethod));
- return new Assignments(new ArrayList<PotentialAssignment>(),
- signatures, testClass);
- }
-
- public boolean isComplete() {
- return fUnassigned.size() == 0;
- }
-
- public ParameterSignature nextUnassigned() {
- return fUnassigned.get(0);
- }
-
- public Assignments assignNext(PotentialAssignment source) {
- List<PotentialAssignment> assigned= new ArrayList<PotentialAssignment>(
- fAssigned);
- assigned.add(source);
-
- return new Assignments(assigned, fUnassigned.subList(1, fUnassigned
- .size()), fClass);
- }
-
- public Object[] getActualValues(int start, int stop, boolean nullsOk)
- throws CouldNotGenerateValueException {
- Object[] values= new Object[stop - start];
- for (int i= start; i < stop; i++) {
- Object value= fAssigned.get(i).getValue();
- if (value == null && !nullsOk)
- throw new CouldNotGenerateValueException();
- values[i - start]= value;
- }
- return values;
- }
-
- public List<PotentialAssignment> potentialsForNextUnassigned()
- throws InstantiationException, IllegalAccessException {
- ParameterSignature unassigned= nextUnassigned();
- return getSupplier(unassigned).getValueSources(unassigned);
- }
-
- public ParameterSupplier getSupplier(ParameterSignature unassigned)
- throws InstantiationException, IllegalAccessException {
- ParameterSupplier supplier= getAnnotatedSupplier(unassigned);
- if (supplier != null)
- return supplier;
-
- return new AllMembersSupplier(fClass);
- }
-
- public ParameterSupplier getAnnotatedSupplier(ParameterSignature unassigned)
- throws InstantiationException, IllegalAccessException {
- ParametersSuppliedBy annotation= unassigned
- .findDeepAnnotation(ParametersSuppliedBy.class);
- if (annotation == null)
- return null;
- return annotation.value().newInstance();
- }
-
- public Object[] getConstructorArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(0, getConstructorParameterCount(), nullsOk);
- }
-
- public Object[] getMethodArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(getConstructorParameterCount(),
- fAssigned.size(), nullsOk);
- }
-
- public Object[] getAllArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(0, fAssigned.size(), nullsOk);
- }
-
- private int getConstructorParameterCount() {
- List<ParameterSignature> signatures= ParameterSignature
- .signatures(fClass.getOnlyConstructor());
- int constructorParameterCount= signatures.size();
- return constructorParameterCount;
- }
-
- public Object[] getArgumentStrings(boolean nullsOk)
- throws CouldNotGenerateValueException {
- Object[] values= new Object[fAssigned.size()];
- for (int i= 0; i < values.length; i++) {
- values[i]= fAssigned.get(i).getDescription();
- }
- return values;
- }
+ private final List<PotentialAssignment> assigned;
+
+ private final List<ParameterSignature> unassigned;
+
+ private final TestClass clazz;
+
+ private Assignments(List<PotentialAssignment> assigned,
+ List<ParameterSignature> unassigned, TestClass clazz) {
+ this.unassigned = unassigned;
+ this.assigned = assigned;
+ this.clazz = clazz;
+ }
+
+ /**
+ * Returns a new assignment list for {@code testMethod}, with no params
+ * assigned.
+ */
+ public static Assignments allUnassigned(Method testMethod,
+ TestClass testClass) {
+ List<ParameterSignature> signatures;
+ signatures = ParameterSignature.signatures(testClass
+ .getOnlyConstructor());
+ signatures.addAll(ParameterSignature.signatures(testMethod));
+ return new Assignments(new ArrayList<PotentialAssignment>(),
+ signatures, testClass);
+ }
+
+ public boolean isComplete() {
+ return unassigned.size() == 0;
+ }
+
+ public ParameterSignature nextUnassigned() {
+ return unassigned.get(0);
+ }
+
+ public Assignments assignNext(PotentialAssignment source) {
+ List<PotentialAssignment> assigned = new ArrayList<PotentialAssignment>(
+ this.assigned);
+ assigned.add(source);
+
+ return new Assignments(assigned, unassigned.subList(1,
+ unassigned.size()), clazz);
+ }
+
+ public Object[] getActualValues(int start, int stop)
+ throws CouldNotGenerateValueException {
+ Object[] values = new Object[stop - start];
+ for (int i = start; i < stop; i++) {
+ values[i - start] = assigned.get(i).getValue();
+ }
+ return values;
+ }
+
+ public List<PotentialAssignment> potentialsForNextUnassigned()
+ throws Throwable {
+ ParameterSignature unassigned = nextUnassigned();
+ List<PotentialAssignment> assignments = getSupplier(unassigned).getValueSources(unassigned);
+
+ if (assignments.size() == 0) {
+ assignments = generateAssignmentsFromTypeAlone(unassigned);
+ }
+
+ return assignments;
+ }
+
+ private List<PotentialAssignment> generateAssignmentsFromTypeAlone(ParameterSignature unassigned) {
+ Class<?> paramType = unassigned.getType();
+
+ if (paramType.isEnum()) {
+ return new EnumSupplier(paramType).getValueSources(unassigned);
+ } else if (paramType.equals(Boolean.class) || paramType.equals(boolean.class)) {
+ return new BooleanSupplier().getValueSources(unassigned);
+ } else {
+ return emptyList();
+ }
+ }
+
+ private ParameterSupplier getSupplier(ParameterSignature unassigned)
+ throws Exception {
+ ParametersSuppliedBy annotation = unassigned
+ .findDeepAnnotation(ParametersSuppliedBy.class);
+
+ if (annotation != null) {
+ return buildParameterSupplierFromClass(annotation.value());
+ } else {
+ return new AllMembersSupplier(clazz);
+ }
+ }
+
+ private ParameterSupplier buildParameterSupplierFromClass(
+ Class<? extends ParameterSupplier> cls) throws Exception {
+ Constructor<?>[] supplierConstructors = cls.getConstructors();
+
+ for (Constructor<?> constructor : supplierConstructors) {
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ if (parameterTypes.length == 1
+ && parameterTypes[0].equals(TestClass.class)) {
+ return (ParameterSupplier) constructor.newInstance(clazz);
+ }
+ }
+
+ return cls.newInstance();
+ }
+
+ public Object[] getConstructorArguments()
+ throws CouldNotGenerateValueException {
+ return getActualValues(0, getConstructorParameterCount());
+ }
+
+ public Object[] getMethodArguments() throws CouldNotGenerateValueException {
+ return getActualValues(getConstructorParameterCount(), assigned.size());
+ }
+
+ public Object[] getAllArguments() throws CouldNotGenerateValueException {
+ return getActualValues(0, assigned.size());
+ }
+
+ private int getConstructorParameterCount() {
+ List<ParameterSignature> signatures = ParameterSignature
+ .signatures(clazz.getOnlyConstructor());
+ int constructorParameterCount = signatures.size();
+ return constructorParameterCount;
+ }
+
+ public Object[] getArgumentStrings(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ Object[] values = new Object[assigned.size()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = assigned.get(i).getDescription();
+ }
+ return values;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java
new file mode 100644
index 0000000..5f7032f
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java
@@ -0,0 +1,18 @@
+package org.junit.experimental.theories.internal;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+
+public class BooleanSupplier extends ParameterSupplier {
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ return Arrays.asList(PotentialAssignment.forValue("true", true),
+ PotentialAssignment.forValue("false", false));
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java
new file mode 100644
index 0000000..1f3ab90
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java
@@ -0,0 +1,30 @@
+package org.junit.experimental.theories.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+
+public class EnumSupplier extends ParameterSupplier {
+
+ private Class<?> enumType;
+
+ public EnumSupplier(Class<?> enumType) {
+ this.enumType = enumType;
+ }
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ Object[] enumValues = enumType.getEnumConstants();
+
+ List<PotentialAssignment> assignments = new ArrayList<PotentialAssignment>();
+ for (Object value : enumValues) {
+ assignments.add(PotentialAssignment.forValue(value.toString(), value));
+ }
+
+ return assignments;
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
index 285bc3a..5b9e947 100644
--- a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
+++ b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
@@ -1,49 +1,50 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
-
-public class ParameterizedAssertionError extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public ParameterizedAssertionError(Throwable targetException,
- String methodName, Object... params) {
- super(String.format("%s(%s)", methodName, join(", ", params)),
- targetException);
- }
-
- @Override public boolean equals(Object obj) {
- return toString().equals(obj.toString());
- }
-
- public static String join(String delimiter, Object... params) {
- return join(delimiter, Arrays.asList(params));
- }
-
- public static String join(String delimiter,
- Collection<Object> values) {
- StringBuffer buffer = new StringBuffer();
- Iterator<Object> iter = values.iterator();
- while (iter.hasNext()) {
- Object next = iter.next();
- buffer.append(stringValueOf(next));
- if (iter.hasNext()) {
- buffer.append(delimiter);
- }
- }
- return buffer.toString();
- }
-
- private static String stringValueOf(Object next) {
- try {
- return String.valueOf(next);
- } catch (Throwable e) {
- return "[toString failed]";
- }
- }
+public class ParameterizedAssertionError extends AssertionError {
+ private static final long serialVersionUID = 1L;
+
+ public ParameterizedAssertionError(Throwable targetException,
+ String methodName, Object... params) {
+ super(String.format("%s(%s)", methodName, join(", ", params)));
+ this.initCause(targetException);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ParameterizedAssertionError && toString().equals(obj.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ public static String join(String delimiter, Object... params) {
+ return join(delimiter, Arrays.asList(params));
+ }
+
+ public static String join(String delimiter, Collection<Object> values) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<Object> iter = values.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ sb.append(stringValueOf(next));
+ if (iter.hasNext()) {
+ sb.append(delimiter);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String stringValueOf(Object next) {
+ try {
+ return String.valueOf(next);
+ } catch (Throwable e) {
+ return "[toString failed]";
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java
new file mode 100644
index 0000000..7b571e3
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java
@@ -0,0 +1,90 @@
+package org.junit.experimental.theories.internal;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+public class SpecificDataPointsSupplier extends AllMembersSupplier {
+
+ public SpecificDataPointsSupplier(TestClass testClass) {
+ super(testClass);
+ }
+
+ @Override
+ protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) {
+ Collection<Field> fields = super.getSingleDataPointFields(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<Field> fieldsWithMatchingNames = new ArrayList<Field>();
+
+ for (Field field : fields) {
+ String[] fieldNames = field.getAnnotation(DataPoint.class).value();
+ if (Arrays.asList(fieldNames).contains(requestedName)) {
+ fieldsWithMatchingNames.add(field);
+ }
+ }
+
+ return fieldsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<Field> getDataPointsFields(ParameterSignature sig) {
+ Collection<Field> fields = super.getDataPointsFields(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<Field> fieldsWithMatchingNames = new ArrayList<Field>();
+
+ for (Field field : fields) {
+ String[] fieldNames = field.getAnnotation(DataPoints.class).value();
+ if (Arrays.asList(fieldNames).contains(requestedName)) {
+ fieldsWithMatchingNames.add(field);
+ }
+ }
+
+ return fieldsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) {
+ Collection<FrameworkMethod> methods = super.getSingleDataPointMethods(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>();
+
+ for (FrameworkMethod method : methods) {
+ String[] methodNames = method.getAnnotation(DataPoint.class).value();
+ if (Arrays.asList(methodNames).contains(requestedName)) {
+ methodsWithMatchingNames.add(method);
+ }
+ }
+
+ return methodsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) {
+ Collection<FrameworkMethod> methods = super.getDataPointsMethods(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>();
+
+ for (FrameworkMethod method : methods) {
+ String[] methodNames = method.getAnnotation(DataPoints.class).value();
+ if (Arrays.asList(methodNames).contains(requestedName)) {
+ methodsWithMatchingNames.add(method);
+ }
+ }
+
+ return methodsWithMatchingNames;
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
index d6ede64..a19f20a 100644
--- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
@@ -1,13 +1,31 @@
package org.junit.experimental.theories.suppliers;
+import static java.lang.annotation.ElementType.PARAMETER;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import org.junit.experimental.theories.ParametersSuppliedBy;
-
+/**
+ * Annotating a {@link org.junit.experimental.theories.Theory Theory} method int
+ * parameter with &#064;TestedOn causes it to be supplied with values from the
+ * ints array given when run as a theory by the
+ * {@link org.junit.experimental.theories.Theories Theories} runner. For
+ * example, the below method would be called three times by the Theories runner,
+ * once with each of the int parameters specified.
+ *
+ * <pre>
+ * &#064;Theory
+ * public void shouldPassForSomeInts(&#064;TestedOn(ints={1, 2, 3}) int param) {
+ * ...
+ * }
+ * </pre>
+ */
@ParametersSuppliedBy(TestedOnSupplier.class)
@Retention(RetentionPolicy.RUNTIME)
+@Target(PARAMETER)
public @interface TestedOn {
- int[] ints();
+ int[] ints();
}
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
index f80298f..dc3d0c9 100644
--- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
@@ -1,23 +1,25 @@
package org.junit.experimental.theories.suppliers;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import org.junit.experimental.theories.ParameterSignature;
import org.junit.experimental.theories.ParameterSupplier;
import org.junit.experimental.theories.PotentialAssignment;
-
-
+/**
+ * @see org.junit.experimental.theories.suppliers.TestedOn
+ * @see org.junit.experimental.theories.ParameterSupplier
+ */
public class TestedOnSupplier extends ParameterSupplier {
- @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
- List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
- TestedOn testedOn = sig.getAnnotation(TestedOn.class);
- int[] ints = testedOn.ints();
- for (final int i : ints) {
- list.add(PotentialAssignment.forValue(Arrays.asList(ints).toString(), i));
- }
- return list;
- }
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
+ TestedOn testedOn = sig.getAnnotation(TestedOn.class);
+ int[] ints = testedOn.ints();
+ for (final int i : ints) {
+ list.add(PotentialAssignment.forValue("ints", i));
+ }
+ return list;
+ }
}