summaryrefslogtreecommitdiff
path: root/src/main/java/au/id/zancanaro/javacheck/Generators.java
diff options
context:
space:
mode:
authorCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-03 19:43:14 +1000
committerCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-03 19:43:14 +1000
commite0fc94269698982d937b80ff5fd5b1ef8ef28cf4 (patch)
tree22c335361971363d6856a59ef0c54242f92e74e1 /src/main/java/au/id/zancanaro/javacheck/Generators.java
parent7b1a783b749ab04ab8219ef28f9b1abb0ded6ca4 (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.java147
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);
};
}