import {Option, getOrElse, isSome, none, some} from 'fp-ts/lib/Option';
import {LazyArg, pipe} from 'fp-ts/lib/function';
import Prando from 'prando';
type Result = 'OK' | 'Falsified' | 'Exception';
type ShrinkResult = {counterexample: A; shrinks: number};
type TestResult = {
seed: number | null;
result: Result;
labels: string[];
counterexample: ShrinkResult | null;
};
type Predicate = (a: A) => Promise;
export type Gen = (rng: Prando) => (size: number) => A;
type Shrinker = (a: A) => A[];
/// Label test input with some arbitrary string for classification purpose
type Label = (a: A) => string;
/// classify test result
export function classify(results: TestResult): [number, string][] {
if (results.labels.length == 0) return [];
const distribution = new Map();
const result: [number, string][] = [];
results.labels.sort();
results.labels.forEach(s => {
const num = distribution.get(s) || 0;
distribution.set(s, num + 1);
});
distribution.forEach((v, k) => {
result.push([(v / results.labels.length) * 100.0, k]);
});
return result;
}
type Property = (seed: number, size: number) => Promise>;
const MAX_SUCCESS = 100;
export async function findMinimalCounterExample(
x: A,
predicate: Predicate,
shrinker: Shrinker,
depth = 0,
): Promise> {
const xs = shrinker(x);
let counterexample = x;
let shrinks = depth;
for (const y of xs) {
if (!(await predicate(y))) {
const shrink = await findMinimalCounterExample(
y,
predicate,
shrinker,
depth + 1,
);
if (shrink.shrinks > depth) {
counterexample = shrink.counterexample;
shrinks = shrink.shrinks;
}
}
}
return {counterexample, shrinks};
}
export function generate(gen: Gen): A {
const rng = new Prando(Math.random() * 1000);
return gen(rng)(100);
}
export async function property(
generator: Gen,
predicate: Predicate,
shrinker?: Shrinker,
label?: Label,
): Promise> {
return async (seed: number, size: number) => {
const rng = new Prando(seed);
const gen = generator(rng);
const labels: string[] = [];
let i = 0;
for (; i < MAX_SUCCESS; i++) {
const x = gen(size);
if (!(await predicate(x))) {
let counterexample = {counterexample: x, shrinks: 0};
if (shrinker !== undefined) {
counterexample = await findMinimalCounterExample(
x,
predicate,
shrinker,
);
}
return {
seed,
result: 'Falsified',
tests: i + 1,
labels: [],
counterexample,
};
}
if (label !== undefined) {
labels.push(label(x));
}
size++;
}
return {seed, result: 'OK', tests: i, labels, counterexample: null};
};
}
// basic generators
export function listOf(gen: Gen): Gen {
return (rng: Prando) => {
const g = gen(rng);
return (size: number) => {
const result = [];
for (let i = 0; i < size; i++) {
result.push(g(size));
}
return result;
};
};
}
/// generates a map with exactly size elements
export function mapOf(genK: Gen, genV: Gen): Gen