diff options
| author | Arnaud Bailly <arnaud@pankzsoft.com> | 2025-12-01 09:08:49 +0100 |
|---|---|---|
| committer | Arnaud Bailly <arnaud@pankzsoft.com> | 2025-12-01 09:08:49 +0100 |
| commit | 8736fba2ab94cd307905585f81f87d4df9eef3e4 (patch) | |
| tree | 39afadbf91b807f385c8f0be64a1b08fb208f23a /pbt | |
| parent | a8c27de3f4434d8a08e953b95b47a4161f9473ed (diff) | |
| download | lambda-nantes-8736fba2ab94cd307905585f81f87d4df9eef3e4.tar.gz | |
more checks on shrinking
Diffstat (limited to 'pbt')
| -rw-r--r-- | pbt/README.md | 3 | ||||
| -rw-r--r-- | pbt/ts/src/bank.ts | 10 | ||||
| -rw-r--r-- | pbt/ts/src/property.test.ts | 34 | ||||
| -rw-r--r-- | pbt/ts/src/property.ts | 45 |
4 files changed, 88 insertions, 4 deletions
diff --git a/pbt/README.md b/pbt/README.md index 3fcafa0..786740a 100644 --- a/pbt/README.md +++ b/pbt/README.md @@ -77,7 +77,8 @@ Quelques liens intéressants glanés au fil de mes recherches sur le sujet : * [PBT in practice](https://harrisongoldste.in/papers/icse24-pbt-in-practice.pdf): une étude à base d'entretiens sur l'efficacité _en pratique_ du PBT * [Integrated vs. type-based shrinking](https://hypothesis.works/articles/integrated-shrinking/) : un post du créateur d'[hypothesis](https://hypothesis.works), outil de PBT en Python, justifiant la _minimisation intégrée_ * Une [présentation](https://slides.com/ktorz/deck-7b00fc) sur le PBT de Matthias Benkort aka. [KtorZ](https://x.com/_KtorZ_) - +* [Un article](https://seelengrab.github.io/articles/The%20properties%20of%20QuickCheck,%20Hedgehog%20and%20Hypothesis/) comparant QC, Hedgehog et Hypothesis +* Projet de recherche sur le [test aléatoire](https://plum-umd.github.io/projects/random-testing.html) ## Un exemple en Haskell diff --git a/pbt/ts/src/bank.ts b/pbt/ts/src/bank.ts index bee9dfc..c5adc05 100644 --- a/pbt/ts/src/bank.ts +++ b/pbt/ts/src/bank.ts @@ -5,6 +5,16 @@ export type Account = string; export interface Bank { balance: (account: Account) => Option<number>; + + /// Deposit amount into account + /// + /// This function increases the balance of the given account by the + /// specified amount. It's a `void` function hence implicitly + /// stateful: It's expected to change some internal state of the + /// implementing class. + /// + /// @param account The account to deposit into + /// @param amt The amount to deposit deposit: (account: Account, amt: number) => void; withdraw: (account: Account, amt: number) => void; } diff --git a/pbt/ts/src/property.test.ts b/pbt/ts/src/property.test.ts new file mode 100644 index 0000000..c4e089d --- /dev/null +++ b/pbt/ts/src/property.test.ts @@ -0,0 +1,34 @@ +import { + pairOf, + positiveInt, + property, + shrinkPair, + shrinkPositive, + someInt, +} from './property'; + +// from https://github.com/jlink/shrinking-challenge/blob/main/challenges/difference.md +const predicate1: (pair: [number, number]) => boolean = ([x, y]) => + x < 10 || x != y; + +describe('Find minimal counterexample', () => { + test('do not shrink example if no shrinker given', () => { + const result = property(pairOf(positiveInt), predicate1)(42, 1); + expect(result.result).toBe('Falsified'); + expect(result.counterexample).toBeNull; + }); + + test('shrink example if shrinker given', () => { + const result = property( + pairOf(positiveInt), + predicate1, + shrinkPair(shrinkPositive, shrinkPositive), + )(42, 1); + expect(result.result).toBe('Falsified'); + if (result.counterexample !== null) { + expect(result.counterexample.counterexample).toStrictEqual([10, 10]); + } else { + fail('no counterexample'); + } + }); +}); diff --git a/pbt/ts/src/property.ts b/pbt/ts/src/property.ts index 7b3ee59..500c423 100644 --- a/pbt/ts/src/property.ts +++ b/pbt/ts/src/property.ts @@ -20,7 +20,7 @@ type Property<A> = (seed: number, size: number) => TestResult<A>; const MAX_SUCCESS = 100; -function findMinimalCounterExample<A>( +export function findMinimalCounterExample<A>( x: A, predicate: Predicate<A>, shrinker: Shrinker<A>, @@ -117,6 +117,7 @@ export function mapOf1<K, V>(genK: Gen<K>, genV: Gen<V>): Gen<Map<K, V>> { }; } +/// Select randomly one element from given list export function elements<A>(arr: A[]): Gen<A> { return (rng: Prando) => (_size: number) => { let index = rng.nextInt(0, arr.length - 1); @@ -124,6 +125,7 @@ export function elements<A>(arr: A[]): Gen<A> { }; } +/// Select one key-value pair from given map export function keyValueOf<K, V>(map: Map<K, V>): Gen<[K, V]> { let keys = Array.from(map.keys()); return (rng: Prando) => { @@ -136,12 +138,27 @@ export function keyValueOf<K, V>(map: Map<K, V>): Gen<[K, V]> { }; } +/// Choose number between some bounds. 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); +/// Choose some random integer. +export let someInt: Gen<number> = (rng: Prando) => (size: number) => { + const bound = Math.pow(2, size); + return rng.nextInt(-bound, bound); +}; + +/// Choose some random positive integer. +export let positiveInt: Gen<number> = (rng: Prando) => (size: number) => { + const bound = Math.pow(2, Math.round(Math.sqrt(size))); + return rng.nextInt(0, bound); +}; + +/// Generate a pair of generated values +export function pairOf<A>(gen: Gen<A>): Gen<[A, A]> { + return (rng: Prando) => (size: number) => [gen(rng)(size), gen(rng)(size)]; +} // basic shrinkers export function shrinklist<A>(arr: A[]): A[][] { @@ -162,6 +179,28 @@ export function shrinklist<A>(arr: A[]): A[][] { return result; } +export function shrinkPositive(i: number): number[] { + if (i == 0) return []; + + return [i - 1, Math.round(Math.sqrt(i))]; +} + +// shrink pair +export function shrinkPair<A, B>( + shrinkA: Shrinker<A>, + shrinkB: Shrinker<B>, +): Shrinker<[A, B]> { + return ([x, y]) => { + const shrinkx = shrinkA(x); + const shrinky = shrinkB(y); + // assumes x >= y + const resulta: [A, B][] = shrinkx.map(e => [e, y]); + const resultb: [A, B][] = shrinky.map(e => [x, e]); + const resultboth: [A, B][] = shrinkx.map((e, i) => [e, shrinky[i]]); + return resulta.concat(resultb, resultboth); + }; +} + export const Property = { run<A>(prop: Property<A>): TestResult<A> { let seed = Math.floor(Math.random() * 1000000); |
