From e0fc94269698982d937b80ff5fd5b1ef8ef28cf4 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 3 Jun 2015 19:43:14 +1000 Subject: Change shrinking a bit, add more generators, fix some types, moved suchThat Shrinking is now done using a "ShrinkStrategy". It's pretty similar to what it used to be in the end, but instead of generating new ShrinkTrees yourself, you just generate smaller s, and the generator framework will re-call your strategy to shrink smaller elements. (So, essentially, ShrinkStrategy.shrink(T obj) returns an Iterator which then has smaller trees calculated from it.) Added some more generators. In particular: longs and doubles. Fixed some types, so now Generator.tuple(integer(), string()) will work. Yay! Move suchThat to Generator, so now integer().suchThat(x -> x < 10) will work instead of the old Generators.suchThat(x -> x < 10, integer()), which felt a bit weird. --- .../java/au/id/zancanaro/javacheck/Generators.java | 147 ++++++++++++++++----- 1 file changed, 113 insertions(+), 34 deletions(-) (limited to 'src/main/java/au/id/zancanaro/javacheck/Generators.java') diff --git a/src/main/java/au/id/zancanaro/javacheck/Generators.java b/src/main/java/au/id/zancanaro/javacheck/Generators.java index 5dee924..e5699ac 100644 --- a/src/main/java/au/id/zancanaro/javacheck/Generators.java +++ b/src/main/java/au/id/zancanaro/javacheck/Generators.java @@ -1,41 +1,40 @@ package au.id.zancanaro.javacheck; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.function.Function; -import java.util.function.Predicate; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "WeakerAccess"}) public final class Generators { private Generators() { } + /** + * Create a generator which explicitly depends on the current "size" + * parameter. + * + * @param makeGenerator A function from size to a generator + * @param The static type of the returned generator + * @return The generator returned by makeGenerator + */ public static Generator sized(Function> makeGenerator) { return (random, size) -> makeGenerator.apply(size).generate(random, size); } - public static Generator suchThat(Generator gen, Predicate predicate) { - return (random, size) -> { - ShrinkTree result = gen.generate(random, size); - if (predicate.test(result.getValue())) { - return result.filter(predicate); - } else { - return suchThat(gen, predicate).generate(random, size); - } - }; - } - + /** + * Remove a generator's shrink tree. + * + * @param gen The generator + * @param The generator's static type + * @return A new generator which is the previous generator with shrink tree + * removed + */ public static Generator noShrink(Generator gen) { - return (random, size) -> new ShrinkTree<>( - gen.generate(random, size).getValue(), - Collections.emptyList()); + return (random, size) -> ShrinkTree.pure(gen.generate(random, size).getValue()); } @SafeVarargs - public static Generator oneOf(Generator... gens) { - return integer(0, gens.length).flatMap(index -> gens[index]); + public static Generator oneOf(Generator... gens) { + return integer(0, gens.length).flatMap(index -> gens[index].map(x -> (T) x)); } public static Generator elements(T[] elements) { @@ -48,16 +47,67 @@ public final class Generators { public static Generator bool() { return (random, size) -> - random.nextBoolean() ? - new ShrinkTree<>(true, Collections.singletonList(new ShrinkTree<>(false, Collections.emptyList()))) : - new ShrinkTree<>(false, Collections.emptyList()); + ShrinkTree.pure(random.nextBoolean()) + .withShrinkStrategy(boolShrinkStrategy()); + } + + private static ShrinkStrategy boolShrinkStrategy() { + return value -> new Iterator() { + boolean hasMore = value; + + @Override + public boolean hasNext() { + return hasMore; + } + + @Override + public Boolean next() { + if (hasMore) { + return (hasMore = false); + } else { + throw new NoSuchElementException("Boolean shrink tree exhausted"); + } + } + }; + } + + public static Generator longInteger(long lower, long upper) { + return (random, size) -> { + long value = random.longs(lower, upper).findFirst().getAsLong(); + long bound = lower > 0 ? lower : (upper < 0 ? upper : 0); + return ShrinkTree.pure(value) + .withShrinkStrategy(longShrinkStrategy(bound)); + }; + } + + public static Generator longInteger() { + return sized(size -> longInteger(-size, size)); + } + + private static ShrinkStrategy longShrinkStrategy(final long bound) { + return value -> new Iterator() { + long curr = value - bound; + + @Override + public boolean hasNext() { + return curr != 0; + } + + @Override + public Long next() { + long prevCurr = curr; + curr = curr / 2; + return value - prevCurr; + } + }; } public static Generator integer(int lower, int upper) { return (random, size) -> { - int value = lower + random.nextInt(upper - lower); + int value = random.ints(lower, upper).findFirst().getAsInt(); int bound = lower > 0 ? lower : (upper < 0 ? upper : 0); - return new ShrinkTree<>(value, intShrinkingIterable(value, bound)); + return ShrinkTree.pure(value) + .withShrinkStrategy(intShrinkStrategy(bound)); }; } @@ -69,8 +119,8 @@ public final class Generators { return sized(size -> integer(0, size)); } - private static Iterable> intShrinkingIterable(final int value, final int bound) { - return () -> new Iterator>() { + public static ShrinkStrategy intShrinkStrategy(int bound) { + return value -> new Iterator() { int curr = value - bound; @Override @@ -79,10 +129,41 @@ public final class Generators { } @Override - public ShrinkTree next() { + public Integer next() { int prevCurr = curr; curr = curr / 2; - return new ShrinkTree<>(value - prevCurr, intShrinkingIterable(value - prevCurr, bound)); + return value - prevCurr; + } + }; + } + + public static Generator doublePrecision(double lower, double upper) { + return (random, size) -> { + double value = random.doubles(lower, upper).findFirst().getAsDouble(); + double bound = lower > 0 ? lower : (upper < 0 ? upper : 0); + return ShrinkTree.pure(value) + .withShrinkStrategy(doubleShrinkStrategy(bound, Double.MIN_NORMAL /* maybe pick a bigger epsilon? */)); + }; + } + + public static Generator doublePrecision() { + return sized(size -> doublePrecision(-size, size)); + } + + public static ShrinkStrategy doubleShrinkStrategy(double bound, double epsilon) { + return value -> new Iterator() { + double curr = value - bound; + + @Override + public boolean hasNext() { + return Math.abs(curr) > epsilon; + } + + @Override + public Double next() { + double prevCurr = curr; + curr = curr / 2; + return value - prevCurr; } }; } @@ -93,9 +174,7 @@ public final class Generators { int count = countGen.generate(random, size).getValue(); return Generator.list(count, gen) .generate(random, size) - .filter(list -> - minElements <= list.size() - && list.size() < maxElements) + .filter(list -> minElements <= list.size() && list.size() < maxElements) .map(Collections::unmodifiableList); }; } -- cgit v1.2.3