From d735e674e1eef06f5cc305582de38728996a8495 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 14 Sep 2026 19:52:41 +0800 Subject: [PATCH] =?UTF-8?q?docs(dev-tooling):=20=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E7=BA=AA=E5=BE=8B=E9=82=A3=E8=8A=82=20=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E4=B8=89=E7=A7=8D"=E7=9C=8B=E8=B5=B7=E6=9D=A5=E9=AA=8C?= =?UTF-8?q?=E8=BF=87=E4=BA=86"=E7=9A=84=E5=A4=B1=E6=95=88=E5=BD=A2?= =?UTF-8?q?=E6=80=81=20+=20=E5=8F=98=E5=BC=82=E7=BA=AA=E5=BE=8B=20+=20?= =?UTF-8?q?=E4=B8=A4=E6=9D=A1=E5=AE=9E=E7=8E=B0=E7=BA=AA=E5=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi 复核后提出的收尾:跨文件的教训现在没有容器(实证散在三个文件的头注释里)。 先 `ls docs/` 看过:`docs/DEV-TOOLING.md` 已经有"三条判据,以及它们各自防的那个错" 那一节,主题完全相同 —— 所以**并进去而不是新开一篇**(这是 pi 明确要求的形状)。 写的是**可迁移**的那部分,实证不复述、只给索引: - 三种失效形态:**钉装饰**(断言落在注释上)、**分支退化**(绑在会变的环境上)、 **跑不到的分支**(断言在、区分力不在 —— 短路是无声的,比退化更狠); - 变异纪律 5 条:先证明能还原再注入、只对已干净提交的文件做、还原路径不得依赖被测资源、 判据不得用被测物证明自己、别只看过滤后的输出(也**不要**为此引入手抄的期望数常量); - 两条实现纪律:测试文件之间不许互相 import(会二次注册整套用例,实测 475→459); 写点覆盖范围不得取决于入口(这条在这套代码里被漏过三次); 以及"判据过宽和过窄都是坏的"—— 那条"测试文件互相引用"判据自己同时踩过两边。 - 索引指出:`check-deploy-drift.mjs` 的运行输出**就是**部署状态的判据来源, **不要**抄成哈希清单往外发(发给 jianf 的那份在两次提交后就作废了)。 --- docs/DEV-TOOLING.md | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/DEV-TOOLING.md b/docs/DEV-TOOLING.md index 9e756ff..cd23bbe 100644 --- a/docs/DEV-TOOLING.md +++ b/docs/DEV-TOOLING.md @@ -122,3 +122,69 @@ bash deploy/prune-deploy-artifacts.sh --self-check # 判据自检(16 项, **改法不是"下次小心"**:① 传对变量名;② 自检的根目录必须在临时区,否则拒跑; ③ "自检不碰生产"进判据(前后指纹比对)。这条与 `pkill -f` 杀自己同类: **安全装置自己出错时,产出的是一份看着正常的报告**。 + +## 判据纪律:三种"看起来验过了"的失效形态(2026-09-14) + +同一天里,三条自己写的判据各以一种方式失效 —— 它们**都绿着**,但都不再判别任何东西。 +记在这里,因为这是**可迁移**的那部分;具体实证留在各自的头注释里(下面有索引)。 + +### 一、钉装饰:断言落在注释上,不落在机制上 + +`env-guard.test.mjs` 曾对源码文本断言 `/ENOSPC/` 与 `/环境/`,而那段**解释性注释里 +本来就有这两个词** ⇒ 把整段翻译逻辑删掉、只留注释,判据照样绿。 +**改法**:能被反面样本喂的抽成纯函数(`translateEnvError`),断言**行为** +(ENOSPC 要翻译;普通错误必须**原样返回同一个对象** —— "什么都翻译"比不翻译更坏)。 + +### 二、分支退化:判据绑在一个会变的环境上 + +"空间不足 ⇒ exit 2"那条原本靠"本机 `/tmp` 恰好是满的"来验。机器一恢复健康 +(`/tmp` 被清空),那条就自动跳过、**无声失效**。 +**改法**:给被测物开一个**只为测试存在**的开关(`--inject-avail`),让两种机器状态 +都验得了;两个方向都要验(只验"不足⇒2",一个恒报不足的坏守卫也能绿)。 + +### 三、跑不到的分支:断言在,区分力不在 + +按"真实测量"分叉的那版写成了 +`realAvail < MIN ? (不足分支) : (充足分支)`,而**本机真实可用就是 0** ⇒ 永远走 +不足分支。于是把开关**整个忽略掉**,断言**照样绿**。 +比"分支退化"更狠:退化至少留了一行自我声明("此条退化为弱检查"),**短路是无声的** —— +代码看起来两个方向都验了,实际只跑了一个。 +**改法**:不与真实测量比,让**两个探针互为反面**(注入 1 字节必须 exit 2, +注入 128 MiB 必须放行),并断言**输出里的判定词**而不只是退出码 —— +退出码可能与真实状态巧合相同。论证对真实值**任意取值**都成立。 + +### 变异纪律(做"删掉机制看判据红不红"时) + +1. **先证明你能撤回来,再注入变异。** +2. **变异只对"已在 HEAD 里干净提交"的文件做**;还原只走 `git checkout HEAD -- `。 +3. **还原路径不得依赖被测资源。** 一次把备份写进 `/tmp` —— 正是当时被占满的那个资源, + 备份没写成而变异已覆盖源文件;是同一次"手写 `||` 兜底把失败吞掉"才让恢复变得不确定。 +4. **判据不得用被测物证明自己**(第 3 条是它在"还原"上的投影)。 +5. **别只看过滤后的输出。** `grep '^not ok'` 会丢掉"整份文件没跑起来"这个信息 + (语法错时只报一条 `not ok 1 - test/xxx.test.mjs`)。退出码 + `# pass`/`# fail` + 汇总行才是可靠信号 —— 也**不要**为此引入手抄的"期望用例数"常量:手抄常量会过期。 + +### 两条同族的实现纪律 + +- **测试文件之间不许互相 `import`**(启用 `node --test` 时每个文件一个进程、 + 模块导入是进程内的)⇒ 被引的那个文件的用例会在**引用者那个进程里再注册一遍**。 + 实测:从测试文件取夹具,让巨行用例(单条往临时目录写 ~12 MiB)跑了**两次** + (测试总数 475;修完 459)。夹具放**非测试模块**(`lib/session-fixtures.mjs`), + 判据也进套件(`env-guard.test.mjs` 里那条扫描)。 +- **写点要"一处覆盖全部",且覆盖范围不取决于入口。** 同一个"ENOSPC 被翻译成环境问题" + 在这套代码里被漏掉过三次:只包了 5 个写点里的 1 个;`mkdtempSync` 在 try 之外; + 兜底层放在 `main()` 里而**被兜的函数是导出的**。判据过宽过窄都是坏的 —— + 上面"测试文件互相引用"那条判据自己就同时踩了过窄(漏动态导入)和过宽 + (把注释里的散文引用也算违规),最后只能收成"解析真引用"。 + +### 索引(实证在各自文件头注释里,此处不复述) + +- `plugins/pi-mail-bridge/lib/tmp-space.mjs` —— 测量层:`bavail × bsize`、 + **`0` 是"真的没有"而不是"不知道"**(只有 `null` 才是不知道)。 +- `plugins/pi-mail-bridge/lib/env-error.mjs` —— ENOSPC ⇒ 人话;为什么它是纯函数、 + 为什么**不与** `deploy/` 的实现合并(`deploy/` 的独立性比去重值钱)。 +- `deploy/check-deploy-drift.mjs` —— 现场比树(判据自己算,不靠手抄常量)+ + 判据自检的两侧验证 + 为什么它自带兜底层。 +- `deploy/check-deploy-drift.mjs` 的运行输出本身**就是**部署状态的判据来源; + **不要**把它抄成一份哈希清单往外发 —— 抄出来的那一刻就开始过期 + (2026-09-14 发给 `jianf` 的清单在两次提交后就作废了)。