/** * 会话文件的测试夹具(**不是测试模块**)。 * * # 一、为什么放在 `lib/` 而不是 `test/` 里 * * `node --test` 默认**每个文件一个子进程**,但 `import` 是进程内的。所以 * `env-guard.test.mjs` 里 `import { writeSession } from './session-scan.test.mjs'` * 会把**整个 `session-scan.test.mjs` 的用例在那个进程里再注册一遍** —— * 2026-09-14 实测确认:巨行用例(单条写 ~12 MiB)在 TAP 里出现**两次** * (`ok 87` 与 `ok 353`),也就是那条用例被跑了两次、往临时目录写了两次。 * 这台机器的 `/tmp` 正是被这次事件的主角(tmpfs 满)—— 判据自己加倍压它是自伤。 * * 这跟 `translateEnvError` 被抽到 `test/lib/env-error.mjs` 是同一个手法、同一个理由: * **可被别的模块引用,而不被注册进套件。** * * # 二、写点必须全部被兜住 * * `session-scan.test.mjs` 的第一版只把 `writeFileSync` 包在 try 里, * 而 `mkdirSync` 在 try 之外 —— 目录建不出来时(ENOSPC 也可能从这里出来) * 抛的仍是原始英文堆栈。所以这里**整段**包一层,与 `selfCheck()` 那条同一个形状: * **覆盖范围不能取决于"我以为的入口/哪一行"**。 * * ENOSPC 一律翻译成"环境不足"(`test/lib/env-error.mjs`)—— 不管套件是怎么被调起来的 * (`npm test` 会先跑 `test/env-preflight.mjs`,但 `node --test 'test/*.test.mjs'` * 会绕过它,那就只剩这一道)。 */ import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { translateEnvError } from './env-error.mjs'; /** * 写一条会话文件。`lines` 是 header 之后的行(对象,会被 JSON 化)。 * * `write` 只为测试存在(默认 `writeFileSync`):让"ENOSPC 被翻译"这件事 * 能被**行为**判据验证 —— 喂一个必然 ENOSPC 的假写,而不是去 grep 源码文本 * (注释里本来就有 "ENOSPC" 这个词,文本判据会被注释满足)。 * * @param {string} root 根目录 * @param {string} cwdSlug 该 cwd 的子目录名 * @param {string} fileName 文件名 * @param {object} header 会话 header * @param {object[]} lines 后续行 * @param {(file: string, body: string) => void} [write] 写实现(测试用接缝) * @returns {string} 写出的文件路径 */ export function writeSession(root, cwdSlug, fileName, header, lines = [], write = writeFileSync) { const dir = join(root, cwdSlug); const file = join(dir, fileName); const body = [JSON.stringify({ type: 'session', version: 3, ...header })] .concat(lines.map((l) => JSON.stringify(l))) .join('\n'); try { mkdirSync(dir, { recursive: true }); write(file, `${body}\n`); } catch (e) { throw translateEnvError(e).error; } return file; }