Files
MailUI4Agents/plugins/pi-mail-bridge/lib/session-fixtures.mjs
JianFeeeee f1059c5a6b fix(pi-bridge): 夹具移出测试文件(import 它会二次注册整套用例)+ 测试侧写点全部兜住 + 判据不再往 /tmp 留垃圾
pi 复核了探针形状(论证闭合),又报了三条,全部实测成立。

## 一、从测试文件 import 助手 ⇒ 那套用例被**再注册一遍**(最实质)

`env-guard.test.mjs` 曾从 `session-scan.test.mjs` 取 `writeSession`。`node --test`
默认每个文件一个子进程,模块导入是进程内的 ⇒ 那个文件的 16 条用例在 env-guard
的进程里**又注册了一遍**。

实测确认:TAP 里巨行用例(单条往临时目录写 ~12 MiB)出现**两次**
(`ok 87` / `ok 353`),测试总数 475。**判据自己在加倍压 /tmp** —— 而 /tmp 正是
这次事件的主角。修完:459 条,巨行用例 1 次。

修法就是 pi 指的形状,也正是 `translateEnvError` 那次的同一手法:
夹具移到**非测试模块** `lib/session-fixtures.mjs`(可被引用,不被注册进套件)。

## 二、测试侧写点还是裸的

`makeRoot()` 的 `mkdtempSync`、以及 `writeSession` 里在 try **之外**的 `mkdirSync`
(ENOSPC 也可能从这里出来)⇒ 绕过前置脚本时抛的仍是原始英文堆栈,
而"绕过前置也要说人话"正是这套兜底存在的理由。现在整段包一层,与 `selfCheck()`
同一形状:**覆盖范围不取决于"我以为的哪一行"**。

## 三、判据往共享 /tmp 里留垃圾

`writeSession(tmpdir(), '--probe--', …)` / `'--probe2--'` 每跑一次就留两个目录、
且永不清理。现在改用 `os.tmpdir()`(纯字符串,不 statfs)当根:那两条的创建都被
假写打断 ⇒ 目录根本不会建出来 ⇒ 既不读也不写真实临时目录。

## 四、一条新判据替代原来的文本接线检查

守**机制**:解析测试文件里的模块引用(静态 `from` / 动态 `import()` / `require()`),
任何指向另一个 `.test.mjs` 的引用都算违规 —— 注释里提到文件名不算(注释不会注册用例)。

这条判据自己踩了两次,都留在注释里:
  第 1 版 只匹配静态 from ⇒ 漏掉动态导入;
  第 2 版 "文件里出现别的测试文件名" ⇒ 把**注释里的散文引用**也算成违规
          (本仓库有 3 处这样的注释,逼人删掉有用的注释),
          而且**它被自己注释里的示例字面量扫到**。
**过宽和过窄都是坏的** —— 这正是这一串评审反复出现的同一族错误。

验证:`npm test` **459/459**(少了 16 条重复注册);巨行用例出现 1 次;
`--self-check` 18 条全过;`TMPDIR=/tmp node deploy/check-deploy-drift.mjs --self-check` ⇒ exit 2 + 人话。
2026-09-14 19:51:44 +08:00

62 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 会话文件的测试夹具(**不是测试模块**)。
*
* # 一、为什么放在 `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` 被抽到 `lib/env-error.mjs` 是同一个手法、同一个理由:
* **可被别的模块引用,而不被注册进套件。**
*
* # 二、写点必须全部被兜住
*
* `session-scan.test.mjs` 的第一版只把 `writeFileSync` 包在 try 里,
* 而 `mkdirSync` 在 try 之外 —— 目录建不出来时(ENOSPC 也可能从这里出来)
* 抛的仍是原始英文堆栈。所以这里**整段**包一层,与 `selfCheck()` 那条同一个形状:
* **覆盖范围不能取决于"我以为的入口/哪一行"**。
*
* ENOSPC 一律翻译成"环境不足"(`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;
}