JianFeeeee
765b77ea06
修复: 五个自检**全仓没有任何入口调用** —— npm test 一个 flag 都不带,判据存在但不在路径上
pi 2026-09-18 报的。**成立,我独立复现了**,读数与 pi 逐字相同。
## 一、缺陷:自检本身到不了任何入口
五个自检(`probe`/`exitcode`/`skip`/`verdict`/`mutants-line`)原来**只**由
`process.argv.includes('--…')` 守着,而**全仓没有任何调用方传那个 flag**:
`npm test` = `node test/run-all.mjs && vitest run && npm run typecheck` —— 一个都不带。
实测:
· 默认跑输出里这些自检的行数 = **0**;
· 把 `UPSTREAM_RC['baseline-residue']` 改坏(表与真脚本不符)⇒ **默认跑**
`checks=459 pass=455 fail=4 skip=0 red=9 …`,与干净树**逐字相同**;带 flag 才 `rc=1`;
· 全仓 `grep` 这些 flag ⇒ **只命中 run-all.mjs 自己内部**的 5 处 guard + 注释,
没有任何调用方、没有任何文档。
⇒ **"跑得动的锚点"确实存在,但它不在任何一条会被人跑的路径上。**
★ 形状与这几轮同族,但**伪装得最好**:guard 看起来就在跑、注释声称"每次跑都钉住"、
`grep` 也找得到那一大段代码 —— **三者都不是"在路径上"的证据**。
前几轮是"结论到不了读者 / 到不了 verdict / 被关掉";
**这次是"自检本身到不了入口"** ⇒ **"判据存在 vs 判据在路径上"**。
★ 而且**方向最危险**:这五个守的恰好是最要紧的几格(`UPSTREAM_RC` 与真脚本一致、
⑤ 双向口径、跳过解析、探针三值、决策顺序)。`--probe-selftest` 从 09-14 就在,
**四天里一次没跑过**。
## 二、修法:进程内接线(pi 建议 ①,但按实测改了做法)
把五个 `if (process.argv.includes(…))` 块改成 `function …SelfTest()`,
`SELFTESTS` 里**直接调用**,红**并入 `reds`**(⇒ 进 `verdict`,不是"只打印不改颜色")。
每个函数后面紧跟一个 CLI 守卫 ⇒ **单独跑时行为完全不变**。
★★ **我没有照 pi 的原话用子进程**,因为它的代价估计不准:
pi 写"多几次进程启动(可忽略)",而我实测 —— **每个 flag 都会先把整个套件跑完**
(guard 在 SUITE 循环**之后**),默认 ~4.3s、带任意 flag ~4.3–4.9s
⇒ **5 个子进程 ≈ 22s**。而自检 body 合计只 ~950ms。
⇒ 进程内调用的实测代价:**4327ms → 4917ms(+590ms)**,而不是 +22s。
## 三、接线时我踩的两个坑(都当场红,照实记)
1. **TDZ,同一个坑第二次**:我把接线块放在定义旁边(行 ~1448),而 `DIAG` 是 `const`、
定义在 ~1721 ⇒ `verdict`/`mutants-line` 两个自检抛
`ReferenceError: Cannot access 'DIAG' before initialization`。
**这个坑我在 `--mutants-line-selftest` 上已经踩过一次**。⇒ 移到所有 `const` 表与
自检函数**之后**,并在注释里写明"任何调用自检的代码都必须排在所有定义之后"。
⚠️ 顺带一个教训:**"接线成功"与"被接的东西真跑了"是两件事** ——
我第一版接线后红数也变了,但其中两条是**抛异常**贡献的,不是自检的结论。
我是看到"(它没打出 RED 行,真因见上)"这句**自己写的话**才回去查的。
2. **吞 stdout 不能吞 stderr**:只吞 `console.log`,`console.error` 不动 ——
否则"自检抛了异常"会被我自己盖住(本仓"错误信息被吞掉"那条老教训)。
## 四★ 变异时发现的**更大一条**:判官坏了,红被静默吞掉(M39)
把 `verdictOf` 改成**恒返回 `'green'`** ⇒ 默认跑
`red=10 … verdict=green`、**exit=0**、而且**红清单一行都不打**(green 分支提前 return)。
⇒ 自检红**确实进了 `reds`**,但"决定要不要报红"的那一格**正是被变异的那一个**:
**10 条红被静默吞掉**。这条老形状这次坏的是**判官自己**。
⇒ 加一条**独立**的安全网(不靠 `verdictOf` 自证):
`reds`/`brokens`/`selfCheckFailed`/`dueFailed` 有任何一个非空而 `verdictOf` 却说绿
⇒ **判官与账本不一致**,直接 `exit(1)` 并说明。
**判据与判官互为对照,谁单独说了都不算数。**
## 五、结构防线:**"写了自检但没接线"必须红**(pi 建议 ②)
pi 那条观察很准且我照抄进注释:本文件**自检 2** 的存在理由就是"防**写好了没接线**",
而它只扫 `*.test.mjs` ⇒ 这五个自检**因为不是 `*.test.mjs` 正好扫不到**。
**同一句话,换了个对象就漏了。**
⇒ 扫自己的源码:`function *SelfTest()` 必须在 `SELFTESTS` 里被调用(`prose`/`stripComments`),
另加"登记了不存在的函数"的反向检查。
## 六、变异验证(都已还原)
| 变异 | 结果 |
|---|---|
| **破坏 `UPSTREAM_RC`**(pi 的那一条) | 默认跑 **red 9→11**,两条自检红并指名不符的码 |
| **M36** 加第 6 个自检但不登记 | **红**:`写了但没接线:sixthSelfTest` |
| **M37** `probeIsDue` → `v === true`(闸锈死) | **红**:`--probe-selftest 红了:unknown…(probeIsDue("unknown") = false)` |
| **M39** `verdictOf` 恒 green(判官坏了) | **红**:`判官与账本不一致…`(修复前 **exit=0、红清单不打印**) |
## 七、验证与状态
· 五个自检**单独跑仍全部 rc=0**(CLI 路径行为不变)。
· 默认跑:`checks=459 pass=455 fail=4 skip=0 red=9 broken=0 unreported=0 verdict=red`、
`mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=none baseline=7/7✓` —— 与 pi 读数一致,
并新增一行 `自检段:5 个自检已在**默认路径**内跑过…源码里 5 个 *SelfTest 函数全部在册`。
· 耗时 **4327 → 4917 ms**(进程内;子进程方案会是 ~22s)。
· 顺手改掉 pi 指出与实现相反的那句注释("每次跑都钉住"⇒ 当时是假的,现在才成立)。
· 残留全清;`git status` 仅本文件;`HEAD=62297e3`。
2026-09-18 07:04:11 +08:00
..
2026-09-13 06:16:59 +08:00
2026-09-14 11:07:03 +08:00
2026-09-15 13:55:01 +08:00
2026-09-18 01:08:04 +08:00
2026-09-18 07:04:11 +08:00
2026-09-08 19:16:35 +08:00
2026-09-14 14:57:18 +08:00
2026-09-14 11:07:03 +08:00
2026-09-08 19:16:35 +08:00
2026-09-14 16:00:34 +08:00
2026-09-08 19:16:35 +08:00
2026-09-12 09:54:31 +08:00
2026-09-08 19:16:35 +08:00
2026-09-14 09:11:36 +08:00
2026-09-12 08:02:30 +08:00