Skip to content

Commit

Permalink
🐛 Path wrongly reported when invalid (#4344)
Browse files Browse the repository at this point in the history
Related to #4339

Following that PR, the path will start being properly reported whenever it cannot be really leveraged for the replay.
  • Loading branch information
dubzzz authored Oct 21, 2023
1 parent 76de582 commit 7472657
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 35 deletions.
8 changes: 8 additions & 0 deletions .yarn/versions/b05ec612.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
releases:
fast-check: patch

declined:
- "@fast-check/ava"
- "@fast-check/jest"
- "@fast-check/vitest"
- "@fast-check/worker"
16 changes: 1 addition & 15 deletions packages/fast-check/src/check/runner/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,6 @@ async function asyncRunIt<Ts>(
return runner.runExecution;
}

/** @internal */
function applyPath<Ts>(
valueProducers: IterableIterator<() => Value<Ts>>,
shrink: (value: Value<Ts>) => Stream<Value<Ts>>,
nonEmptyPath: string,
): IterableIterator<Value<Ts>> {
const pathPoints = nonEmptyPath.split(':');
const pathStream = stream(valueProducers)
.drop(pathPoints.length > 0 ? +pathPoints[0] : 0)
.map((producer) => producer());
const adaptedPath = ['0', ...pathPoints.slice(1)].join(':');
return pathWalk(adaptedPath, pathStream, shrink);
}

/**
* Run the property, do not throw contrary to {@link assert}
*
Expand Down Expand Up @@ -144,7 +130,7 @@ function check<Ts>(rawProperty: IRawProperty<Ts>, params?: Parameters<Ts>): unkn
const initialValues =
qParams.path.length === 0
? toss(property, qParams.seed, qParams.randomType, qParams.examples)
: applyPath(lazyToss(property, qParams.seed, qParams.randomType, qParams.examples), shrink, qParams.path);
: pathWalk(qParams.path, stream(lazyToss(property, qParams.seed, qParams.randomType, qParams.examples)), shrink);
const sourceValues = new SourceValuesIterator(initialValues, maxInitialIterations, maxSkips);
const finalShrink = !qParams.endOnFailure ? shrink : Stream.nil;
return property.isAsync()
Expand Down
23 changes: 11 additions & 12 deletions packages/fast-check/src/check/runner/Sampler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Stream, stream } from '../../stream/Stream';
import { stream } from '../../stream/Stream';
import { Arbitrary } from '../arbitrary/definition/Arbitrary';
import { Value } from '../arbitrary/definition/Value';
import { IRawProperty } from '../property/IRawProperty';
import { Property } from '../property/Property.generic';
import { UnbiasedProperty } from '../property/UnbiasedProperty';
import { readConfigureGlobal } from './configuration/GlobalParameters';
import { Parameters } from './configuration/Parameters';
import { QualifiedParameters } from './configuration/QualifiedParameters';
import { toss } from './Tosser';
import { lazyToss, toss } from './Tosser';
import { pathWalk } from './utils/PathWalker';

/** @internal */
Expand All @@ -33,15 +32,15 @@ function streamSample<Ts>(
const qParams: QualifiedParameters<Ts> = QualifiedParameters.read<Ts>(extendedParams);
const nextProperty = toProperty(generator, qParams);
const shrink = nextProperty.shrink.bind(nextProperty);
const tossedValues: Stream<Value<Ts>> = stream(
toss(nextProperty, qParams.seed, qParams.randomType, qParams.examples),
);
if (qParams.path.length === 0) {
return tossedValues.take(qParams.numRuns).map((s) => s.value_);
}
return pathWalk(qParams.path, tossedValues, shrink)
.take(qParams.numRuns)
.map((s) => s.value_);
const tossedValues =
qParams.path.length === 0
? stream(toss(nextProperty, qParams.seed, qParams.randomType, qParams.examples))
: pathWalk(
qParams.path,
stream(lazyToss(nextProperty, qParams.seed, qParams.randomType, qParams.examples)),
shrink,
);
return tossedValues.take(qParams.numRuns).map((s) => s.value_);
}

/**
Expand Down
13 changes: 9 additions & 4 deletions packages/fast-check/src/check/runner/utils/PathWalker.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { Value } from '../../arbitrary/definition/Value';
import { Stream } from '../../../stream/Stream';

/** @internal */
function produce<Ts>(producer: () => Value<Ts>): Value<Ts> {
return producer();
}

/** @internal */
export function pathWalk<Ts>(
path: string,
initialValues: Stream<Value<Ts>>,
initialProducers: Stream<() => Value<Ts>>,
shrink: (value: Value<Ts>) => Stream<Value<Ts>>,
): Stream<Value<Ts>> {
let values: Stream<Value<Ts>> = initialValues;
const producers: Stream<() => Value<Ts>> = initialProducers;
const segments: number[] = path.split(':').map((text: string) => +text);
if (segments.length === 0) {
return values;
return producers.map(produce);
}
if (!segments.every((v) => !Number.isNaN(v))) {
throw new Error(`Unable to replay, got invalid path=${path}`);
}
values = values.drop(segments[0]);
let values: Stream<Value<Ts>> = producers.drop(segments[0]).map(produce);
for (const s of segments.slice(1)) {
const valueToShrink = values.getNthOrLast(0);
if (valueToShrink == null) {
Expand Down
34 changes: 34 additions & 0 deletions packages/fast-check/test/e2e/ReplayFailures.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,39 @@ describe(`ReplayFailures (seed: ${seed})`, () => {
expect(outMiddlePath.counterexample).toEqual(out.counterexample);
}
});
it('Should print the rejected path when unable to replay for path', () => {
fc.assert(
fc.property(
fc.integer(),
fc.array(fc.nat({ max: 100 }), { minLength: 3 }).map((elements) => elements.join(':')),
(internalSeed, path) => {
expect(() =>
fc.check(
fc.property(fc.constant(0), () => {}), // no shrink available on constant, our path should break
{ seed: internalSeed, path },
),
).toThrowError(new RegExp(`wrong path=${path}`));
},
),
{ seed },
);
});
it('Should print the rejected path when unable to replay for path on sample', () => {
fc.assert(
fc.property(
fc.integer(),
fc.array(fc.nat({ max: 100 }), { minLength: 3 }).map((elements) => elements.join(':')),
(internalSeed, path) => {
expect(() =>
fc.sample(
fc.constant(0), // no shrink available on constant, our path should break
{ seed: internalSeed, path },
),
).toThrowError(new RegExp(`wrong path=${path}`));
},
),
{ seed },
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Got TypeError: v is not a function
at Property.p [as predicate] (packages/fast-check/src/check/property/Property.ts:23:56)
at Property.predicate [as run] (packages/fast-check/src/check/property/Property.generic.ts:136:27)
at run (packages/fast-check/src/check/runner/Runner.ts:37:26)
at runIt (packages/fast-check/src/check/runner/Runner.ts:154:7)
at Object.check (packages/fast-check/src/check/runner/Runner.ts:206:15)
at runIt (packages/fast-check/src/check/runner/Runner.ts:140:7)
at Object.check (packages/fast-check/src/check/runner/Runner.ts:192:15)
at assert (packages/fast-check/test/e2e/NoRegressionStack.spec.ts:24:12)
at run (packages/fast-check/test/e2e/NoRegressionStack.spec.ts:40:7)
at _toThrowErrorMatchingSnapshot (node_modules/jest-snapshot/build/index.js:?:?)
Expand All @@ -31,8 +31,8 @@ Got error: a must be >= b
at Property.p [as predicate] (packages/fast-check/src/check/property/Property.ts:23:56)
at Property.predicate [as run] (packages/fast-check/src/check/property/Property.generic.ts:136:27)
at run (packages/fast-check/src/check/runner/Runner.ts:37:26)
at runIt (packages/fast-check/src/check/runner/Runner.ts:154:7)
at Object.check (packages/fast-check/src/check/runner/Runner.ts:206:15)
at runIt (packages/fast-check/src/check/runner/Runner.ts:140:7)
at Object.check (packages/fast-check/src/check/runner/Runner.ts:192:15)
at assert (packages/fast-check/test/e2e/NoRegressionStack.spec.ts:9:12)
at run (packages/fast-check/test/e2e/NoRegressionStack.spec.ts:40:7)
at _toThrowErrorMatchingSnapshot (node_modules/jest-snapshot/build/index.js:?:?)
Expand Down

0 comments on commit 7472657

Please sign in to comment.