diff options
author | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-01 10:38:08 +1000 |
---|---|---|
committer | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-01 10:38:08 +1000 |
commit | edfce37bc21699042baf14ad6d172d3187fe530c (patch) | |
tree | 7e3379e7a0f2a1c5ba203bea72f468941ca13dad | |
parent | 7e1182355d54f0dc8461ce7df7c4aca8c40d2a92 (diff) |
Add @DataSource, allow printing of RoseTrees, other small changes to generators
-rw-r--r-- | src/main/java/au/id/zancanaro/Generators.java | 25 | ||||
-rw-r--r-- | src/main/java/au/id/zancanaro/Properties.java | 46 | ||||
-rw-r--r-- | src/main/java/au/id/zancanaro/PropertyError.java | 12 | ||||
-rw-r--r-- | src/main/java/au/id/zancanaro/RoseTree.java | 20 | ||||
-rw-r--r-- | src/main/java/au/id/zancanaro/annotations/DataSource.java | 11 | ||||
-rw-r--r-- | src/test/java/au/id/zancanaro/SimpleListOperationsTest.java | 8 |
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); |