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 + 人话。
100 lines
4.1 KiB
JavaScript
100 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 测试前置自检:**临时目录还有空间吗**。
|
||
*
|
||
* 名字**不带 `.test.`** —— 所以它不会被 `node --test 'test/*.test.mjs'` 收进去
|
||
* 当用例跑;它由 `package.json` 的 `test` 脚本**先**跑一次。
|
||
*
|
||
* # 它在挡什么
|
||
*
|
||
* 2026-09-14 实测:`/tmp` 是 tmpfs,被别的项目/别的 agent 的东西占满,
|
||
* `bavail` 只剩 0.70 MiB。此时 `npm test` 会红一条
|
||
* `★巨大的 message 行不进内存也不影响解析`(`ENOSPC`)—— 那条红**长得像内存缺陷**,
|
||
* 而真相是环境不足。这个脚本把"环境不足"在**跑测试之前**就大声说出来。
|
||
*
|
||
* # 退出码约定(与 `deploy/redeploy-plugin.sh` 同一套)
|
||
*
|
||
* 0 = 环境够用,继续跑测试
|
||
* 2 = **环境问题**(不是断言失败)
|
||
*
|
||
* 写清 `2` 的意义很关键:CI 或人看到 `1` 会去查代码,看到 `2` 才知道去查机器。
|
||
*/
|
||
|
||
import { tmpdir } from 'node:os';
|
||
|
||
import { judgeSpace, measureAvailBytes, MIN_FREE_BYTES } from '../lib/tmp-space.mjs';
|
||
|
||
/*
|
||
* 两个**只为测试存在**的开关(`src/pool.mjs` 的 `workerPath` 是同一手法)。
|
||
*
|
||
* 为什么需要:端到端那条判据要验"不足 ⇒ exit 2",而本机 `/tmp` 现在恰好是满的。
|
||
* 靠"机器恰好是满的"来验,等于把判据绑在一个**会变**的环境上 —— `/tmp` 一被清空,
|
||
* 那条判据就自动跳过、无声失效。开关让同一个行为在任何机器上都验得了。
|
||
*
|
||
* --measure=<dir> 只量并打印 JSON(`{"dir":…,"availBytes":…}`),总是 exit 0
|
||
* --inject-avail=<n> 绕过测量,直接按 `n` 字节判定(`null` 表示"没测到")
|
||
*/
|
||
const argOf = (name) => {
|
||
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
||
return hit ? hit.slice(name.length + 3) : undefined;
|
||
};
|
||
|
||
/** 未知参数一律拒绝(exit 2,与"环境/参数问题"同义)。
|
||
* 静默忽略未知参数会让 `--inject-avail=abc` 这类笔误退化成"没测到 ⇒ 放行" ——
|
||
* 在这条链上就等于**悄悄跳过守卫**。拼错参数必须炸,不能忍。 */
|
||
const KNOWN = ['--measure', '--inject-avail'];
|
||
for (const a of process.argv.slice(2)) {
|
||
if (!KNOWN.some((k) => a.startsWith(`${k}=`))) {
|
||
console.error(`[env-preflight] 不认识这个参数:${a}\n用法:--measure=<dir> | --inject-avail=<字节数|null>`);
|
||
process.exit(2);
|
||
}
|
||
}
|
||
|
||
const measureDir = argOf('measure');
|
||
if (measureDir !== undefined) {
|
||
console.log(JSON.stringify({ dir: measureDir, availBytes: measureAvailBytes(measureDir) }));
|
||
process.exit(0);
|
||
}
|
||
|
||
const injected = argOf('inject-avail');
|
||
const dir = tmpdir();
|
||
let availBytes;
|
||
if (injected === undefined) {
|
||
availBytes = measureAvailBytes(dir);
|
||
} else if (injected === 'null') {
|
||
availBytes = null;
|
||
} else {
|
||
availBytes = Number(injected);
|
||
// `Number('abc')` = NaN ⇒ 判据会当成"没测到"而**放行**。笔误在这条链上等于跳过守卫,
|
||
// 所以非法值按参数错误处理(exit 2),不给它静默放行的机会。
|
||
if (!Number.isFinite(availBytes) || availBytes < 0) {
|
||
console.error(`[env-preflight] --inject-avail 的值不合法:${injected}(要字节数或 null)`);
|
||
process.exit(2);
|
||
}
|
||
}
|
||
const verdict = judgeSpace({ availBytes });
|
||
|
||
if (verdict.ok) {
|
||
console.log(`[env-preflight] 临时目录 ${dir}:${verdict.note}`);
|
||
process.exit(0);
|
||
}
|
||
|
||
console.error(`
|
||
[env-preflight] 环境不足:临时目录写不下,**这是环境不足,不是断言失败**。
|
||
|
||
目录 ${dir}
|
||
可用 ${(availBytes / 1048576).toFixed(1)} MiB
|
||
需要 ${(MIN_FREE_BYTES / 1048576).toFixed(1)} MiB
|
||
判定 ${verdict.note}
|
||
|
||
测试用例会往临时目录写十几 MiB 的文件。空间不够时它报的是 ENOSPC,
|
||
而那看起来像代码缺陷 —— 曾经就是这么误导过一次。
|
||
|
||
药方(任选其一):
|
||
TMPDIR=<有空间的目录> npm test # 换临时目录,最省事
|
||
清理 ${dir} 下不属于你的东西之前先问主人 # 别误删别人的缓存
|
||
|
||
退出码 2 = 环境问题(与 deploy/redeploy-plugin.sh 的 2 同义),不是代码问题。
|
||
`);
|
||
process.exit(2);
|