jetCheck: re-run top-level generator when deep suchThat condition fails

This commit is contained in:
peter
2017-12-02 11:29:36 +01:00
parent 39ae01fef5
commit d174fa2065
4 changed files with 22 additions and 4 deletions
+3
View File
@@ -26,9 +26,12 @@ public interface DataStructure {
return drawInt(IntDistribution.uniform(0, getSizeHint()));
}
/** Runs the given generator on this data structure and returns the result */
<T> T generate(@NotNull Generator<T> generator);
/** @see Generator#noShrink() */
<T> T generateNonShrinkable(@NotNull Generator<T> generator);
/** @see Generator#suchThat */
<T> T generateConditional(@NotNull Generator<T> generator, @NotNull Predicate<T> condition);
}
+11 -3
View File
@@ -74,10 +74,18 @@ public class Generator<T> {
}
/**
* Skips all generated data that doesn't satisfy the given condition. Useful to avoid infrequent corner cases.
* If the condition fails too often, data generation is stopped prematurely due to inability to produce the data.<p/>
* Attempts to generate the value several times, until one comes out that satisfies the given condition. That value is returned as generator result.
* Results of all previous attempts are discarded. During shrinking, the underlying data structures from those attempts
* won't be used for re-running generation, so be careful that those attempts don't leave any traces of themselves
* (e.g. side effects, even ones internal to an outer generator).<p/>
*
* To eliminate large portions of search space, consider changing the generator instead of using {@code suchThat}.
* If the condition still fails after a large number of attempts, data generation is stopped prematurely and {@link CannotSatisfyCondition} exception is thrown.<p/>
*
* This method is useful to avoid infrequent corner cases (e.g. {@code integers().suchThat(i -> i != 0)}).
* To eliminate large portions of search space, this strategy might prove ineffective
* and result in generator failure due to inability to come up with satisfying examples
* (e.g. {@code integers().suchThat(i -> i > 0 && i <= 10)}
* where the condition would be {@code true} in just 10 of about 4 billion times). In such cases, please consider changing the generator instead of using {@code suchThat}.
*/
public Generator<T> suchThat(@NotNull Predicate<T> condition) {
return from(data -> data.generateConditional(this, condition));
+4 -1
View File
@@ -45,6 +45,9 @@ class Iteration<T> {
try {
value = session.generator.getGeneratorFunction().apply(new GenerativeDataStructure(random, node, sizeHint));
}
catch (CannotSatisfyCondition e) {
continue;
}
catch (Throwable e) {
throw new GeneratorException(this, e);
}
@@ -52,7 +55,7 @@ class Iteration<T> {
return CounterExampleImpl.checkProperty(session.property, value, node);
}
throw new CannotSatisfyCondition(DATA_IS_DIFFERENT);
throw new GeneratorException(this, new CannotSatisfyCondition(DATA_IS_DIFFERENT));
}
String printToReproduce() {
@@ -77,6 +77,10 @@ public class GeneratorTest extends PropertyCheckerTestCase {
PropertyChecker.forAll(integers().suchThat(i -> i < 0)).shouldHold(i -> i < 0);
}
public void testNestedSometimesVeryRareSuchThat() {
forAllStable(frequency(50, constant(0), 1, integers(1, 1000)).suchThat(i -> i > 0)).shouldHold(i -> i > 0);
}
public void testStringOfStringChecksAllChars() {
checkFalsified(stringsOf("abc "),
s -> !s.contains(" "),