diff --git a/jetCheck/src/jetCheck/PropertyFailureImpl.java b/jetCheck/src/jetCheck/PropertyFailureImpl.java index 47c95e2ae5aa..b6f255b267d3 100644 --- a/jetCheck/src/jetCheck/PropertyFailureImpl.java +++ b/jetCheck/src/jetCheck/PropertyFailureImpl.java @@ -76,20 +76,35 @@ class PropertyFailureImpl implements PropertyFailure { } private void shrink() { - ShrinkStep step = minimized.data.shrink(); - while (step != null) { - step = findSuccessfulShrink(step); - if (step != null) { - step = step.onSuccess(minimized.data); - } + NodeId limit = null; + while (true) { + ShrinkStep lastSuccessfulShrink = shrinkIteration(limit); + if (lastSuccessfulShrink == null) break; + limit = lastSuccessfulShrink.getNodeAfter(); } } + private ShrinkStep shrinkIteration(NodeId limit) { + ShrinkStep lastSuccessfulShrink = null; + ShrinkStep step = minimized.data.shrink(); + while (step != null) { + step = findSuccessfulShrink(step, limit); + if (step != null) { + lastSuccessfulShrink = step; + step = step.onSuccess(minimized.data); + } + } + return lastSuccessfulShrink; + } + @Nullable - private ShrinkStep findSuccessfulShrink(ShrinkStep step) { + private ShrinkStep findSuccessfulShrink(ShrinkStep step, @Nullable NodeId limit) { List combinatorial = new ArrayList<>(); while (step != null) { + if (limit != null && limit.number <= step.getNodeAfter().number) { + break; + } StructureNode node = step.apply(minimized.data); if (node != null && iteration.session.generatedHashes.add(node.hashCode())) { CombinatorialIntCustomizer customizer = new CombinatorialIntCustomizer(); diff --git a/jetCheck/src/jetCheck/RemoveListRange.java b/jetCheck/src/jetCheck/RemoveListRange.java index 1066a5a197a5..d78c9c804d39 100644 --- a/jetCheck/src/jetCheck/RemoveListRange.java +++ b/jetCheck/src/jetCheck/RemoveListRange.java @@ -3,6 +3,7 @@ */ package jetCheck; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -70,4 +71,20 @@ class RemoveListRange extends ShrinkStep { int newLength = Math.min(length * 2, start - 1); return new RemoveListRange(inheritor, start, start - newLength, newLength); } + + @NotNull + @Override + NodeId getNodeAfter() { + return node.id; + } + + @Override + public String toString() { + return "RemoveListRange{" + + "last=" + lastSuccessfulRemove + + ", start=" + start + + ", length=" + length + + ", node=" + node.id + ": " + node + + '}'; + } } diff --git a/jetCheck/src/jetCheck/ShrinkStep.java b/jetCheck/src/jetCheck/ShrinkStep.java index 2d6bc4becdd0..63c035621e6b 100644 --- a/jetCheck/src/jetCheck/ShrinkStep.java +++ b/jetCheck/src/jetCheck/ShrinkStep.java @@ -23,6 +23,9 @@ abstract class ShrinkStep { @Nullable abstract ShrinkStep onFailure(); + @NotNull + abstract NodeId getNodeAfter(); + static ShrinkStep create(@NotNull NodeId replaced, @NotNull StructureElement replacement, @Nullable Function onSuccess, @@ -45,6 +48,17 @@ abstract class ShrinkStep { ShrinkStep onFailure() { return onFailure == null ? null : onFailure.get(); } + + @NotNull + @Override + NodeId getNodeAfter() { + return replacement.id; + } + + @Override + public String toString() { + return "replace " + replaced + " with " + replacement; + } }; } } diff --git a/jetCheck/src/jetCheck/StructureNode.java b/jetCheck/src/jetCheck/StructureNode.java index af04c1ec82a0..c8ac7807a1f4 100644 --- a/jetCheck/src/jetCheck/StructureNode.java +++ b/jetCheck/src/jetCheck/StructureNode.java @@ -110,6 +110,17 @@ class StructureNode extends StructureElement { ShrinkStep onFailure() { return wrapChildShrink(index, step.onFailure()); } + + @NotNull + @Override + NodeId getNodeAfter() { + return step.getNodeAfter(); + } + + @Override + public String toString() { + return "-" + step.toString(); + } }; } diff --git a/jetCheck/test/jetCheck/GeneratorTest.java b/jetCheck/test/jetCheck/GeneratorTest.java index 85bbcf40c1f9..efb64763b92f 100644 --- a/jetCheck/test/jetCheck/GeneratorTest.java +++ b/jetCheck/test/jetCheck/GeneratorTest.java @@ -20,7 +20,7 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testListSumMod() { checkFalsified(nonEmptyLists(integers()), l -> l.stream().mapToInt(Integer::intValue).sum() % 10 != 0, - 181); + 390); } public void testListContainsDivisible() { @@ -44,7 +44,7 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testIsSorted() { PropertyFailure> failure = checkFalsified(nonEmptyLists(integers()), l -> l.stream().sorted().collect(Collectors.toList()).equals(l), - 20); + 22); List value = failure.getMinimalCounterexample().getExampleValue(); assertEquals(2, value.size()); assertTrue(value.toString(), value.stream().allMatch(i -> Math.abs(i) < 2)); @@ -57,7 +57,7 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testSortedDoublesNonDescending() { PropertyFailure> failure = checkFalsified(listsOf(doubles()), l -> isSorted(l.stream().sorted().collect(Collectors.toList())), - 35); + 41); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } diff --git a/jetCheck/test/jetCheck/ShrinkTest.java b/jetCheck/test/jetCheck/ShrinkTest.java index e87884a6d191..b14ca3a2a5cc 100644 --- a/jetCheck/test/jetCheck/ShrinkTest.java +++ b/jetCheck/test/jetCheck/ShrinkTest.java @@ -3,7 +3,11 @@ */ package jetCheck; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; import static jetCheck.Generator.*; @@ -17,7 +21,7 @@ public class ShrinkTest extends PropertyCheckerTestCase { String s = l.toString(); return !"abcdefghijklmnopqrstuvwxyz()[]#!".chars().allMatch(c -> s.indexOf((char)c) >= 0); }, - 225); + 258); } public void testShrinkingNonEmptyList() { @@ -27,4 +31,19 @@ public class ShrinkTest extends PropertyCheckerTestCase { assertEquals(1, list.size()); } + public void testWhenEarlyObjectsCannotBeShrunkBeforeLater() { + Generator gen = listsOf(IntDistribution.uniform(0, 2), listsOf(IntDistribution.uniform(0, 2), sampledFrom('a', 'b'))).map(List::toString); + Set failing = new HashSet<>(Arrays.asList("[[a, b], [a, b]]", "[[a, b], [a]]", "[[a], [a]]", "[[a]]", "[]")); + Predicate property = s -> !failing.contains(s); + checkFalsified(gen, property, 0); // prove that it sometimes fails + for (int i = 0; i < 1000; i++) { + try { + PropertyChecker.forAll(gen).shouldHold(property); + } + catch (PropertyFalsified e) { + assertEquals("[]", e.getBreakingValue()); + } + } + } + }