Skip to content

Commit

Permalink
Manually add concrete examples to test
Browse files Browse the repository at this point in the history
This first version has no shrinking ability. It might come later.

Related to #141
  • Loading branch information
dubzzz committed Jul 12, 2018
1 parent aca0239 commit ef47d39
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 38 deletions.
20 changes: 10 additions & 10 deletions src/check/runner/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ async function asyncRunIt<Ts>(
}

/** @hidden */
function decorateProperty<Ts>(rawProperty: IProperty<Ts>, qParams: QualifiedParameters) {
function decorateProperty<Ts>(rawProperty: IProperty<Ts>, qParams: QualifiedParameters<Ts>) {
const propA =
rawProperty.isAsync() && qParams.timeout != null ? new TimeoutProperty(rawProperty, qParams.timeout) : rawProperty;
return qParams.unbiased === true ? new UnbiasedProperty(propA) : propA;
Expand All @@ -127,7 +127,7 @@ function runnerPathWalker<Ts>(valueProducers: IterableIterator<() => Shrinkable<
*
* @returns Test status and other useful details
*/
function check<Ts>(property: AsyncProperty<Ts>, params?: Parameters): Promise<RunDetails<Ts>>;
function check<Ts>(property: AsyncProperty<Ts>, params?: Parameters<Ts>): Promise<RunDetails<Ts>>;
/**
* Run the property, do not throw contrary to {@link assert}
*
Expand All @@ -136,16 +136,16 @@ function check<Ts>(property: AsyncProperty<Ts>, params?: Parameters): Promise<Ru
*
* @returns Test status and other useful details
*/
function check<Ts>(property: Property<Ts>, params?: Parameters): RunDetails<Ts>;
function check<Ts>(property: IProperty<Ts>, params?: Parameters): Promise<RunDetails<Ts>> | RunDetails<Ts>;
function check<Ts>(rawProperty: IProperty<Ts>, params?: Parameters) {
function check<Ts>(property: Property<Ts>, params?: Parameters<Ts>): RunDetails<Ts>;
function check<Ts>(property: IProperty<Ts>, params?: Parameters<Ts>): Promise<RunDetails<Ts>> | RunDetails<Ts>;
function check<Ts>(rawProperty: IProperty<Ts>, params?: Parameters<Ts>) {
if (rawProperty == null || rawProperty.generate == null)
throw new Error('Invalid property encountered, please use a valid property');
if (rawProperty.run == null)
throw new Error('Invalid property encountered, please use a valid property not an arbitrary');
const qParams = QualifiedParameters.read(params);
const property = decorateProperty(rawProperty, qParams);
const generator = toss(property, qParams.seed);
const generator = toss(property, qParams.seed, qParams.examples);

const maxInitialIterations = qParams.path.length === 0 ? qParams.numRuns : -1;
const maxSkips = qParams.numRuns * qParams.maxSkipsPerRun;
Expand Down Expand Up @@ -173,7 +173,7 @@ function check<Ts>(rawProperty: IProperty<Ts>, params?: Parameters) {
* @param property Asynchronous property to be checked
* @param params Optional parameters to customize the execution
*/
function assert<Ts>(property: AsyncProperty<Ts>, params?: Parameters): Promise<void>;
function assert<Ts>(property: AsyncProperty<Ts>, params?: Parameters<Ts>): Promise<void>;
/**
* Run the property, throw in case of failure
*
Expand All @@ -183,9 +183,9 @@ function assert<Ts>(property: AsyncProperty<Ts>, params?: Parameters): Promise<v
* @param property Synchronous property to be checked
* @param params Optional parameters to customize the execution
*/
function assert<Ts>(property: Property<Ts>, params?: Parameters): void;
function assert<Ts>(property: IProperty<Ts>, params?: Parameters): Promise<void> | void;
function assert<Ts>(property: IProperty<Ts>, params?: Parameters) {
function assert<Ts>(property: Property<Ts>, params?: Parameters<Ts>): void;
function assert<Ts>(property: IProperty<Ts>, params?: Parameters<Ts>): Promise<void> | void;
function assert<Ts>(property: IProperty<Ts>, params?: Parameters<Ts>) {
const out = check(property, params);
if (property.isAsync()) return (out as Promise<RunDetails<Ts>>).then(throwIfFailed);
else throwIfFailed(out as RunDetails<Ts>);
Expand Down
14 changes: 8 additions & 6 deletions src/check/runner/Sampler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import toss from './Tosser';
import { pathWalk } from './utils/PathWalker';

/** @hidden */
function toProperty<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, qParams: QualifiedParameters): IProperty<Ts> {
function toProperty<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, qParams: QualifiedParameters<Ts>): IProperty<Ts> {
const prop = !generator.hasOwnProperty('isAsync')
? new Property(generator as Arbitrary<Ts>, () => true)
: (generator as IProperty<Ts>);
Expand All @@ -21,10 +21,12 @@ function toProperty<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, qParams: Quali
/** @hidden */
function streamSample<Ts>(
generator: IProperty<Ts> | Arbitrary<Ts>,
params?: Parameters | number
params?: Parameters<Ts> | number
): IterableIterator<Ts> {
const qParams: QualifiedParameters = QualifiedParameters.readOrNumRuns(params);
const tossedValues: Stream<() => Shrinkable<Ts>> = stream(toss(toProperty(generator, qParams), qParams.seed));
const qParams: QualifiedParameters<Ts> = QualifiedParameters.readOrNumRuns(params);
const tossedValues: Stream<() => Shrinkable<Ts>> = stream(
toss(toProperty(generator, qParams), qParams.seed, qParams.examples)
);
if (qParams.path.length === 0) {
return tossedValues.take(qParams.numRuns).map(s => s().value);
}
Expand All @@ -45,7 +47,7 @@ function streamSample<Ts>(
* @param generator {@link IProperty} or {@link Arbitrary} to extract the values from
* @param params Integer representing the number of values to generate or {@link Parameters} as in {@link assert}
*/
function sample<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, params?: Parameters | number): Ts[] {
function sample<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, params?: Parameters<Ts> | number): Ts[] {
return [...streamSample(generator, params)];
}

Expand Down Expand Up @@ -73,7 +75,7 @@ function sample<Ts>(generator: IProperty<Ts> | Arbitrary<Ts>, params?: Parameter
function statistics<Ts>(
generator: IProperty<Ts> | Arbitrary<Ts>,
classify: (v: Ts) => string | string[],
params?: Parameters | number
params?: Parameters<Ts> | number
): void {
const qParams = QualifiedParameters.readOrNumRuns(params);
const recorded: { [key: string]: number } = {};
Expand Down
7 changes: 6 additions & 1 deletion src/check/runner/Tosser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ function lazyGenerate<Ts>(generator: IProperty<Ts>, rng: prand.RandomGenerator,
}

/** @hidden */
export default function* toss<Ts>(generator: IProperty<Ts>, seed: number): IterableIterator<() => Shrinkable<Ts>> {
export default function* toss<Ts>(
generator: IProperty<Ts>,
seed: number,
examples: Ts[]
): IterableIterator<() => Shrinkable<Ts>> {
yield* examples.map(e => () => new Shrinkable(e));
let idx = 0;
let rng = prand.mersenne(seed);
for (;;) {
Expand Down
8 changes: 7 additions & 1 deletion src/check/runner/configuration/Parameters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Customization of the parameters used to run the properties
*/
export interface Parameters {
export interface Parameters<T = void> {
/**
* Initial seed of the generator: `Date.now()` by default
*
Expand Down Expand Up @@ -49,4 +49,10 @@ export interface Parameters {
* It can prove very useful to detect pattern in the inputs causing the problem to occur
*/
verbose?: boolean;
/**
* Custom values added at the beginning of generated ones
*
* It enables users to come with examples they want to test at every run
*/
examples?: T[];
}
28 changes: 16 additions & 12 deletions src/check/runner/configuration/QualifiedParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Parameters } from './Parameters';
*
* It handles and set the default settings that will be used by runners.
*/
export class QualifiedParameters {
export class QualifiedParameters<T> {
seed: number;
numRuns: number;
maxSkipsPerRun: number;
Expand All @@ -16,34 +16,37 @@ export class QualifiedParameters {
logger: (v: string) => void;
unbiased: boolean;
verbose: boolean;
examples: T[];

private static readSeed = (p?: Parameters): number => (p != null && p.seed != null ? p.seed : Date.now());
private static readNumRuns = (p?: Parameters): number => {
private static readSeed = <T>(p?: Parameters<T>): number => (p != null && p.seed != null ? p.seed : Date.now());
private static readNumRuns = <T>(p?: Parameters<T>): number => {
const defaultValue = 100;
if (p == null) return defaultValue;
if (p.numRuns != null) return p.numRuns;
if ((p as { num_runs?: number }).num_runs != null) return (p as { num_runs: number }).num_runs;
return defaultValue;
};
private static readMaxSkipsPerRun = (p?: Parameters): number =>
private static readMaxSkipsPerRun = <T>(p?: Parameters<T>): number =>
p != null && p.maxSkipsPerRun != null ? p.maxSkipsPerRun : 100;
private static readTimeout = (p?: Parameters): number | null => (p != null && p.timeout != null ? p.timeout : null);
private static readPath = (p?: Parameters): string => (p != null && p.path != null ? p.path : '');
private static readUnbiased = (p?: Parameters): boolean => p != null && p.unbiased === true;
private static readVerbose = (p?: Parameters): boolean => p != null && p.verbose === true;
private static readLogger = (p?: Parameters): ((v: string) => void) => {
private static readTimeout = <T>(p?: Parameters<T>): number | null =>
p != null && p.timeout != null ? p.timeout : null;
private static readPath = <T>(p?: Parameters<T>): string => (p != null && p.path != null ? p.path : '');
private static readUnbiased = <T>(p?: Parameters<T>): boolean => p != null && p.unbiased === true;
private static readVerbose = <T>(p?: Parameters<T>): boolean => p != null && p.verbose === true;
private static readLogger = <T>(p?: Parameters<T>): ((v: string) => void) => {
if (p != null && p.logger != null) return p.logger;
return (v: string) => {
// tslint:disable-next-line:no-console
console.log(v);
};
};
private static readExamples = <T>(p?: Parameters<T>): T[] => (p != null && p.examples != null ? p.examples : []);

/**
* Extract a runner configuration from Parameters
* @param p Incoming Parameters
*/
static read(p?: Parameters): QualifiedParameters {
static read<T>(p?: Parameters<T>): QualifiedParameters<T> {
return {
seed: QualifiedParameters.readSeed(p),
numRuns: QualifiedParameters.readNumRuns(p),
Expand All @@ -52,7 +55,8 @@ export class QualifiedParameters {
logger: QualifiedParameters.readLogger(p),
path: QualifiedParameters.readPath(p),
unbiased: QualifiedParameters.readUnbiased(p),
verbose: QualifiedParameters.readVerbose(p)
verbose: QualifiedParameters.readVerbose(p),
examples: QualifiedParameters.readExamples(p)
};
}

Expand All @@ -62,7 +66,7 @@ export class QualifiedParameters {
*
* @param p Incoming Parameters or maximal number of runs
*/
static readOrNumRuns(p?: Parameters | number): QualifiedParameters {
static readOrNumRuns<T>(p?: Parameters<T> | number): QualifiedParameters<T> {
if (p == null) return QualifiedParameters.read();
if (typeof p === 'number') return QualifiedParameters.read({ numRuns: p });
return QualifiedParameters.read(p);
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/WithProvidedExamples.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as assert from 'assert';
import * as fc from '../../src/fast-check';

const seed = Date.now();
describe(`WithProvidedExamples (seed: ${seed})`, () => {
it('should fail on one of the provided examples', () => {
// no shrink on examples for the moment
const out = fc.check(fc.property(fc.integer(-100, -1), fc.integer(1, 100), (x, y) => x < y), {
examples: [[0, 1], [42, 42], [1, 100]]
});
assert.ok(out.failed);
assert.deepStrictEqual(out.counterexample, [42, 42]);
});
it('should fail after examples', () => {
const out = fc.check(fc.property(fc.integer(), fc.integer(), (x, y) => x < y), {
examples: [[0, 1], [42, 43], [1, 100]]
});
assert.ok(out.failed);
assert.ok(out.counterexample![0] >= out.counterexample![1]);
});
});
22 changes: 17 additions & 5 deletions test/unit/check/runner/Tosser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Tosser', () => {
it('Should offset the random number generator between calls', () =>
fc.assert(
fc.property(fc.integer(), fc.nat(100), (seed, start) => {
const s = stream(toss(wrap(stubArb.forwardArray(4)), seed));
const s = stream(toss(wrap(stubArb.forwardArray(4)), seed, []));
const [g1, g2] = [
...s
.drop(start)
Expand All @@ -38,12 +38,12 @@ describe('Tosser', () => {
fc.property(fc.integer(), fc.nat(20), (seed, num) => {
assert.deepStrictEqual(
[
...stream(toss(wrap(stubArb.forward()), seed))
...stream(toss(wrap(stubArb.forward()), seed, []))
.take(num)
.map(f => f().value)
],
[
...stream(toss(wrap(stubArb.forward()), seed))
...stream(toss(wrap(stubArb.forward()), seed, []))
.take(num)
.map(f => f().value)
]
Expand All @@ -53,8 +53,8 @@ describe('Tosser', () => {
it('Should not depend on the order of iteration', () =>
fc.assert(
fc.property(fc.integer(), fc.nat(20), (seed, num) => {
const onGoingItems1 = [...stream(toss(wrap(stubArb.forward()), seed)).take(num)];
const onGoingItems2 = [...stream(toss(wrap(stubArb.forward()), seed)).take(num)];
const onGoingItems1 = [...stream(toss(wrap(stubArb.forward()), seed, [])).take(num)];
const onGoingItems2 = [...stream(toss(wrap(stubArb.forward()), seed, [])).take(num)];
assert.deepStrictEqual(
onGoingItems2
.reverse()
Expand All @@ -64,5 +64,17 @@ describe('Tosser', () => {
);
})
));
it('Should offset toss with the provided examples', () =>
fc.assert(
fc.property(fc.integer(), fc.nat(20), fc.array(fc.integer()), (seed, num, examples) => {
const noExamplesProvided = [
...stream(toss(wrap(stubArb.forward()), seed, [])).take(num - examples.length)
].map(f => f().value);
const examplesProvided = [...stream(toss(wrap(stubArb.forward()), seed, examples)).take(num)].map(
f => f().value
);
assert.deepStrictEqual([...examples, ...noExamplesProvided].slice(0, num), examplesProvided);
})
));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import * as fc from '../../../../../lib/fast-check';

import { QualifiedParameters } from '../../../../../src/check/runner/configuration/QualifiedParameters';

const extract = (conf: QualifiedParameters) => {
const extract = <T>(conf: QualifiedParameters<T>) => {
const { logger, ...others } = conf;
return others;
};
const extractExceptSeed = (conf: QualifiedParameters) => {
const extractExceptSeed = <T>(conf: QualifiedParameters<T>) => {
const { seed, ...others } = extract(conf);
return others;
};
Expand All @@ -19,7 +19,8 @@ const parametersArbitrary = fc.record(
timeout: fc.nat(),
path: fc.array(fc.nat()).map(arr => arr.join(':')),
unbiased: fc.boolean(),
verbose: fc.boolean()
verbose: fc.boolean(),
examples: fc.array(fc.nat())
},
{ withDeletedKeys: true }
);
Expand Down

0 comments on commit ef47d39

Please sign in to comment.