import Prando from "prando"; type Result = 'OK' | 'Falsified' | 'Exception'; type ShrinkResult = { counterexample: A, shrinks: number }; type TestResult = { result: Result, counterexample: ShrinkResult | null }; type Predicate = (a: A) => boolean; export type Gen = (rng: Prando) => ((size: number) => A); type Shrinker = (a: A) => A[]; type Property = (rng: Prando, size: number) => TestResult; const MAX_SUCCESS = 100; function findMinimalCounterExample(x: A, predicate: Predicate, shrinker: Shrinker, depth: number = 0): ShrinkResult { let xs = shrinker(x); let counterexample = x; let shrinks = depth; for (let y of xs) { if (!predicate(y)) { console.log("Shrinking with " + y); let shrink = 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 { let rng = new Prando(Math.random() * 1000); return gen(rng)(100); } export function property( predicate: Predicate, generator: Gen, shrinker: Shrinker): Property { return (rng: Prando, size: number) => { let gen = generator(rng); let i = 0; for (; i < MAX_SUCCESS; i++) { let x = gen(size); if (!predicate(x)) { let counterexample = findMinimalCounterExample(x, predicate, shrinker); return { result: 'Falsified', tests: i, counterexample }; } size++; } return { result: 'OK', tests: i, counterexample: null }; }; }