pi 读了 `5bc579f` 之后报了两条新的 + 三条小的,全部认下并落地。
## 一、"开关真的被认"那条判据在 /tmp 被清空后失去分辨力
上一版只在"真实测量不足"那个分支里断言(注入大数必须放行)。问题是:
**"不足"正是机器恢复健康后会消失的条件** —— 那天这条判据就退化成"只验
`--measure` 可用"的弱检查,而它守的恰恰是"开关别静默失效"。
两个方向是对偶的、各守一个机器状态,所以改成**按实测分叉、在两个分支里断言相反的方向**:
真实不足 ⇒ 注入大数必须放行 (开关被忽略则回退测量 ⇒ 2 ≠ 0 ⇒ 红)
真实充足 ⇒ 注入 0 必须 exit 2(开关被忽略则回退测量 ⇒ 0 ≠ 2 ⇒ 红)
量不到就 `assert.fail` 并说明"无法分叉"—— 不静默跳过(跳过会把"失去分辨力"
伪装成"验过了")。另把"端到端"那条的两个方向拆明白:只验"不足⇒2"时,
一个恒报不足的坏守卫也能绿。
## 二、"一处覆盖全部写点"成立的前提是"从 main() 进来"
`selfCheck()` 是**导出**的(用途就是被直接调),而兜住那三处裸写的 catch 在
`main()` 里 ⇒ 任何绕过 `main()` 的调用者撞上 ENOSPC 拿到的仍是原始英文堆栈。
**"覆盖范围取决于我以为的入口"正是这一串 bug 的共同病根**,所以把整段包一层
(`body()` + 统一 catch):与入口无关,`main()` 那个退化为冗余的第二道。
实测:`TMPDIR=/tmp node -e 'import("./deploy/check-deploy-drift.mjs").then(m=>m.selfCheck())'`
现在拿到的是「环境不足…这是环境问题,不是检查器的问题」。
## 三、`--inject-avail=abc` 静默放行(笔误 = 跳过守卫)
`Number('abc')` = NaN ⇒ 判据当"没测到" ⇒ 放行。现在按仓库约定处理:
**非法值 exit 2,未知参数也 exit 2**(`--measure` 少写 `=` 同样炸)。
`null` 仍是合法值("没测到 ⇒ 放行"是有意的),加了判据把这两个方向都钉住。
## 四、三条小的
- 两份实现(`lib/env-error.mjs` 的 `translateEnvError` 与 `deploy/` 的
`describeEnvError`)**不去重**,但两边各写一句"为什么不复用":
`deploy/` 的独立性比去重值钱(那份文件头整段在讲"服务不该依赖仓库是否存在")。
并写明**第三份拷贝出现时再考虑共用**。
- 写点计数口径写进注释:本函数 **6 处写** = `mkdtempSync`×2 + `mk()` 内 ×2
+ 三处裸写。免得与别处"五处"的说法对不上(上一封信里两个实测数字就是这么被误读的)。
- 变异自检的纪律补进 `lib/env-error.mjs` 头注释:**先证明能撤回来再注入变异,
且还原路径不能依赖被测对象**(那次把备份写进 `/tmp` —— 正是当时被占满的资源,
备份没写成而变异已覆盖源文件)。现在只对"已在 HEAD 干净提交"的文件做变异,
还原一律 `git checkout HEAD -- <file>`。
验证:`npm test` **475/475**;`--self-check` 18 条全过;
`TMPDIR=/tmp node deploy/check-deploy-drift.mjs --self-check` ⇒ exit 2 + 人话。
56 lines
3.0 KiB
JavaScript
56 lines
3.0 KiB
JavaScript
/**
|
||
* 「临时目录写不进去」⇒ 人话。**纯函数,可被反面样本喂**。
|
||
*
|
||
* # 为什么它必须是一个函数,而不是散在写点里的 try/catch
|
||
*
|
||
* 2026-09-14 实测:`/tmp` 是满的 tmpfs,`bavail` 一度真是 **0**。此时这条
|
||
* `npm test` 红的是
|
||
*
|
||
* not ok 323 - ★巨大的 message 行不进内存也不影响解析
|
||
* error: 'ENOSPC: no space left on device, write'
|
||
*
|
||
* 那条红的**形状指向内存**(用例名里就写着"不进内存",而它恰好是往临时目录
|
||
* 写文件的用例)⇒ 下一个踩到的人会去 `session-scan.mjs` 找一个**不存在**的
|
||
* 内存缺陷。翻译成人话("这是环境问题,不是内存缺陷")就治这个。
|
||
*
|
||
* # 为什么抽出来(只有一个调用点也值得抽)
|
||
*
|
||
* **不是为了复用,是为了可被反面样本喂**:`translateEnvError` 能被直接喂一个
|
||
* 构造出来的 ENOSPC 错误,验"该翻译的翻译了、不该翻译的原样返回"。
|
||
* 反例:第一版把这个判断留在 `session-scan.test.mjs` 里,判据只能靠**读源码文本**
|
||
* (断言文件里出现 `/ENOSPC/`)—— 而那段解释性注释里本来就有 "ENOSPC" 这个词,
|
||
* 于是**删掉整个翻译逻辑、只留注释,判据照样绿**。这正是
|
||
* `permission-note.test.mjs` 警告过的"钉装饰不钉机制"。
|
||
*
|
||
* # 为什么不去和 `deploy/check-deploy-drift.mjs` 的 `describeEnvError` 合并
|
||
*
|
||
* 判据相同、措辞不同,看起来该合并 —— 但不合:**`deploy/` 的独立性比去重值钱**
|
||
* (那份文件头整段在讲"服务不该依赖仓库是否存在",`deploy/` 下的工具同理,
|
||
* 让它 import 插件目录里的模块等于把它绑死在插件的目录结构上)。
|
||
* **第三份拷贝出现时再考虑共用。**
|
||
*
|
||
* # 变异自检的纪律(这条是踩出来的)
|
||
*
|
||
* 验证本函数"真的被接上"要注入变异(删掉调用、看判据红不红)。做这件事时:
|
||
* **先证明你能撤回来,再注入**;而且**还原路径不能依赖被测对象** ——
|
||
* 2026-09-14 那次把备份写进 `/tmp`(正是当时被占满的那个资源),备份没写成,
|
||
* 变异却已经覆盖了源文件。现在只对"已在 HEAD 里干净提交"的文件做变异,
|
||
* 还原一律走 `git checkout HEAD -- <file>`。
|
||
*
|
||
* @param {unknown} e 捕获到的错误
|
||
* @returns {{ translated: boolean, error: Error }} 翻译过的新错误,或原样返回
|
||
*/
|
||
export function translateEnvError(e) {
|
||
const message = e && typeof e.message === 'string' ? e.message : String(e ?? '');
|
||
const isEnospc = (e && e.code === 'ENOSPC') || /no space left on device/i.test(message);
|
||
if (!isEnospc) return { translated: false, error: e };
|
||
|
||
const err = new Error(
|
||
'环境不足:临时目录写不进去(ENOSPC)—— 这是环境问题,不是内存缺陷。' +
|
||
'药方:TMPDIR=<有空间的目录> npm test'
|
||
);
|
||
err.code = 'ENOSPC';
|
||
err.cause = e;
|
||
return { translated: true, error: err };
|
||
}
|