Skip to content

Commit

Permalink
Add the counterexample_path to the failure output
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Mar 28, 2018
1 parent e7f61ab commit d25d233
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ interface RunDetails<Ts> {
num_shrinks: number, // number of shrinks (depth required to get the minimal failing example)
seed: number, // seed used for the test
counterexample: Ts|null, // failure only: shrunk conterexample causig the property to fail
counterexample_path: string|null, // failure only: the exact path to re-run the counterexample
error: string|null, // failure only: stack trace and error details
}
```
Expand Down
11 changes: 6 additions & 5 deletions src/check/runner/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ interface RunDetails<Ts> {
seed: number,
counterexample: Ts|null,
error: string|null,
counterexample_path: string|null,
}

function successFor<Ts>(qParams: QualifiedParameters): RunDetails<Ts> {
return {failed: false, num_runs: qParams.num_runs, num_shrinks: 0, seed: qParams.seed, counterexample: null, error: null};
return {failed: false, num_runs: qParams.num_runs, num_shrinks: 0, seed: qParams.seed, counterexample: null, counterexample_path: null, error: null};
}
function failureFor<Ts>(qParams: QualifiedParameters, num_runs: number, num_shrinks: number, counterexample: Ts, error: string): RunDetails<Ts> {
return {failed: true, num_runs, num_shrinks, seed: qParams.seed, counterexample, error};
function failureFor<Ts>(qParams: QualifiedParameters, num_runs: number, num_shrinks: number, counterexample: Ts, counterexample_path: string, error: string): RunDetails<Ts> {
return {failed: true, num_runs, num_shrinks, seed: qParams.seed, counterexample, counterexample_path, error};
}

class RunExecution<Ts> {
Expand All @@ -67,7 +68,7 @@ class RunExecution<Ts> {
public toRunDetails(qParams: QualifiedParameters): RunDetails<Ts> {
return this.isSuccess()
? successFor<Ts>(qParams)
: failureFor<Ts>(qParams, this.firstFailure() +1, this.numShrinks(), this.value!, this.failure);
: failureFor<Ts>(qParams, this.firstFailure() +1, this.numShrinks(), this.value!, this.pathToFailure!, this.failure);
}
}

Expand All @@ -94,7 +95,7 @@ function pretty<Ts>(value: any): string {
function throwIfFailed<Ts>(out: RunDetails<Ts>) {
if (out.failed) {
throw new Error(
`Property failed after ${out.num_runs} tests (seed: ${out.seed}): ${pretty(out.counterexample)}\n` +
`Property failed after ${out.num_runs} tests (seed: ${out.seed}, path: ${out.counterexample_path}): ${pretty(out.counterexample)}\n` +
`Shrunk ${out.num_shrinks} time(s)\n` +
`Got error: ${out.error}`);
}
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/Shadows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ describe(`Shadows (seed: ${seed})`, () => {
catch (err) {
failed = true;
const msg = err.message as string;
assert.ok(msg.indexOf(`(seed: ${seed})`) !== -1, `Message contains the seed, got: ${err}`);
assert.ok(msg.indexOf(`(seed: ${seed}, path:`) !== -1, `Message contains the seed, got: ${err}`);
assert.ok(/\[Space\(grid\{x:\d+,y:\d+\},solution\{x:\d+,y:\d+\},initial\{x:\d+,y:\d+\}\),\d+\]/.exec(msg) !== null, `Message contains the failing entry, got: ${err}`);
assert.ok(/failed after \d+ test/.exec(msg) !== null, `Message contains the number of tests, got: ${err}`)
}
Expand Down
41 changes: 39 additions & 2 deletions test/unit/check/runner/Runner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import IProperty from '../../../../src/check/property/IProperty';
import { check, assert as rAssert } from '../../../../src/check/runner/Runner';
import Random from '../../../../src/random/generator/Random';
import { RunDetails } from '../../../../src/check/runner/utils/utils';
import Stream from '../../../../src/stream/Stream';
import { stream, Stream } from '../../../../src/stream/Stream';

const MAX_NUM_RUNS = 1000;
describe('Runner', () => {
Expand Down Expand Up @@ -119,6 +119,43 @@ describe('Runner', () => {
return true;
})
));
it('Should build the right counterexample_path', () => fc.assert(
fc.property(fc.integer(), fc.array(fc.nat(99), 1, 100), (seed, failurePoints) => {
// Each entry (at index idx) in failurePoints represents the number of runs
// required before failing for the level <idx>
// Basically it must fail before the end of the execution (100 runs by default)
// so failure points are between 0 and 99 inclusive

const deepShrinkable = function(depth): Shrinkable<[number]> {
if (depth <= 0)
return new Shrinkable([0]) as Shrinkable<[number]>;
function* g(subDepth): IterableIterator<Shrinkable<[number]>> {
while(true)
yield deepShrinkable(subDepth);
}
return new Shrinkable([0], () => stream(g(depth -1))) as Shrinkable<[number]>;
};

let idx = 0;
let remainingBeforeFailure = failurePoints[idx];
const p: IProperty<[number]> = {
isAsync: () => false,
generate: (rng: Random) => deepShrinkable(failurePoints.length -1),
run: (value: [number]) => {
if (--remainingBeforeFailure >= 0) return null;
remainingBeforeFailure = failurePoints[++idx];
return 'failure';
}
};
const expectedFailurePath = failurePoints.join(':');
const out = check(p, {seed: seed}) as RunDetails<[number]>;
assert.ok(out.failed);
assert.equal(out.seed, seed);
assert.equal(out.num_runs, failurePoints[0] +1);
assert.equal(out.num_shrinks, failurePoints.length);
assert.equal(out.counterexample_path, expectedFailurePath);
})
));
it('Should wait on async properties to complete', async () => fc.assert(
fc.asyncProperty(fc.integer(1, 100), fc.integer(), async (num, seed) => {
const delay = () => new Promise((resolve, reject) => setTimeout(resolve, 0));
Expand Down Expand Up @@ -245,7 +282,7 @@ describe('Runner', () => {
rAssert(failingProperty, {seed: 42});
}
catch (err) {
assert.ok(err.message.indexOf(`(seed: 42)`) !== -1, `Cannot find the seed in: ${err.message}`);
assert.ok(err.message.indexOf(`(seed: 42, path:`) !== -1, `Cannot find the seed in: ${err.message}`);
return;
}
assert.ok(false, "Expected an exception, got success");
Expand Down

0 comments on commit d25d233

Please sign in to comment.