From edfce37bc21699042baf14ad6d172d3187fe530c Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Mon, 1 Jun 2015 10:38:08 +1000 Subject: Add @DataSource, allow printing of RoseTrees, other small changes to generators --- src/main/java/au/id/zancanaro/Generators.java | 25 +++++++++--- src/main/java/au/id/zancanaro/Properties.java | 46 ++++++++++++++++++---- src/main/java/au/id/zancanaro/PropertyError.java | 12 ++++-- src/main/java/au/id/zancanaro/RoseTree.java | 20 +++++++++- .../au/id/zancanaro/annotations/DataSource.java | 11 ++++++ .../au/id/zancanaro/SimpleListOperationsTest.java | 8 ++-- 6 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 src/main/java/au/id/zancanaro/annotations/DataSource.java 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 Generator sized(Function> makeGenerator) { + return (random, size) -> makeGenerator.apply(size).generate(random, size); + } + public static Generator suchThat(Generator gen, Predicate pred) { return (random, size) -> { RoseTree result = gen.generate(random, size); @@ -22,6 +28,13 @@ public class Generators { return integer(0, gens.length).flatMap(index -> gens[index]); } + public static Generator 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(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() { - return (random, size) -> integer(-size, size).generate(random, size); + return sized(size -> integer(-size, size)); } public static Generator natural() { - return (random, size) -> integer(0, size).generate(random, size); + return sized(size -> integer(0, size)); } private static Iterable> intShrinkingIterable(final int value, final int bound) { @@ -57,11 +70,11 @@ public class Generators { } public static Generator> listOf(Generator gen) { - return (random, size) -> integer(0, size).flatMap(count -> { + return (Generator>) sized(size -> { @SuppressWarnings("unchecked") - Generator[] gens = (Generator[]) new Generator[count]; + Generator[] gens = (Generator[]) 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> generators = new HashMap<>(); + private final Map> 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 errors) { super.collectInitializationErrors(errors); - validateGeneratorFields(errors); + Set generated = validateGeneratorFields(errors); + validateTestMethodParameters(errors, generated); } - private void validateGeneratorFields(List errors) { + private Set validateGeneratorFields(List errors) { + Set 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 errors, Set 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> computeGenerators() { + private Map> 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> generators; + private final Map> generators; - public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map> generators) { + public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map> 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[] generators = (Generator[]) 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 generator = Generator.tuple(generators); + @SuppressWarnings("unchecked") + Generator generator = Generator.tuple((Generator[]) 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 { 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 toString) throws IOException { + output.write(toString.apply(this.getValue())); + output.write('['); + for (RoseTree 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 integers = integer(); - - @SuppressWarnings("unused") + @DataSource public static Generator> listOfIntegers = listOf(integer()); - @Property(maxSize = 10000, runs = 10000) + @Property public void sortingIsIdempotent(List list) { List left = new ArrayList<>(list); Collections.sort(left); -- cgit v1.2.3