diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index 64c0d91..219d3e9 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -487,6 +487,43 @@ console.log('主题:通过'); // 有输出 那正是要看见的事(顺手删两条判据、某条被跳过、`check` 实现被改坏)。 代价:**故意删判据时要同步改数字**(这是一次显式的、能被复核的编辑,可以接受)。 +### 条数登记校验**曾经只在"绿"的那条路上**(dsh 2026-09-19 设备在场时发现) + +上面那条校验原来写在**最后一个 `else`** 里("退出码 0"那条路)。而 `r.status !== 0` +会**先**在 `else if` 里 `reds.push(...)` 并**跳过**它 ⇒ **文件越红,它的条数登记越没人守。** + +**实测**(同刻、设备在场、一次全套): + +``` +test/narrow-layout.test.mjs: 登记=64 实际=88 exit=0 绿 ⇒ 校验生效(报了) +test/nav-merge.test.mjs: 登记=8 实际=9 exit=0 绿 ⇒ 校验生效(报了) +test/background.test.mjs: 登记=43 实际=44 exit=0 绿 ⇒ 校验生效(报了) +test/harmony-presets.test.mjs: 登记=5 实际=6 exit=0 绿 ⇒ 校验生效(报了) +test/harmony-admin.test.mjs: 登记=22 实际=27 exit=1 ★ 红 ⇒ 校验**走不到**(0 命中) +``` + +⇒ ★ 这是本仓那条母规则的又一例:**"判据在,但走不到"**, +而且方向是**假绿**:`harmony-admin` 那多出来的 5 条判据**不在"被删会红"的保护内**, +而它恰好是红的 ⇒ **只要它一直红,这个缺口就一直是隐形的**; +等它被修绿那天校验才第一次生效,那时多出来的几条**可能早被删了**。 +(同族:`unlisted`/`blind`/`baseline=` 的结论到不了 `verdict`,§16.1。) + +**修法**:把条数校验移进 `else { … }`(红绿都跑;先 push 退出码红,再判条数)。 +⚠️ 但 **broken(崩了/一条条数都没自报)不在这里判** —— `diedWithoutReporting` +已经把它归入 broken,再叠一条"没找到自报条数"只是噪音: +**"判据没答 ≠ 判据答错了"**(§17)。 + +**修后实测**:`harmony-admin` 那一段现在报 +`自报 27 条 > 清单里登记的 22 条 —— 新加的那几条不在"被删会红"的保护内`,`red` 10→11。 +**变异**:把红的 `harmony-admin` 登记数改成 `99` ⇒ 报 `自报 27 条 < 登记的 99 条` ✓; +改成 `27`(对齐)⇒ **不报条数**、只剩"退出码 1" ✓。 + +★ **通用规则**: + +> **校验写在哪条分支上,决定它保护谁。** +> 凡"出错时要额外检查 X"的守卫,先问:**这条分支真红的时候,它还跑得到吗?** +> —— 把校验放在"一切正常"的那条路上,等于**只在没出事时守着**。 + 计数必须写在 **`check()` 内部**:写在调用点或靠扫源码,"实现被换空"就看不见了 —— 上面那个反例的 `pass` 会是 0,正是靠这一条才有分辨力。 diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 7f13964..ec7ed35 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -109,7 +109,7 @@ const SUITE = [ ['test/criteria-hygiene.test.mjs', [], 6], // 用户管理页(P4c 同批):动作↔服务端调用同名 / 门禁只认严格 admin / // 启停只发 status / 「受限」徽标口径 / 页面零写死色值 / 接线(纯逻辑真被调用) - ['test/harmony-admin.test.mjs', ['--experimental-strip-types', '--no-warnings'], 22], + ['test/harmony-admin.test.mjs', ['--experimental-strip-types', '--no-warnings'], 27], // P4c 图片上传:阈值与两档策略 / 失败必带原因 / 退档判定只有一处 / // release 都 await / 解码按目标尺寸 / multipart 字段名 / 上传后重新同步 ['test/harmony-imageprep.test.mjs', ['--experimental-strip-types', '--no-warnings'], 30], @@ -513,8 +513,27 @@ for (const [file, flags, expected] of SUITE) { '\n 常见成因:语法/标识符错(`X is not defined`)、import 写错、编译不过、' + '\n **按相对路径读文件但 cwd 不对**(`ENOENT: … open \'src/index.css\'`)。' + '\n 用于变体验证时:broken **不算这次变异有效**,要重做。'); - } else if (r.status !== 0) reds.push(`${file}(退出码 ${r.status})`); - else { + } else { + if (r.status !== 0) reds.push(`${file}(退出码 ${r.status})`); + /* + * ★★ 「条数登记」校验必须在**这里**(红绿都跑),**不能留在下面那个 `else` 里**。 + * + * 实测(dsh 2026-09-19,设备在场时跑全套发现的): + * `test/harmony-admin.test.mjs` **登记 22 条、实际 27 条**(差 5 条, + * 那 5 条**不在"被删会红"的保护内**),而**它当时是红的**(1 fail)⇒ + * 走的是上面 `退出码 1` 那条分支 ⇒ **这条校验一次都没被评估**: + * `grep '自报.*harmony-admin' dev.log` ⇒ **0 命中** + * (同时刻另有 4 个**绿的**文件条数不符,它们**全都正常报了** ⇒ + * 说明不是校验写错,是**它在红的那条路上到不了**。) + * + * ★ 这正是本仓反复消的那个形状:**"判据在,但走不到"** —— + * 而且方向是**假绿**:一个文件越红,它的登记数越没人守; + * 等到它被修绿那天,那条校验才第一次生效 —— 那时"多出来的几条"可能早被删了。 + * (同族:`unlisted`/`blind`/`baseline=` 的结论到不了 `verdict`,§16.1。) + * + * ⚠️ 但 broken(崩了/没自报)**不在这里**判:`diedWithoutReporting` 已把它吃掉, + * 再叠一条"没找到自报条数"只是噪音 ——"判据没答 ≠ 判据答错了"(§17)。 + */ /* * ★ 这里**只保留"没自报条数"的那条红**;**任何累加都不在这里做** —— * 投影之后累加根本不作为语句存在,上面那条 push 出来的记录是唯一的信息来源。 @@ -582,8 +601,8 @@ for (const [file, flags, expected] of SUITE) { } } } - /* + * ─── 投影:把 `records` 算成那四个计数器(**这里是它们唯一的"来处"**)─── * * ★ 为什么是这个形状(pi 2026-09-15 建议,我采纳并实测):