diff options
| author | Arnaud Bailly <arnaud@pankzsoft.com> | 2026-08-30 18:13:01 +0200 |
|---|---|---|
| committer | Arnaud Bailly <arnaud@pankzsoft.com> | 2026-09-01 07:59:01 +0200 |
| commit | 32f9be77d18d3ad0cb86e7290c6df7c51ab99ca5 (patch) | |
| tree | 6633ba3302f8f294e4609aedf9ffdd3693b2ac11 /pbt/ts/src | |
| parent | b3285b3f5878d705fcbeb7165097b6623a02f553 (diff) | |
| download | lambda-nantes-32f9be77d18d3ad0cb86e7290c6df7c51ab99ca5.tar.gz | |
Flesh out simple integrated shrinker
Diffstat (limited to 'pbt/ts/src')
| -rw-r--r-- | pbt/ts/src/gen.test.ts | 19 | ||||
| -rw-r--r-- | pbt/ts/src/gen.ts | 73 |
2 files changed, 92 insertions, 0 deletions
diff --git a/pbt/ts/src/gen.test.ts b/pbt/ts/src/gen.test.ts new file mode 100644 index 0000000..eaba36f --- /dev/null +++ b/pbt/ts/src/gen.test.ts @@ -0,0 +1,19 @@ +import Prando from 'prando'; +import {none} from 'fp-ts/lib/Option.js'; +import * as G from './gen.js'; +import {test, expect} from 'vitest'; + +test('can generate and shrink bytes', async () => { + const prng = new Prando(Math.random() * 1000); + const gen = new G.BytesGen(prng); + const bytes: number[][] = []; + for (let i = 1; i < 10; i++) { + bytes.push(gen.generate(i)); + } + console.log('Generated bytes:', bytes); + const replay = gen.replay(); + for (let i = 1; i < 10; i++) { + const replayed = replay.generate(i); + expect(replayed, `Replaying at ${i}`).toEqual(bytes[i - 1]); + } +}); diff --git a/pbt/ts/src/gen.ts b/pbt/ts/src/gen.ts new file mode 100644 index 0000000..531a118 --- /dev/null +++ b/pbt/ts/src/gen.ts @@ -0,0 +1,73 @@ +import {Option, none, some} from 'fp-ts/lib/Option.js'; +import Prando from 'prando'; + +// a shrinkable generator needs to maintain some state, +// namely the sequence of generated values in order to be +// able to backtrack and shrink. + +type GenMode = 'gen' | 'shrink'; + +export interface Gen<A> { + generate(): A; + + generateN(n: number): A[]; +} + +export interface GenBytes { + generate(n: number): number[]; +} + +export class BytesGen implements GenBytes { + prng: Prando; + bytes: number[][]; // the internal sequence of generated values + + constructor(prng: Prando) { + this.prng = prng; + this.bytes = []; + } + + generate(n: number): number[] { + if (n <= 0) { + throw new Error('n must be greater than 0'); + } + return this.generateBytes(n); + } + + generateBytes(n: number): number[] { + const gens = []; + for (let i = 0; i < n; i++) { + gens.push(this.prng.nextInt(0, 255)); + } + this.bytes.push([...gens]); + return gens; + } + + replay(): GenBytes { + return new ReplayBytes(this.bytes); + } +} + +export class ReplayBytes implements GenBytes { + bytes: number[][]; // the internal sequence of generated values + index: number = 0; // the current index in the sequence of generated values + + constructor(bytes: number[][]) { + this.bytes = bytes; + this.index = 0; + } + + generate(n: number): number[] { + if (this.index >= this.bytes.length) { + throw new Error('No more bytes to shrink'); + } + + const chunk = [...this.bytes[this.index++]]; + + if (chunk.length < n) { + const zeros = new Array(n - chunk.length).fill(0); + chunk.unshift(...zeros); + } + + return chunk; + } +} |
