repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
test/fuzz.ts
Rawimport { readdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { assertProperty, fc } from "./property.ts";
export { fc };
/** Hostile strings: arbitrary UTF-16 units, control chars, long runs. */
export const hostileString: fc.Arbitrary<string> = fc.string({
unit: "binary",
maxLength: 8192,
});
export interface FuzzOptions {
seed?: number;
numRuns?: number;
/** Documented error types that are acceptable rejections, not findings. */
allow?: ReadonlyArray<abstract new (...args: never[]) => Error>;
}
/**
* Fuzz a parser over hostile string input. Any throw outside `allow` is a
* finding: fast-check shrinks it to a minimal input and `assertProperty` stores
* it under `<target dir>/regressions/`, where the paired replay test picks it up.
*/
export function fuzz(
targetFile: string,
name: string,
exercise: (input: string) => void,
options: FuzzOptions = {},
): void {
const allow = options.allow ?? [];
assertProperty(
targetFile,
name,
fc.property(hostileString, (input) => {
try {
exercise(input);
return true;
} catch (error) {
return (
error instanceof Error && allow.some((kind) => error instanceof kind)
);
}
}),
{
...(options.seed !== undefined ? { seed: options.seed } : {}),
numRuns: options.numRuns ?? 2000,
},
);
}
/** Minimal inputs stored by earlier fuzz runs beside `targetFile`. */
export function fuzzRegressions(targetFile: string): string[] {
const dir = join(dirname(fileURLToPath(targetFile)), "regressions");
try {
return readdirSync(dir)
.filter((file) => file.endsWith(".json"))
.map((file) => {
const record = JSON.parse(readFileSync(join(dir, file), "utf8")) as {
counterexample?: unknown[];
};
return record.counterexample?.[0];
})
.filter((input): input is string => typeof input === "string");
} catch {
return [];
}
}
/** Re-run a stored input; rethrow only errors outside the documented `allow` set. */
export function replay(
exercise: (input: string) => void,
allow: ReadonlyArray<abstract new (...args: never[]) => Error>,
input: string,
): void {
try {
exercise(input);
} catch (error) {
if (
!(error instanceof Error && allow.some((kind) => error instanceof kind))
)
throw error;
}
}
/** True when run directly as `node <target>.fuzz.ts`, not when imported. */
export function isMain(importMetaUrl: string): boolean {
const entry = process.argv[1];
return entry ? importMetaUrl === `file://${entry}` : false;
}