Process and API-only Playwright harness: binary and image setup, server/CLI runners, fake Docker Engine socket, read-only database inspection, secret redaction in reports, seeded fuzzing, leak detection and harness self-checks.
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||
|
||
/**
|
||
* Small deterministic PRNG (sfc32 seeded through a string hash) for reproducible fuzzing.
|
||
* The run seed comes from $DPSAPI_E2E_SEED (default DEFAULT_SEED); each test derives its own
|
||
* stream from the run seed and its title path, so results do not depend on scheduling.
|
||
*/
|
||
import crypto from 'node:crypto';
|
||
|
||
export class Rng {
|
||
private a: number;
|
||
private b: number;
|
||
private c: number;
|
||
private d: number;
|
||
|
||
/** `seed` is any string; equal seeds produce equal streams. */
|
||
constructor(readonly seed: string) {
|
||
const h = crypto.createHash('sha256').update(seed, 'utf8').digest();
|
||
this.a = h.readUInt32LE(0);
|
||
this.b = h.readUInt32LE(4);
|
||
this.c = h.readUInt32LE(8);
|
||
this.d = h.readUInt32LE(12);
|
||
for (let i = 0; i < 12; i++) this.nextUint32();
|
||
}
|
||
|
||
/** Independent child stream, e.g. rng.fork('descriptions'). */
|
||
fork(label: string): Rng {
|
||
return new Rng(`${this.seed}/${label}`);
|
||
}
|
||
|
||
nextUint32(): number {
|
||
const t = (((this.a + this.b) >>> 0) + this.d) >>> 0;
|
||
this.d = (this.d + 1) >>> 0;
|
||
this.a = this.b ^ (this.b >>> 9);
|
||
this.b = (this.c + (this.c << 3)) >>> 0;
|
||
this.c = ((this.c << 21) | (this.c >>> 11)) >>> 0;
|
||
this.c = (this.c + t) >>> 0;
|
||
return t;
|
||
}
|
||
|
||
/** Uniform float in [0, 1). */
|
||
next(): number {
|
||
return this.nextUint32() / 4294967296;
|
||
}
|
||
|
||
/** Uniform integer in [min, max] (inclusive). */
|
||
int(min: number, max: number): number {
|
||
return min + Math.floor(this.next() * (max - min + 1));
|
||
}
|
||
|
||
bool(pTrue = 0.5): boolean {
|
||
return this.next() < pTrue;
|
||
}
|
||
|
||
pick<T>(items: readonly T[]): T {
|
||
if (items.length === 0) throw new Error('pick from empty array');
|
||
return items[this.int(0, items.length - 1)];
|
||
}
|
||
|
||
/** Fisher–Yates shuffle (returns a new array). */
|
||
shuffle<T>(items: readonly T[]): T[] {
|
||
const a = [...items];
|
||
for (let i = a.length - 1; i > 0; i--) {
|
||
const j = this.int(0, i);
|
||
[a[i], a[j]] = [a[j], a[i]];
|
||
}
|
||
return a;
|
||
}
|
||
|
||
bytes(n: number): Buffer {
|
||
const b = Buffer.alloc(n);
|
||
for (let i = 0; i < n; i++) b[i] = this.nextUint32() & 0xff;
|
||
return b;
|
||
}
|
||
|
||
/** Random string of `length` characters drawn from `alphabet` (code points). */
|
||
string(length: number, alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): string {
|
||
const chars = [...alphabet];
|
||
let s = '';
|
||
for (let i = 0; i < length; i++) s += chars[this.int(0, chars.length - 1)];
|
||
return s;
|
||
}
|
||
}
|
||
|
||
/** Seed of a test: `<run seed>::<title path joined by ' > '>`. */
|
||
export function testSeed(runSeed: string, titlePath: readonly string[]): string {
|
||
return `${runSeed}::${titlePath.join(' > ')}`;
|
||
}
|