Skip to content

Commit

Permalink
Add support for async/await and Promises
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Jan 30, 2018
1 parent 89f7787 commit 7b842cf
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 11 deletions.
207 changes: 207 additions & 0 deletions src/check/property/AsyncProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import Arbitrary from '../arbitrary/definition/Arbitrary'
import Shrinkable from '../arbitrary/definition/Shrinkable'
import { tuple } from '../arbitrary/TupleArbitrary'
import MutableRandomGenerator from '../../random/generator/MutableRandomGenerator'
import IProperty from './IProperty'

export class AsyncProperty<Ts> implements IProperty<Ts> {
constructor(readonly arb: Arbitrary<Ts>, readonly predicate: (t: Ts) => Promise<boolean | void>) { }
isAsync = () => true;
generate(mrng: MutableRandomGenerator): Shrinkable<Ts> {
return this.arb.generate(mrng);
}
async run(v: Ts): Promise<string|null> {
try {
const output = await this.predicate(v);
return output == null || output == true ? null : "Property failed by returning false";
}
catch (err) {
if (err instanceof Error && err.stack)
return `${err}\n\nStack trace: ${err.stack}`
return `${err}`;
}
}
}

function asyncProperty<T1>(
arb1: Arbitrary<T1>,
predicate: (t1:T1) => Promise<boolean|void>
): AsyncProperty<[T1]>;

function asyncProperty<T1,T2>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
predicate: (t1:T1,t2:T2) => Promise<boolean|void>
): AsyncProperty<[T1,T2]>;

function asyncProperty<T1,T2,T3>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
predicate: (t1:T1,t2:T2,t3:T3) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3]>;

function asyncProperty<T1,T2,T3,T4>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4]>;

function asyncProperty<T1,T2,T3,T4,T5>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
arb5: Arbitrary<T5>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4,T5]>;

function asyncProperty<T1,T2,T3,T4,T5,T6>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
arb5: Arbitrary<T5>,
arb6: Arbitrary<T6>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4,T5,T6]>;

function asyncProperty<T1,T2,T3,T4,T5,T6,T7>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
arb5: Arbitrary<T5>,
arb6: Arbitrary<T6>,
arb7: Arbitrary<T7>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4,T5,T6,T7]>;

function asyncProperty<T1,T2,T3,T4,T5,T6,T7,T8>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
arb5: Arbitrary<T5>,
arb6: Arbitrary<T6>,
arb7: Arbitrary<T7>,
arb8: Arbitrary<T8>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4,T5,T6,T7,T8]>;

function asyncProperty<T1,T2,T3,T4,T5,T6,T7,T8,T9>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2>,
arb3: Arbitrary<T3>,
arb4: Arbitrary<T4>,
arb5: Arbitrary<T5>,
arb6: Arbitrary<T6>,
arb7: Arbitrary<T7>,
arb8: Arbitrary<T8>,
arb9: Arbitrary<T9>,
predicate: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8,t9:T9) => Promise<boolean|void>
): AsyncProperty<[T1,T2,T3,T4,T5,T6,T7,T8,T9]>;

function asyncProperty<T1,T2,T3,T4,T5,T6,T7,T8,T9>(
arb1: Arbitrary<T1>,
arb2: Arbitrary<T2> | ((t1:T1) => Promise<boolean|void>),
arb3?: Arbitrary<T3> | ((t1:T1,t2:T2) => Promise<boolean|void>),
arb4?: Arbitrary<T4> | ((t1:T1,t2:T2,t3:T3) => Promise<boolean|void>),
arb5?: Arbitrary<T5> | ((t1:T1,t2:T2,t3:T3,t4:T4) => Promise<boolean|void>),
arb6?: Arbitrary<T6> | ((t1:T1,t2:T2,t3:T3,t4:T4,t5:T5) => Promise<boolean|void>),
arb7?: Arbitrary<T7> | ((t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6) => Promise<boolean|void>),
arb8?: Arbitrary<T8> | ((t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7) => Promise<boolean|void>),
arb9?: Arbitrary<T9> | ((t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8) => Promise<boolean|void>),
predicate?: (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8,t9:T9) => Promise<boolean|void>
) {
if (predicate) {
const p = predicate as (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8,t9:T9) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>,
arb5 as Arbitrary<T5>,
arb6 as Arbitrary<T6>,
arb7 as Arbitrary<T7>,
arb8 as Arbitrary<T8>,
arb9 as Arbitrary<T9>
), t => p(t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]));
}
if (arb9) {
const p = arb9 as (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7,t8:T8) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>,
arb5 as Arbitrary<T5>,
arb6 as Arbitrary<T6>,
arb7 as Arbitrary<T7>,
arb8 as Arbitrary<T8>
), t => p(t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7]));
}
if (arb8) {
const p = arb8 as (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6,t7:T7) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>,
arb5 as Arbitrary<T5>,
arb6 as Arbitrary<T6>,
arb7 as Arbitrary<T7>
), t => p(t[0],t[1],t[2],t[3],t[4],t[5],t[6]));
}
if (arb7) {
const p = arb7 as (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5,t6:T6) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>,
arb5 as Arbitrary<T5>,
arb6 as Arbitrary<T6>
), t => p(t[0],t[1],t[2],t[3],t[4],t[5]));
}
if (arb6) {
const p = arb6 as (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>,
arb5 as Arbitrary<T5>
), t => p(t[0],t[1],t[2],t[3],t[4]));
}
if (arb5) {
const p = arb5 as (t1:T1,t2:T2,t3:T3,t4:T4) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>,
arb4 as Arbitrary<T4>
), t => p(t[0],t[1],t[2],t[3]));
}
if (arb4) {
const p = arb4 as (t1:T1,t2:T2,t3:T3) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>,
arb3 as Arbitrary<T3>
), t => p(t[0],t[1],t[2]));
}
if (arb3) {
const p = arb3 as (t1:T1,t2:T2) => Promise<boolean|void>;
return new AsyncProperty(tuple(
arb1 as Arbitrary<T1>,
arb2 as Arbitrary<T2>
), t => p(t[0],t[1]));
}
const p = arb2 as (t1:T1) => Promise<boolean|void>;
return new AsyncProperty(tuple(arb1), t => p(t[0]));
}

export { asyncProperty };
3 changes: 2 additions & 1 deletion src/check/property/IProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import MutableRandomGenerator from '../../random/generator/MutableRandomGenerato
import Stream from '../../stream/Stream'

export default interface IProperty<Ts> {
isAsync(): boolean;
generate(mrng: MutableRandomGenerator): Shrinkable<Ts>;
run(v: Ts): (string|null);
run(v: Ts): Promise<string|null> | (string|null);
}
2 changes: 1 addition & 1 deletion src/check/property/Property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import Arbitrary from '../arbitrary/definition/Arbitrary'
import Shrinkable from '../arbitrary/definition/Shrinkable'
import { tuple } from '../arbitrary/TupleArbitrary'
import MutableRandomGenerator from '../../random/generator/MutableRandomGenerator'
import Stream from '../../stream/Stream'
import IProperty from './IProperty'

export class Property<Ts> implements IProperty<Ts> {
constructor(readonly arb: Arbitrary<Ts>, readonly predicate: (t: Ts) => (boolean | void)) { }
isAsync = () => false;
generate(mrng: MutableRandomGenerator): Shrinkable<Ts> {
return this.arb.generate(mrng);
}
Expand Down
45 changes: 41 additions & 4 deletions src/check/runner/Runner.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import Shrinkable from '../arbitrary/definition/Shrinkable'
import { RandomGenerator, skip_n } from '../../random/generator/RandomGenerator'
import IProperty from '../property/IProperty'
import { Property } from '../property/Property'
import { AsyncProperty } from '../property/AsyncProperty'
import toss from './Tosser'
import { Parameters, QualifiedParameters, RunDetails, successFor, failureFor, throwIfFailed } from './utils/utils'

function shrinkIt<Ts>(property: IProperty<Ts>, value: Shrinkable<Ts>, error: string, num_shrinks: number = 0): [Ts, number, string] {
for (const v of value.shrink()) {
const out = property.run(v.value);
const out = property.run(v.value) as (string|null);
if (out != null) {
return shrinkIt(property, v, out, num_shrinks+1);
}
}
return [value.value, num_shrinks, error];
}
async function asyncShrinkIt<Ts>(property: IProperty<Ts>, value: Shrinkable<Ts>, error: string, num_shrinks: number = 0): Promise<[Ts, number, string]> {
for (const v of value.shrink()) {
const out = await property.run(v.value);
if (out != null) {
return await asyncShrinkIt(property, v, out, num_shrinks+1);
}
}
return [value.value, num_shrinks, error];
}

function check<Ts>(property: IProperty<Ts>, params?: Parameters): RunDetails<Ts> {
function internalCheck<Ts>(property: IProperty<Ts>, params?: Parameters): RunDetails<Ts> {
const qParams = QualifiedParameters.read(params);
const generator = toss(property, qParams.seed);

for (let idx = 0 ; idx < qParams.num_runs ; ++idx) {
const g = generator.next().value;
const out = property.run(g.value);
Expand All @@ -28,10 +38,37 @@ function check<Ts>(property: IProperty<Ts>, params?: Parameters): RunDetails<Ts>
}
return successFor<Ts>(qParams);
}
async function asyncInternalCheck<Ts>(property: IProperty<Ts>, params?: Parameters): Promise<RunDetails<Ts>> {
const qParams = QualifiedParameters.read(params);
const generator = toss(property, qParams.seed);
for (let idx = 0 ; idx < qParams.num_runs ; ++idx) {
const g = generator.next().value;
const out = await property.run(g.value);
if (out != null) {
const [shrinkedValue, numShrinks, error] = await shrinkIt(property, g, out as string);
return failureFor(qParams, idx+1, numShrinks, shrinkedValue, error);
}
}
return successFor<Ts>(qParams);
}

function check<Ts>(property: AsyncProperty<Ts>, params?: Parameters) : Promise<RunDetails<Ts>>;
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>(property: IProperty<Ts>, params?: Parameters) {
return property.isAsync()
? asyncInternalCheck(property, params)
: internalCheck(property, params);
}

function assert<Ts>(property: AsyncProperty<Ts>, params?: Parameters) : Promise<void>;
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) {
const out = check(property, params);
throwIfFailed(out);
return property.isAsync()
? (out as Promise<RunDetails<Ts>>).then(throwIfFailed)
: throwIfFailed(out as RunDetails<Ts>);
}

export { check, assert };
3 changes: 2 additions & 1 deletion src/fast-check.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { check, assert } from './check/runner/Runner'
import { sample, statistics } from './check/runner/Sampler'
import { Parameters, RunDetails } from './check/runner/utils/utils'
import { asyncProperty } from './check/property/AsyncProperty'
import { property } from './check/property/Property'

import Arbitrary from './check/arbitrary/definition/Arbitrary'
Expand Down Expand Up @@ -34,7 +35,7 @@ export {
// check the property
check, assert,
// property definition
property,
property, asyncProperty,
// pre-built arbitraries
boolean, // boolean
float, double, // floating point types
Expand Down
Loading

0 comments on commit 7b842cf

Please sign in to comment.