diff options
author | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-03 19:43:14 +1000 |
---|---|---|
committer | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-03 19:43:14 +1000 |
commit | e0fc94269698982d937b80ff5fd5b1ef8ef28cf4 (patch) | |
tree | 22c335361971363d6856a59ef0c54242f92e74e1 /src/main/java/au/id/zancanaro/javacheck/Generators.java | |
parent | 7b1a783b749ab04ab8219ef28f9b1abb0ded6ca4 (diff) |
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 ShrinkTree<T>s yourself,
you just generate smaller <T>s, and the generator framework will re-call your
strategy to shrink smaller elements. (So, essentially, ShrinkStrategy.shrink(T
obj) returns an Iterator<T> 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.
Diffstat (limited to 'src/main/java/au/id/zancanaro/javacheck/Generators.java')
-rw-r--r-- | src/main/java/au/id/zancanaro/javacheck/Generators.java | 147 |
1 files changed, 113 insertions, 34 deletions
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 <T> The static type of the returned generator + * @return The generator returned by makeGenerator + */ 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> predicate) { - return (random, size) -> { - ShrinkTree<T> 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 <T> The generator's static type + * @return A new generator which is the previous generator with shrink tree + * removed + */ public static <T> Generator<T> noShrink(Generator<T> 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 <T> Generator<T> oneOf(Generator<T>... gens) { - return integer(0, gens.length).flatMap(index -> gens[index]); + public static <T> Generator<T> oneOf(Generator<? extends T>... gens) { + return integer(0, gens.length).flatMap(index -> gens[index].map(x -> (T) x)); } public static <T> Generator<T> elements(T[] elements) { @@ -48,16 +47,67 @@ public final class Generators { public static Generator<Boolean> 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<Boolean> boolShrinkStrategy() { + return value -> new Iterator<Boolean>() { + 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<Long> 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<Long> longInteger() { + return sized(size -> longInteger(-size, size)); + } + + private static ShrinkStrategy<Long> longShrinkStrategy(final long bound) { + return value -> new Iterator<Long>() { + 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> 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<ShrinkTree<Integer>> intShrinkingIterable(final int value, final int bound) { - return () -> new Iterator<ShrinkTree<Integer>>() { + public static ShrinkStrategy<Integer> intShrinkStrategy(int bound) { + return value -> new Iterator<Integer>() { int curr = value - bound; @Override @@ -79,10 +129,41 @@ public final class Generators { } @Override - public ShrinkTree<Integer> 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<Double> 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<Double> doublePrecision() { + return sized(size -> doublePrecision(-size, size)); + } + + public static ShrinkStrategy<Double> doubleShrinkStrategy(double bound, double epsilon) { + return value -> new Iterator<Double>() { + 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); }; } |