/** Step log of the unattended end-to-end self-test: one entry per step with its verdict and timing. */ export interface StepResult { name: string; ok: boolean; ms: number; detail: string; } export interface StepLog { steps: StepResult[]; /** * Runs one step. The body returns its detail text, or throws to fail. A body may also return * `{ ok: false, detail }` for a failed expectation. Never throws; resolves to the recorded result. */ run(name: string, body: () => Promise): Promise; } const errText = (e: unknown) => (e instanceof Error ? `${e.name}: ${e.message}` : String(e)); export function createStepLog(now: () => number = () => performance.now()): StepLog { const steps: StepResult[] = []; return { steps, async run(name, body) { const t0 = now(); let result: StepResult; try { const r = await body(); const { ok, detail } = typeof r === "string" ? { ok: true, detail: r } : r; result = { name, ok, ms: Math.round(now() - t0), detail }; } catch (e) { result = { name, ok: false, ms: Math.round(now() - t0), detail: errText(e) }; } steps.push(result); return result; }, }; } /** The directory part of an absolute path, whichever separator it uses. */ export function dirOf(path: string): string { const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")); return i <= 0 ? path.slice(0, i + 1) : path.slice(0, i); } export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { if (a.byteLength !== b.byteLength) return false; for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false; return true; }