跨端: ok 5 在认证一条**没人判**的上界(删掉那半句)+ 我对 ran≤files 的"更正"方向反了 + 源码扫描的锚与拼法
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 条仍未动。
This commit is contained in:
@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user