summaryrefslogtreecommitdiff
path: root/pbt/ts
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud@pankzsoft.com>2025-12-02 09:59:30 +0100
committerArnaud Bailly <arnaud@pankzsoft.com>2025-12-02 09:59:30 +0100
commit03dd55c022c6077b50429b33f2889bbcec9f0435 (patch)
tree7d9942a6f344ed119444672bbd0db850aa34dcd6 /pbt/ts
parent67a318eeb886da7b9917d1809464dfdcb8f4e578 (diff)
downloadlambda-nantes-03dd55c022c6077b50429b33f2889bbcec9f0435.tar.gz
add simple classification
Diffstat (limited to 'pbt/ts')
-rw-r--r--pbt/ts/src/index.test.ts12
-rw-r--r--pbt/ts/src/index.ts4
-rw-r--r--pbt/ts/src/property.ts39
3 files changed, 51 insertions, 4 deletions
diff --git a/pbt/ts/src/index.test.ts b/pbt/ts/src/index.test.ts
index b102fe3..f3a51e1 100644
--- a/pbt/ts/src/index.test.ts
+++ b/pbt/ts/src/index.test.ts
@@ -1,5 +1,8 @@
-import {prop_reverse_with_two_lists} from './index';
-import {Property} from './property';
+import {
+ prop_reverse_is_self_inverse,
+ prop_reverse_with_two_lists,
+} from './index';
+import {Property, classify} from './property';
describe('testing lists reversal', () => {
test('reverse of two appended lists is append of each list reversed', async () => {
@@ -8,4 +11,9 @@ describe('testing lists reversal', () => {
);
expect(reverse_append_result.result).toBe('OK');
});
+ test('reverse is self-inverse', async () => {
+ const result = await Property.run(prop_reverse_is_self_inverse);
+ console.log(classify(result));
+ expect(result.result).toBe('OK');
+ });
});
diff --git a/pbt/ts/src/index.ts b/pbt/ts/src/index.ts
index 2f59f0e..7a58ffe 100644
--- a/pbt/ts/src/index.ts
+++ b/pbt/ts/src/index.ts
@@ -63,4 +63,8 @@ export const prop_reverse_is_self_inverse = property(
listOf(someInt),
reverse_is_self_inverse,
shrinklist,
+ l => {
+ const tens = Math.floor(l.length / 10);
+ return '< ' + 10 * (tens + 1);
+ },
);
diff --git a/pbt/ts/src/property.ts b/pbt/ts/src/property.ts
index 635e731..ea7a62b 100644
--- a/pbt/ts/src/property.ts
+++ b/pbt/ts/src/property.ts
@@ -9,6 +9,7 @@ type ShrinkResult<A> = {counterexample: A; shrinks: number};
type TestResult<A> = {
seed: number | null;
result: Result;
+ labels: string[];
counterexample: ShrinkResult<A> | null;
};
@@ -18,6 +19,28 @@ export type Gen<A> = (rng: Prando) => (size: number) => A;
type Shrinker<A> = (a: A) => A[];
+/// Label test input with some arbitrary string for classification purpose
+type Label<A> = (a: A) => string;
+
+/// classify test result
+export function classify<A>(results: TestResult<A>): [number, string][] {
+ if (results.labels.length == 0) return [];
+
+ const distribution = new Map<string, number>();
+ 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<A> = (seed: number, size: number) => Promise<TestResult<A>>;
const MAX_SUCCESS = 100;
@@ -57,10 +80,13 @@ export async function property<A>(
generator: Gen<A>,
predicate: Predicate<A>,
shrinker?: Shrinker<A>,
+ label?: Label<A>,
): Promise<Property<A>> {
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);
@@ -73,11 +99,20 @@ export async function property<A>(
shrinker,
);
}
- return {seed, result: 'Falsified', tests: i + 1, counterexample};
+ return {
+ seed,
+ result: 'Falsified',
+ tests: i + 1,
+ labels: [],
+ counterexample,
+ };
+ }
+ if (label !== undefined) {
+ labels.push(label(x));
}
size++;
}
- return {seed, result: 'OK', tests: i, counterexample: null};
+ return {seed, result: 'OK', tests: i, labels, counterexample: null};
};
}