diff --git a/jetCheck/src/jetCheck/DataStructure.java b/jetCheck/src/jetCheck/DataStructure.java index 30aad21b8248..61b7cd62bf8d 100644 --- a/jetCheck/src/jetCheck/DataStructure.java +++ b/jetCheck/src/jetCheck/DataStructure.java @@ -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 generate(@NotNull Generator generator); + /** @see Generator#noShrink() */ T generateNonShrinkable(@NotNull Generator generator); + /** @see Generator#suchThat */ T generateConditional(@NotNull Generator generator, @NotNull Predicate condition); } diff --git a/jetCheck/src/jetCheck/Generator.java b/jetCheck/src/jetCheck/Generator.java index 2e8bc41c314c..0fcc6697e2a4 100644 --- a/jetCheck/src/jetCheck/Generator.java +++ b/jetCheck/src/jetCheck/Generator.java @@ -74,10 +74,18 @@ public class Generator { } /** - * 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.

+ * 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).

* - * 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.

+ * + * 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 suchThat(@NotNull Predicate condition) { return from(data -> data.generateConditional(this, condition)); diff --git a/jetCheck/src/jetCheck/Iteration.java b/jetCheck/src/jetCheck/Iteration.java index 1c8b0156f90e..f137af981c26 100644 --- a/jetCheck/src/jetCheck/Iteration.java +++ b/jetCheck/src/jetCheck/Iteration.java @@ -45,6 +45,9 @@ class Iteration { 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 { return CounterExampleImpl.checkProperty(session.property, value, node); } - throw new CannotSatisfyCondition(DATA_IS_DIFFERENT); + throw new GeneratorException(this, new CannotSatisfyCondition(DATA_IS_DIFFERENT)); } String printToReproduce() { diff --git a/jetCheck/test/jetCheck/GeneratorTest.java b/jetCheck/test/jetCheck/GeneratorTest.java index 1abe4892a61b..0370adba459b 100644 --- a/jetCheck/test/jetCheck/GeneratorTest.java +++ b/jetCheck/test/jetCheck/GeneratorTest.java @@ -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(" "),