summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-01 10:38:08 +1000
committerCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-01 10:38:08 +1000
commitedfce37bc21699042baf14ad6d172d3187fe530c (patch)
tree7e3379e7a0f2a1c5ba203bea72f468941ca13dad
parent7e1182355d54f0dc8461ce7df7c4aca8c40d2a92 (diff)
Add @DataSource, allow printing of RoseTrees, other small changes to generators
-rw-r--r--src/main/java/au/id/zancanaro/Generators.java25
-rw-r--r--src/main/java/au/id/zancanaro/Properties.java46
-rw-r--r--src/main/java/au/id/zancanaro/PropertyError.java12
-rw-r--r--src/main/java/au/id/zancanaro/RoseTree.java20
-rw-r--r--src/main/java/au/id/zancanaro/annotations/DataSource.java11
-rw-r--r--src/test/java/au/id/zancanaro/SimpleListOperationsTest.java8
6 files changed, 97 insertions, 25 deletions
diff --git a/src/main/java/au/id/zancanaro/Generators.java b/src/main/java/au/id/zancanaro/Generators.java
index e0927ff..01ab64a 100644
--- a/src/main/java/au/id/zancanaro/Generators.java
+++ b/src/main/java/au/id/zancanaro/Generators.java
@@ -1,11 +1,17 @@
package au.id.zancanaro;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Predicate;
public class Generators {
+ public static <T> Generator<T> sized(Function<Integer, Generator<T>> makeGenerator) {
+ return (random, size) -> makeGenerator.apply(size).generate(random, size);
+ }
+
public static <T> Generator<T> suchThat(Generator<T> gen, Predicate<T> pred) {
return (random, size) -> {
RoseTree<T> result = gen.generate(random, size);
@@ -22,6 +28,13 @@ public class Generators {
return integer(0, gens.length).flatMap(index -> gens[index]);
}
+ public static Generator<Boolean> bool() {
+ return (random, size) ->
+ random.nextBoolean() ?
+ new RoseTree<>(true, Collections.singletonList(new RoseTree<>(false, Collections.emptyList()))) :
+ new RoseTree<>(false, Collections.emptyList());
+ }
+
public static Generator<Integer> integer(int lower, int upper) {
return (random, size) -> {
int value = lower + random.nextInt(upper - lower);
@@ -31,11 +44,11 @@ public class Generators {
}
public static Generator<Integer> integer() {
- return (random, size) -> integer(-size, size).generate(random, size);
+ return sized(size -> integer(-size, size));
}
public static Generator<Integer> natural() {
- return (random, size) -> integer(0, size).generate(random, size);
+ return sized(size -> integer(0, size));
}
private static Iterable<RoseTree<Integer>> intShrinkingIterable(final int value, final int bound) {
@@ -57,11 +70,11 @@ public class Generators {
}
public static <T> Generator<List<T>> listOf(Generator<T> gen) {
- return (random, size) -> integer(0, size).flatMap(count -> {
+ return (Generator<List<T>>) sized(size -> {
@SuppressWarnings("unchecked")
- Generator<T>[] gens = (Generator<T>[]) new Generator[count];
+ Generator<T>[] gens = (Generator<T>[]) new Generator[size];
Arrays.fill(gens, gen);
- return Generator.tuple(gens).fmap(Arrays::asList);
- }).generate(random, size);
+ return Generator.tuple(gens).fmap(Arrays::asList).fmap(Collections::unmodifiableList);
+ });
}
}
diff --git a/src/main/java/au/id/zancanaro/Properties.java b/src/main/java/au/id/zancanaro/Properties.java
index fab48f4..b5a0649 100644
--- a/src/main/java/au/id/zancanaro/Properties.java
+++ b/src/main/java/au/id/zancanaro/Properties.java
@@ -1,5 +1,6 @@
package au.id.zancanaro;
+import au.id.zancanaro.annotations.DataSource;
import au.id.zancanaro.annotations.Property;
import au.id.zancanaro.annotations.Seed;
import org.junit.AssumptionViolatedException;
@@ -14,7 +15,7 @@ import java.util.*;
public class Properties extends BlockJUnit4ClassRunner {
- private final Map<Type, Generator<Object>> generators = new HashMap<>();
+ private final Map<Type, Generator<?>> generators = new HashMap<>();
public Properties(Class<?> klass) throws InitializationError {
super(klass);
@@ -23,13 +24,18 @@ public class Properties extends BlockJUnit4ClassRunner {
@Override
protected void collectInitializationErrors(List<Throwable> errors) {
super.collectInitializationErrors(errors);
- validateGeneratorFields(errors);
+ Set<Type> generated = validateGeneratorFields(errors);
+ validateTestMethodParameters(errors, generated);
}
- private void validateGeneratorFields(List<Throwable> errors) {
+ private Set<Type> validateGeneratorFields(List<Throwable> errors) {
+ Set<Type> result = new HashSet<>();
Field[] fields = getTestClass().getJavaClass().getDeclaredFields();
for (Field field : fields) {
+ if (!field.isAnnotationPresent(DataSource.class)) {
+ continue;
+ }
Type type = field.getGenericType();
if (!(type instanceof ParameterizedType)) {
continue;
@@ -42,11 +48,29 @@ public class Properties extends BlockJUnit4ClassRunner {
if (c != Generator.class) {
continue;
}
+ boolean error = false;
if (!Modifier.isStatic(field.getModifiers())) {
errors.add(new Error("Generator field " + field.getName() + " must be static"));
+ error = true;
}
if (!Modifier.isPublic(field.getModifiers())) {
errors.add(new Error("Generator field " + field.getName() + " must be public"));
+ error = true;
+ }
+ if (!error) {
+ result.add(ptype.getActualTypeArguments()[0]);
+ }
+ }
+ return result;
+ }
+
+ private void validateTestMethodParameters(List<Throwable> errors, Set<Type> generated) {
+ for (FrameworkMethod each : computeTestMethods()) {
+ for (Type type : each.getMethod().getGenericParameterTypes()) {
+ if (!generated.contains(type)) {
+ errors.add(new Error("No @DataSource for type: " + type));
+ generated.add(type); // ignore future errors on this type
+ }
}
}
}
@@ -66,11 +90,14 @@ public class Properties extends BlockJUnit4ClassRunner {
rawTypes = Collections.unmodifiableMap(types);
}
- private Map<Type, Generator<Object>> computeGenerators() {
+ private Map<Type, Generator<?>> computeGenerators() {
if (generators.isEmpty()) {
Field[] fields = getTestClass().getJavaClass().getDeclaredFields();
for (Field field : fields) {
+ if (!field.isAnnotationPresent(DataSource.class)) {
+ continue;
+ }
Type type = field.getGenericType();
if (!(type instanceof ParameterizedType)) {
continue;
@@ -133,9 +160,9 @@ public class Properties extends BlockJUnit4ClassRunner {
public static class GenerativeTester extends Statement {
private final FrameworkMethod testMethod;
private final TestClass testClass;
- private final Map<Type, Generator<Object>> generators;
+ private final Map<Type, Generator<?>> generators;
- public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map<Type, Generator<Object>> generators) {
+ public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map<Type, Generator<?>> generators) {
this.testMethod = testMethod;
this.testClass = testClass;
this.generators = generators;
@@ -157,12 +184,14 @@ public class Properties extends BlockJUnit4ClassRunner {
runTest(new Object[0]);
} else {
@SuppressWarnings("unchecked")
- Generator<Object>[] generators = (Generator<Object>[]) new Generator[method.getParameterCount()];
+ Generator<?>[] generators = (Generator<?>[]) new Generator[method.getParameterCount()];
int index = 0;
for (Type type : method.getGenericParameterTypes()) {
+ // TODO: validate ahead of time that this generator will exist (ideally in the constructor validation time)
generators[index++] = this.generators.get(type);
}
- Generator<Object[]> generator = Generator.tuple(generators);
+ @SuppressWarnings("unchecked")
+ Generator<Object[]> generator = Generator.tuple((Generator<Object>[]) generators);
long seed = getSeed(method);
Random random = new Random(seed);
@@ -184,6 +213,7 @@ public class Properties extends BlockJUnit4ClassRunner {
}
;
} catch (Throwable ex) {
+// tree.print(new OutputStreamWriter(System.out), Arrays::toString);
throw new PropertyError(method.getName(), seed, shrink(tree, ex));
}
}
diff --git a/src/main/java/au/id/zancanaro/PropertyError.java b/src/main/java/au/id/zancanaro/PropertyError.java
index b2fb589..a0ee6df 100644
--- a/src/main/java/au/id/zancanaro/PropertyError.java
+++ b/src/main/java/au/id/zancanaro/PropertyError.java
@@ -6,10 +6,14 @@ import java.util.Iterator;
public class PropertyError extends AssertionError {
public PropertyError(String methodName, long seed, ShrinkResult shrunk) {
- super(String.format("%s(%s)\n\tSeed: %s\n%s",
- methodName, join(", ", shrunk.args),
- seed,
- shrunk.thrown.getMessage()));
+ super(shrunk.thrown.getMessage() == null ?
+ String.format("%s(%s)\n\tSeed: %s",
+ methodName, join(", ", shrunk.args),
+ seed):
+ String.format("%s(%s)\n\tSeed: %s\n%s",
+ methodName, join(", ", shrunk.args),
+ seed,
+ shrunk.thrown.getMessage()));
initCause(shrunk.thrown);
}
diff --git a/src/main/java/au/id/zancanaro/RoseTree.java b/src/main/java/au/id/zancanaro/RoseTree.java
index 6ba5c1c..458d441 100644
--- a/src/main/java/au/id/zancanaro/RoseTree.java
+++ b/src/main/java/au/id/zancanaro/RoseTree.java
@@ -1,7 +1,7 @@
package au.id.zancanaro;
-import java.io.OutputStream;
-import java.util.Arrays;
+import java.io.IOException;
+import java.io.Writer;
import java.util.Collections;
import java.util.Iterator;
import java.util.function.Function;
@@ -84,4 +84,20 @@ public class RoseTree<T> {
throw new IllegalArgumentException("Current value doesn't match predicate: whoops!");
}
}
+
+ @SuppressWarnings("unused")
+ public void print(Writer output) throws IOException {
+ print(output, Object::toString);
+ }
+
+ @SuppressWarnings("unused")
+ public void print(Writer output, Function<T, String> toString) throws IOException {
+ output.write(toString.apply(this.getValue()));
+ output.write('[');
+ for (RoseTree<T> child : children) {
+ child.print(output, toString);
+ }
+ output.write(']');
+ output.flush();
+ }
}
diff --git a/src/main/java/au/id/zancanaro/annotations/DataSource.java b/src/main/java/au/id/zancanaro/annotations/DataSource.java
new file mode 100644
index 0000000..9fe255b
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/annotations/DataSource.java
@@ -0,0 +1,11 @@
+package au.id.zancanaro.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataSource {
+}
diff --git a/src/test/java/au/id/zancanaro/SimpleListOperationsTest.java b/src/test/java/au/id/zancanaro/SimpleListOperationsTest.java
index 2ff44e7..20f419a 100644
--- a/src/test/java/au/id/zancanaro/SimpleListOperationsTest.java
+++ b/src/test/java/au/id/zancanaro/SimpleListOperationsTest.java
@@ -1,5 +1,6 @@
package au.id.zancanaro;
+import au.id.zancanaro.annotations.DataSource;
import au.id.zancanaro.annotations.Property;
import org.junit.runner.RunWith;
@@ -14,13 +15,10 @@ import static org.junit.Assert.assertEquals;
@RunWith(Properties.class)
public class SimpleListOperationsTest {
- @SuppressWarnings("unused")
- public static Generator<Integer> integers = integer();
-
- @SuppressWarnings("unused")
+ @DataSource
public static Generator<List<Integer>> listOfIntegers = listOf(integer());
- @Property(maxSize = 10000, runs = 10000)
+ @Property
public void sortingIsIdempotent(List<Integer> list) {
List<Integer> left = new ArrayList<>(list);
Collections.sort(left);