From efb1c1d2ca39140c351c28b42775363137c3613f Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 20 Sep 2026 04:49:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E7=AC=AC=E5=9B=9B?= =?UTF-8?q?=E4=B8=AA=E8=90=BD=E7=82=B9=20=E2=80=94=E2=80=94=20**=E5=8F=98?= =?UTF-8?q?=E5=BC=82=E6=9D=A1=E7=9B=AE=E7=9A=84=E9=94=9A=E7=82=B9=E5=A4=B1?= =?UTF-8?q?=E6=95=88**=EF=BC=88hits=3D0=EF=BC=89=EF=BC=9A=E5=AE=88?= =?UTF-8?q?=E5=85=B7=E6=9C=89=E9=BD=BF=EF=BC=8C=E5=8D=B4=E4=B8=8D=E5=9C=A8?= =?UTF-8?q?=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi `6aa2b17f` 报的第三例(`unlisted`/`ghosts` 退 0)**我复核后已经在 `6ee9902` 修好了** —— 而它的**父提交正是 pi 报信时读的 `4b841e0`**(差 11 分钟)。又是同一形状的竞态。 但顺着同一条线**审计全部 61 个变异条目的锚点**,发现**还有一个同形状的落点, 而且它在真树上是活的**(不是构造的): hits=0 SettingsPage.ets 「管理入口不做门禁」(jobs-all.json) 根因:该锚点写的是 **6 空格**,而 `c523c21`(09-17 17:43)把该文件**重排成 4 空格** ⇒ 锚点从此命中 0 次(写进清单时 `bcd4f97`(09-15)它是**对的**)。 为什么没人发现:`summary.py` 把「hits=0 … 过期条目」**只打印**、**不进严重度链** ⇒ `rc=0`、`diag=none` ⇒ 套件 `whyLines: (note || status!==0) ? … : []` 为假 ⇒ **整段丢掉**。端到端实测(真树、干净工作树):套件输出里 grep「过期条目」= **0 次**。 ★ 危害是"**一个变异守具被静默关掉**",不是"数字错了":`ran` 少 1、`skipped=1` 是个**中性数字**,读者看不出少了哪一个。而**手工施加那个变异仍能让判据红** (`harmony-admin.test.mjs` `# fail 1`)⇒ **有齿,只是没挂上**。 修法(沿用 `summary.py` 的**唯一严重度链**): · 新增 `mutant-anchor-stale` ⇒ **1 档**(清单/数据该改;**不是** 2 档 —— 照 `env-defaults.sh:25` 的反方向:别让"清单没跟上"冒充环境)。 · 修锚点 6→4 空格 ⇒ `ran` 47→48、`skipped` 1→0,守具重新挂上(实测变异能红)。 · `whyLines` 过滤器加 `^\s*hits=`:只说"有锚点过期"不够,**点名的才是可行动的**。 · 两张表都登记(`UPSTREAM_RC` + `DIAG`,`blocksGreen: true`)+ 真跑案例 (迷你仓库里放一个不含锚点的同名文件 ⇒ `hits=0`)+ 读者侧具名案例。 ★ 射程如实标出:判据只看 **`hits == 0`**,**不看 `hits == -1`**(文件打不开是另一回事, 且迷你夹具里目标文件本来就不在 ⇒ 算进来会造假红)。**`-1` 那一半无判据守着**(真树 0 条)。 变异验证(4 个方向全抓): ① 新档关闭(回到"只打印")⇒ exitcode-selftest rc=1、2 条红 ✓ ② 从 UPSTREAM_RC 删掉新码 ⇒ 反向覆盖点名 ✓ ③ blocksGreen true→false ⇒ 双向口径漂移红 ✓ ④ 放宽成任何 skipped_detail(含 hits=-1)⇒ rc=1、**5 条红**(夹具假红) ⇒ **`h == 0` 这个射程是承重的** ✓ 端到端 A/B(隔离 worktree,同刻对照): A 锚点已修 ⇒ rc=0、diag=none、ran=48/skipped=0、"锚点已失效" grep **0** B 锚点退 6 格 ⇒ rc=1、diag=mutant-anchor-stale、ran=47/skipped=1、grep **2** ★ 通用规则(本仓第 4 次同一形状):**"跑了多少个"与"该跑多少个"之间也要有判据。** 被跳过时 `ran` 只少 1、`skipped` 只多 1 —— 都是中性数字,而"少了哪一个"没有通道。 凡"登记一批东西、再逐个挂上"的结构(变异条目、判据、样本表)都要问: **挂不上的那一个,谁来说?** 自检 5 个各 rc=0;全套 checks=516 pass=511 fail=5 red=5 verdict=red; mutants=48 ran=48 skipped=0 on_new_criteria=36 diag=none baseline=7/7✓。 CRITERIA.md §16.1.1 记这一笔(含 4 个变异与 A/B 表)。 --- client/electron/test/CRITERIA.md | 69 +++++++++++++++++++ .../electron/test/mutants/jobs/jobs-all.json | 4 +- client/electron/test/mutants/summary.py | 30 +++++++- client/electron/test/run-all.mjs | 66 +++++++++++++++++- 4 files changed, 165 insertions(+), 4 deletions(-) diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index 529f26f..7fc4865 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -900,6 +900,75 @@ Go 在同一包内**按源文件顺序**跑测试 ⇒ 登记那条先跑就读 | "未列入清单" 在套件输出 | grep **0** | grep **1** | | `(summary.py)manifest-mismatch` 红 | **0** | **1** | +#### 16.1.1 ★★ 第四个落点:**变异条目的锚点失效**(`hits=0`)—— 守具有齿,却不在位(dsh 2026-09-19) + +pi 报的第 1–3 例都修好之后,我顺着同一条线**审计全部 61 个变异条目的锚点**, +发现**还有一个同形状的落点**,而且它在**真树上是活的**(不是构造的): + +``` +活跃条目 61 个 · 锚点命中 ≠ 1 的:**1 个** + hits=0 client/harmony/.../pages/SettingsPage.ets 「管理入口不做门禁」(jobs-all.json) +``` + +**根因**:该锚点写的是 **6 空格**(` if (this.isAdmin) {`), +而 `f4b8bc1`(09-17 17:43,"邮件详情与「我的」页 1:1 对齐 WebUI")把该文件**重排成 4 空格** +⇒ 锚点从此命中 **0** 次。写进清单时(`e2f117f`,09-15)它是**对的**(当时 6 空格命中 1 次)。 + +**为什么没人发现**:`summary.py` 把「hits=0 … 过期条目」**只打印**, +**完全不进严重度链** ⇒ `rc=0`、`diag=none` ⇒ 套件那边 +`whyLines: (note || status !== 0) ? … : []` 为假 ⇒ **整段丢掉**。 +端到端实测(真树、干净工作树): + +| | 修前(锚点 6 空格) | 修后(锚点 4 空格) | +|---|---|---| +| `mutants= / ran= / skipped=` | `48 / 47 / 1` | `48 / **48** / **0**` | +| `diag=` | `none` | `none`(无此码,因为已修) | +| "过期条目"在套件输出 | grep **0** | — | + +★ **危害是"一个变异守具被静默关掉",不是"数字错了"**: +`ran` 少 1、`skipped=1` 是个**中性数字**,读者看不出少了哪一个、也不知道少了。 +而**手工施加那个变异仍能让判据红**(`harmony-admin.test.mjs` `# fail 1`, +`★ 管理入口没有 isAdmin 门禁 ⇒ 每个普通用户都会看到一个点进去 403 的入口`) +⇒ **守具有齿,只是不再被挂上**。 + +**修法**(与第 3 例同一条链,`summary.py` 的**唯一严重度链**): +新增 `mutant-anchor-stale` ⇒ **1 档**(清单/数据该改), +不是 2 档(那才是环境问题)——照 `env-defaults.sh:25` 的**反方向**:别让"清单没跟上"冒充环境。 + +★ **射程如实标出**:判据只看 **`hits == 0`**,**不看 `hits == -1`**。 +`-1` 是"目标文件打不开"(文件没了/权限),与"锚点写法过期"是两回事, +且 `--exitcode-selftest` 的迷你夹具里 `jobs-one.json` 指向的文件**本来就不在** +(夹具设计,不是缺陷)⇒ 把 `-1` 算进来会造**假红**。 +**`-1` 那一半目前没有判据守着**(真树实测 0 条)。 + +**端到端 A/B**(dsh,隔离 worktree,同刻对照): + +| | A:锚点已修(4 空格) | B:锚点退回 6 空格(制造 hits=0) | +|---|---|---| +| `summary.py` rc | `0` | **`1`** | +| `diag=` | `none` | **`mutant-anchor-stale`** | +| `ran= / skipped=` | `48 / 0` | `47 / 1` | +| "锚点已失效"在套件输出 | grep **0** | grep **2** | + +**变异验证**(dsh,4 个方向全部抓住): + +| 变异 | 结果 | +|---|---| +| 新档关闭(`elif False`)=回到旧的"只打印" | `exitcode-selftest` rc=1、2 条红(`rc=0(期望 rc=1`)✓ | +| `UPSTREAM_RC` 删掉新码 | 反向覆盖点名 + 案例不符 ✓ | +| `blocksGreen: true` → `false` | `两侧口径漂移(双向比对 UPSTREAM_RC ↔ blocksGreen)` ✓ | +| 判定放宽成任何 `skipped_detail`(含 `hits=-1`) | rc=1、**5 条红**(迷你夹具假红)⇒ **`h == 0` 这个射程是承重的** ✓ | + +★★ **通用规则(本仓第 4 次同一形状,值得单列)**: + +> **"跑了多少个"与"该跑多少个"之间,也要有一条判据。** +> 一个变异/检查被**跳过**时,`ran` 只少 1、`skipped` 只多 1 —— 两个**中性数字**, +> 而"少了哪一个、为什么少"没有任何通道。 +> ⇒ 凡有"登记了一批东西、再逐个挂上"的结构(变异条目、判据、样本表), +> 都要问一句:**挂不上的那一个,谁来说?** +> 同族的第三个自查问(pi 归纳):"守着它的判据,它自己的样本由谁守"; +> 这一条是它的**镜像**:"**被守的那个东西没挂上时,谁来说**"。 + ### 16.2 ★★ **同一优先级写了两遍 ⇒ 两个顺序**:不变式只在**对角线**上被验过(pi + dsh 2026-09-18) §16.1 讲"结论到不了决定颜色的那一格"。这一节是它的**镜像**: diff --git a/client/electron/test/mutants/jobs/jobs-all.json b/client/electron/test/mutants/jobs/jobs-all.json index fd07549..fcdb12e 100644 --- a/client/electron/test/mutants/jobs/jobs-all.json +++ b/client/electron/test/mutants/jobs/jobs-all.json @@ -141,8 +141,8 @@ }, { "file": "client/harmony/entry/src/main/ets/pages/SettingsPage.ets", - "pat": " if \\(this\\.isAdmin\\) \\{", - "repl": " if (true) {", + "pat": " if \\(this\\.isAdmin\\) \\{", + "repl": " if (true) {", "test": "admin", "why": "管理入口不做门禁" }, diff --git a/client/electron/test/mutants/summary.py b/client/electron/test/mutants/summary.py index 3b7715f..68a7045 100644 --- a/client/electron/test/mutants/summary.py +++ b/client/electron/test/mutants/summary.py @@ -335,7 +335,7 @@ def main(): # # 顺序按**严重度递减**: # 2 档(**没读数**,连是环境还是代码都判不了):blind / unreadable / baseline-unrunnable|unknown - # 1 档(**数据/清单该修**):unlisted / ghosts / baseline-residue|deleted + # 1 档(**数据/清单该修**):unlisted / ghosts / mutant-anchor-stale / baseline-residue|deleted # 0 档:其余(`baseline-stale`/`baseline-absent` 仍带码,但**不必红**) if blind: severity = ('counts-unusable', 2) @@ -347,6 +347,34 @@ def main(): severity = ('manifest-mismatch', 1) elif bl_diag in ('baseline-residue', 'baseline-deleted'): severity = (bl_diag, 1) + # ★★ 第五类(dsh 2026-09-19,**同一形状的第四个落点**):**变异条目的锚点失效**。 + # + # 这一格原来**只打印**(上面那段 `hits=0 … 过期条目`)、**完全不进严重度链** + # ⇒ `rc=0`、`diag=none` ⇒ 套件那边 `whyLines: (note || status!==0) ? … : []` + # 把它整段丢掉。端到端实测(真树、干净工作树): + # `summary.py` 说「hits=0 … 过期条目」 · 套件输出里 grep **0 次** · `rc=0` + # ⇒ 后果不是"数字错",是**一个变异守具被静默关掉**: + # 实测那一条(`SettingsPage.ets` 的「管理入口不做门禁」)锚点写成 6 空格, + # 而 `f4b8bc1`(09-17 17:43)把该文件重排成 4 空格 ⇒ 锚点从此命中 **0** 次、 + # `ran` 少 1(47 而非 48)、`skipped=1`、**没有任何东西变红**;而手工施加那个 + # 变异**仍能让判据红**(`harmony-admin.test.mjs` `# fail 1`) + # —— 也就是说**守具有齿,只是不再被挂上**。 + # + # 归 **1** 档(不是 2):锚点过期是**清单/数据该改**(去改 `jobs/jobs-*.json` + # 里的锚点,或标 `retired`),与 `manifest-mismatch` 同类; + # 与"权限不够读不到"(2 档)**不是一回事** —— 照 `env-defaults.sh:25` 的 + # 反方向:**别让"清单没跟上"冒充环境**。 + # ★ 放在 `residue`/`deleted` **之后**:同为 1 档时,让最危险的那两个码优先报出来 + # (锚点过期只是"守具没挂上",残留是"守具正在被绕过")。 + # ★ 判据只看 **`hits == 0`**(旧的锚点写法漂移了 ⇒ 去改锚点),**不看 `hits == -1`**: + # `-1` 是"目标文件打不开"(文件没了/权限),与"锚点写法过期"是两回事, + # 而且 `--exitcode-selftest` 的迷你夹具里 `jobs-one.json` 指向的文件**本来就不在** + # (那是夹具的设计,不是仓库的缺陷)⇒ 把 `-1` 也算进来会造出**假红**。 + # ⚠️ 如实标出射程:**`-1` 那一半没有判据守着**(真树实测 0 条,故暂不覆盖)。 + elif any(h == 0 for _, _, h in skipped_detail): + severity = ('mutant-anchor-stale', 1) + elif bl_diag in ('baseline-residue', 'baseline-deleted'): + severity = (bl_diag, 1) elif bl_diag != 'none': severity = (bl_diag, 0) else: diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 07394a1..99b1fde 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -902,6 +902,7 @@ const UPSTREAM_RC = { 'counts-unusable': 2, 'manifest-mismatch': 1, 'baseline-residue': 1, + 'mutant-anchor-stale': 1, 'baseline-stale': 0, 'baseline-unrunnable': 2, 'baseline-unknown': 2, @@ -1050,6 +1051,18 @@ function exitcodeSelfTest() { rmSync(join(d, 'baseline.sha')); } } + /* + * `anchorstale`:**变异条目的锚点失效**(`hits == 0`)—— + * 标准夹具 `jobs-one.json` 指向 `client/harmony/.../api/ApiClient.ets`。 + * 在迷你仓库里建一个**同名但不含那个锚点**的文件 ⇒ 锚点命中 **0** 次 + * (`hits=-1`(文件不存在)是**另一回事**,见 `summary.py` 里那句射程说明) + * ⇒ `diag=mutant-anchor-stale`、`rc=1`:**守具没挂上必须显形**。 + */ + if (mode === 'anchorstale') { + const anchorAbs = join(root, 'client/harmony/entry/src/main/ets/api/ApiClient.ets'); + mkdirSync(dirname(anchorAbs), { recursive: true }); + writeFileSync(anchorAbs, '// 故意不含锚点:模拟"锚点写法漂移后命中 0 次"\n'); + } if (mode === 'blind' || mode === 'unreadable') { // 整棵给 nobody 可达(mkdtemp 默认 700),最后一层按场景收紧 spawnSync('chmod', ['755', root]); @@ -1177,6 +1190,14 @@ function exitcodeSelfTest() { */ ['组合:未列入清单 + 跑不了 sha256sum ⇒ diag=baseline-unrunnable(2 档优先)且 rc=2', ['jobs-one.json'], ['jobs-one.json', 'jobs-extra.json'], 2, 'diag=baseline-unrunnable', 'no-sha256sum'], + /* + * ★★ 第五类(dsh 2026-09-19):**变异条目的锚点失效**(`hits == 0`)。 + * 这条就是它的端到端锚点 —— 也是"反向覆盖"要求的那条真跑案例 + * (加新码若没有案例真跑过,`--exitcode-selftest` 会当场点名)。 + * 期望 `rc=1`(数据/清单该改),**不是 2**(那不是环境问题)。 + */ + ['变异条目锚点失效(hits=0)⇒ rc=1 且 diag=mutant-anchor-stale', + ['jobs-one.json'], ['jobs-one.json'], 1, 'diag=mutant-anchor-stale', 'anchorstale'], ]; let bad = 0; const seenDiags = []; // 反向覆盖用:本组案例**真跑出来**的码(不是声明的) @@ -2019,6 +2040,28 @@ const DIAG = { note: '底本过期(文件与 HEAD 逐字节相同 ⇒ 是正常提交改过,不是残留)', blocksGreen: false, // 不是缺陷;退非零会让"正常提交"天天假红(pi 与我 §七 同意) }, + /* + * ★★ 第五类(dsh 2026-09-19):**变异条目的锚点失效**(`hits == 0`)。 + * + * 这一格原来只出现在 `summary.py` 的 stdout 里,**不进严重度链** ⇒ `rc=0`、 + * `diag=none` ⇒ 套件那边 `whyLines` 的转印条件 `(note || status!==0)` 为假 + * ⇒ **整段丢掉**。端到端实测(真树、干净工作树):`summary.py` 说「hits=0 … + * 过期条目」· 套件输出里 grep **0 次** · `rc=0`。 + * + * 后果是**一个变异守具被静默关掉**:真树那一条(`SettingsPage.ets` 的 + * 「管理入口不做门禁」)锚点是 6 空格,而 `f4b8bc1` 把该文件重排成 4 空格 + * ⇒ 命中 0 次、`ran` 少 1、**没有任何东西红**;但手工施加那个变异**仍能让 + * `harmony-admin.test.mjs` 红** —— 有齿,只是没挂上。 + * + * `blocksGreen: true`:锚点过期是**清单该改**(去改锚点或标 `retired`), + * 与 `manifest-mismatch`(1 档)同类 —— **不是**环境问题(那才是 2 档)。 + */ + 'mutant-anchor-stale': { + checked: true, + note: '**注意:有变异条目的锚点已失效(`hits=0`)** —— 那个变异**根本没被挂上**' + + '(守具有齿但不在位)⇒ 请改锚点或标 `retired`;`ran` 比登记的少', + blocksGreen: true, + }, 'baseline-unknown': { checked: false, note: '**环境:判不了 baseline 那一格**(git 答不了 ⇒ 判不出是残留还是过期)—— ' @@ -2071,7 +2114,12 @@ function summarizeMutants(stdout, status, stderr) { */ const whyLines = (stdout || '').split('\n') .filter(l => !/^RESULT /.test(l.trim())) - .filter(l => /✗✗|⚠️|★ 清单与磁盘不一致|在但读不到|未列入清单|清单里有、磁盘上没有/.test(l)) + /* + * ★ 加 `hits=` 这一支(dsh 2026-09-19):只说"有锚点过期"不够 —— + * 读者要知道**是哪一条**(哪个文件、哪句 why)才能去改。 + * 与 `未列入清单`/`清单里有、磁盘上没有` 同形状:**点名的才是可行动的**。 + */ + .filter(l => /✗✗|⚠️|★ 清单与磁盘不一致|在但读不到|未列入清单|清单里有、磁盘上没有|^\s*hits=/.test(l)) .map(l => l.trim()); /* * 上游加了新码而这里不认识 ⇒ **报警,并且红**(不是静静当成"没问题")。 @@ -2472,6 +2520,15 @@ function mutantsLineSelfTest() { + ' ★ 清单与磁盘不一致 —— 上面的数字**不代表"磁盘上现在有多少个变异体"**:\n' + ' 未列入清单:jobs-UNLISTED-probe.json(新加的 job 文件必须显式加进 jobs.manifest.json)\n' + ' 清单里有、磁盘上没有:jobs-ghost.json\n'; + /* + * ⑤(dsh 2026-09-19)**变异条目锚点失效**:`hits=0` ⇒ 那个变异根本没被挂上。 + * `whyLines` 的过滤器里 `hits=0` 那两行含 `⚠️` ⇒ 会被收进来; + * 关键是**转印条件必须为真**(`diag=mutant-anchor-stale` ⇒ `blocksGreen` ⇒ 红 ⇒ 转印)。 + */ + const anchorstale = 'RESULT mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=mutant-anchor-stale\n' + + '跳过(锚点命中≠1 ⇒ 跑不起来):\n' + + ' hits=0 client/harmony/entry/src/main/ets/pages/SettingsPage.ets 「管理入口不做门禁」 (1 条条目:jobs-all.json)\n' + + ' ⚠️ hits=0 通常是**过期条目**(锚点是旧写法)—— 请标 retired 或删除,\n'; /* * `baseline-stale`:底本过期(与 HEAD 逐字节相同)⇒ **退 0**(不是缺陷), * 但那一格必须能被读者看见。注意它在 `RESULT` 行里有 `baseline=` 段落。 @@ -2498,6 +2555,13 @@ function mutantsLineSelfTest() { */ ['清单与磁盘不一致(status 1,正则匹配)', unlisted, 1, { line: '不代表磁盘上现在有多少个变异体', why: 3 }], + /* + * ⑤(dsh 2026-09-19)**变异条目锚点失效**:`status=1`、正则匹配。 + * 这一格是"守具没挂上"—— 必须把「锚点已失效」转印给读者, + * 否则 `ran` 少 1 而读者只看得到 `mutants=… skipped=1` 这个**中性数字**。 + */ + ['变异条目锚点失效(status 1,正则匹配)', anchorstale, 1, + { line: '锚点已失效', why: 2 }], // ④ 异常退出码但正则匹配(既不是 0/1/2)⇒ 也要留痕,不许静默 ['未知退出码', normal, 3, { line: 'status=3', why: 0 }], // ⑤ 完全没打出 RESULT(如脚本不存在)⇒ 报 status 与 stderr 末行