Skip to content

Commit dbe8a97

Browse files
author
David Saff
committed
Merge pull request #621 from pimterry/named-datapoints-#65
Added named datapoint(s) support to theories, fixing #65.
2 parents 63acecd + 45524a9 commit dbe8a97

19 files changed

+742
-63
lines changed

Diff for: src/main/java/org/junit/experimental/theories/DataPoint.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
@Retention(RetentionPolicy.RUNTIME)
1111
@Target({FIELD, METHOD})
1212
public @interface DataPoint {
13-
13+
String[] value() default {};
1414
}

Diff for: src/main/java/org/junit/experimental/theories/DataPoints.java

+45-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,52 @@
77
import java.lang.annotation.RetentionPolicy;
88
import java.lang.annotation.Target;
99

10+
/**
11+
* <p>
12+
* Annotating an array-typed field or method with &#064;DataPoints will cause
13+
* the values in the array (or returned array) to be used as potential
14+
* parameters for theories in that class, when run with the
15+
* {@link org.junit.experimental.theories.Theories Theories} runner.
16+
* </p>
17+
* <p>
18+
* DataPoints will only be considered as potential values for parameters for
19+
* which their types are assignable. When multiple sets of DataPoints exist with
20+
* overlapping types more control can be obtained by naming the DataPoints using
21+
* the value of this annotation, e.g. with
22+
* <code>&#064;DataPoints({"dataset1", "dataset2"})</code>, and then specifying
23+
* which named set to consider as potential values for each parameter using the
24+
* {@link org.junit.experimental.theories.FromDataPoints &#064;FromDataPoints}
25+
* annotation.
26+
* </p>
27+
* <p>
28+
* Parameters with no specified source (i.e. without &#064;FromDataPoints or
29+
* other {@link org.junit.experimental.theories.ParametersSuppliedBy
30+
* &#064;ParameterSuppliedBy} annotations) will use all DataPoints that are
31+
* assignable to the parameter type as potential values, including named sets of
32+
* DataPoints.
33+
* </p>
34+
*
35+
* <pre>
36+
* &#064;DataPoints
37+
* public static String[] dataPoints = new String[] { ... };
38+
*
39+
* &#064;DataPoints
40+
* public static String[] generatedDataPoints() {
41+
* return new String[] { ... };
42+
* }
43+
*
44+
* &#064;Theory
45+
* public void theoryMethod(String param) {
46+
* ...
47+
* }</pre>
48+
*
49+
* @see org.junit.experimental.theories.Theories
50+
* @see org.junit.experimental.theories.Theory
51+
* @see org.junit.experimental.theories.DataPoint
52+
* @see org.junit.experimental.theories.FromDataPoints
53+
*/
1054
@Retention(RetentionPolicy.RUNTIME)
1155
@Target({FIELD, METHOD})
1256
public @interface DataPoints {
13-
57+
String[] value() default {};
1458
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.junit.experimental.theories;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import org.junit.experimental.theories.internal.SpecificDataPointsSupplier;
9+
10+
/**
11+
* <p>
12+
* Annotating a parameter of a {@link org.junit.experimental.theories.Theory
13+
* &#064Theory} method with <code>&#064;FromDataPoints</code> will limit the
14+
* datapoints considered as potential values for that parameter to just the
15+
* {@link org.junit.experimental.theories.DataPoints DataPoints} with the given
16+
* name. DataPoint names can be given as the value parameter of the
17+
* &#064DataPoints annotation.
18+
* </p>
19+
* <p>
20+
* DataPoints without names will not be considered as values for any parameters
21+
* annotated with &#064FromDataPoints.
22+
* </p>
23+
*
24+
* <pre>
25+
* &#064;DataPoints
26+
* public static String[] unnamed = new String[] { ... };
27+
*
28+
* &#064;DataPoints("regexes")
29+
* public static String[] regexStrings = new String[] { ... };
30+
*
31+
* &#064;DataPoints({"forMatching", "alphanumeric"})
32+
* public static String[] testStrings = new String[] { ... };
33+
*
34+
* &#064;Theory
35+
* public void stringTheory(String param) {
36+
* // This will be called with every value in 'regexStrings',
37+
* // 'testStrings' and 'unnamed'.
38+
* }
39+
*
40+
* &#064;Theory
41+
* public void regexTheory(&#064;FromDataPoints("regexes") String regex,
42+
* &#064;FromDataPoints("forMatching") String value) {
43+
* // This will be called with only the values in 'regexStrings' as
44+
* // regex, only the values in 'testStrings' as value, and none
45+
* // of the values in 'unnamed'.
46+
* }
47+
* </pre>
48+
*
49+
* @see org.junit.experimental.theories.Theory
50+
* @see org.junit.experimental.theories.DataPoint
51+
* @see org.junit.experimental.theories.DataPoints
52+
*/
53+
@Retention(RetentionPolicy.RUNTIME)
54+
@Target(ElementType.PARAMETER)
55+
@ParametersSuppliedBy(SpecificDataPointsSupplier.class)
56+
public @interface FromDataPoints {
57+
String value();
58+
}

Diff for: src/main/java/org/junit/experimental/theories/Theories.java

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.junit.experimental.theories;
22

3+
import java.lang.reflect.Constructor;
34
import java.lang.reflect.Field;
45
import java.lang.reflect.InvocationTargetException;
56
import java.lang.reflect.Method;
@@ -76,6 +77,28 @@ protected void validateTestMethods(List<Throwable> errors) {
7677
} else {
7778
each.validatePublicVoidNoArg(false, errors);
7879
}
80+
81+
for (ParameterSignature signature : each.getParameterSignatures()) {
82+
ParametersSuppliedBy annotation = signature.findDeepAnnotation(ParametersSuppliedBy.class);
83+
if (annotation != null) {
84+
validateParameterSupplier(annotation.value(), errors);
85+
}
86+
}
87+
}
88+
}
89+
90+
private void validateParameterSupplier(Class<? extends ParameterSupplier> supplierClass, List<Throwable> errors) {
91+
Constructor<?>[] constructors = supplierClass.getConstructors();
92+
93+
if (constructors.length != 1) {
94+
errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
95+
" must have only one constructor (either empty or taking only a TestClass)"));
96+
} else {
97+
Class<?>[] paramTypes = constructors[0].getParameterTypes();
98+
if (!(paramTypes.length == 0) && !paramTypes[0].equals(TestClass.class)) {
99+
errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
100+
" constructor must take either nothing or a single TestClass instance"));
101+
}
79102
}
80103
}
81104

Diff for: src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java

+58-24
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
import java.lang.reflect.Array;
44
import java.lang.reflect.Field;
5-
import java.lang.reflect.Modifier;
65
import java.util.ArrayList;
6+
import java.util.Collection;
77
import java.util.List;
88

99
import org.junit.experimental.theories.DataPoint;
1010
import org.junit.experimental.theories.DataPoints;
1111
import org.junit.experimental.theories.ParameterSignature;
1212
import org.junit.experimental.theories.ParameterSupplier;
1313
import org.junit.experimental.theories.PotentialAssignment;
14+
import org.junit.runners.model.FrameworkField;
1415
import org.junit.runners.model.FrameworkMethod;
1516
import org.junit.runners.model.TestClass;
1617

@@ -45,8 +46,8 @@ public Object getValue() throws CouldNotGenerateValueException {
4546
public String getDescription() throws CouldNotGenerateValueException {
4647
return fMethod.getName();
4748
}
48-
}
49-
49+
}
50+
5051
private final TestClass fClass;
5152

5253
/**
@@ -60,16 +61,16 @@ public AllMembersSupplier(TestClass type) {
6061
public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
6162
List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
6263

63-
addFields(sig, list);
64+
addSinglePointFields(sig, list);
65+
addMultiPointFields(sig, list);
6466
addSinglePointMethods(sig, list);
6567
addMultiPointMethods(sig, list);
6668

6769
return list;
6870
}
6971

7072
private void addMultiPointMethods(ParameterSignature sig, List<PotentialAssignment> list) {
71-
for (FrameworkMethod dataPointsMethod : fClass
72-
.getAnnotatedMethods(DataPoints.class)) {
73+
for (FrameworkMethod dataPointsMethod : getDataPointsMethods(sig)) {
7374
try {
7475
addMultiPointArrayValues(sig, dataPointsMethod.getName(), list, dataPointsMethod.invokeExplosively(null));
7576
} catch (Throwable e) {
@@ -80,33 +81,35 @@ private void addMultiPointMethods(ParameterSignature sig, List<PotentialAssignme
8081

8182
private void addSinglePointMethods(ParameterSignature sig,
8283
List<PotentialAssignment> list) {
83-
for (FrameworkMethod dataPointMethod : fClass
84-
.getAnnotatedMethods(DataPoint.class)) {
84+
for (FrameworkMethod dataPointMethod : getSingleDataPointMethods(sig)) {
8585
if (sig.canAcceptType(dataPointMethod.getType())) {
8686
list.add(new MethodParameterValue(dataPointMethod));
8787
}
8888
}
8989
}
90-
91-
private void addFields(ParameterSignature sig,
90+
91+
private void addMultiPointFields(ParameterSignature sig,
9292
List<PotentialAssignment> list) {
93-
for (final Field field : fClass.getJavaClass().getFields()) {
94-
if (Modifier.isStatic(field.getModifiers())) {
95-
Class<?> type = field.getType();
96-
if (sig.canAcceptArrayType(type)
97-
&& field.getAnnotation(DataPoints.class) != null) {
98-
try {
99-
addArrayValues(field.getName(), list, getStaticFieldValue(field));
100-
} catch (Throwable e) {
101-
// ignore and move on
102-
}
103-
} else if (sig.canAcceptType(type)
104-
&& field.getAnnotation(DataPoint.class) != null) {
105-
list.add(PotentialAssignment
106-
.forValue(field.getName(), getStaticFieldValue(field)));
93+
for (final Field field : getDataPointsFields(sig)) {
94+
Class<?> type = field.getType();
95+
if (sig.canAcceptArrayType(type)) {
96+
try {
97+
addArrayValues(field.getName(), list, getStaticFieldValue(field));
98+
} catch (Throwable e) {
99+
// ignore and move on
107100
}
108101
}
109102
}
103+
}
104+
105+
private void addSinglePointFields(ParameterSignature sig,
106+
List<PotentialAssignment> list) {
107+
for (final Field field : getSingleDataPointFields(sig)) {
108+
Class<?> type = field.getType();
109+
if (sig.canAcceptType(type)) {
110+
list.add(PotentialAssignment.forValue(field.getName(), getStaticFieldValue(field)));
111+
}
112+
}
110113
}
111114

112115
private void addArrayValues(String name, List<PotentialAssignment> list, Object array) {
@@ -136,4 +139,35 @@ private Object getStaticFieldValue(final Field field) {
136139
"unexpected: getFields returned an inaccessible field");
137140
}
138141
}
142+
143+
protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) {
144+
return fClass.getAnnotatedMethods(DataPoints.class);
145+
}
146+
147+
protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) {
148+
List<FrameworkField> fields = fClass.getAnnotatedFields(DataPoint.class);
149+
Collection<Field> validFields = new ArrayList<Field>();
150+
151+
for (FrameworkField frameworkField : fields) {
152+
validFields.add(frameworkField.getField());
153+
}
154+
155+
return validFields;
156+
}
157+
158+
protected Collection<Field> getDataPointsFields(ParameterSignature sig) {
159+
List<FrameworkField> fields = fClass.getAnnotatedFields(DataPoints.class);
160+
Collection<Field> validFields = new ArrayList<Field>();
161+
162+
for (FrameworkField frameworkField : fields) {
163+
validFields.add(frameworkField.getField());
164+
}
165+
166+
return validFields;
167+
}
168+
169+
protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) {
170+
return fClass.getAnnotatedMethods(DataPoint.class);
171+
}
172+
139173
}

Diff for: src/main/java/org/junit/experimental/theories/internal/Assignments.java

+25-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.junit.experimental.theories.internal;
22

3+
import java.lang.reflect.Constructor;
34
import java.lang.reflect.Method;
45
import java.util.ArrayList;
56
import java.util.List;
@@ -56,8 +57,8 @@ public Assignments assignNext(PotentialAssignment source) {
5657
fAssigned);
5758
assigned.add(source);
5859

59-
return new Assignments(assigned, fUnassigned.subList(1, fUnassigned
60-
.size()), fClass);
60+
return new Assignments(assigned, fUnassigned.subList(1,
61+
fUnassigned.size()), fClass);
6162
}
6263

6364
public Object[] getActualValues(int start, int stop, boolean nullsOk)
@@ -74,29 +75,36 @@ public Object[] getActualValues(int start, int stop, boolean nullsOk)
7475
}
7576

7677
public List<PotentialAssignment> potentialsForNextUnassigned()
77-
throws InstantiationException, IllegalAccessException {
78+
throws Exception {
7879
ParameterSignature unassigned = nextUnassigned();
7980
return getSupplier(unassigned).getValueSources(unassigned);
8081
}
8182

82-
public ParameterSupplier getSupplier(ParameterSignature unassigned)
83-
throws InstantiationException, IllegalAccessException {
84-
ParameterSupplier supplier = getAnnotatedSupplier(unassigned);
85-
if (supplier != null) {
86-
return supplier;
87-
}
83+
private ParameterSupplier getSupplier(ParameterSignature unassigned)
84+
throws Exception {
85+
ParametersSuppliedBy annotation = unassigned
86+
.findDeepAnnotation(ParametersSuppliedBy.class);
8887

89-
return new AllMembersSupplier(fClass);
88+
if (annotation != null) {
89+
return buildParameterSupplierFromClass(annotation.value());
90+
} else {
91+
return new AllMembersSupplier(fClass);
92+
}
9093
}
9194

92-
public ParameterSupplier getAnnotatedSupplier(ParameterSignature unassigned)
93-
throws InstantiationException, IllegalAccessException {
94-
ParametersSuppliedBy annotation = unassigned
95-
.findDeepAnnotation(ParametersSuppliedBy.class);
96-
if (annotation == null) {
97-
return null;
95+
private ParameterSupplier buildParameterSupplierFromClass(
96+
Class<? extends ParameterSupplier> cls) throws Exception {
97+
Constructor<?>[] supplierConstructors = cls.getConstructors();
98+
99+
for (Constructor<?> constructor : supplierConstructors) {
100+
Class<?>[] parameterTypes = constructor.getParameterTypes();
101+
if (parameterTypes.length == 1
102+
&& parameterTypes[0].equals(TestClass.class)) {
103+
return (ParameterSupplier) constructor.newInstance(fClass);
104+
}
98105
}
99-
return annotation.value().newInstance();
106+
107+
return cls.newInstance();
100108
}
101109

102110
public Object[] getConstructorArguments(boolean nullsOk)

0 commit comments

Comments
 (0)