jetCheck: improve shrinking

after recursive substitution, re-run shrinking for parent in case it can now optimize more
allow recursive shrinking for ImperativeCommand lists
This commit is contained in:
peter
2017-12-30 22:49:08 +01:00
parent 83e780f605
commit d2286a59c6
3 changed files with 79 additions and 34 deletions
+25 -13
View File
@@ -50,17 +50,26 @@ class ScenarioImpl implements Scenario {
@Override
public void executeCommands(IntDistribution count, Generator<? extends ImperativeCommand> cmdGen) {
data.generate(Generator.listsOf(count, innerCommands(cmdGen)));
innerCommandLists(Generator.listsOf(count, innerCommands(cmdGen)));
}
@Override
public void executeCommands(Generator<? extends ImperativeCommand> cmdGen) {
data.generate(Generator.nonEmptyLists(innerCommands(cmdGen)));
innerCommandLists(Generator.nonEmptyLists(innerCommands(cmdGen)));
}
private void innerCommandLists(final Generator<List<Object>> listGen) {
data.generate(Generator.from(new EquivalentGenerator<List<Object>>() {
@Override
public List<Object> apply(DataStructure data) {
return listGen.getGeneratorFunction().apply(data);
}
}));
}
@NotNull
private Generator<Object> innerCommands(Generator<? extends ImperativeCommand> cmdGen) {
return Generator.from(new Function<DataStructure, Object>() {
return Generator.from(new EquivalentGenerator<Object>() {
@Override
public Object apply(DataStructure cmdData) {
List<Object> 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<T> implements Function<DataStructure, T> {
@Override
public boolean equals(Object obj) {
return getClass() == obj.getClass(); // for recursive shrinking to work
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
}
+11 -5
View File
@@ -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());
}
};
}
@@ -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<ImperativeCommand> 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 -> {