Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257// Derived from Node.js v24.16.0 (MIT; Copyright Node.js contributors, and Joyent, Inc. and// other Node contributors). See LICENSE-node.// node:internal/test_runner/snapshot.js: SnapshotFile (one per entry file, backed by a// `.snapshot` sibling written as plain JS — `exports[\`key\`] = \`value\`;` lines),// SnapshotManager (resolves the file, serializes, compares or records), and the two// `test.snapshot.*` setters. `t.assert.snapshot`/`t.assert.fileSnapshot` are wired in// test.ts's TestContext#assert getter via createAssert()/createFileAssert(), one// SnapshotManager per harness (test_runner/test.ts's lazy assert-map bootstrap),// matching Node's own lazyAssertObject(harness).//// Node evaluates the snapshot file with vm.createContext + runInContext, handing it a// bare `exports` object; a Function constructor given only `exports` as a parameter is// the same sandboxing this runtime-privileged code needs (it is not guest code// reached through the CSP'd virtual origin — see DESIGN.md's refusals section for what// *is* refused there).import { assert } from "../assert";import { invalidArgType, invalidState } from "../errors";
const kMissingSnapshotTip = "Missing snapshots can be generated by rerunning the command with the --test-update-snapshots flag.";const defaultSerializers: Array<(value: unknown) => unknown> = [(value) => JSON.stringify(value, null, 2)];
function defaultResolveSnapshotPath(testPath: unknown): unknown { if (typeof testPath !== "string") return testPath; return `${testPath}.snapshot`;}
let resolveSnapshotPathFn: (testPath: unknown) => unknown = defaultResolveSnapshotPath;let serializerFns: Array<(value: unknown) => unknown> = defaultSerializers;
// No shared validateFunction/validateObject/validateString/validateArray copy exists// in src/runtime; this module's small local copies mirror internal/validators.js.function validateFunction(value: unknown, name: string): asserts value is (...args: unknown[]) => unknown { if (typeof value !== "function") throw invalidArgType(name, "of type function", value);}function validateObject(value: unknown, name: string): void { if (value === null || Array.isArray(value) || typeof value !== "object") throw invalidArgType(name, "of type object", value);}function validateString(value: unknown, name: string): void { if (typeof value !== "string") throw invalidArgType(name, "of type string", value);}function validateArray(value: unknown, name: string): void { if (!Array.isArray(value)) throw invalidArgType(name, "an instance of Array", value);}function validateFunctionArray(fns: unknown, name: string): void { validateArray(fns, name); for (let i = 0; i < (fns as unknown[]).length; i++) validateFunction((fns as unknown[])[i], `${name}[${i}]`);}
export function setResolveSnapshotPath(fn: unknown): void { validateFunction(fn, "fn"); resolveSnapshotPathFn = fn as (testPath: unknown) => unknown;}
export function setDefaultSnapshotSerializers(serializers: unknown): void { validateFunctionArray(serializers, "serializers"); serializerFns = (serializers as Array<(value: unknown) => unknown>).slice(0);}
/** fs/path sync hooks SnapshotFile needs, bound once per worker from test.ts's * makeTest the way harness.ts's bindProcess binds the guest process. Snapshot files * are read and written through the kernel's own fs, like Node's require('fs'). */export interface SnapshotFsHooks { readFileSync(path: string, encoding: "utf8"): string; writeFileSync(path: string, data: string, encoding: "utf8"): void; mkdirSync(path: string, options: { recursive: boolean }): void; dirname(path: string): string;}let fsHooks: SnapshotFsHooks | null = null;export function bindSnapshotFs(hooks: SnapshotFsHooks): void { fsHooks = hooks;}
function throwReadError(err: unknown, filename: string): never { let msg = `Cannot read snapshot file '${filename}.'`; if ((err as { code?: string } | null)?.code === "ENOENT") msg += ` ${kMissingSnapshotTip}`; throw Object.assign(invalidState(msg), { cause: err, filename });}function throwWriteError(err: unknown, filename: string): never { throw Object.assign(invalidState(`Cannot write snapshot file '${filename}.'`), { cause: err, filename });}function throwSerializationError(input: unknown, err: unknown): never { throw Object.assign(invalidState("The provided serializers did not generate a string."), { input, cause: err });}
function serializeValue(value: unknown, serializers: Array<(value: unknown) => unknown>): unknown { let v = value; for (const fn of serializers) v = fn(v); return v;}
/** Node's escapeSnapshotKey/templateEscape: both escape backslashes, backticks and * `${` the same way (a snapshot id and a snapshot value are both embedded in a * template literal on write) — escapeSnapshotKey additionally runs the id through * JSON.stringify first (so a key with newlines or quotes stays on one line) and * strips the surrounding quotes JSON.stringify added. */function escapeSnapshotKey(str: string): string { let result = JSON.stringify(str).slice(1, -1); result = result.replaceAll("`", "\\`").replaceAll("${", "\\${"); return result;}function templateEscape(str: unknown): string { let result = String(str); result = result.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${"); return result;}
class SnapshotFile { snapshotFile: string; snapshots: Record<string, string> = Object.create(null); #nameCounts = new Map<string, number>(); loaded = false;
constructor(snapshotFile: string) { this.snapshotFile = snapshotFile; }
getSnapshot(id: string): string { if (!(id in this.snapshots)) { throw Object.assign(invalidState(`Snapshot '${id}' not found in '${this.snapshotFile}.' ${kMissingSnapshotTip}`), { snapshot: id, filename: this.snapshotFile, }); } return this.snapshots[id]!; }
setSnapshot(id: string, value: string): void { this.snapshots[escapeSnapshotKey(id)] = value; }
nextId(name: string): string { const count = this.#nameCounts.get(name) ?? 1; this.#nameCounts.set(name, count + 1); return `${name} ${count}`; }
readFile(): void { if (this.loaded) return; try { const source = fsHooks!.readFileSync(this.snapshotFile, "utf8"); const exportsObj: Record<string, unknown> = Object.create(null); new Function("exports", source)(exportsObj); if (exportsObj === null || typeof exportsObj !== "object") throw invalidState(`Malformed snapshot file '${this.snapshotFile}'.`); for (const key in exportsObj) this.snapshots[key] = templateEscape(exportsObj[key]); this.loaded = true; } catch (err) { throwReadError(err, this.snapshotFile); } }
writeFile(): void { try { const keys = Object.keys(this.snapshots).sort(); const output = keys.map((key) => `exports[\`${key}\`] = \`${this.snapshots[key]}\`;\n`).join("\n"); fsHooks!.mkdirSync(fsHooks!.dirname(this.snapshotFile), { recursive: true }); fsHooks!.writeFileSync(this.snapshotFile, output, "utf8"); } catch (err) { throwWriteError(err, this.snapshotFile); } }}
export interface SnapshotAssertContext { filePath?: string; fullName: string;}
export class SnapshotManager { updateSnapshots: boolean; #cache = new Map<string, SnapshotFile>();
constructor(updateSnapshots: boolean) { this.updateSnapshots = updateSnapshots; }
resolveSnapshotFile(entryFile: string | undefined): SnapshotFile { let snapshotFile = this.#cache.get(entryFile ?? ""); if (snapshotFile === undefined) { const resolved = resolveSnapshotPathFn(entryFile); if (typeof resolved !== "string") throw Object.assign(invalidState("Invalid snapshot filename."), { filename: resolved }); snapshotFile = new SnapshotFile(resolved); snapshotFile.loaded = this.updateSnapshots; this.#cache.set(entryFile ?? "", snapshotFile); } return snapshotFile; }
serialize(input: unknown, serializers: Array<(value: unknown) => unknown> = serializerFns): string { try { const value = serializeValue(input, serializers); return `\n${templateEscape(value)}\n`; } catch (err) { throwSerializationError(input, err); } }
serializeWithoutEscape(input: unknown, serializers: Array<(value: unknown) => unknown> = serializerFns): string { try { return serializeValue(input, serializers) as string; } catch (err) { throwSerializationError(input, err); } }
writeSnapshotFiles(): void { if (!this.updateSnapshots) return; for (const snapshotFile of this.#cache.values()) snapshotFile.writeFile(); }
createAssert(): (this: SnapshotAssertContext, actual: unknown, options?: unknown) => void { const manager = this; return function snapshotAssertion(this: SnapshotAssertContext, actual: unknown, options: unknown = {}): void { validateObject(options, "options"); const { serializers = serializerFns } = options as { serializers?: Array<(value: unknown) => unknown> }; validateFunctionArray(serializers, "options.serializers"); const { filePath, fullName } = this; const snapshotFile = manager.resolveSnapshotFile(filePath); const value = manager.serialize(actual, serializers); const id = snapshotFile.nextId(fullName); if (manager.updateSnapshots) { snapshotFile.setSnapshot(id, value); } else { snapshotFile.readFile(); assert.strictEqual(value, snapshotFile.getSnapshot(id)); } }; }
createFileAssert(): (this: SnapshotAssertContext, actual: unknown, path: unknown, options?: unknown) => void { const manager = this; return function fileSnapshotAssertion(this: SnapshotAssertContext, actual: unknown, path: unknown, options: unknown = {}): void { validateString(path, "path"); validateObject(options, "options"); const { serializers = serializerFns } = options as { serializers?: Array<(value: unknown) => unknown> }; validateFunctionArray(serializers, "options.serializers"); const value = manager.serializeWithoutEscape(actual, serializers);
if (manager.updateSnapshots) { try { fsHooks!.mkdirSync(fsHooks!.dirname(path as string), { recursive: true }); fsHooks!.writeFileSync(path as string, value, "utf8"); } catch (err) { throwWriteError(err, path as string); } } else { let expected: string; try { expected = fsHooks!.readFileSync(path as string, "utf8"); } catch (err) { throwReadError(err, path as string); } assert.strictEqual(value, expected); } }; }}