修复: 五个自检**全仓没有任何入口调用** —— 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`。
This commit is contained in:
@ -764,7 +764,7 @@ function parseSkips(out) {
|
||||
* "unknown 到底会不会红"。没有这条,`probeIsDue` 哪天被改成 `v === true`
|
||||
* 也没人会发现,而那正是"闸门锈死"的写法。
|
||||
*/
|
||||
if (process.argv.includes('--probe-selftest')) {
|
||||
function probeSelfTest() {
|
||||
const cases = [
|
||||
['ok 可用 → 到期', true, true],
|
||||
['none 不可用 → 不到期', false, false],
|
||||
@ -776,8 +776,10 @@ if (process.argv.includes('--probe-selftest')) {
|
||||
console.log(`${got === want ? 'ok ' : 'RED '} ${what}(probeIsDue(${JSON.stringify(value)}) = ${got})`);
|
||||
if (got !== want) bad++;
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
return bad;
|
||||
}
|
||||
/* ★ 单独跑(CLI)时行为不变:跑完这一个就退出。 */
|
||||
if (process.argv.includes('--probe-selftest')) process.exit(probeSelfTest() ? 1 : 0);
|
||||
|
||||
/*
|
||||
* ★★ **上游退出码的唯一权威**:`diag` 码 → `summary.py` 真跑出来的退出码。
|
||||
@ -840,7 +842,7 @@ const UPSTREAM_RC = {
|
||||
* 逐字节复制进一个临时目录、配上构造的 `jobs/` 与清单,就能在不碰仓库的前提下逐态验证。
|
||||
* 这是"证据走默认路径":验的是**仓库里那份** `summary.py`,不是重写的替身。
|
||||
*/
|
||||
if (process.argv.includes('--exitcode-selftest')) {
|
||||
function exitcodeSelfTest() {
|
||||
const src = join(HERE, 'mutants', 'summary.py');
|
||||
const realJob = join(HERE, 'mutants', 'jobs', 'jobs-one.json');
|
||||
/*
|
||||
@ -1158,8 +1160,10 @@ if (process.argv.includes('--exitcode-selftest')) {
|
||||
console.log(`ok UPSTREAM_RC 的 ${Object.keys(UPSTREAM_RC).length} 个码都有案例真跑验过`);
|
||||
}
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
return bad;
|
||||
}
|
||||
/* ★ 单独跑(CLI)时行为不变:跑完这一个就退出。 */
|
||||
if (process.argv.includes('--exitcode-selftest')) process.exit(exitcodeSelfTest() ? 1 : 0);
|
||||
|
||||
/*
|
||||
* 跳过解析自检(`--skip-selftest`):与上面那些同形状 —— 判的是**解析器的分辨力**。
|
||||
@ -1169,7 +1173,7 @@ if (process.argv.includes('--exitcode-selftest')) {
|
||||
* **表现是"跳过的判据"那一行不打印** —— 而"没打印"和"没有跳过"长得一模一样。
|
||||
* ⇒ 拿合成 TAP 行把三种情形钉住:带原因的、不带原因的、以及**不该被当成跳过的**普通通过行。
|
||||
*/
|
||||
if (process.argv.includes('--skip-selftest')) {
|
||||
function skipSelfTest() {
|
||||
// ★ 调**生产那一个**函数,不抄副本 —— 抄副本的话自检验的是副本(见 parseSkips 的注释)。
|
||||
const parse = parseSkips;
|
||||
const cases = [
|
||||
@ -1197,8 +1201,10 @@ if (process.argv.includes('--skip-selftest')) {
|
||||
(ok ? '' : `(期望 ${JSON.stringify(want)},实际 ${JSON.stringify(got)})`));
|
||||
if (!ok) bad++;
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
return bad;
|
||||
}
|
||||
/* ★ 单独跑(CLI)时行为不变:跑完这一个就退出。 */
|
||||
if (process.argv.includes('--skip-selftest')) process.exit(skipSelfTest() ? 1 : 0);
|
||||
const STATIC_ONLY = [
|
||||
['test/harmony-appearance.test.mjs', '壁纸/令牌/遮罩渲染:观感与运行期换肤要设备', 'device'],
|
||||
['test/harmony-logic.test.mjs', '页面状态机与文案:`.ets` 状态要跑起来才算数', 'device'],
|
||||
@ -1409,6 +1415,37 @@ try {
|
||||
selfCheckFailed = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ★★ **把五个自检本身接进默认路径**(pi 2026-09-18 报的,我独立复现)。
|
||||
*
|
||||
* 缺陷:这五个自检(`probe`/`exitcode`/`skip`/`verdict`/`mutants-line`)原来**只**由
|
||||
* `process.argv.includes('--…')` 守着,而**全仓没有任何调用方传那个 flag** ——
|
||||
* `npm test` = `node test/run-all.mjs && vitest run && npm run typecheck`,**一个 flag 都不带**。
|
||||
* 实测(读数与 pi 逐字相同):
|
||||
* · 默认跑输出里这些自检的行数 = **0**;
|
||||
* · 把 `UPSTREAM_RC['baseline-residue']` 改坏(表与真脚本不符)后**默认跑**
|
||||
* `checks=459 pass=455 fail=4 skip=0 red=9 …` —— 与干净树**逐字相同**;
|
||||
* 带 `--exitcode-selftest` 才 `rc=1`。
|
||||
* ⇒ **"跑得动的锚点"确实存在,但它不在任何一条会被人跑的路径上。**
|
||||
*
|
||||
* ★ 与这几轮那些形状同族,但**伪装得最好**:guard 看起来就像在跑、注释还声称
|
||||
* "每次跑都钉住"、`grep` 也能找到那一大段代码 —— 三者都不是"在路径上"的证据。
|
||||
* 前几轮是"结论到不了读者 / 到不了 `verdict` / 被关掉";
|
||||
* **这次是"自检本身到不了任何入口"** ⇒ 即 **"判据存在 vs 判据在路径上"**。
|
||||
*
|
||||
* ★ 为什么用**进程内调用**,而不是"子进程调自己带 flag"(pi 建议 ① 的原形):
|
||||
* 我实测过代价 —— **每个 flag 都会先把整个套件跑完**(guard 在 SUITE 循环之后),
|
||||
* 所以默认 ~4.3s、带任意 flag ~4.3–4.9s ⇒ **5 个子进程 ≈ 22s**。
|
||||
* pi 说"多几次进程启动(可忽略)"**不准确**,这条我按实测改了做法。
|
||||
* 而自检 body 本身合计只 ~950ms ⇒ 把 guard 改成 `function …SelfTest()` + **直接调**才对。
|
||||
* (上面那五块已改成函数;**单独跑时行为完全不变** —— 每个函数后面紧跟一个 CLI 守卫。)
|
||||
*
|
||||
* ★ 红要并入 `reds`(与 `applyMutants` 同一条路径)⇒ 它进 `verdict`,
|
||||
* 而不是"只打印、不改颜色"(那正是 `diag` 那一轮修过的病)。
|
||||
* ⚠️ `reds` 的语义是"某个判据文件红了",这几条没有文件记录可挂,
|
||||
* 所以措辞里写明**是哪一层**红的,免得读者去找一个不存在的文件。
|
||||
*/
|
||||
|
||||
/*
|
||||
* pi 2026-09-15:这块原先在报文末尾 `process.exit(1)`,而**它在汇总之前** ⇒
|
||||
* 只要到期闸红了,`RESULT` 行、欠账余额、条数不符、以及**所有其它红**都不会打出来。
|
||||
@ -1589,7 +1626,13 @@ try {
|
||||
* "只在我手跑 `runuser -u nobody` 时才经过"的分支,等于**没有判据守着**:
|
||||
* 哪天 `status === 2` 那格被写错(把 2 写成 '2' 的字符串比较、或把顺序调回
|
||||
* "先看正则"),没有任何东西会响,而它恰好是"读数器说自己没读数"的唯一通道。
|
||||
* ⇒ 纯函数 + `--mutants-line-selftest` 把它每次跑都钉住。
|
||||
* ⇒ 纯函数 + `--mutants-line-selftest` 把它钉住。
|
||||
*
|
||||
* ⚠️⚠️ **这句原来写的是"每次跑都钉住",而它与实现相反**(pi 2026-09-18 报的):
|
||||
* 那时这个自检只由 `process.argv.includes('--mutants-line-selftest')` 守着,
|
||||
* 而**全仓没有任何入口传那个 flag** ⇒ 它**一次都没在默认路径上跑过**。
|
||||
* "每次跑都钉住"当时是**假的**。现在才成立:五个自检已被接进默认路径
|
||||
* (见 `SELFTESTS` 那段),并且有一条结构防线要求"写了自检就必须接线"。
|
||||
*
|
||||
* 决策顺序要紧:**先看退出码,再看正则**。
|
||||
* 反过来的话,盲读/部分可读时 `summary.py` 照样打得出一行**看着正常**的
|
||||
@ -1873,7 +1916,7 @@ function applyMutants(sp, reds) {
|
||||
* —— 这比"断言 `DIAG[diag].blocksGreen === true`"强:它证明的是**接线真的通了**,
|
||||
* 而不是"表里写了个 true"(后者在 `r.red` 忘了 push 时照样绿 —— 那正是上一轮的缺陷)。
|
||||
*/
|
||||
if (process.argv.includes('--verdict-selftest')) {
|
||||
function verdictSelfTest() {
|
||||
const clean = { brokens: [], dueFailed: false, selfCheckFailed: false };
|
||||
const cases = [
|
||||
/*
|
||||
@ -2098,8 +2141,10 @@ if (process.argv.includes('--verdict-selftest')) {
|
||||
+ `(applyMutants ${calls} 处、直接调 summarizeMutants ${direct} 处)`);
|
||||
}
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
return bad;
|
||||
}
|
||||
/* ★ 单独跑(CLI)时行为不变:跑完这一个就退出。 */
|
||||
if (process.argv.includes('--verdict-selftest')) process.exit(verdictSelfTest() ? 1 : 0);
|
||||
|
||||
/*
|
||||
* 变异体播报自检(`--mutants-line-selftest`):钉住"**先看退出码,再看正则**"。
|
||||
@ -2111,7 +2156,7 @@ if (process.argv.includes('--verdict-selftest')) {
|
||||
* (我实测过:盲读/部分可读时 `summary.py` 照样打得出一行**看着正常**的
|
||||
* `RESULT mutants=<数字>`,所以这个顺序不是风格问题。)
|
||||
*/
|
||||
if (process.argv.includes('--mutants-line-selftest')) {
|
||||
function mutantsLineSelfTest() {
|
||||
const blind = 'RESULT mutants=NO-READING ran=NO-READING skipped=NO-READING on_new_criteria=NO-READING diag=counts-unusable(**没读数**)\n'
|
||||
+ ' ✗✗ **读不到 jobs/ 目录**(/x/jobs)—— 上面的数字**全部无效**:\n';
|
||||
const partial = 'RESULT mutants=47 ran=0 skipped=47 on_new_criteria=0 diag=counts-unusable(…原始条目 73…)\n'
|
||||
@ -2201,7 +2246,89 @@ if (process.argv.includes('--mutants-line-selftest')) {
|
||||
`(=它确实会被骗,这正是本自检要守的)`);
|
||||
if (!ok) bad++;
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
return bad;
|
||||
}
|
||||
/* ★ 单独跑(CLI)时行为不变:跑完这一个就退出。 */
|
||||
if (process.argv.includes('--mutants-line-selftest')) process.exit(mutantsLineSelfTest() ? 1 : 0);
|
||||
|
||||
/*
|
||||
* ★★ 五个自检的**接线放在这里**(不是定义旁边)—— 这是个我踩过的坑,必须写明:
|
||||
*
|
||||
* `verdictSelfTest` 与 `mutantsLineSelfTest` 读 `DIAG` 表,而 `DIAG` 是 `const`
|
||||
* ⇒ **`const` 不提升**(TDZ)。我第一版把接线块放在 `selfCheckFailed` 那一段旁边
|
||||
* (行 ~1448),而 `DIAG` 定义在 ~1721 ⇒ 两个自检当场
|
||||
* `ReferenceError: Cannot access 'DIAG' before initialization`。
|
||||
* —— 这个坑我在 `--mutants-line-selftest` 上**已经踩过一次**(见那段注释),这是第二次。
|
||||
* ⇒ 任何"调用自检"的代码都必须排在**所有** `const` 表与自检函数定义之后。
|
||||
* (所以这里紧贴生产区段入口:`let mutantsLine` 之前。)
|
||||
*/
|
||||
let selfCheckRuns = 0;
|
||||
{
|
||||
const SELFTESTS = [
|
||||
['probe-selftest', probeSelfTest],
|
||||
['exitcode-selftest', exitcodeSelfTest],
|
||||
['skip-selftest', skipSelfTest],
|
||||
['verdict-selftest', verdictSelfTest],
|
||||
['mutants-line-selftest', mutantsLineSelfTest],
|
||||
];
|
||||
/*
|
||||
* 自检的输出**不是**这个阶段的正常输出(各打几十行 `ok …`)⇒ 吞掉 stdout,
|
||||
* 只在红时把它自己的 RED 行带进报告。
|
||||
* ⚠️ 只吞 `console.log`,**`console.error` 不动** —— 有东西往 stderr 说话时
|
||||
* 不许被我盖住(本仓"错误信息被吞掉"那条老教训)。
|
||||
*/
|
||||
const swallow = console.log;
|
||||
for (const [name, fn] of SELFTESTS) {
|
||||
const seen = [];
|
||||
console.log = (...a) => seen.push(a.join(' '));
|
||||
let failed = 0;
|
||||
try {
|
||||
failed = fn();
|
||||
} catch (e) {
|
||||
failed = 1;
|
||||
console.error(`✗ 自检 ${name} 抛了异常:${e && e.message}`);
|
||||
seen.push(`**自检自己抛了异常**:${e && e.message}`);
|
||||
} finally {
|
||||
console.log = swallow;
|
||||
}
|
||||
if (failed) {
|
||||
const why = seen.filter(l => /^RED /.test(l)).map(l => l.replace(/^RED\s+/, ''));
|
||||
reds.push(`(自检)\`--${name}\` 红了:${why.length ? why.join(';') : '(它没打出 RED 行,真因见上)'}`
|
||||
+ ' —— **自检自己不该红**(它守的是"判据有没有分辨力")');
|
||||
}
|
||||
}
|
||||
selfCheckRuns = SELFTESTS.length;
|
||||
/*
|
||||
* ★★ 结构性防线:**"写了自检但没接线"必须红**(pi 建议 ②,我做成硬的)。
|
||||
*
|
||||
* pi 的原话值得抄下来:本文件**自检 2** 的存在理由就是"防**写好了没接线**",
|
||||
* 而它只扫 `readdirSync(test).filter(f => f.endsWith('.test.mjs'))`
|
||||
* ⇒ 这五个自检因为**不是 `*.test.mjs`**(它们是"自检",不是判据文件)**正好扫不到**。
|
||||
* **同一句话,换了个对象就漏了。**
|
||||
*
|
||||
* ⇒ 扫自己的源码:**每一个 `function …SelfTest()` 都必须在 `SELFTESTS` 里被调用**。
|
||||
* 将来谁再加第 6 个自检却忘了登记,这里立刻红 —— 不必等人去 `grep` 调用方。
|
||||
* 用 `prose`/`stripComments`(本仓既有读取器),不自己搓正则。
|
||||
*/
|
||||
const selfSrc = stripComments(prose(fileURLToPath(import.meta.url)));
|
||||
const declared = [...selfSrc.matchAll(/^function (\w*SelfTest)\(\)/gm)].map(m => m[1]);
|
||||
const wired = new Set(SELFTESTS.map(([, fn]) => fn.name));
|
||||
const unwired = declared.filter(n => !wired.has(n));
|
||||
const phantom = [...wired].filter(n => !declared.includes(n));
|
||||
if (unwired.length) {
|
||||
console.log(`RED 这些自检**写了但没接线**(不在 SELFTESTS 里)⇒ 默认路径不会跑它们:${unwired.join('、')}`);
|
||||
reds.push(`(自检)**写了没接线**:${unwired.join('、')} —— 它们只由 CLI flag 守着,`
|
||||
+ '而全仓没有任何入口传那个 flag(这正是本轮修的那个形状,不许再长出来)');
|
||||
}
|
||||
if (phantom.length) {
|
||||
console.log(`RED SELFTESTS 里登记了不存在的函数:${phantom.join('、')}`);
|
||||
reds.push(`(自检)SELFTESTS 里登记了不存在的函数:${phantom.join('、')}`);
|
||||
}
|
||||
if (!unwired.length && !phantom.length) {
|
||||
console.log(`自检段:${SELFTESTS.length} 个自检已在**默认路径**内跑过`
|
||||
+ `(${SELFTESTS.map(([n]) => `--${n}`).join('、')});`
|
||||
+ `源码里 ${declared.length} 个 \`*SelfTest\` 函数**全部**在册(无"写了没接线")`);
|
||||
}
|
||||
}
|
||||
|
||||
let mutantsLine = '';
|
||||
@ -2311,7 +2438,28 @@ const totalsLine = (verdict) => `RESULT files=${SUITE.length} ran=${ranFilesCoun
|
||||
* 不必动 SUITE 登记数。原来这条条件表达式是**内联**的,
|
||||
* 于是"结论到不了这一格"只能靠人读源码发现(pi 本封就是读出来的)。
|
||||
*/
|
||||
if (verdictOf({ reds, brokens, dueFailed, selfCheckFailed }) === 'green') {
|
||||
/*
|
||||
* ★★ **判官坏了也要看得见**(我自己变异出来的 M39,这条是独立的安全网)。
|
||||
*
|
||||
* 实测:把 `verdictOf` 改成**恒返回 `'green'`**,默认跑得到
|
||||
* `red=10 … verdict=green`、**exit=0**、而且**红清单一行都不打**
|
||||
* (`green` 分支提前 `process.exit(0)`)。
|
||||
* ⇒ 自检红**确实进了 `reds`**,但**决定"要不要报红"的那一格**正是被变异的那一个:
|
||||
* 于是 10 条红被静默吞掉。**这条形状的老朋友** —— 只是这次坏的是**判官自己**。
|
||||
*
|
||||
* ⇒ 不靠 `verdictOf` 自证:这里用**独立**的一行把矛盾挑出来。
|
||||
* `reds`/`brokens`/`selfCheckFailed` 非空而 `verdictOf` 却说绿 ⇒ 判官与账本不一致,
|
||||
* 必须红(而不是相信判官)。**判据与判官互为对照,谁单独说了都不算数。**
|
||||
*/
|
||||
const ledgerBusy = reds.length > 0 || brokens.length > 0 || selfCheckFailed || dueFailed;
|
||||
const judged = verdictOf({ reds, brokens, dueFailed, selfCheckFailed });
|
||||
if (judged === 'green' && ledgerBusy) {
|
||||
console.error('✗ **判官与账本不一致**:`verdictOf` 说 green,但账本上' +
|
||||
`reds=${reds.length} brokens=${brokens.length} selfCheckFailed=${selfCheckFailed} ` +
|
||||
`dueFailed=${dueFailed} ⇒ **不许静默通过**(判官自己可能就是被改坏的那个)。`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (judged === 'green') {
|
||||
// 报**本相位实际跑了**几条,不报清单长度 —— 上面刚写完"不能声称自己做过的事",
|
||||
// 这里如果打 SUITE.length 就是在同一行里犯同一个错(2 条构建相位判据被跳过了)。
|
||||
const ranFiles = SUITE.map(([f]) => f).filter(f => !skippedByPhase.includes(f));
|
||||
|
||||
Reference in New Issue
Block a user