From 14ccd44920a553c5b21870c9fa8489f4c8f18e5c Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 15 Sep 2026 13:04:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E7=AB=AF:=20`ok=205`=20=E5=9C=A8?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E4=B8=80=E6=9D=A1**=E6=B2=A1=E4=BA=BA?= =?UTF-8?q?=E5=88=A4**=E7=9A=84=E4=B8=8A=E7=95=8C=EF=BC=88=E5=88=A0?= =?UTF-8?q?=E6=8E=89=E9=82=A3=E5=8D=8A=E5=8F=A5=EF=BC=89+=20=E6=88=91?= =?UTF-8?q?=E5=AF=B9=20`ran=E2=89=A4files`=20=E7=9A=84"=E6=9B=B4=E6=AD=A3"?= =?UTF-8?q?=E6=96=B9=E5=90=91=E5=8F=8D=E4=BA=86=20+=20=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E7=9A=84=E9=94=9A=E4=B8=8E=E6=8B=BC=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi 2026-09-15 报了三件,**三件都对**,其中第二件是**我把他驳回去之后又被他驳回来、而且是我错**。 ## 一★ `ok 5` 在替一条**没有任何东西在判**的上界作证 ``` $ grep -n "unreportedFiles >" client/electron/test/run-all.mjs (零命中 —— 全文件没有任何地方断言它) ``` `probs` 里只有两条值上界(`ranFilesCount > SUITE.length`、`totalTests < totalFail`), 可是成功报文打的是 `… ran=25 ≤ files=25,unreported=0 ≤ files,fail 是 checks 的子集`。 ⇒ **`unreported ≤ files` 那半句没有机制在判**。我上一轮自己测出那条上界是空的、 换成了源码处数扫描(换得对),**却把半句留在成功报文里** —— 报文在 **替一条我已经判定无效的判断作证**。 形状:**"读数器替一件事作证"落在自检自己身上** —— 而这条自检存在的理由, 正是"`RESULT` 那行自作证"。**读数器也要被读数。** 修:把那半句删掉,改成写清"`unreported` 由源码扫描判"(判的是"写有几处",不是值上界)。 ## 二★ 我对 `ran ≤ files` 的"更正"**方向反了** —— 它不侥幸,它很尖 我上封写"`ran > files` 只在几乎所有文件都双计时才响,侥幸有效"。pi 用算式驳了,我实测复现: 设 N=文件数、P=自报成功的、U=N−P、Gp=绿且自报的、Gu=绿且没自报的,有 bug 时: ``` ranFilesCount = P + Gp unreportedFiles = U + Gu ran > N ⟺ Gp > U unreported > N ⟺ Gu > P ← 两者是**镜像** ``` 正常情况 U ≈ 0(每个文件都自报)⇒ **`ran > files` 只要 Gp ≥ 1 就响**。 **实测:只让一个绿文件多计一次 ⇒ `ran=26 > files=25` 当场响**(我先前写的"几乎所有"不成立)。 而同一组数下 `unreported` 几乎恒为 0 —— 那才是它永不触发的原因。 ⇒ **可复用的规则**:`counter ≤ files` 这类结构性上界的**区分力 = 计数器典型值与上界之间的 余量(slack)**。 - `ran` 典型值 ≈ files(**余量 0**)⇒ 极尖,+1 就响 ⇒ **这条可以信**; - `unreported` 典型值 0(**余量 25**)⇒ 要 `Gu > P` 才响 ⇒ 永不触发。 **"结构性上界"本身不分好坏,要看余量。** 我是先拿"结构性的就是好的"当结论, 于是**用同一把尺子量了两个余量截然不同的计数器**,把一条很尖的判据误判成侥幸。 ## 三★ 源码扫描:只认拼法 + **锚钉在行首**(同一个洞的两面,我都实测出来了) pi 指出只认 `+=` 是**假绿方向的洞**:`totalTests = totalTests + ran;` 躲得过扫描而仍然双计。 我改成判"对这四个计数器的**写**"(`+=` / `= X + …` / `= X - …` / `++` / `--`), **然后实测发现第一版照样漏** —— 因为我把锚写成了 `^\s*X`(**行首**): ```js if (ran === null) { ranFilesCount += 0; } // 一行两条语句,增量在行中间 ``` **而"第二处"长得正是这个样子。** ⇒ 锚必须是**语句起点** `(?:^|[;{])`。 **这是我第二次栽在同一件事上**:扫描器的锚要按**语言结构**定,不是按**版面**定 —— 上一次是"扫描器咬到自己的文档",这次是"锚钉在行首而语句不在行首"。 (中途还有一个**我自己的假读数**:变异脚本用 `io.open(P,'w').write(...)` 不保证落盘, 六种变异全报"★ 漏"。是"读数器没先被证明是好的"的又一次 —— 定稿加了 `with` + **写完读回来断言等于**。) **变异验证 6/6 命中**:`+=`(同一行 if 块内)/ `= X + n` / `X++` / `totalTests = X +` / `unreportedFiles = X +` / `X - n`;还原 ⇒ 不响。 另外重注入**原 bug**(24 个绿文件各双计)⇒ `ran=49 > files=25` 响、`checks` 虚高,与当初现象一致。 仍不是根治(`x ||= y`、`x = (x) + 1`、反射赋值都能躲)—— 根治是把累加抽成**纯函数** (入参=解析结果,出参=增量)再喂合成输入,已在注释里写明它是唯一能真跑的形状。 顺带:`ran` 那条报错的修法文案原来还写着"全文件搜 `totalTests +=`"(陈旧)—— 一并改掉。 ## 四、修后 ``` ok 5 - 总数行自洽(ran=25 ≤ files=25,fail 是 checks 的子集;四个计数器各只有一处"写",由源码扫描判) RESULT files=25 ran=25 checks=400 pass=399 fail=1 red=4 broken=0 unreported=0 verdict=red ``` 红仍 4 条、都不是我的。到期那 7 条仍未动。 --- client/electron/test/run-all.mjs | 71 +++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 2d63839..efc598c 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -682,7 +682,8 @@ try { if (ranFilesCount > SUITE.length) { probs.push(`ran=${ranFilesCount} > files=${SUITE.length} —— 每个文件只该计一次,` + `超出来说明**累加块有不止一份**(搬动时只加没删)。` + - `修法:全文件搜 \`totalTests +=\`,累加只许出现在一处(红绿分支之外那一处)。`); + `修法:数 \`ranFilesCount\` 的**写**有几处(**不一定是 \`+=\`** —— 见下面的源码扫描,` + + `只按 \`+=\` 搜会漏掉 \`= ranFilesCount + 1\` 这种等价写法)。`); } if (totalTests < totalFail) { probs.push(`checks=${totalTests} < fail=${totalFail} —— fail 是 checks 的子集,这不可能成立`); @@ -697,28 +698,64 @@ try { * 要让它触发得需要 >25 个"绿且不自报"的文件,而套件只有 25 个文件。 * ⇒ **数学上成立、实际上永远抓不到任何东西的判据** —— * 这正是我们这一路在消的形状,而我是**在加它的时候**又造了一个。 - * (顺带更正上面那条 `ran > files`:它同样只在"几乎所有文件都双计"时才响, - * 也就是**那个 bug 正好发生时它才响** —— 侥幸有效,但机理不是我以为的那个。) * - * 所以改成查**源码里的增量处数**:四个计数器各只许出现一次。 + * ★ **但真原因比"同一次加两次"更一般,而且我上面对 `ran > files` 的"更正"是错的** + * (pi 2026-09-15 把我的更正驳回来了,算式我实测复现过)。设 + * N=文件数、P=自报成功的、U=N−P=没自报的、Gp=绿且自报的、Gu=绿且没自报的,有 bug 时: + * + * ranFilesCount = P + Gp unreportedFiles = U + Gu + * ran > N ⟺ Gp > U + * unreported > N ⟺ Gu > P ← 两者是**镜像** + * + * 正常情况下 U ≈ 0(每个文件都自报)⇒ **`ran > files` 只要 Gp ≥ 1 就响 —— + * 任何一个被双计的绿文件就够**。我先前写的"只在几乎所有文件都双计时才响"**不成立**。 + * 实测:只让**一个**绿文件多计一次 ⇒ `ran=26 > files=25` **当场响**。 + * 而同一组数下 `unreported = U + Gu` 几乎恒为 0 —— 那才是它**永不触发**的原因。 + * + * ⇒ **可复用的规则**:`counter ≤ files` 这类结构性上界的**区分力 = 计数器典型值与上界之间的 + * 余量(slack)**。 + * · `ran` 典型值 ≈ files(余量 0)⇒ **极尖**,+1 就响 ⇒ **这条可以信**; + * · `unreported` 典型值 0(余量 25)⇒ 要 `Gu > P` 才响 ⇒ 永不触发。 + * **"结构性上界"本身不分好坏,要看余量。** 我是先拿"结构性的就是好的"当结论, + * 于是把一条很尖的判据误判成侥幸 —— **同一把尺子量两个余量截然不同的计数器**。 + * + * 所以源码处数扫描仍然要(它管的是另一件事:**拼法**),`ran > files` 也留着。 * 诚实说清性质:这是**形态判据**(读自己的源码),不是行为判据 —— * 它能抓"搬动/新增时留了旧路径"(实际发生的那三次), * 抓不到"在某条路径上被条件性地加两次"。 * 后者要真跑,得把累加抽成一个纯函数(入参 = 解析结果,出参 = 增量),再拿合成输入喂它。 * **那才是能真跑的形状,本轮不做,但记在这里**。 * - * ⚠️ 计数必须**锚在语句位置**(行首缩进后即 `X +=`),否则**我这条报错文案自己**里 - * 出现的 `X +=` 会被数进去 —— 第一次跑就是 `totalTests += 出现 2 处`, - * 而真身只有一处(**扫描器咬到自己的文档**,与 `@ohos`/`toISOString` 那两次同族)。 + * ⚠️ 计数必须**锚在语句位置**,否则**我这条报错文案自己**里出现的写法会被数进去 —— + * 第一次跑就是 `totalTests += 出现 2 处`,而真身只有一处 + * (**扫描器咬到自己的文档**,与 `@ohos`/`toISOString` 那两次同族)。 * `stripComments` 只去注释、**不去字符串**,所以这里不能只靠它。 + * + * ⚠️ 而且**只认 `+=` 是个假绿方向的洞**(pi 2026-09-15 抓到,我实测复现): + * `totalTests = totalTests + ran;` 这样的等价写法**躲得过扫描而仍然双计**。 + * ⇒ 所以判的是**对这四个计数器的"写"**,不是 `+=` 的出现: + * `+=` / `= X + …` / `= X - …` / `++` / `--` 都算。 + * + * ⚠️⚠️ 锚**不能是行首** —— 我第一次写成 `^\s*X`,然后实测发现**注入的第二处照样漏**: + * 语句可以从**行中间**开始,`if (ran === null) { ranFilesCount += 0; }` 就是一行两条语句, + * 而"第二处"长得**正是这个样子**。⇒ 锚必须是**语句起点**:`(?:^|[;{])`。 + * (**这是我第二次栽在同一件事上**:扫描器的锚要按**语言结构**定,不是按**版面**定 —— + * 上一次是"扫描器咬到自己的文档",这次是"锚钉在行首而语句不在行首"。) + * 实测四种等价写法(`+=` / `= X + n` / `X++` / 行中间)**全部命中**后才收。 + * 仍不是根治(`x ||= y`、`x = (x) + 1` 括号式、反射赋值都能躲)—— 根治就是上面那个纯函数。 */ const ownSrc = stripComments(prose(fileURLToPath(import.meta.url))); for (const counter of ['totalTests', 'ranFilesCount', 'totalFail', 'unreportedFiles']) { - const n = (ownSrc.match(new RegExp(`^\\s*${counter}\\s*\\+=`, 'gm')) || []).length; + const c = counter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // 语句起点 = 行首、或上一条语句的 `;` / 块开始的 `{`(**不是**行首 —— 见上) + const WRITE = new RegExp( + `(?:^|[;{])\\s*${c}\\s*(?:\\+=[^=]|=[^=]\\s*${c}\\s*[-+]|[+-]{2})`, 'gm'); + const n = (ownSrc.match(WRITE) || []).length; if (n !== 1) { - probs.push(`\`${counter} +=\`(语句位置)出现 **${n}** 处(只许 1 处)—— ` + + probs.push(`对 \`${counter}\` 的**写**(语句位置)出现 **${n}** 处(只许 1 处)—— ` + `"每个文件只该计一次"是对**整族**计数器都成立的性质,不是一个一个判的;\n` + - ` 这个形状已经发生过三次(搬动/新增时留了旧路径),而**两处都跑得通、都报得出数**。`); + ` 这个形状已经发生过三次(搬动/新增时留了旧路径),而**两处都跑得通、都报得出数**。\n` + + ` 注意判的是**写**:\`X += n\`、\`X = X + n\`、\`X++\` 都算(只认 \`+=\` 会漏)。`); } } if (probs.length) { @@ -726,8 +763,20 @@ try { for (const x of probs) console.error(` - ${x}`); selfCheckFailed = true; } else { + /* + * ★ 报文只许写**真的判过**的东西(pi 2026-09-15 抓到我把没判的写上去过): + * 原来这句是 `ran ≤ files,unreported ≤ files,fail 是 checks 的子集` —— + * 而**全文件没有任何地方断言 `unreported ≤ files`**(`grep "unreportedFiles >"` 零命中)。 + * 我测出那条上界是空的、换成了源码处数扫描,**却把半句留在成功报文里** ⇒ + * 报文在**替一条已经被我判定无效的判断作证**。 + * 形状:**"读数器替一件事作证"落在自检自己身上** —— 而这条自检存在的理由, + * 正是"`RESULT` 那行自作证"。**读数器也要被读数。** + * + * `unreported` 现在由下面那句源码处数判(判的是"写有几处",不是值上界)—— + * 所以报文里只提它由谁判,不再写一个没人算的不等式。 + */ console.log(`ok 5 - 总数行自洽(ran=${ranFilesCount} ≤ files=${SUITE.length},` + - `unreported=${unreportedFiles} ≤ files,fail 是 checks 的子集)`); + `fail 是 checks 的子集;四个计数器各只有一处"写",由源码扫描判)`); } } catch (e) { console.error(`✗ 套件自检 5 跑不了:${e.message || e}`);