Skip to content

Commit

Permalink
Merge 33dc9ee into 3c1592f
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Jan 27, 2019
2 parents 3c1592f + 33dc9ee commit 72d7568
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 24 deletions.
42 changes: 26 additions & 16 deletions src/check/model/commands/CommandsArbitrary.ts
Expand Up @@ -24,11 +24,10 @@ class CommandsArbitrary<Model extends object, Real, RunResult, CheckAsync extend
}
private wrapper(
items: Shrinkable<CommandWrapper<Model, Real, RunResult, CheckAsync>>[],
shrunkOnce: boolean,
alwaysKeepOneCommand: boolean
shrunkOnce: boolean
): Shrinkable<CommandsIterable<Model, Real, RunResult, CheckAsync>> {
return new Shrinkable(new CommandsIterable(items.map(s => s.value_)), () =>
this.shrinkImpl(items, shrunkOnce, alwaysKeepOneCommand).map(v => this.wrapper(v, true, true))
this.shrinkImpl(items, shrunkOnce).map(v => this.wrapper(v, true))
);
}
generate(mrng: Random): Shrinkable<CommandsIterable<Model, Real, RunResult, CheckAsync>> {
Expand All @@ -38,12 +37,11 @@ class CommandsArbitrary<Model extends object, Real, RunResult, CheckAsync extend
const item = this.oneCommandArb.generate(mrng);
items[idx] = item;
}
return this.wrapper(items, false, false);
return this.wrapper(items, false);
}
private shrinkImpl(
itemsRaw: Shrinkable<CommandWrapper<Model, Real, RunResult, CheckAsync>>[],
shrunkOnce: boolean,
alwaysKeepOneCommand: boolean
shrunkOnce: boolean
): Stream<Shrinkable<CommandWrapper<Model, Real, RunResult, CheckAsync>>[]> {
const items = itemsRaw.filter(c => c.value_.hasRan); // filter out commands that have not been executed
if (items.length === 0) {
Expand All @@ -52,20 +50,32 @@ class CommandsArbitrary<Model extends object, Real, RunResult, CheckAsync extend

// The shrinker of commands have to keep the last item
// because it is the one causing the failure
const emptyOrNil = alwaysKeepOneCommand
let allShrinks = shrunkOnce
? Stream.nil<Shrinkable<CommandWrapper<Model, Real, RunResult, CheckAsync>>[]>()
: new Stream([[]][Symbol.iterator]());
const size = this.lengthArb.shrinkableFor(items.length - 1, shrunkOnce);

return emptyOrNil
.join(size.shrink().map(l => items.slice(items.length - (l.value + 1)))) // try: remove items except the last one
.join(this.shrinkImpl(items.slice(1), false, true).map(vs => [items[0]].concat(vs))) // try: keep first, shrink remaining (rec)
.join(items[items.length - 1].shrink().map(v => items.slice(0, -1).concat([v]))) // try: shrink last, keep others
.map(shrinkables => {
return shrinkables.map(c => {
return new Shrinkable(c.value_.clone(), c.shrink);
});
// keep fixed number commands at the beginnign
// remove items in remaining part except the last one
for (let numToKeep = 0; numToKeep !== items.length; ++numToKeep) {
const size = this.lengthArb.shrinkableFor(items.length - 1 - numToKeep, false);
const fixedStart = items.slice(0, numToKeep);
allShrinks = allShrinks.join(
size.shrink().map(l => fixedStart.concat(items.slice(items.length - (l.value + 1))))
);
}

// shrink one by one
for (let itemAt = 0; itemAt !== items.length; ++itemAt) {
allShrinks = allShrinks.join(
items[itemAt].shrink().map(v => items.slice(0, itemAt).concat([v], items.slice(itemAt + 1)))
);
}

return allShrinks.map(shrinkables => {
return shrinkables.map(c => {
return new Shrinkable(c.value_.clone(), c.shrink);
});
});
}
}

Expand Down
18 changes: 10 additions & 8 deletions test/e2e/model/CommandsArbitrary.spec.ts
Expand Up @@ -4,18 +4,20 @@ type M1 = { count: number };
type R1 = {};

class IncreaseCommand implements fc.Command<M1, R1> {
constructor(readonly n: number) {}
check = (m: Readonly<M1>) => true;
run = (m: M1, r: R1) => {
m.count += 1;
m.count += this.n;
};
toString = () => 'inc';
toString = () => `inc[${this.n}]`;
}
class DecreaseCommand implements fc.Command<M1, R1> {
constructor(readonly n: number) {}
check = (m: Readonly<M1>) => true;
run = (m: M1, r: R1) => {
m.count -= 1;
m.count -= this.n;
};
toString = () => 'dec';
toString = () => `dec[${this.n}]`;
}
class EvenCommand implements fc.Command<M1, R1> {
check = (m: Readonly<M1>) => m.count % 2 === 0;
Expand Down Expand Up @@ -68,11 +70,11 @@ describe(`CommandsArbitrary (seed: ${seed})`, () => {
fc.property(
fc.commands(
[
fc.constant(new IncreaseCommand()),
fc.constant(new DecreaseCommand()),
fc.nat().map(n => new IncreaseCommand(n)),
fc.nat().map(n => new DecreaseCommand(n)),
fc.constant(new EvenCommand()),
fc.constant(new OddCommand()),
fc.integer(1, 10).map(v => new CheckLessThanCommand(v))
fc.nat().map(n => new CheckLessThanCommand(n + 1))
],
1000
),
Expand All @@ -90,7 +92,7 @@ describe(`CommandsArbitrary (seed: ${seed})`, () => {

const cmdsRepr = out.counterexample![0].toString();
expect(cmdsRepr).toMatch(/check\[(\d+)\]$/);
expect(cmdsRepr).toEqual('inc,check[1]');
expect(cmdsRepr).toEqual('inc[1],check[1]');
});
it('Should result in empty commands if failures happen after the run', () => {
const out = fc.check(
Expand Down

0 comments on commit 72d7568

Please sign in to comment.