diff --git a/plugins/pi-mail-bridge/test/lib/run-suite.mjs b/plugins/pi-mail-bridge/test/lib/run-suite.mjs index 4e3e557..499787f 100644 --- a/plugins/pi-mail-bridge/test/lib/run-suite.mjs +++ b/plugins/pi-mail-bridge/test/lib/run-suite.mjs @@ -106,6 +106,24 @@ const SELF_CHECK_SAMPLES = [ }, ]; +/** + * 判据自检:拿合成 TAP 样本验 `findDuplicates` 的**分辨力**。 + * + * ★★ 返回失败条数,**不自己 `process.exit`** —— 这样它能在默认路径里被调用。 + * 为什么必须能(pi 2026-09-18 实测报的洞): + * · `package.json` 的 `npm test` = `env-preflight && run-suite`,**不带 `--self-check`**; + * `deploy/install.sh` 也是 `npm test`,同样不带。全仓 `--self-check` 零引用, + * 且本插件下**没有任何 .md** 提到它。 + * · ⇒ 它此前**一次都没在真实路径上跑过**。实测:把 `findDuplicates` 改成恒返回 `[]` + * ⇒ **`npm test` 照样 `exit=0`**(而带 `--self-check` 才红)。 + * · 后果不是小事:`findDuplicates` 是**跨文件重名**那条判据的引擎, + * 而它**唯一**的分辨力判据就是这个自检 ⇒ 它哪天退化成恒空, + * 重名判据会**安静地永远放行**,而整套测试全绿。 + * ⚠️ 判"自检在不在路径上"**不能靠 `grep` 数命中**:本仓库实测 `grep -c '判据自检'` + * 在默认跑里得到 **16 次**,全是若干 `.test.mjs` 的**用例名**恰好含这四个字, + * 而 `selfCheck()` 自己的输出(`^ (通过|失败) `、`干净样本`…)**一次都没有**。 + * **数命中 = 数到的是词,不是调用。** + */ function selfCheck() { let bad = 0; for (const s of SELF_CHECK_SAMPLES) { @@ -118,12 +136,40 @@ function selfCheck() { } } console.log(bad === 0 ? '\n判据自检:全部通过。' : `\n判据自检:${bad} 条失败。`); - process.exit(bad === 0 ? 0 : 1); + return bad; } -if (process.argv.includes('--self-check')) selfCheck(); +// `--self-check`:只跑自检、不跑套件(原有行为不变)。 +if (process.argv.includes('--self-check')) process.exit(selfCheck() === 0 ? 0 : 1); // ── 跑套件 ──────────────────────────────────────────────────────── +/* + * ★★ **默认路径也要先跑自检**(pi 2026-09-18)。 + * + * 顺序要紧:**自检排在跑套件之前**。理由是量纲 —— 自检判的是"引擎的分辨力", + * 套件判的是"代码对不对"。引擎坏了的时候,套件那份结论**本来就不可信**, + * 拿它当主结论是把两件事混在一起(`docs/DEV-TOOLING.md:218` 那条 = 退出码也有量纲)。 + * 而先跑自检还省一整轮测试进程(引擎坏了就没必要跑 509 条)。 + * + * 退出码用本文件已经约定的那一套,并**与套件红分开**: + * 0 = 套件全绿且无重名(且自检通过) + * 1 = 断言失败 / 发现重名(代码问题) + * 2 = 环境(env-preflight 那套)—— 自检失败**不走这一格** + * ★ 自检失败 = **判据本身坏了**,这是"工具问题"而不是"被测代码问题", + * 所以它**不复用 `1`**(那会让 `install.sh` 把"引擎坏了"读成"代码有问题")。 + * 这里用 **3** 表达"这次结论不可信(工具坏了,不是代码坏了)", + * 并在 stderr 里**明说**是哪个量纲 —— 量纲说不清就退化成"又一个红"。 + * (`install.sh` 那侧的退出码语义**本次不动**:它只判"插件装好没有", + * `set -e` 下非零即中止,3 与 1 对它的行为一致;改它的语义是另一件事, + * 且要先想清楚 `install.sh` 该不该为此新增一档。) + */ +const selfCheckBad = selfCheck(); +if (selfCheckBad > 0) { + console.error(`[tap-dup] 判据自检 ${selfCheckBad} 条失败 —— \`findDuplicates\` 的分辨力坏了,` + + '**本次重名判据的结论不可信**(工具坏了,不是被测代码坏了)。'); + process.exit(3); +} + // `--test-reporter=tap` 让报告格式稳定(`--test` 在 TTY 下会自动换格式, // 那样就锚不住结果行)。输出走管道,本进程负责原样转写到 stderr。 const args = ['--test', '--test-reporter=tap', 'test/*.test.mjs'];