修复: baseline-absent 同时是"关掉残留检测"的开关 —— 一个 rm 就能让残留仍在而那条红整条消失
pi 2026-09-18 报的。**成立,我端到端复现了**,而且是这条线上同一形状的**最后一跳**。
## 一、缺陷:结论不是"到不了读者",是**被关掉了**
`baseline-absent` 原来是 `{note: null, blocksGreen: false}` —— **既不说话、也不阻断**,
而它同时是"**关掉残留检测**"的开关。同一处残留,**只切 `baseline.sha` 在不在**:
| | summary.py | 套件 | 红清单里有 residue 吗 |
|---|---|---|---|
| `baseline.sha` 在 | `diag=baseline-residue` | `red=10 … verdict=red` | **有** |
| **删掉** | `diag=baseline-absent` | `red=9 … verdict=red` | **没有**(整条消失、`rc=0`、静默) |
`git status` 里 ` D baseline.sha` 与 ` M <被改文件>` **同时都在** ⇒ **残留还在**,
而唯一能报告它的机制被一个 `rm` 关掉,**不需要特权、不会红、也不说话**。
前几轮分别是"结论到不了读者"、"结论到不了 `verdict`";**这次是"结论被关掉了"**。
## 二、修法:把"缺席"分成两态,并给非阻断码再分两类
1. **`baseline-deleted`(新码,退 1)**:`baseline.sha` **受版本控制却被删**
⇒ 那不是"可接受的配置",是**缺文件**(该恢复它)。判别用既有的东西、不新造:
`git ls-files --error-unmatch` —— 三态:**0=受跟踪 / 1=没跟踪 / 128=git 答不了**,
`128` 时**不许猜**,退回缺席语义但**必须说话**。
2. **`baseline-absent` 补 `note`**(原来 `null` ⇒ 连读者都看不到):
说清"**这一格没检查**:没有底本 ⇒ 残留检测是关着的",并区分"本来就没跟踪它"与"判不出"。
—— 这是 pi 的"最小版",我认为它**不足以**独当(沉默正是这个洞的成因),所以两条都做。
3. ★ **`DIAG` 每个码新增 `checked` 字段**(pi 那条建议的落地):
· `checked: true` = "**检查了、结论无害**"(`baseline-stale`:可以静默);
· `checked: false` = "**根本没检查**"(`baseline-absent`/`counts-unusable`/
`baseline-unrunnable`/`baseline-unknown`:**必须说话**)。
⇒ 把 pi 的"应当分两类"从**文案要求**变成**结构字段**。
## 三★ 我加第一版时漏了一条判据,自己变异出来(M34)
`checked` 只是一个字段,**它的内容当时没有任何判据守着**:我把 `baseline-absent` 的
`note` 改回 `null`(= pi 说的"最小版没做"),`--verdict-selftest` **全绿** ——
因为此前的判据只管 `blocksGreen` 存不存在、两侧口径对不对。
⇒ 补第 ⑦ 条:**`checked: false` 的码必须有 `note`**,且**每个码都必须声明 `checked`**。
又一条"判据在,但走不到":字段加上了,没人查它的内容。
## 四、变异验证(都已还原)
| 变异 | 结果 |
|---|---|
| **M32** `baseline-deleted` 退回"当配置缺席"(**即 pi 报的那个洞本身**) | **红**:`UPSTREAM_RC[baseline-deleted]=1 与真跑出来的 rc=0 不符` |
| **M33** 关掉 `bl_tracked` 判别(一律当缺席) | **红** 2 条:端到端恢复 pi 那个洞(`diag=baseline-absent`)+ 反向覆盖报"该码没案例" |
| **M34** `baseline-absent` 的 note 改回 `null` | **红**:`这些码根本没检查、却一句话都不说:baseline-absent`(**上一版这里是 rc=0**) |
| **M35** 删掉某个码的 `checked` 字段 | **红**:`这些码没声明 checked:baseline-stale` |
## 五、端到端锚点(新增一条 `--exitcode-selftest` 案例)
`runIn` 加 `'deleted'` 模式:**先提交 `baseline.sha` 纳入跟踪、再把目标文件改脏、然后 unlink**
⇒ 判 `rc=1 且 diag=baseline-deleted`。这条**就是 pi 那个实验的自动化版本**:
残留是真的存在的,而底本被删了。反向覆盖随之报 `9 个码都有案例真跑验过`。
**真树端到端复核**(真残留 + 真删 `baseline.sha`):`red=10`(修复前 9)、`verdict=red`、
红清单里出现 `(summary.py)baseline-deleted —— 底本 baseline.sha **受版本控制却被删** ⇒ **残留检测已失效**`。
## 六、验证与状态
· 五个自检全 exit 0(`--verdict-selftest` 新增第 ⑦ 条;`--exitcode-selftest` 11→12 条案例、9 个码全覆盖)。
· 正常态与 pi 读数一致:`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✓`。
· `baseline.sha` 的 7 条都是鸿蒙源码文件,**不含** `summary.py`/`run-all.mjs`
⇒ 本次改动**不需要重算底本**(`baseline=7/7✓` 亦为证)。
· 残留全清;`git status` 仅这两个文件;`HEAD=2870d47`。
This commit is contained in:
@ -148,6 +148,38 @@ def main():
|
||||
# ⇒ 两件事一起修:① `r` 先绑 None;② "**跑不了这项检查**"与"**检查了、没过**"
|
||||
# 分成两种状态,不许合并(合并正是这个 bug 的成因)。
|
||||
bl_unrunnable = False
|
||||
# ★★ 缺席的**两种成因**必须先分开(pi 2026-09-18 实测到的那个洞)。
|
||||
#
|
||||
# 原来 `baseline.sha` 不在就一律 `baseline-absent` ⇒ 退 0、`note: null`、**连读者都看不到**。
|
||||
# 而它同时是"**关掉残留检测**"的开关 —— 实测(pi 报、我复现):
|
||||
# 同一处残留(`ImagePrep.ts` 末尾追加一行),**只切 `baseline.sha` 在不在**:
|
||||
# 在 ⇒ `diag=baseline-residue`、`red=10`、红清单里**有**那条 `(summary.py)baseline-residue`
|
||||
# 删掉 ⇒ `diag=baseline-absent`、`red=9`、那条红**整条消失**、`rc=0`、静默
|
||||
# 而 `git status` 里 ` D baseline.sha` 与 ` M ImagePrep.ts` **同时都在**。
|
||||
# ⇒ **残留还在,唯一能报告它的机制被一个 `rm` 关掉了**,不需要任何特权、不会红、也不说话。
|
||||
#
|
||||
# ⇒ 判法(pi 建议,且**不必新造**):`baseline.sha` 是**受版本控制**的
|
||||
# (`git ls-files --error-unmatch` 有它)⇒ "跟踪着、却不在工作树里"**本身就是可疑**,
|
||||
# 那是 ` D`(被人删了),不是"可接受的配置"。
|
||||
# 只有"**本来就没跟踪它**"(别人 clone 的变体 / future)才保留"缺席即配置"的语义。
|
||||
#
|
||||
# ★ 判别是三态(与我修 `git diff --quiet` 那次同形):
|
||||
# rc=0 ⇒ 受跟踪 ⇒ 删除 ⇒ **缺文件**(该改的是仓库/数据)
|
||||
# rc=1 ⇒ git 说没跟踪它 ⇒ 真·配置缺席
|
||||
# rc=128 ⇒ git 答不了(非仓库)⇒ **不许猜**,退回"配置缺席"但**必须说话**
|
||||
bl_tracked = None # None=判不了;True=受跟踪;False=没跟踪
|
||||
if not os.path.exists(bl):
|
||||
try:
|
||||
tr = subprocess.run(['git', 'ls-files', '--error-unmatch', '--',
|
||||
os.path.relpath(bl, REPO)],
|
||||
cwd=REPO, capture_output=True, timeout=30)
|
||||
if tr.returncode == 0:
|
||||
bl_tracked = True
|
||||
elif tr.returncode == 1:
|
||||
bl_tracked = False
|
||||
# 其它(128 等)⇒ 保持 None = 判不了
|
||||
except Exception: # noqa: BLE001 —— 判不了就退化为"配置缺席 + 说话"
|
||||
bl_tracked = None
|
||||
if os.path.exists(bl):
|
||||
try:
|
||||
r = subprocess.run(['sha256sum', '-c', bl], cwd=REPO,
|
||||
@ -171,8 +203,20 @@ def main():
|
||||
bl_note = (f' baseline=(**跑不了 sha256sum 校验**:{bl_unrunnable})'
|
||||
f'—— 这一格**不是**"底本对"也**不是**"有残留",是**没读数**')
|
||||
elif baseline_ok is None:
|
||||
bl_diag = 'baseline-absent'
|
||||
bl_note = ' baseline=(没有 baseline.sha)'
|
||||
# ★★ 缺席分成两态(见上面 `bl_tracked` 那段的总说明)。
|
||||
if bl_tracked is True:
|
||||
# 受跟踪却被删 ⇒ **不是配置,是缺文件**(谁把它删了?残留检测因此关掉了)
|
||||
bl_diag = 'baseline-deleted'
|
||||
bl_note = (' baseline=(**底本被删了**:`baseline.sha` 受版本控制,'
|
||||
'却不在工作树里 ⇒ **残留检测已失效**)')
|
||||
else:
|
||||
bl_diag = 'baseline-absent'
|
||||
# ★ 即使是"真·配置缺席",也**必须说话** —— 原来这里是 `note: null`、连读者都看不到。
|
||||
# "这一格没检查"与"检查了、结论无害"**不是一回事**(本仓反复立的那条:
|
||||
# "跑不了" ≠ "检查了没过"),所以它不能再靠沉默通过。
|
||||
bl_note = (' baseline=(**没有底本 ⇒ 这一格没检查**:'
|
||||
+ ('判不出它是不是该受跟踪' if bl_tracked is None else '本来就没跟踪它')
|
||||
+ ',但**残留检测是关着的**)')
|
||||
else:
|
||||
good, ok, total = baseline_ok
|
||||
# ★ 措辞必须区分**两种完全不同的成因**(2026-09-18 实测):
|
||||
@ -259,7 +303,15 @@ def main():
|
||||
# baseline-residue 真·变异残留(与 HEAD 也不同)⇒ 1
|
||||
# baseline-stale 底本过期(与 HEAD 相同)⇒ 0,但**必须让读者看见**
|
||||
# baseline-unrunnable 跑不了 sha256sum ⇒ 2(没读数)
|
||||
# baseline-absent 没有 baseline.sha ⇒ 0(这是可接受的配置)
|
||||
# baseline-deleted `baseline.sha` **受跟踪却被删** ⇒ 1(缺文件,不是配置)
|
||||
# baseline-absent **本来就没跟踪它** ⇒ 0,但**必须说话**
|
||||
#
|
||||
# ★★ `baseline-deleted` 与 `baseline-absent` 是这一轮(pi 2026-09-18)新分出来的:
|
||||
# `baseline-absent` 原来同时是"**关掉残留检测**"的开关 —— 一个 `rm` 就能让
|
||||
# 残留仍在、而唯一能报告它的那条红**整条消失**(实测 `red 10→9`、`rc=0`、静默)。
|
||||
# ⇒ 按本仓反复立的那条口径分家:**"检查了、结论无害"**(`baseline-stale`)
|
||||
# 与 **"根本没检查"**(`baseline-absent`)**不该同格**;
|
||||
# 而"受跟踪却被删"更重一档 —— 它连"配置"都不是,是**缺文件**。
|
||||
if blind:
|
||||
print('RESULT mutants=NO-READING ran=NO-READING skipped=NO-READING '
|
||||
'on_new_criteria=NO-READING diag=counts-unusable'
|
||||
@ -385,7 +437,10 @@ def main():
|
||||
# 与 `baseline-unrunnable` 同类 —— **没读数**,不是"有残留"。
|
||||
if blind or unreadable or bl_diag in ('baseline-unrunnable', 'baseline-unknown'):
|
||||
return 2
|
||||
if unlisted or ghosts or bl_diag == 'baseline-residue':
|
||||
# ★ `baseline-deleted` 归 **1**(pi 建议):`baseline.sha` 是**受版本控制**的,
|
||||
# "跟踪着却不在工作树里"就是 ` D` —— 该改的是**仓库/数据**(把它恢复),
|
||||
# 不是环境、也不是"没读数"。与 `manifest-mismatch` 同类(都是"清单/数据该修")。
|
||||
if unlisted or ghosts or bl_diag in ('baseline-residue', 'baseline-deleted'):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@ -822,6 +822,7 @@ const UPSTREAM_RC = {
|
||||
'baseline-unrunnable': 2,
|
||||
'baseline-unknown': 2,
|
||||
'baseline-absent': 0,
|
||||
'baseline-deleted': 1,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -910,6 +911,9 @@ if (process.argv.includes('--exitcode-selftest')) {
|
||||
* 'residue' 底本哈希错、文件**已改脏** ⇒ `baseline-residue`,rc=1
|
||||
* 'nogit' 底本哈希错、**没有 git** ⇒ `baseline-unknown`,rc=2
|
||||
* 'nobaseline' 不写 baseline.sha ⇒ `baseline-absent`,rc=0
|
||||
* 'deleted' **写了并提交** baseline.sha,**然后删掉它**(受跟踪却被删)
|
||||
* ⇒ `baseline-deleted`,rc=1 —— 这条就是 pi 2026-09-18 报的那个洞:
|
||||
* 残留检测被一个 `rm` 关掉,而残留其实还在。
|
||||
*/
|
||||
const TARGET = 'client/harmony/entry/src/main/ets/model/Target.ts';
|
||||
const runIn = (manifest, files, mode) => {
|
||||
@ -951,6 +955,16 @@ if (process.argv.includes('--exitcode-selftest')) {
|
||||
g('add', '-A');
|
||||
g('-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', 'init');
|
||||
if (mode === 'residue') writeFileSync(targetAbs, `${content}// dirty\n`);
|
||||
/*
|
||||
* ★ `deleted`:**提交之后**才删 —— 于是它"受跟踪却不在工作树里"(` D`)。
|
||||
* 顺序要紧:先 `git add -A` + commit 把它纳入跟踪,再 unlink。
|
||||
* 同时把目标文件改脏 ⇒ **残留是真的存在的**,验的正是
|
||||
* "残留还在、而报告它的机制被关掉了"这个形状。
|
||||
*/
|
||||
if (mode === 'deleted') {
|
||||
writeFileSync(targetAbs, `${content}// dirty\n`);
|
||||
rmSync(join(d, 'baseline.sha'));
|
||||
}
|
||||
}
|
||||
if (mode === 'blind' || mode === 'unreadable') {
|
||||
// 整棵给 nobody 可达(mkdtemp 默认 700),最后一层按场景收紧
|
||||
@ -1053,6 +1067,15 @@ if (process.argv.includes('--exitcode-selftest')) {
|
||||
['jobs-one.json'], ['jobs-one.json'], 2, 'diag=baseline-unknown', 'nogit'],
|
||||
['没有 baseline.sha ⇒ rc=0 且 diag=baseline-absent',
|
||||
['jobs-one.json'], ['jobs-one.json'], 0, 'diag=baseline-absent', 'nobaseline'],
|
||||
/*
|
||||
* ★★ pi 2026-09-18 报的洞,**这条是它的端到端锚点**:
|
||||
* `baseline.sha` 受版本控制、被提交后又删掉 ⇒ 以前退 0、`note: null`、**静默**,
|
||||
* 而残留其实还在(目标文件同时被改脏)⇒ **检测被一个 `rm` 关掉了**。
|
||||
* 现在:`baseline-deleted`、**退 1**("该改的是仓库/数据",与 `manifest-mismatch` 同类)。
|
||||
* —— 与 `nobaseline`(本来就没跟踪它 ⇒ 真·配置缺席 ⇒ 0)**分家**。
|
||||
*/
|
||||
['底本受跟踪却被删 ⇒ rc=1 且 diag=baseline-deleted',
|
||||
['jobs-one.json'], ['jobs-one.json'], 1, 'diag=baseline-deleted', 'deleted'],
|
||||
];
|
||||
let bad = 0;
|
||||
const seenDiags = []; // 反向覆盖用:本组案例**真跑出来**的码(不是声明的)
|
||||
@ -1620,36 +1643,70 @@ try {
|
||||
* · **不认识的码**:红(报警),见下面那条。
|
||||
*/
|
||||
const DIAG = {
|
||||
'none': { note: null, blocksGreen: false },
|
||||
'none': {
|
||||
checked: true,
|
||||
note: null, // 一切正常 ⇒ 不需要说话(这是"检查了、结论无害"的极限情形)
|
||||
blocksGreen: false,
|
||||
},
|
||||
'counts-unusable': {
|
||||
checked: false,
|
||||
note: '**环境:没读数** —— 上面的 mutants 数字**不可信**(读不到 jobs/ 目录)',
|
||||
blocksGreen: true,
|
||||
},
|
||||
'manifest-mismatch': {
|
||||
checked: true,
|
||||
note: '**注意:清单与磁盘不一致** —— 上面的数字**不代表磁盘上现在有多少个变异体**',
|
||||
blocksGreen: true,
|
||||
},
|
||||
'baseline-unrunnable': {
|
||||
checked: false,
|
||||
note: '**环境:baseline 那一格没读数**(跑不了 `sha256sum`)—— '
|
||||
+ 'mutants 数字**仍然有效**,但"底本对不对"**没答案**',
|
||||
blocksGreen: true,
|
||||
},
|
||||
'baseline-residue': {
|
||||
checked: true,
|
||||
note: '**注意:有文件既不在底本、也与 HEAD 不同 ⇒ 优先按"变异残留"查**'
|
||||
+ '(数字按清单算是有效的,但**可能有变异没还原**)',
|
||||
blocksGreen: true,
|
||||
},
|
||||
'baseline-stale': {
|
||||
checked: true,
|
||||
note: '底本过期(文件与 HEAD 逐字节相同 ⇒ 是正常提交改过,不是残留)',
|
||||
blocksGreen: false, // 不是缺陷;退非零会让"正常提交"天天假红(pi 与我 §七 同意)
|
||||
},
|
||||
'baseline-unknown': {
|
||||
checked: false,
|
||||
note: '**环境:判不了 baseline 那一格**(git 答不了 ⇒ 判不出是残留还是过期)—— '
|
||||
+ '这一格**没读数**,**不是**"有残留"',
|
||||
blocksGreen: true,
|
||||
},
|
||||
// 没装 baseline.sha 是可接受的配置,不必刷屏 —— 但**仍算"知道这个状态"**(与"不认识的码"分开)
|
||||
'baseline-absent': { note: null, blocksGreen: false },
|
||||
/*
|
||||
* ★★ `baseline-deleted`(pi 2026-09-18 报的洞,我复现并修):` baseline.sha` **受跟踪却被删**。
|
||||
*
|
||||
* 它与 `baseline-absent` 的分家是本轮的要害 —— 实测(同一处残留,只切 `baseline.sha` 在不在):
|
||||
* 在 ⇒ `diag=baseline-residue`、`red=10`、红清单里**有**那条 residue 红
|
||||
* 删掉 ⇒ `diag=baseline-absent`、`red=9`、那条红**整条消失**、`rc=0`、静默
|
||||
* 而 `git status` 里 ` D baseline.sha` 与 ` M <被改的文件>` **同时都在**。
|
||||
* ⇒ **残留还在,唯一能报告它的机制被一个 `rm` 关掉了**,不需要特权、不会红、也不说话。
|
||||
*
|
||||
* ★ 口径(pi 的建议,我采纳):`blocksGreen=false` 的码**应当分两类** ——
|
||||
* "**检查了、结论无害**"(`baseline-stale`:可以静默)与
|
||||
* "**根本没检查**"(`baseline-absent`:**必须说话**)。
|
||||
* 前者仍然逐条比对了、只是结论是"过期";后者**不是"检查了、结论是无害"**。
|
||||
* 而"受跟踪却被删"更重一档:它连"配置"都不是,是**缺文件**(该恢复它)⇒ 红。
|
||||
*/
|
||||
'baseline-deleted': {
|
||||
checked: true,
|
||||
note: '底本 `baseline.sha` **受版本控制却被删** ⇒ **残留检测已失效**(不是"没有残留")',
|
||||
blocksGreen: true,
|
||||
},
|
||||
'baseline-absent': {
|
||||
checked: false,
|
||||
note: '没有底本 ⇒ **这一格没检查**(残留检测关着);不是"检查了、结论无害"',
|
||||
blocksGreen: false,
|
||||
},
|
||||
};
|
||||
|
||||
function summarizeMutants(stdout, status, stderr) {
|
||||
@ -1917,6 +1974,41 @@ if (process.argv.includes('--verdict-selftest')) {
|
||||
* **白名单随之消失**(数据说话)—— 这也是 `DIAG` 那条"一个码一行、一次定完"的延伸:
|
||||
* 上游退出码也该是那一行的一部分。
|
||||
*/
|
||||
/*
|
||||
* ★★ ⑦ **"根本没检查"的码必须说话**(pi 2026-09-18 报的那条的真实诉求)。
|
||||
*
|
||||
* pi 的原话是让 `blocksGreen=false` 分成两类:
|
||||
* · "**检查了、结论无害**"(`baseline-stale`)⇒ **可以静默**;
|
||||
* · "**根本没检查**"(`baseline-absent`)⇒ **必须说话**。
|
||||
* ⇒ 这不是文案问题,是**结构**问题:用 `checked` 字段把它写死,并在这里守住 ——
|
||||
* 凡是 `checked: false` 的码,**必须有 note**(否则读者看到的就是一片空白,
|
||||
* 而空白会被读成"没问题")。
|
||||
*
|
||||
* ★ 为什么这条判据非有不可(我自己变异出来的 M34):
|
||||
* 我把 `baseline-absent` 的 note 改回 `null`(= pi 说的"最小版没做"的状态),
|
||||
* `--verdict-selftest` **全绿** —— 因为此前的判据只查 `blocksGreen` 存不存在、
|
||||
* 两侧口径对不对,**没有一条管"该说话的那个码说没说"**。
|
||||
* ⇒ 又一条"判据在,但走不到":`note` 这个字段的**内容**当时没有任何判据守着。
|
||||
*/
|
||||
{
|
||||
const mute = Object.entries(DIAG).filter(([c, e]) => e.checked === false && !e.note).map(([c]) => c);
|
||||
const undeclared = Object.keys(DIAG).filter(c => typeof DIAG[c].checked !== 'boolean');
|
||||
if (undeclared.length) {
|
||||
console.log(`RED 这些码没声明 \`checked\`:${undeclared.join('、')}`
|
||||
+ ' ⇒ 必须显式说明"这一格到底检查了没有"(不声明就会被当成"没问题")');
|
||||
bad++;
|
||||
}
|
||||
if (mute.length) {
|
||||
console.log(`RED 这些码**根本没检查、却一句话都不说**:${mute.join('、')}`
|
||||
+ ' ⇒ 空白会被读成"没问题"("没读数"必须看得见)');
|
||||
bad++;
|
||||
}
|
||||
if (!undeclared.length && !mute.length) {
|
||||
const nUnchecked = Object.values(DIAG).filter(e => e.checked === false).length;
|
||||
console.log(`ok "没检查的码必须说话":${Object.keys(DIAG).length} 个码都声明了 \`checked\`,`
|
||||
+ `其中 ${nUnchecked} 个是"没检查"且都有说明`);
|
||||
}
|
||||
}
|
||||
{
|
||||
const missing = Object.keys(DIAG).filter(c => !(c in UPSTREAM_RC));
|
||||
const extra = Object.keys(UPSTREAM_RC).filter(c => !(c in DIAG));
|
||||
|
||||
Reference in New Issue
Block a user