summaryrefslogtreecommitdiff
path: root/pbt/ts/src/property.ts
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud@pankzsoft.com>2025-11-29 09:07:09 +0100
committerArnaud Bailly <arnaud@pankzsoft.com>2025-11-29 09:07:09 +0100
commita8c27de3f4434d8a08e953b95b47a4161f9473ed (patch)
tree426e6840756dc40881a6bb7384e7f7107e3a2d88 /pbt/ts/src/property.ts
parent97e0dfa41a50d9b7af7d1769e2e712625efb3175 (diff)
downloadlambda-nantes-a8c27de3f4434d8a08e953b95b47a4161f9473ed.tar.gz
start simple bank example
Diffstat (limited to 'pbt/ts/src/property.ts')
-rw-r--r--pbt/ts/src/property.ts119
1 files changed, 95 insertions, 24 deletions
diff --git a/pbt/ts/src/property.ts b/pbt/ts/src/property.ts
index 7714747..7b3ee59 100644
--- a/pbt/ts/src/property.ts
+++ b/pbt/ts/src/property.ts
@@ -1,30 +1,31 @@
-import Prando from "prando";
+import Prando from 'prando';
type Result = 'OK' | 'Falsified' | 'Exception';
-type ShrinkResult<A> = { counterexample: A, shrinks: number };
+type ShrinkResult<A> = {counterexample: A; shrinks: number};
type TestResult<A> = {
- seed: number | null,
- result: Result,
- counterexample: ShrinkResult<A> | null
+ seed: number | null;
+ result: Result;
+ counterexample: ShrinkResult<A> | null;
};
type Predicate<A> = (a: A) => boolean;
-export type Gen<A> = (rng: Prando) => ((size: number) => A);
+export type Gen<A> = (rng: Prando) => (size: number) => A;
type Shrinker<A> = (a: A) => A[];
-type Property<A> = (seed: number,
- size: number) => TestResult<A>;
+type Property<A> = (seed: number, size: number) => TestResult<A>;
const MAX_SUCCESS = 100;
-function findMinimalCounterExample<A>(x: A,
+function findMinimalCounterExample<A>(
+ x: A,
predicate: Predicate<A>,
shrinker: Shrinker<A>,
- depth: number = 0): ShrinkResult<A> {
+ depth: number = 0,
+): ShrinkResult<A> {
let xs = shrinker(x);
let counterexample = x;
let shrinks = depth;
@@ -37,33 +38,35 @@ function findMinimalCounterExample<A>(x: A,
}
}
}
- return { counterexample, shrinks };
+ return {counterexample, shrinks};
}
-
export function generate<A>(gen: Gen<A>): A {
let rng = new Prando(Math.random() * 1000);
return gen(rng)(100);
}
export function property<A>(
- predicate: Predicate<A>,
generator: Gen<A>,
- shrinker: Shrinker<A>): Property<A> {
- return (seed: number,
- size: number) => {
+ predicate: Predicate<A>,
+ shrinker?: Shrinker<A>,
+): Property<A> {
+ return (seed: number, size: number) => {
let rng = new Prando(seed);
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 { seed, result: 'Falsified', tests: i + 1, counterexample };
+ let counterexample = {counterexample: x, shrinks: 0};
+ if (shrinker !== undefined) {
+ counterexample = findMinimalCounterExample(x, predicate, shrinker);
+ }
+ return {seed, result: 'Falsified', tests: i + 1, counterexample};
}
size++;
}
- return { seed, result: 'OK', tests: i, counterexample: null };
+ return {seed, result: 'OK', tests: i, counterexample: null};
};
}
@@ -82,6 +85,65 @@ export function listOf<A>(gen: Gen<A>): Gen<A[]> {
};
}
+/// generates a map with exactly size elements
+export function mapOf<K, V>(genK: Gen<K>, genV: Gen<V>): Gen<Map<K, V>> {
+ return (rng: Prando) => {
+ let gk = genK(rng);
+ let gv = genV(rng);
+ return (size: number) => {
+ let result = new Map<K, V>();
+ for (let i = 0; i < size; i++) {
+ result.set(gk(size), gv(size));
+ }
+ return result;
+ };
+ };
+}
+
+/// generates a map with at least size+1 elements. As size is expected
+/// to be positive or null this guarantees at least one element hence
+/// the name
+export function mapOf1<K, V>(genK: Gen<K>, genV: Gen<V>): Gen<Map<K, V>> {
+ return (rng: Prando) => {
+ let gk = genK(rng);
+ let gv = genV(rng);
+ return (size: number) => {
+ let result = new Map<K, V>();
+ for (let i = 0; i <= size; i++) {
+ result.set(gk(size), gv(size));
+ }
+ return result;
+ };
+ };
+}
+
+export function elements<A>(arr: A[]): Gen<A> {
+ return (rng: Prando) => (_size: number) => {
+ let index = rng.nextInt(0, arr.length - 1);
+ return arr[index];
+ };
+}
+
+export function keyValueOf<K, V>(map: Map<K, V>): Gen<[K, V]> {
+ let keys = Array.from(map.keys());
+ return (rng: Prando) => {
+ let gkey = elements(keys)(rng);
+ return (size: number) => {
+ let key = gkey(size);
+ let value = map.get(key)!;
+ return [key, value];
+ };
+ };
+}
+
+export function choose(min: number, max: number): Gen<number> {
+ return (rng: Prando) => (size: number) => rng.nextInt(min, max);
+}
+
+export let someInt: Gen<number> = (rng: Prando) => (size: number) =>
+ rng.nextInt(-size, size);
+
+// basic shrinkers
export function shrinklist<A>(arr: A[]): A[][] {
let result = [];
@@ -100,13 +162,22 @@ export function shrinklist<A>(arr: A[]): A[][] {
return result;
}
-export let someInt: Gen<number> = (rng: Prando) =>
- (size: number) =>
- rng.nextInt(-size, size);
-
export const Property = {
run<A>(prop: Property<A>): TestResult<A> {
let seed = Math.floor(Math.random() * 1000000);
return prop(seed, 0);
- }
+ },
};
+
+// helpers
+
+export function forall<A, B>(
+ gen: Gen<A>,
+ k: (a: A) => Property<B>,
+): Property<B> {
+ return (seed: number, size: number) => {
+ let rng = new Prando(seed);
+ let a = gen(rng)(size);
+ return k(a)(seed, size);
+ };
+}