From 03dd55c022c6077b50429b33f2889bbcec9f0435 Mon Sep 17 00:00:00 2001 From: Arnaud Bailly Date: Tue, 2 Dec 2025 09:59:30 +0100 Subject: add simple classification --- pbt/ts/src/index.test.ts | 12 ++++++++++-- pbt/ts/src/index.ts | 4 ++++ pbt/ts/src/property.ts | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 4 deletions(-) (limited to 'pbt/ts/src') 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 = {counterexample: A; shrinks: number}; type TestResult = { seed: number | null; result: Result; + labels: string[]; counterexample: ShrinkResult | null; }; @@ -18,6 +19,28 @@ 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; @@ -57,10 +80,13 @@ 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); @@ -73,11 +99,20 @@ export async function property( 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}; }; } -- cgit v1.3.1