diff --git a/slowCheck/src/slowCheck/CounterExampleImpl.java b/slowCheck/src/slowCheck/CounterExampleImpl.java new file mode 100644 index 000000000000..71a6ed268720 --- /dev/null +++ b/slowCheck/src/slowCheck/CounterExampleImpl.java @@ -0,0 +1,41 @@ +package slowCheck; + +import org.jetbrains.annotations.Nullable; + +import java.util.function.Predicate; + +class CounterExampleImpl implements PropertyFailure.CounterExample { + final StructureNode data; + private final T value; + @Nullable private final Throwable exception; + + private CounterExampleImpl(StructureNode data, T value, @Nullable Throwable exception) { + this.data = data; + this.value = value; + this.exception = exception; + } + + @Override + public T getExampleValue() { + return value; + } + + @Nullable + @Override + public Throwable getExceptionCause() { + return exception; + } + + static CounterExampleImpl checkProperty(Predicate property, T value, StructureNode node) { + try { + if (!property.test(value)) { + return new CounterExampleImpl<>(node, value, null); + } + } + catch (Throwable e) { + return new CounterExampleImpl<>(node, value, e); + } + return null; + } + +} \ No newline at end of file diff --git a/slowCheck/src/slowCheck/DataStructure.java b/slowCheck/src/slowCheck/DataStructure.java index 22568f204285..163e5aacd578 100644 --- a/slowCheck/src/slowCheck/DataStructure.java +++ b/slowCheck/src/slowCheck/DataStructure.java @@ -14,7 +14,12 @@ public interface DataStructure { } int drawInt(@NotNull IntDistribution distribution); - + + /** + * @return a non-negative number used by various generators to guide the sizes of structures (e.g. collections) they create. + * The sizes need not be exactly equal to this hint, but in average bigger hints should in average correspond to bigger structures. When generators invoke other generators using {@link #subStructure}, the size hint of the sub-structure is + * generally less than the parent's one. + */ int getSizeHint(); default int suggestCollectionSize() { diff --git a/slowCheck/src/slowCheck/GeneratorException.java b/slowCheck/src/slowCheck/GeneratorException.java index 6cc9ea143a50..4e5c289a9a30 100644 --- a/slowCheck/src/slowCheck/GeneratorException.java +++ b/slowCheck/src/slowCheck/GeneratorException.java @@ -5,7 +5,7 @@ package slowCheck; */ public class GeneratorException extends RuntimeException { - GeneratorException(long seed, Throwable cause) { - super("Exception while generating data, seed=" + seed, cause); + GeneratorException(Iteration iteration, Throwable cause) { + super("Exception while generating data, " + iteration.printSeeds(), cause); } } diff --git a/slowCheck/src/slowCheck/Iteration.java b/slowCheck/src/slowCheck/Iteration.java new file mode 100644 index 000000000000..5ec409fda6f7 --- /dev/null +++ b/slowCheck/src/slowCheck/Iteration.java @@ -0,0 +1,110 @@ +package slowCheck; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.function.IntUnaryOperator; +import java.util.function.Predicate; + +class Iteration { + + private static final Predicate DATA_IS_DIFFERENT = new Predicate() { + @Override + public boolean test(Object o) { + return false; + } + + @Override + public String toString() { + return ": cannot generate enough sufficiently different values"; + } + }; + + final CheckSession session; + final long iterationSeed; + final int sizeHint; + final int iterationNumber; + + Iteration(CheckSession session, long iterationSeed, int iterationNumber) { + this.session = session; + this.iterationSeed = iterationSeed; + this.sizeHint = session.sizeHintFun.applyAsInt(iterationNumber); + this.iterationNumber = iterationNumber; + if (sizeHint < 0) { + throw new IllegalArgumentException("Size hint should be non-negative, found " + sizeHint); + } + } + + @Nullable + private CounterExampleImpl findCounterExample(Random random) { + for (int i = 0; i < 100; i++) { + StructureNode node = new StructureNode(); + T value; + try { + value = session.generator.getGeneratorFunction().apply(new GenerativeDataStructure(random, node, sizeHint)); + } + catch (Throwable e) { + throw new GeneratorException(this, e); + } + if (!session.generatedHashes.add(node.hashCode())) continue; + + return CounterExampleImpl.checkProperty(session.property, value, node); + } + throw new CannotSatisfyCondition(DATA_IS_DIFFERENT); + } + + String printToReproduce() { + return "To reproduce the last iteration, run PropertyChecker.forAll(...).rechecking(" + iterationSeed + "L, " + sizeHint + ").shouldHold(...)\n" + + "Global seed: " + session.globalSeed + "L"; + } + + String printSeeds() { + return "iteration seed=" + iterationSeed + "L, " + + "size hint=" + sizeHint + ", " + + "global seed=" + session.globalSeed + "L"; + } + + @Nullable + Iteration performIteration() { + session.notifier.iterationStarted(iterationNumber); + + Random random = new Random(iterationSeed); + CounterExampleImpl example = findCounterExample(random); + if (example != null) { + session.notifier.counterExampleFound(this); + PropertyFailureImpl failure = new PropertyFailureImpl<>(example, this); + throw new PropertyFalsified(failure, () -> new ReplayDataStructure(failure.getMinimalCounterexample().data, sizeHint)); + } + + if (iterationNumber >= session.iterationCount) { + return null; + } + + return new Iteration<>(session, random.nextLong(), iterationNumber + 1); + } +} + +class CheckSession { + final Generator generator; + final Predicate property; + final long globalSeed; + final Set generatedHashes = new HashSet<>(); + final StatusNotifier notifier; + final int iterationCount; + final IntUnaryOperator sizeHintFun; + + CheckSession(Generator generator, Predicate property, long globalSeed, int iterationCount, IntUnaryOperator sizeHintFun) { + this.generator = generator; + this.property = property; + this.globalSeed = globalSeed; + this.iterationCount = iterationCount; + this.sizeHintFun = sizeHintFun; + notifier = new StatusNotifier(iterationCount); + } + + Iteration firstIteration() { + return new Iteration<>(this, globalSeed, 1); + } +} diff --git a/slowCheck/src/slowCheck/PropertyChecker.java b/slowCheck/src/slowCheck/PropertyChecker.java index d6e49b809193..4746c262d450 100644 --- a/slowCheck/src/slowCheck/PropertyChecker.java +++ b/slowCheck/src/slowCheck/PropertyChecker.java @@ -1,201 +1,81 @@ package slowCheck; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.HashSet; import java.util.Random; -import java.util.Set; +import java.util.function.IntUnaryOperator; import java.util.function.Predicate; /** - * @author peter + * An entry point to property-based testing. The main usage pattern: {@code PropertyChecker.forAll(generator).shouldHold(property)}. */ public class PropertyChecker { - private static final Predicate DATA_IS_DIFFERENT = new Predicate() { - @Override - public boolean test(Object o) { - return false; - } - - @Override - public String toString() { - return ": cannot generate enough sufficiently different values"; - } - }; private final Generator generator; - private Predicate property; - private final Set generatedHashes = new HashSet<>(); - private long seed = new Random().nextLong(); + private long globalSeed = new Random().nextLong(); + private IntUnaryOperator sizeHintFun = iteration -> (iteration - 1) % 100 + 1; private int iterationCount = 100; - private StatusNotifier notifier; private PropertyChecker(Generator generator) { this.generator = generator; } + /** + * Creates a property checker for the given generator. It can be further customized using {@code with*}-methods, + * and should finally used for property to check via {@link #shouldHold(Predicate)} call. + */ public static PropertyChecker forAll(Generator generator) { return new PropertyChecker<>(generator); } - + + /** + * This function allows to start the test with a fixed random seed. It's useful to reproduce some previous test run and debug it. + * @param seed A random seed to use for the first iteration. + * The following iterations will use other, pseudo-random seeds, but still derived from this one. + * @return this PropertyChecker + */ public PropertyChecker withSeed(long seed) { - this.seed = seed; + globalSeed = seed; return this; } + /** + * @param iterationCount the number of iterations to try. By default it's 100. + * @return this PropertyChecker + */ public PropertyChecker withIterationCount(int iterationCount) { this.iterationCount = iterationCount; return this; } + /** + * @param sizeHintFun a function determining how size hint should be distributed depending on the iteration number. + * By default the size hint will be 1 in the first iteration, 2 in the second one, and so on until 100, + * then again 1,...,100,1,...,100, etc. + * @return this PropertyChecker + * @see DataStructure#getSizeHint() + */ + public PropertyChecker withSizeHint(@NotNull IntUnaryOperator sizeHintFun) { + this.sizeHintFun = sizeHintFun; + return this; + } + + /** + * Checks the property within a single iteration by using specified seed and size hint. Useful to debug the test after it's failed. + */ + public PropertyChecker rechecking(long seed, int sizeHint) { + return withSeed(seed).withSizeHint(whatever -> sizeHint).withIterationCount(1); + } + + /** + * Checks that the given property returns {@code true} and doesn't throw exceptions by running the generator and the property + * given number of times (see {@link #withIterationCount(int)}). + */ public void shouldHold(@NotNull Predicate property) { - if (this.property != null) throw new IllegalArgumentException("Property " + property + " already checked"); - this.property = property; - notifier = new StatusNotifier(iterationCount, this.seed); - - Random random = new Random(seed); - - for (int i = 1; i <= iterationCount; i++) { - notifier.iterationStarted(i); - - CounterExampleImpl example = findCounterExample(i, random); - if (example != null) { - notifier.counterExampleFound(); - PropertyFailureImpl failure = new PropertyFailureImpl(example, i); - throw new PropertyFalsified(seed, failure, () -> new ReplayDataStructure(failure.getMinimalCounterexample().data, failure.sizeHint)); - } + Iteration iteration = new CheckSession<>(generator, property, globalSeed, iterationCount, sizeHintFun).firstIteration(); + while (iteration != null) { + iteration = iteration.performIteration(); } } - @Nullable - private CounterExampleImpl findCounterExample(int sizeHint, Random random) { - for (int i = 0; i < 100; i++) { - StructureNode node = new StructureNode(); - T value; - try { - value = generator.getGeneratorFunction().apply(new GenerativeDataStructure(random, node, sizeHint)); - } - catch (Throwable e) { - throw new GeneratorException(seed, e); - } - if (!generatedHashes.add(node.hashCode())) continue; - - return CounterExampleImpl.checkProperty(property, value, node); - } - throw new CannotSatisfyCondition(DATA_IS_DIFFERENT); - } - - private class PropertyFailureImpl implements PropertyFailure { - private final CounterExampleImpl initial; - private CounterExampleImpl minimized; - private int totalSteps; - private int successfulSteps; - private int sizeHint; - private Throwable stoppingReason; - - PropertyFailureImpl(@NotNull CounterExampleImpl initial, int sizeHint) { - this.initial = initial; - this.minimized = initial; - this.sizeHint = sizeHint; - try { - shrink(); - } - catch (Throwable e) { - stoppingReason = e; - } - } - - @NotNull - @Override - public CounterExampleImpl getFirstCounterExample() { - return initial; - } - - @NotNull - @Override - public CounterExampleImpl getMinimalCounterexample() { - return minimized; - } - - @Nullable - @Override - public Throwable getStoppingReason() { - return stoppingReason; - } - - @Override - public int getTotalMinimizationExampleCount() { - return totalSteps; - } - - @Override - public int getMinimizationStageCount() { - return successfulSteps; - } - - private void shrink() { - ShrinkRunner shrinkRunner = new ShrinkRunner(); - while (true) { - CounterExampleImpl shrank = shrinkRunner.findShrink(minimized.data, node -> { - if (!generatedHashes.add(node.hashCode())) return null; - - notifier.shrinkAttempt(this); - - try { - T value = generator.getGeneratorFunction().apply(new ReplayDataStructure(node, sizeHint)); - totalSteps++; - return CounterExampleImpl.checkProperty(property, value, node); - } - catch (CannotRestoreValue e) { - return null; - } - }); - if (shrank != null) { - minimized = shrank; - successfulSteps++; - } else { - break; - } - } - } - - - } -} - -class CounterExampleImpl implements PropertyFailure.CounterExample { - final StructureNode data; - private final T value; - @Nullable private final Throwable exception; - - private CounterExampleImpl(StructureNode data, T value, @Nullable Throwable exception) { - this.data = data; - this.value = value; - this.exception = exception; - } - - @Override - public T getExampleValue() { - return value; - } - - @Nullable - @Override - public Throwable getExceptionCause() { - return exception; - } - - static CounterExampleImpl checkProperty(Predicate property, T value, StructureNode node) { - try { - if (!property.test(value)) { - return new CounterExampleImpl<>(node, value, null); - } - } - catch (Throwable e) { - return new CounterExampleImpl<>(node, value, e); - } - return null; - } - } diff --git a/slowCheck/src/slowCheck/PropertyFailure.java b/slowCheck/src/slowCheck/PropertyFailure.java index 60f215c26cab..17df0b502333 100644 --- a/slowCheck/src/slowCheck/PropertyFailure.java +++ b/slowCheck/src/slowCheck/PropertyFailure.java @@ -19,6 +19,14 @@ public interface PropertyFailure { int getTotalMinimizationExampleCount(); int getMinimizationStageCount(); + + int getIterationNumber(); + + long getIterationSeed(); + + long getGlobalSeed(); + + int getSizeHint(); interface CounterExample { T getExampleValue(); diff --git a/slowCheck/src/slowCheck/PropertyFailureImpl.java b/slowCheck/src/slowCheck/PropertyFailureImpl.java new file mode 100644 index 000000000000..9f1c077c8200 --- /dev/null +++ b/slowCheck/src/slowCheck/PropertyFailureImpl.java @@ -0,0 +1,100 @@ +package slowCheck; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class PropertyFailureImpl implements PropertyFailure { + private final CounterExampleImpl initial; + private CounterExampleImpl minimized; + private int totalSteps; + private int successfulSteps; + final Iteration iteration; + private Throwable stoppingReason; + + PropertyFailureImpl(@NotNull CounterExampleImpl initial, Iteration iteration) { + this.initial = initial; + this.minimized = initial; + this.iteration = iteration; + try { + shrink(); + } + catch (Throwable e) { + stoppingReason = e; + } + } + + @NotNull + @Override + public CounterExampleImpl getFirstCounterExample() { + return initial; + } + + @NotNull + @Override + public CounterExampleImpl getMinimalCounterexample() { + return minimized; + } + + @Nullable + @Override + public Throwable getStoppingReason() { + return stoppingReason; + } + + @Override + public int getTotalMinimizationExampleCount() { + return totalSteps; + } + + @Override + public int getMinimizationStageCount() { + return successfulSteps; + } + + @Override + public int getIterationNumber() { + return iteration.iterationNumber; + } + + @Override + public long getIterationSeed() { + return iteration.iterationSeed; + } + + @Override + public long getGlobalSeed() { + return iteration.session.globalSeed; + } + + @Override + public int getSizeHint() { + return iteration.sizeHint; + } + + private void shrink() { + ShrinkRunner shrinkRunner = new ShrinkRunner(); + while (true) { + CounterExampleImpl shrank = shrinkRunner.findShrink(minimized.data, node -> { + if (!iteration.session.generatedHashes.add(node.hashCode())) return null; + + iteration.session.notifier.shrinkAttempt(this, iteration); + + try { + T value = iteration.session.generator.getGeneratorFunction().apply(new ReplayDataStructure(node, iteration.sizeHint)); + totalSteps++; + return CounterExampleImpl.checkProperty(iteration.session.property, value, node); + } + catch (CannotRestoreValue e) { + return null; + } + }); + if (shrank != null) { + minimized = shrank; + successfulSteps++; + } + else { + break; + } + } + } +} diff --git a/slowCheck/src/slowCheck/PropertyFalsified.java b/slowCheck/src/slowCheck/PropertyFalsified.java index ec8803ed2bad..c4a6fa239693 100644 --- a/slowCheck/src/slowCheck/PropertyFalsified.java +++ b/slowCheck/src/slowCheck/PropertyFalsified.java @@ -11,13 +11,11 @@ import java.util.function.Supplier; public class PropertyFalsified extends RuntimeException { static final String FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION = "!!! FAILURE REASON HAS CHANGED DURING MINIMIZATION !!!"; private static final String SEPARATOR = "\n==========================\n"; - private final long seed; - private final PropertyFailure failure; + private final PropertyFailureImpl failure; private final Supplier data; - PropertyFalsified(long seed, PropertyFailure failure, Supplier data) { + PropertyFalsified(PropertyFailureImpl failure, Supplier data) { super(failure.getMinimalCounterexample().getExceptionCause()); - this.seed = seed; this.failure = failure; this.data = data; } @@ -26,7 +24,7 @@ public class PropertyFalsified extends RuntimeException { public String getMessage() { String msg = "Falsified on " + failure.getMinimalCounterexample().getExampleValue() + "\n" + getMinimizationStats() + - "Seed=" + seed; + failure.iteration.printToReproduce(); if (failure.getStoppingReason() != null) { msg += "\n Shrinking stopped because of " + StatusNotifier.printStackTrace(failure.getStoppingReason()); diff --git a/slowCheck/src/slowCheck/StatusNotifier.java b/slowCheck/src/slowCheck/StatusNotifier.java index d9122682aa47..ddf3825bcbe6 100644 --- a/slowCheck/src/slowCheck/StatusNotifier.java +++ b/slowCheck/src/slowCheck/StatusNotifier.java @@ -15,13 +15,11 @@ import java.util.Locale; @SuppressWarnings("UseOfSystemOutOrSystemErr") class StatusNotifier { private final int iterationCount; - private final long seed; private int currentIteration; private long lastPrinted = System.currentTimeMillis(); - StatusNotifier(int iterationCount, long seed) { + StatusNotifier(int iterationCount) { this.iterationCount = iterationCount; - this.seed = seed; } void iterationStarted(int iteration) { @@ -31,9 +29,9 @@ class StatusNotifier { } } - void counterExampleFound() { + void counterExampleFound(Iteration iteration) { lastPrinted = System.currentTimeMillis(); - System.err.println(formatCurrentTime() + ": failed on iteration " + currentIteration + " (seed=" + seed + "), shrinking..."); + System.err.println(formatCurrentTime() + ": failed on iteration " + currentIteration + " (" + iteration.printSeeds() + "), shrinking..."); } private boolean shouldPrint() { @@ -46,11 +44,11 @@ class StatusNotifier { private int lastReportedStage = -1; private String lastReportedTrace = null; - void shrinkAttempt(PropertyFailure failure) { + void shrinkAttempt(PropertyFailure failure, Iteration iteration) { if (shouldPrint()) { int stage = failure.getMinimizationStageCount(); - System.err.println(formatCurrentTime() + ": still shrinking (seed=" + seed + "). " + - "Examples tried: " + failure.getTotalMinimizationExampleCount() + + System.out.println(formatCurrentTime() + ": still shrinking (" + iteration.printSeeds() + "). " + + "Examples tried: " + failure.getTotalMinimizationExampleCount() + ", successful minimizations: " + stage); if (lastReportedStage != stage) { lastReportedStage = stage; diff --git a/slowCheck/test/slowCheck/ExceptionTest.java b/slowCheck/test/slowCheck/ExceptionTest.java index 18f37d885751..8a4333333850 100644 --- a/slowCheck/test/slowCheck/ExceptionTest.java +++ b/slowCheck/test/slowCheck/ExceptionTest.java @@ -8,44 +8,30 @@ import static slowCheck.Generator.*; public class ExceptionTest extends PropertyCheckerTestCase { public void testFailureReasonUnchanged() { - try { - forAllStable(integers()).shouldHold(i -> { - throw new AssertionError("fail"); - }); - fail(); - } - catch (PropertyFalsified e) { - assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + throw new AssertionError("fail"); + }); + + assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); } public void testFailureReasonChangedExceptionClass() { - try { - forAllStable(integers()).shouldHold(i -> { - throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail")); - }); - fail(); - } - catch (PropertyFalsified e) { - assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail")); + }); + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); } public void testFailureReasonChangedExceptionTrace() { - try { - forAllStable(integers()).shouldHold(i -> { - if (i == 0) { - throw new AssertionError("fail"); - } - else { - throw new AssertionError("fail2"); - } - }); - fail(); - } - catch (PropertyFalsified e) { - assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + if (i == 0) { + throw new AssertionError("fail"); + } + else { + throw new AssertionError("fail2"); + } + }); + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); } public void testExceptionWhileGeneratingValue() { @@ -60,17 +46,13 @@ public class ExceptionTest extends PropertyCheckerTestCase { } public void testExceptionWhileShrinkingValue() { - try { - forAllStable(listsOf(integers()).suchThat(l -> { - if (l.size() == 1 && l.get(0) == 0) throw new RuntimeException("my exception"); - return true; - })).shouldHold(l -> l.stream().allMatch(i -> i > 0)); - fail(); - } - catch (PropertyFalsified e) { - assertEquals("my exception", e.getFailure().getStoppingReason().getMessage()); - assertTrue(StatusNotifier.printStackTrace(e).contains("my exception")); - } + PropertyFalsified e = checkFails(PropertyChecker.forAll(listsOf(integers()).suchThat(l -> { + if (l.size() == 1 && l.get(0) == 0) throw new RuntimeException("my exception"); + return true; + })), l -> l.stream().allMatch(i -> i > 0)); + + assertEquals("my exception", e.getFailure().getStoppingReason().getMessage()); + assertTrue(StatusNotifier.printStackTrace(e).contains("my exception")); } public void testUnsatisfiableSuchThat() { diff --git a/slowCheck/test/slowCheck/GeneratorTest.java b/slowCheck/test/slowCheck/GeneratorTest.java index e73c948eef70..cdc01781ba2f 100644 --- a/slowCheck/test/slowCheck/GeneratorTest.java +++ b/slowCheck/test/slowCheck/GeneratorTest.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static slowCheck.Generator.*; @@ -28,25 +29,25 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testListContainsDivisible() { checkFalsified(nonEmptyLists(integers()), l -> l.stream().allMatch(i -> i % 10 != 0), - 9); + 3); } public void testStringContains() { checkFalsified(stringsOf(asciiPrintableChars()), s -> !s.contains("a"), - 10); + 7); } public void testLetterStringContains() { checkFalsified(stringsOf(asciiLetters()), s -> !s.contains("a"), - 5); + 3); } public void testIsSorted() { PropertyFailure> failure = checkFalsified(nonEmptyLists(integers()), l -> l.stream().sorted().collect(Collectors.toList()).equals(l), - 69); + 67); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } @@ -57,7 +58,7 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testSortedDoublesNonDescending() { PropertyFailure> failure = checkFalsified(listsOf(doubles()), l -> isSorted(l.stream().sorted().collect(Collectors.toList())), - 141); + 76); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } @@ -77,13 +78,13 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testStringOfStringChecksAllChars() { checkFalsified(stringsOf("abc "), s -> !s.contains(" "), - 3); + 4); } public void testLongListsHappen() { PropertyFailure> failure = checkFalsified(listsOf(integers()), l -> l.size() < 200, - 631); + 504); assertEquals(200, failure.getMinimalCounterexample().getExampleValue().size()); } @@ -114,15 +115,31 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testBoolean() { PropertyFailure> failure = checkFalsified(listsOf(booleans()), l -> !l.contains(true) || !l.contains(false), - 4); + 3); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } public void testShrinkingNonEmptyList() { PropertyFailure> failure = checkFalsified(nonEmptyLists(integers(0, 100)), l -> !l.contains(42), - 10); + 4); assertEquals(1, failure.getMinimalCounterexample().getExampleValue().size()); } + public void testRecheckWithGivenSeeds() { + Generator> gen = nonEmptyLists(integers(0, 100)); + Predicate> property = l -> !l.contains(42); + + PropertyFailure failure = checkFails(PropertyChecker.forAll(gen), property).getFailure(); + assertTrue(failure.getIterationNumber() > 1); + + PropertyFalsified e; + + e = checkFails(PropertyChecker.forAll(gen).rechecking(failure.getIterationSeed(), failure.getSizeHint()), property); + assertEquals(1, e.getFailure().getIterationNumber()); + + e = checkFails(PropertyChecker.forAll(gen).withSeed(failure.getGlobalSeed()), property); + assertEquals(failure.getIterationNumber(), e.getFailure().getIterationNumber()); + } + } diff --git a/slowCheck/test/slowCheck/PropertyCheckerTestCase.java b/slowCheck/test/slowCheck/PropertyCheckerTestCase.java index c8b818c35ca3..cf791df7dd42 100644 --- a/slowCheck/test/slowCheck/PropertyCheckerTestCase.java +++ b/slowCheck/test/slowCheck/PropertyCheckerTestCase.java @@ -9,25 +9,30 @@ import java.util.function.Predicate; */ abstract class PropertyCheckerTestCase extends TestCase { - protected PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { + protected PropertyFalsified checkFails(PropertyChecker checker, Predicate predicate) { try { - forAllStable(generator).shouldHold(predicate); + checker.shouldHold(predicate); throw new AssertionError("Can't falsify " + getName()); } catch (PropertyFalsified e) { - //noinspection unchecked - PropertyFailure failure = (PropertyFailure)e.getFailure(); - - System.out.println(" " + getName()); - System.out.println("Value: " + e.getBreakingValue()); - System.out.println("Data: " + e.getData()); - assertEquals(minimizationSteps, failure.getTotalMinimizationExampleCount()); - assertEquals(e.getBreakingValue(), generator.getGeneratorFunction().apply(e.getData())); - - return failure; + return e; } } + protected PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { + PropertyFalsified e = checkFails(forAllStable(generator), predicate); + //noinspection unchecked + PropertyFailure failure = (PropertyFailure)e.getFailure(); + + System.out.println(" " + getName()); + System.out.println("Value: " + e.getBreakingValue()); + System.out.println("Data: " + e.getData()); + assertEquals(minimizationSteps, failure.getTotalMinimizationExampleCount()); + assertEquals(e.getBreakingValue(), generator.getGeneratorFunction().apply(e.getData())); + + return failure; + } + protected static PropertyChecker forAllStable(Generator generator) { return PropertyChecker.forAll(generator).withSeed(0); }