diff --git a/jetCheck/src/jetCheck/ScenarioImpl.java b/jetCheck/src/jetCheck/ScenarioImpl.java index f37c6c17d0f5..0dbea7d824e4 100644 --- a/jetCheck/src/jetCheck/ScenarioImpl.java +++ b/jetCheck/src/jetCheck/ScenarioImpl.java @@ -50,17 +50,26 @@ class ScenarioImpl implements Scenario { @Override public void executeCommands(IntDistribution count, Generator cmdGen) { - data.generate(Generator.listsOf(count, innerCommands(cmdGen))); + innerCommandLists(Generator.listsOf(count, innerCommands(cmdGen))); } @Override public void executeCommands(Generator cmdGen) { - data.generate(Generator.nonEmptyLists(innerCommands(cmdGen))); + innerCommandLists(Generator.nonEmptyLists(innerCommands(cmdGen))); + } + + private void innerCommandLists(final Generator> listGen) { + data.generate(Generator.from(new EquivalentGenerator>() { + @Override + public List apply(DataStructure data) { + return listGen.getGeneratorFunction().apply(data); + } + })); } @NotNull private Generator innerCommands(Generator cmdGen) { - return Generator.from(new Function() { + return Generator.from(new EquivalentGenerator() { @Override public Object apply(DataStructure cmdData) { List localLog = new ArrayList<>(); @@ -68,16 +77,6 @@ class ScenarioImpl implements Scenario { performCommand(safeGenerate(cmdData, cmdGen), cmdData, localLog); return null; } - - @Override - public boolean equals(Object obj) { - return getClass() == obj.getClass(); // for recursive shrinking to work - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } }); } }); @@ -129,5 +128,18 @@ class ScenarioImpl implements Scenario { if (failure != null) throw new RuntimeException(failure); return true; } + + private static abstract class EquivalentGenerator implements Function { + @Override + public boolean equals(Object obj) { + return getClass() == obj.getClass(); // for recursive shrinking to work + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + } } diff --git a/jetCheck/src/jetCheck/StructureNode.java b/jetCheck/src/jetCheck/StructureNode.java index 37f654336846..af04c1ec82a0 100644 --- a/jetCheck/src/jetCheck/StructureNode.java +++ b/jetCheck/src/jetCheck/StructureNode.java @@ -76,16 +76,18 @@ class StructureNode extends StructureElement { ShrinkStep shrinkChild(int index) { for (; index < children.size(); index++) { ShrinkStep childShrink = children.get(index).shrink(); - if (childShrink != null) return shrinkNextChildAfter(index, childShrink); + if (childShrink != null) return wrapChildShrink(index, childShrink); } return shrinkRecursion(); } @Nullable - private ShrinkStep shrinkNextChildAfter(int index, @Nullable ShrinkStep step) { + private ShrinkStep wrapChildShrink(int index, @Nullable ShrinkStep step) { if (step == null) return shrinkChild(index + 1); - + + NodeId oldChild = children.get(index).id; + return new ShrinkStep() { @Nullable @Override @@ -97,12 +99,16 @@ class StructureNode extends StructureElement { ShrinkStep onSuccess(StructureNode smallerRoot) { StructureNode inheritor = (StructureNode)Objects.requireNonNull(smallerRoot.findChildById(id)); assert inheritor.children.size() == children.size(); - return inheritor.shrinkNextChildAfter(index, step.onSuccess(smallerRoot)); + if (inheritor.children.get(index).id != oldChild) { + return inheritor.shrink(); + } + + return inheritor.wrapChildShrink(index, step.onSuccess(smallerRoot)); } @Override ShrinkStep onFailure() { - return shrinkNextChildAfter(index, step.onFailure()); + return wrapChildShrink(index, step.onFailure()); } }; } diff --git a/jetCheck/test/jetCheck/StatefulGeneratorTest.java b/jetCheck/test/jetCheck/StatefulGeneratorTest.java index e5a05ff7ca55..97156bf21802 100644 --- a/jetCheck/test/jetCheck/StatefulGeneratorTest.java +++ b/jetCheck/test/jetCheck/StatefulGeneratorTest.java @@ -33,27 +33,54 @@ public class StatefulGeneratorTest extends PropertyCheckerTestCase { public void testImperativeInsertDeleteCheckCommands() { Scenario minHistory = checkFalsified(ImperativeCommand.scenarios(() -> env -> { StringBuilder sb = new StringBuilder(); - ImperativeCommand check = env1 -> { - env1.logMessage("check"); - if (sb.indexOf("A") >= 0) throw new AssertionError(); - }; - env.executeCommands(recursive(rec -> { - ImperativeCommand group = env1 -> { - env1.logMessage("Group"); - env1.executeCommands(rec); - }; - return frequency(2, constant(group), - 3, sampledFrom(insertStringCmd(sb), deleteStringCmd(sb), check)); - })); - }), Scenario::ensureSuccessful, 36).getMinimalCounterexample().getExampleValue(); + env.executeCommands(withRecursion(insertStringCmd(sb), deleteStringCmd(sb), checkDoesNotContain(sb, "A"))); + }), Scenario::ensureSuccessful, 42).getMinimalCounterexample().getExampleValue(); assertEquals("commands:\n" + - " Group\n" + - " insert A at 0\n" + - " check", + " insert A at 0\n" + + " check", minHistory.toString()); } + public void testImperativeInsertReplaceDeleteCommands() { + Scenario minHistory = checkFalsified(ImperativeCommand.scenarios(() -> env -> { + StringBuilder sb = new StringBuilder(); + ImperativeCommand replace = env1 -> { + if (sb.length() == 0) return; + int index = env1.generateValue(integers(0, sb.length() - 1), null); + char toReplace = env1.generateValue(asciiLetters().suchThat(c -> c != 'A'), "replace " + sb.charAt(index) + " with %s at " + index); + sb.setCharAt(index, toReplace); + }; + + env.executeCommands(withRecursion(insertStringCmd(sb), replace, deleteStringCmd(sb), checkDoesNotContain(sb, "A"))); + }), Scenario::ensureSuccessful, 76).getMinimalCounterexample().getExampleValue(); + + assertEquals("commands:\n" + + " insert A at 0\n" + + " check", + minHistory.toString()); + } + + @NotNull + private static Generator withRecursion(ImperativeCommand... commands) { + return recursive(rec -> { + ImperativeCommand group = env -> { + env.logMessage("Group"); + env.executeCommands(rec); + }; + return frequency(2, constant(group), 3, sampledFrom(commands)); + }); + } + + @SuppressWarnings("SameParameterValue") + @NotNull + private static ImperativeCommand checkDoesNotContain(StringBuilder sb, String infix) { + return env -> { + env.logMessage("check"); + if (sb.indexOf(infix) >= 0) throw new AssertionError(); + }; + } + @NotNull private static ImperativeCommand insertStringCmd(StringBuilder sb) { return env -> {