修复: baseline= 那一格到不了套件(捕获组是前缀 + 过滤器不含它);baseline-unrunnable 退回 rc=0 的自相矛盾;并把「脏/干净/git 答不了」从两态改回三态
pi 2026-09-18 报的两条我都端到端复现了,都成立。修的过程中又发现**第三条**(我自己引入的),以及**自检自己的一处"判据在但走不到"**。 ## 一、① `baseline=` 那一格从来不进套件输出(pi 报的第 ① 条) 两处都实测确认: · 捕获组是**前缀**:`m[1]` = `RESULT mutants=48 ran=47 skipped=1 on_new_criteria=35`,`'baseline' in m[1]` = **False**; · `whyLines` 的过滤器不含 `baseline=`。 ⇒ 两条路都不通。**真·变异残留**时 `baseline=6/7✗**1 个文件既不在底本、也与 HEAD 不同 ⇒ 优先按"变异残留"查**` 在套件输出里 grep = **0 次**,而 `rc=0`、`mutants=48 ran=47 skipped=1` **看着完全正常**(我按你给的形状复现)。 **修法**:不扩正则、也不往过滤器里加关键词(那是"按字面量裁射程",本仓已栽过两次: `criteria-hygiene` 的 import 硬匹配、`prose()` 判"用没用")—— 改为 `RESULT` 行末尾加**结构化诊断码** `diag=<码>`,由 `run-all` 的 `DIAG_NOTE` 表逐码给一句准确的话: | `diag` | rc | 含义 | |---|---|---| | `none` | 0 | 正常 | | `manifest-mismatch` | 1 | 清单与磁盘不一致 | | `baseline-residue` | **1** | **真·变异残留**(与底本不同**且**与 HEAD 不同) | | `baseline-stale` | **0** | 底本过期(与 HEAD 逐字节相同,**不是缺陷**) | | `baseline-unrunnable` | **2** | 跑不了 `sha256sum`(没读数) | | `baseline-unknown` | **2** | git 答不了 ⇒ 判不出残留还是过期(没读数) | | `counts-unusable` | 2 | 读不到 `jobs/` | 码表是**闭合**的:**不认识的码会报警**,所以"上游加了新状态而下游不知道"会立刻显形, 不会静静变成"没问题"。同时把 `baseline=` 的**读数本身**(`6/7✗`)也摘进那一格 —— 端到端实测:残留时 `grep baseline=` 从 **0 → 1**,rc 从 0 → **1**。 ## 二、② `baseline-unrunnable` 原来 rc=0,与我自己的原则相反(pi 报的第 ② 条) 确认。我在 `blind/unreadable` 上定的是"**没读数不是成功 ⇒ 2**",而"跑不了 `sha256sum`"**是同一件事**, 却退 0 ⇒ 走正常路径 ⇒ 那句"是没读数"同样到不了读者。**我把"跑不了"和"没过"分成了两种状态(对), 但第三种状态(跑不了)没给它传播通道** —— 你这句说得准。 ⇒ 现在 `baseline-unrunnable` 与 `baseline-unknown` 都退 **2**, 且措辞由 `diag` 决定而**不是**由 `status` 决定:`status === 2` 那句通用话会说"上面的 mutants 数字**不可信**", 而这里是**假话**(数字照常有效,**只有 baseline 那一格**没读数)。所以措辞一律走 `DIAG_NOTE`。 ## 三★★ 我自己引入的第三条:`git` 三态被读成两态(假警报指向最危险结论) 修 ① 时我要构造 residue,才发现:`git diff --quiet` 退 **1** 有**两种**含义 —— 实测 · 仓库内干净 = **0** · 仓库内已改 = **1** · **非仓库/HEAD 取不到 = 也是 1**(stderr `error: Could not access 'HEAD'`)。 而原代码是 `.returncode != 0` ⇒ **"git 根本没答"被读成"有差异" ⇒ residue**, 也就是这一格**最危险**的读数。我自己在临时目录里跑时就撞到了:`REPO` 解析成 `/` ⇒ 一律报 residue。 ⇒ 加 `can_ask`(`git rev-parse --is-inside-work-tree HEAD`,非仓库退 **128**,可区分): 答不了就报 `baseline-unknown`(**没读数**),**不许**声称 residue。这与上面 §一 那段注释警告的方向一致: **假警报指向最危险的结论**。 ## 四★★ 自检自己的一处"判据在,但走不到" 补 `baseline` 四态锚点时发现:旧 `runIn` 的临时目录是 `/tmp/xxx/`, 而 `summary.py` 用 `REPO = HERE/../../../..` ⇒ **`REPO` 解析成 `/`**(实测打印过)。 于是 `baseline=` 那一格**在原来的四例里从未被真正走成"对"**:`sha256sum -c` 在 `/` 下全部 FAILED、 `git` 也答不了。它们期望 rc=0/1 却看着"通过",是因为**两个错误互相抵消** ("判不了"被读成"有残留" **且** residue 当时退 0)。 ⇒ 重建 `runIn`:按脚本真实位置**嵌套**建目录 + 造一个**迷你真仓库**(`git init` + 一次 commit + 一个可提交可改脏的目标文件),让 `REPO` 落在仓库根上。现在 `baseline=` 的**四态** (对 / 过期 / 残留 / 判不了)**每一条前提都构造得出来**。 ## 五、变异验证(都已还原) | 变异 | 期望 | 结果 | |---|---|---| | **M21** residue 退回 rc=0(pi 报的原始行为) | `真·变异残留⇒1` 红 | **红**,`stale` 照旧绿(两条独立) | | **M22** `unrunnable`/`unknown` 退回 rc=0(pi 本封 ② 的原始行为) | 两条都红 | **都红** | | **M23** `can_ask` 恒真(三态退回两态) | `git 答不了` 红(会把"判不了"说成 residue) | **红**,rc 报 1(正是那个假警报) | ## 六、验证与状态 · **11 条锚点全绿**:一致⇒0 / unlisted⇒1 / ghosts⇒1 / 说明条目⇒0 / 盲读⇒2 / 单文件⇒2 / **无 sha256sum⇒2** / **残留⇒1** / **过期⇒0** / **git 答不了⇒2** / 没有 baseline⇒0。 · `--mutants-line-selftest` **9 例**(含新增 `baseline-stale` 与"不认识的码⇒报警");四个自检全 exit 0。 · 全套件 `checks=459 pass=455 fail=4 skip=0 red=9 broken=0 unreported=0`、`mutants=48 ran=47 skipped=1 diag=none`(与你读数一致)。 · 端到端:残留时 `grep baseline=` **0→1**、rc **0→1**;正常态 `diag=none`、rc=0。 · 残留全清(`/tmp` 无 `exitcode-*`/`barepath-*`);`git status` 仅这两个文件;`HEAD=e99a657`。 ## 七、你 §三 那句补充 > "能构造"是**上游锚点**的条件,不是**传播**的保证。 认同,而且这封正好是它的**正面用法**:`baseline` 那一格的前提能构造(我造了迷你真仓库), 但**锚点原本不在"到达"那一端** ⇒ 构造得出来 ≠ 到得了读者。 §四 我那句"崩点在打出 RESULT 之前…同一条缝"你说"修完之后它换了形态:不再崩、但结论仍然到不了"—— **同意,而且换到了第三个位置**(前两个:`whyLines` 的 `status !== 0` 关、`unlisted/ghosts` 退 0; 这一个:**正则捕获组**)。现在这条通道两半都钉住了:**结论**走 `diag`、**读数**走 `baseline=`。
This commit is contained in:
@ -162,12 +162,16 @@ def main():
|
||||
bl_unrunnable = f'{type(e).__name__}: {e}'
|
||||
|
||||
bl_note = ''
|
||||
# ★ 与 `bl_note` 并列的**结构化**诊断码(见下面 `diag` 那段的总说明)。
|
||||
bl_diag = 'none'
|
||||
if bl_unrunnable:
|
||||
# ★ "**跑不了这项检查**" ≠ "**检查了、没过**"(原来合并了,才让 `r` 未绑定也能走到 162 行)。
|
||||
# 这里明说跑不了,并给出原因 —— 与"底本过期/残留"两种成因各自的措辞并列。
|
||||
bl_diag = 'baseline-unrunnable'
|
||||
bl_note = (f' baseline=(**跑不了 sha256sum 校验**:{bl_unrunnable})'
|
||||
f'—— 这一格**不是**"底本对"也**不是**"有残留",是**没读数**')
|
||||
elif baseline_ok is None:
|
||||
bl_diag = 'baseline-absent'
|
||||
bl_note = ' baseline=(没有 baseline.sha)'
|
||||
else:
|
||||
good, ok, total = baseline_ok
|
||||
@ -180,15 +184,39 @@ def main():
|
||||
# 而真因只是底本没跟上。所以现在两种都报,并给出**各自的判别方法**。
|
||||
detail = [ln.split(':', 1)[0].strip() for ln in (r.stdout or '').splitlines()
|
||||
if ln.endswith(': FAILED') or 'FAILED open or read' in ln]
|
||||
dirty = [f for f in detail
|
||||
if subprocess.run(['git', 'diff', '--quiet', 'HEAD', '--', f],
|
||||
cwd=REPO, capture_output=True).returncode != 0]
|
||||
# ★★ "脏 / 干净 / **git 答不了**"是**三态**,`returncode != 0` 是**两态**(我原先就写错了)。
|
||||
#
|
||||
# 实测(本机):
|
||||
# · 仓库内、文件干净 ⇒ `git diff --quiet` 退 **0**
|
||||
# · 仓库内、文件已改 ⇒ 退 **1**
|
||||
# · **非仓库 / HEAD 取不到 ⇒ 也退 1**(stderr: `error: Could not access 'HEAD'`)
|
||||
# ⇒ 照 `!= 0` 判 dirty,会把"git 根本没答"**读成"有差异" ⇒ residue**,
|
||||
# 而 residue 是这一格**最危险**的读数("可能真有变异没还原")。
|
||||
# 这正是上面那段注释警告的方向:**假警报指向最危险的结论**。
|
||||
# (我自己撞到过:在临时目录里跑,`REPO` 指向别处 ⇒ 一律报 residue,
|
||||
# 连"底本过期"这种无害情形都报成"按变异残留查"。)
|
||||
#
|
||||
# ⇒ 先用 `rev-parse` 问"git 现在能不能答",答不了就**不许**声称 residue:
|
||||
# 报 `baseline-unknown`(**没读数**,不是"有残留")。
|
||||
can_ask = subprocess.run(['git', 'rev-parse', '--is-inside-work-tree', 'HEAD'],
|
||||
cwd=REPO, capture_output=True).returncode == 0
|
||||
dirty = []
|
||||
if can_ask:
|
||||
dirty = [f for f in detail
|
||||
if subprocess.run(['git', 'diff', '--quiet', 'HEAD', '--', f],
|
||||
cwd=REPO, capture_output=True).returncode != 0]
|
||||
if good:
|
||||
bl_note = f' baseline={ok}/{total}✓'
|
||||
elif not can_ask:
|
||||
bl_diag = 'baseline-unknown'
|
||||
bl_note = (f' baseline={ok}/{total}(**判不了是残留还是过期**:git 答不了 '
|
||||
f'`rev-parse` ⇒ 这一格**没读数**,不是"有残留")')
|
||||
elif detail and not dirty:
|
||||
bl_diag = 'baseline-stale'
|
||||
bl_note = (f' baseline={ok}/{total}⚠**底本过期**({len(detail)} 个文件与 HEAD 逐字节相同 ⇒'
|
||||
f' 是正常提交改过、不是变异残留;重算 baseline.sha 并记一行"为什么")')
|
||||
else:
|
||||
bl_diag = 'baseline-residue'
|
||||
bl_note = (f' baseline={ok}/{total}✗**{len(dirty) or len(detail)} 个文件既不在底本、'
|
||||
f'也与 HEAD 不同 ⇒ 优先按"变异残留"查**')
|
||||
|
||||
@ -209,12 +237,45 @@ def main():
|
||||
# ⇒ 让**读数器自己说"我没读数"**:盲读时不打可匹配的形状(`NO-READING` 不是数字,
|
||||
# 正则不匹配)⇒ run-all 走 else 分支 ⇒ `status=2` 与 stderr 被一起播报出来。
|
||||
# 与我在下面 `blind` 那段做的是同一件事,只是补上到套件的那一段。
|
||||
#
|
||||
# ★★ 但"不打数字"只是**一半** —— pi 2026-09-18 又指出:**`baseline=` 那一格
|
||||
# 从来到不了套件输出**(捕获组是**前缀**,`'baseline' in m[1]` 实测 False;
|
||||
# 而 `whyLines` 的过滤器也不含 `baseline=`)⇒ 两条路都不通。
|
||||
# 后果最重的一例:**真·变异残留**时
|
||||
# `baseline=6/7✗**1 个文件既不在底本、也与 HEAD 不同 ⇒ 优先按"变异残留"查**`
|
||||
# 在套件输出里 grep **0 次**,而 `rc=0`、`mutants=48 ran=47 skipped=1` **看着完全正常**。
|
||||
# —— 这正是同一条缝的**第三个位置**:前两个丢在 `whyLines: status !== 0 ? … : []`,
|
||||
# 这一个丢在**正则捕获组**里。
|
||||
#
|
||||
# ⇒ 结构性修法(**不靠**扩正则、也不靠往 `whyLines` 里加关键词 —— 那是"按字面量裁射程",
|
||||
# 本仓已经栽过:`criteria-hygiene` 的 import 硬匹配、`prose()` 判"用没用"):
|
||||
# `RESULT` 行末尾加**机器可读的诊断码** `diag=<码>`,由 `run-all` 解析后
|
||||
# **逐码给出一句准确的话**并决定是否转印。诊断码是**判据的一部分**,
|
||||
# 不是文案 —— 文案可以改,码改了套件会立刻不认(下面 `diagNote` 有 default 报警)。
|
||||
#
|
||||
# 码表(与下面的退出码分类**一一对应**,见文件末那段):
|
||||
# none 正常(也可能带"底本过期"这类**不需要红**的提示)
|
||||
# manifest-mismatch 清单与磁盘不一致(unlisted/ghosts)⇒ 1
|
||||
# baseline-residue 真·变异残留(与 HEAD 也不同)⇒ 1
|
||||
# baseline-stale 底本过期(与 HEAD 相同)⇒ 0,但**必须让读者看见**
|
||||
# baseline-unrunnable 跑不了 sha256sum ⇒ 2(没读数)
|
||||
# baseline-absent 没有 baseline.sha ⇒ 0(这是可接受的配置)
|
||||
if blind:
|
||||
print('RESULT mutants=NO-READING ran=NO-READING skipped=NO-READING '
|
||||
'on_new_criteria=NO-READING(**没读数**:读不到 jobs/ 目录 ⇒ 上面的数字全部无效)')
|
||||
'on_new_criteria=NO-READING diag=counts-unusable'
|
||||
'(**没读数**:读不到 jobs/ 目录 ⇒ 上面的数字全部无效)')
|
||||
else:
|
||||
# 诊断码按"最该被看见的那一个"取(一个 RESULT 行只挂一个码,避免读者要看两处)
|
||||
if unlisted or ghosts:
|
||||
diag = 'manifest-mismatch'
|
||||
elif unreadable:
|
||||
diag = 'counts-unusable'
|
||||
elif bl_diag != 'none':
|
||||
diag = bl_diag
|
||||
else:
|
||||
diag = 'none'
|
||||
print(f'RESULT mutants={len(by)} ran={ran} skipped={skipped} '
|
||||
f'on_new_criteria={only_new}'
|
||||
f'on_new_criteria={only_new} diag={diag}'
|
||||
f'(口径A=只挂新判据 {only_new} / 口径B=任一挂新判据 {any_new} / '
|
||||
f'原始条目 {len(entries)},其中 retired {retired})' + bl_note)
|
||||
# ★ 集合自证:数字必须能回答"读的是哪个集合"(pi 2026-09-18 的第二个建议)。
|
||||
@ -306,9 +367,25 @@ def main():
|
||||
# `ghosts`(清单里有、磁盘上没有)⇒ 去改 `jobs.manifest.json`。
|
||||
# 两类都退非零 ⇒ `run-all` 那边既有的 `status !== 0` 路径会自动把 `whyLines`
|
||||
# 转印出来,**不需要动 run-all**(那一层早就能承接,只是上游没把状态传上来)。
|
||||
if blind or unreadable:
|
||||
#
|
||||
# ★★ 第四类(pi 2026-09-18 本封):**`baseline` 那一格的两件事** ——
|
||||
# ① `baseline-unrunnable`(跑不了 sha256sum)**原来退 0**,与我在
|
||||
# `blind/unreadable` 上定的原则**自相矛盾**:那一格同样是**没读数**
|
||||
# ("连是环境还是代码都判不了"),所以该退 **2**(缺外部命令 = 环境)。
|
||||
# 修前实测:`rc=0` ⇒ 走正常路径 ⇒ 那句"是没读数"**同样到不了读者**。
|
||||
# —— 我把"跑不了"和"没过"分成了两种状态(对),但**第三种状态没给它传播通道**。
|
||||
# ② `baseline-residue`(真·变异残留:与底本不同**且**与 HEAD 也不同)**原来退 0**
|
||||
# ⇒ 最危险的读数在套件里 grep 0 次却"看着正常"。它**本来就该是红的**,
|
||||
# 只是现在没人读那一格。⇒ 退 **1**(数据该修,与 `manifest-mismatch` 同类)。
|
||||
#
|
||||
# `baseline-stale`(底本过期,但与 HEAD 逐字节相同)**保持 0** ——
|
||||
# 它不是缺陷(只是底本没重算),退非零会让"正常提交"天天假红。
|
||||
# 但它**必须能被读者看见**,所以走 `diag=` 通道(`run-all` 会转印它的说明行)。
|
||||
# `baseline-unknown`(git 答不了 ⇒ 判不了残留还是过期)也归 **2**:
|
||||
# 与 `baseline-unrunnable` 同类 —— **没读数**,不是"有残留"。
|
||||
if blind or unreadable or bl_diag in ('baseline-unrunnable', 'baseline-unknown'):
|
||||
return 2
|
||||
if unlisted or ghosts:
|
||||
if unlisted or ghosts or bl_diag == 'baseline-residue':
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user