修复: 我上一笔升级留下的**更糟的灰区** —— 跳过要**具名**、"移出 STATIC_ONLY"要**要求行为层真跑过**(pi 的变异证明我复现了)
pi 2026-09-18 报的是**我上一笔(b84880f)自己的缺陷**,我按两处都实测复现,没有一条靠信。
## 一、缺陷:升级后有一条真实回归**零痕迹**
形状是**两件事同时发生**:
1. `harmony-nav` 移出 `STATIC_ONLY`(7→6)⇒ 到期闸**不再点名它**;
2. 行为条在设备不在时 `t.skip` ⇒ **不跑,也不算红**。
⇒ 这条判据有了一个"既不红、也不算没升级"的状态。升级前它在 `STATIC_ONLY` 里,每轮都被
点名(红但**看得见、可行动**);升级后**不被点名**,而行为条可以**永远不跑**。
**我的复现**(不是转述):注入 `assert.ok(false,'MUTANT: 真实回归')`,同一棵树两种设备态:
| | RESULT | 红清单里有 harmony-nav |
|---|---|---|
| 设备在 | `checks=459 pass=454 fail=5 skip=0 red=10` | **有** |
| 设备不在 | `checks=459 pass=454 fail=4 skip=1 red=9` | **没有** —— 与未变异基线**逐条一致** |
文件级同样:设备在 `fail 1`;设备不在 `fail 0 / skipped 1`(变异**够不着**)。
★ 还有一层 pi 点出的:两次 `verdict` 都是 `red`,是**被别的红兜住的** ——
等那批红清掉,这条回归就能让套件在"绿"的状态下藏着。
## 二、闸 (i):跳过必须**具名**(与红清单同级)
原来 `skip=N` 只在余额里**数得出来**,但**看不出是谁** —— 于是和"设备恰好不在、
什么都没坏"不可区分。node:test 其实**已经**把原因打出来了(`ok 16 - … # SKIP 设备不在 —— …`),
是解析只取了计数、把名字和原因丢了。⇒ 取回来,并在汇总里按**与红清单并列**的格式打印
(文件 + 判据名 + 原因)。实测(设备不在):
```
跳过的判据(1 条,分布在 1/29 个文件)—— **不是通过**,也不等于没问题:
- test/harmony-nav.test.mjs(跳过 1 条)
「★ 行为(设备):底栏真渲染了可点的导航项(dumpLayout 实测,live ⊆ source)」:设备不在 —— 行为部分本次不跑
```
## 三、闸 (ii):移出 `STATIC_ONLY` 必须**出示行为层绿跑记录**
"7→6"原来是纯**记账动作**:从"到期闸点名"挪到"行为条管辖",而**没有任何东西要求
行为条真的执行过**。⇒ 行为条**断言全过之后**留一条本机记录(`noteBehavioralRan`),
`run-all` 拿它当结算前提;没有记录就**点名**(回到被看见的状态)。
★ 方向要紧:宁可**多报**(说你还没验过),不许**漏报**(把没验过的当成验过了)。
★ 我自己在这里补了一层 pi 没提的收口:**只在到期前提成立时才追这条账**。否则没设备
的机器上行为层**不可能**留记录,那条红就是**天天假红且无法行动**(没人能在那台机器上
把它做绿)—— 正是探针三值设计要避免的。前提不成立时,可见性交给闸 (i)(具名 + 连续
轮数 + 超 K 自红)。四态实测:
| 场景 | 闸(ii) | 闸(i) |
|---|---|---|
| 设备在 + 有记录 | 静默 | 静默 |
| 设备在 + 记录被删(真跑不了) | **红** | **点名** |
| 探针说不(无设备机器) | 静默(避免清不掉的假红) | **点名** |
| 设备在但"忙"(前台是别人的) | **红** | **点名** |
## 四、我在实现过程中自己写错的(照实记)
1. **自检抄了一份副本**:我把 `# SKIP` 正则**复制**进 `--skip-selftest`
(当时的想法是"自检不该依赖被测对象")—— 那样自检验的是**副本**,生产那份改坏了
自检照样绿。是变异时 `AssertionError: 命中 2 处` 把它暴露出来的。
⇒ 抽成唯一实现 `parseSkips`,自检**直接调它**。再变异**生产那份**(去掉破折号剥离)
⇒ 自检红(`why: "- 设备忙"` vs `"设备忙"`)、exit 1,**这次验的是真身**。
这正是本仓反复消的"同一个事实多份实现"(`blurStyleFor`、`stripStrings` 兄弟副本同款)。
2. **自检案例我写错了**:第一条拿的是"不带 ` # SKIP` 标记的行"却期望解析出 1 条 ——
自检当场红。真形状一定带标记;不带标记的行正是另一条要钉的"不许被当成跳过"。
(顺带:自检连**我写测试时的错**都抓到了,方向对。)
3. **账本路径两处各拼一次**:`run-all` 与 `lib/harmony-device.mjs` 各按自己的位置算
`.tmp/` 路径。已注释说明"写的那边是唯一权威、这边只读,路径若漂移会**多报**不会漏报"。
## 验证
· 全套件 `checks=459 pass=455 fail=4 skip=0 red=9` —— 与改动前**同样 9 条**
(并发会话的"自报>清单" + 4 条 exit 1),两条新闸在正常路径上**都静默**。
· 变异:M7 自检②、M8 自检③、M12 自检正则、M14 **生产**解析器,全部按预期红并已还原。
· `--skip-selftest` 5/5 绿;它与既有 `--probe-selftest` **同形状**(都先跑套件再判定),
不是我引入的新形状。
This commit is contained in:
@ -12,7 +12,8 @@
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { code, prose, stripComments } from './lib/read.mjs';
|
||||
import { findHdc, hasTarget, foregroundBundle, dumpLayout, walk,
|
||||
noteBusySkip, noteRan, busyLimit, busyStreak } from './lib/harmony-device.mjs';
|
||||
noteBusySkip, noteRan, busyLimit, busyStreak,
|
||||
noteBehavioralRan, behavioralRanAt } from './lib/harmony-device.mjs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { test } from 'node:test';
|
||||
@ -852,6 +853,13 @@ test('★ 行为(设备):底栏真渲染了可点的导航项(dumpLayout
|
||||
`底栏导航项(bounds=${it.attributes.bounds})要至少亮一个源码定义过的标签;` +
|
||||
`实际文字:${texts.join('、')} —— 一个都没命中 NAV_ITEMS(live ⊆ source 被破坏)`);
|
||||
}
|
||||
/*
|
||||
* ★ 走到这里 = 行为层**真的跑绿了** ⇒ 留一条"本机验过"的记录(pi 2026-09-18 闸 (ii))。
|
||||
* 这是"结算(移出 STATIC_ONLY)"的前提:没有这条记录,那条判据就只是被**挪走**、
|
||||
* 不是被**升级** —— 而 `static=` 那个余额会读起来像"又清了一条"。
|
||||
* ⚠️ 位置必须在**全部断言之后**:红了也记 = 把没验过的当成验过了。
|
||||
*/
|
||||
noteBehavioralRan(CRIT);
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@ -104,6 +104,51 @@ export function busyStreak(criterion) {
|
||||
/** 供报文用:本机繁忙上限 K。 */
|
||||
export const busyLimit = () => busyLimitOf();
|
||||
|
||||
/*
|
||||
* ─── "本机真的跑过行为层"的记录(pi 2026-09-18 闸 (ii) 的证据)───
|
||||
*
|
||||
* 问题:把一条判据从 `STATIC_ONLY` **移除**(7→6)现在是个纯**记账动作** ——
|
||||
* 它只是从"到期闸点名"挪到"行为条管辖",而**没有任何东西要求行为条真的执行过**。
|
||||
* ⇒ 一条判据可以在"已升级"的名义下**永远不跑**。
|
||||
*
|
||||
* pi 用变异证明了后果:设备不在时注入一条真实回归,红清单与基线**逐条一致**(零痕迹)。
|
||||
* 于是 `static=6` 这个余额读起来像"又清了一条",实际可能是"又一条进了永久 skip"。
|
||||
* 与上面那条同源:**余额里读不出来的东西等于不存在**。
|
||||
*
|
||||
* ⇒ 行为条**真绿一次**就在这里留一条记录;`run-all` 拿它当"结算(移出 STATIC_ONLY)"
|
||||
* 的前提。记录是**本机的**(`.tmp/`,已 gitignore)—— 判的是"**这台机器上**验过",
|
||||
* 换机器不继承,这正是对的:换一台没设备的机器,"验过"不该跟着走。
|
||||
*/
|
||||
const ranPath = () => process.env.AGENTMAIL_BEHAVIORAL_RAN
|
||||
|| join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', '.tmp', 'harmony-behavioral-ran.json');
|
||||
|
||||
function readRan() {
|
||||
try {
|
||||
const d = JSON.parse(prose(ranPath()));
|
||||
return (d && typeof d === 'object') ? d : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 行为条**真的跑绿了**(断言全过)⇒ 记一笔。
|
||||
* ⚠️ 只在**断言之后**调用:红了也记,就等于"把没验过的当成验过了",比不记更糟。
|
||||
*/
|
||||
export function noteBehavioralRan(criterion) {
|
||||
const d = readRan();
|
||||
d[criterion] = new Date().toISOString();
|
||||
try {
|
||||
mkdirSync(dirname(ranPath()), { recursive: true });
|
||||
writeFileSync(ranPath(), JSON.stringify(d, null, 2));
|
||||
} catch { /* 记不下不该让判据崩;run-all 那边把"没记录"当未升级(保守方向) */ }
|
||||
}
|
||||
|
||||
/** 本机有没有"这条行为层真跑过"的记录?返回时间戳或 null。 */
|
||||
export function behavioralRanAt(criterion) {
|
||||
return readRan()[criterion] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到能跑的 hdc 二进制。候选顺序:SDK 文档安装根下的 hdc,再 PATH 上的 hdc。
|
||||
* 返回**跑通了 `list targets`** 的那条(status 0);都不行返回 null。
|
||||
|
||||
@ -407,6 +407,21 @@ for (const [file, flags, expected] of SUITE) {
|
||||
* `check()` 风格的文件没有跳过机制 ⇒ 恒 0。
|
||||
*/
|
||||
const nodeSkip = /^# skipped (\d+)/m.exec(out);
|
||||
/*
|
||||
* ★★ 跳过的**具名**与**原因**(pi 2026-09-18 的闸 (i) 前半)。
|
||||
*
|
||||
* 上面那格 `skip=N` 让"跳过"在余额里**数得出来**了,但**看不出来是谁**:
|
||||
* 实测(变异验证)——
|
||||
* 设备在 + 注入一条真实回归 ⇒ `fail=5 … red=10`,红清单里有 `harmony-nav`;
|
||||
* 设备不在 + **同一条回归** ⇒ `fail=4 skip=1 … red=9`,红清单与**基线逐条一致**。
|
||||
* ⇒ **一条真实回归在设备不在时零痕迹**。`skip=1` 那个数不具名,
|
||||
* 与"设备恰好不在、什么都没坏"**不可区分**。
|
||||
* (pi 的话:这与"红的判据"应当同级 —— 红的能点名,跳过的也必须能点名。)
|
||||
*
|
||||
* node:test 其实**已经**把原因打出来了(`ok 16 - … # SKIP 设备不在 —— …`),
|
||||
* 是这段解析只取了计数、把名字和原因丢了。⇒ 取回来。
|
||||
*/
|
||||
const nodeSkips = parseSkips(out);
|
||||
/*
|
||||
* `# tests` 缺失时**回退**到 `# pass + # fail + # skipped`(不是 `# pass`)——
|
||||
* 老版本 node 或输出被截断时,和才等于总条数;单取 `# pass` 正是上面那个 bug。
|
||||
@ -447,6 +462,8 @@ for (const [file, flags, expected] of SUITE) {
|
||||
* 也不能让它落在 `pass = checks - fail` 里冒充通过。
|
||||
*/
|
||||
skip: fileChecks === null ? null : fileSkip,
|
||||
/* 跳过的**明细**(名字 + 原因):让"谁没跑"能像"谁红了"一样被点名。 */
|
||||
skips: fileChecks === null ? [] : nodeSkips,
|
||||
exitCode: r.status,
|
||||
});
|
||||
/*
|
||||
@ -725,6 +742,20 @@ function hdcServerLooksHealthy() {
|
||||
/** 探针结论 → 是否等于"前提成立(到期)" */
|
||||
const probeIsDue = v => v === true || v === 'unknown';
|
||||
|
||||
/*
|
||||
* 从 node:test 的 TAP 输出里取"哪几条被跳过了、为什么"。
|
||||
*
|
||||
* ★ 必须是**唯一实现**(`--skip-selftest` 要验的就是这个函数)。
|
||||
* 我第一版把正则**抄了一份**进自检(当时的想法是"自检不该依赖被测对象")——
|
||||
* 而那样一来自检验的是**副本**:生产那份改坏了,自检照样绿。
|
||||
* 这正是本仓反复消的"同一个事实多份实现"(`blurStyleFor`、`stripStrings` 兄弟副本
|
||||
* 都栽在这上面),所以只留这一份,自检**直接调它**。
|
||||
*/
|
||||
function parseSkips(out) {
|
||||
return [...(out || '').matchAll(/^(?:ok|not ok) \d+ - (.+?) # SKIP(.*)$/gm)]
|
||||
.map(m => ({ name: m[1].trim(), why: m[2].trim().replace(/^[-–—::]\s*/, '') }));
|
||||
}
|
||||
|
||||
/** 只能验形态的判据:文件 + 为什么只能静态 + 到期前提 */
|
||||
/*
|
||||
* 探针自检(`--probe-selftest`):它判的是**判据自己的分辨力** ——
|
||||
@ -745,6 +776,45 @@ if (process.argv.includes('--probe-selftest')) {
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* 跳过解析自检(`--skip-selftest`):与上面那条同形状 —— 判的是**解析器的分辨力**。
|
||||
*
|
||||
* 为什么需要:闸 (i) 的"具名跳过"完全依赖从 node:test 的 TAP 输出里取回
|
||||
* 名字与原因。那个正则哪天写坏了(`# SKIP` 大小写、` - ` 分隔、原因前缀的破折号),
|
||||
* **表现是"跳过的判据"那一行不打印** —— 而"没打印"和"没有跳过"长得一模一样。
|
||||
* ⇒ 拿合成 TAP 行把三种情形钉住:带原因的、不带原因的、以及**不该被当成跳过的**普通通过行。
|
||||
*/
|
||||
if (process.argv.includes('--skip-selftest')) {
|
||||
// ★ 调**生产那一个**函数,不抄副本 —— 抄副本的话自检验的是副本(见 parseSkips 的注释)。
|
||||
const parse = parseSkips;
|
||||
const cases = [
|
||||
/*
|
||||
* ⚠️ 第一条我第一版写**错**了:拿的是"不带 ` # SKIP` 标记的行"却期望解析出 1 条 ——
|
||||
* 自检当场红("解析出 0 条")。真输出里的跳过行**一定**带 ` # SKIP <原因>`,
|
||||
* 所以第一条要用**真形状**;而不带标记的行正是后面那条要钉的"不许被当成跳过"。
|
||||
* (顺带:这正是自检该有的样子 —— 它连**我写测试时的错**都抓到了。)
|
||||
*/
|
||||
['真形状:` # SKIP` + 原因', 'ok 16 - ★ 行为(设备):底栏 # SKIP 设备不在 —— 本次不跑\n# skipped 1\n',
|
||||
[{ name: '★ 行为(设备):底栏', why: '设备不在 —— 本次不跑' }]],
|
||||
['原因带破折号前缀要剥掉', 'ok 3 - 某条 # SKIP - 设备忙\n',
|
||||
[{ name: '某条', why: '设备忙' }]],
|
||||
['`not ok` 的跳过行(node 也会这么打)', 'not ok 4 - 某条 # SKIP 不支持的平台\n',
|
||||
[{ name: '某条', why: '不支持的平台' }]],
|
||||
['理由为空也要认出来(别因没原因就当没跳过)', 'ok 5 - 某条 # SKIP\n',
|
||||
[{ name: '某条', why: '' }]],
|
||||
['普通通过行**不该**被当成跳过', 'ok 1 - 一条普通的判据\n# tests 1\n# pass 1\n', []],
|
||||
];
|
||||
let bad = 0;
|
||||
for (const [what, tap, want] of cases) {
|
||||
const got = parse(tap);
|
||||
const ok = JSON.stringify(got) === JSON.stringify(want);
|
||||
console.log(`${ok ? 'ok ' : 'RED '} ${what}:解析出 ${got.length} 条` +
|
||||
(ok ? '' : `(期望 ${JSON.stringify(want)},实际 ${JSON.stringify(got)})`));
|
||||
if (!ok) bad++;
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
}
|
||||
const STATIC_ONLY = [
|
||||
['test/harmony-appearance.test.mjs', '壁纸/令牌/遮罩渲染:观感与运行期换肤要设备', 'device'],
|
||||
['test/harmony-logic.test.mjs', '页面状态机与文案:`.ets` 状态要跑起来才算数', 'device'],
|
||||
@ -964,6 +1034,76 @@ try {
|
||||
let dueFailed = false;
|
||||
const dueStatic = STATIC_ONLY.filter(([, , probe]) => probeIsDue(probeResults[probe]));
|
||||
const unknownProbes = Object.entries(probeResults).filter(([, v]) => v === 'unknown').map(([k]) => k);
|
||||
/*
|
||||
* ★★ 闸 (ii):「**移出 STATIC_ONLY**」必须要求「行为层**真跑过一次**」(pi 2026-09-18)。
|
||||
*
|
||||
* 为什么需要它:把一条判据从 `STATIC_ONLY` 移除,现在是个纯**记账动作** ——
|
||||
* 它从"到期闸点名"挪到"行为条管辖",而**没有任何东西要求行为条真的执行过**。
|
||||
* ⇒ 一条判据可以在"已升级"的名义下**永远不跑**,而 `static=` 那个余额读起来像
|
||||
* "又清了一条"。这与本文件反复消的形状同源:**余额里读不出来的东西等于不存在**。
|
||||
*
|
||||
* pi 用变异把后果钉出来了(我复现,两处都一致):
|
||||
* 设备在 + 注入一条真实回归 ⇒ `fail=5 … red=10`,红清单里有 harmony-nav;
|
||||
* 设备不在 + **同一条回归** ⇒ `fail=4 skip=1 … red=9`,红清单与基线**逐条一致**
|
||||
* ⇒ **一条真实回归,在设备不在时零痕迹。**
|
||||
*
|
||||
* ⇒ 这里记一份"已结算"清单:每条都必须能出示**本机**的行为层绿跑记录
|
||||
* (由 `lib/harmony-device.mjs` 的 `noteBehavioralRan` 在断言**之后**写)。
|
||||
* 记录不存在 ⇒ 到期闸继续**点名**它(回到被看见的状态),而不是让它静静滑过去。
|
||||
* ★ 方向要紧:这条闸宁可**多报**(说"你还没验过"),也不许**漏报**(把没验过的
|
||||
* 当成验过了)—— 所以"记录读不到"一律按"没验过"处理。
|
||||
*/
|
||||
const SETTLED_FROM_STATIC = [
|
||||
// 移出 STATIC_ONLY 的判据要登记在这里,并出示行为层绿跑记录。
|
||||
// 形状:['判据文件', '行为记录里的 key(noteBehavioralRan 的那个字符串)', '行为条在哪', '到期前提探针']
|
||||
['test/harmony-nav.test.mjs', 'harmony-nav/底栏可点项',
|
||||
'test/harmony-nav.test.mjs 的「★ 行为(设备):底栏真渲染了可点的导航项」', 'device'],
|
||||
];
|
||||
let unsettled = [];
|
||||
try {
|
||||
// 账本路径必须与 `lib/harmony-device.mjs` 里 `ranPath()` 的默认值**一致**:
|
||||
// 那个文件在 `test/lib/` 下,用 `../../../../.tmp/…`(= 仓库根);
|
||||
// 这里 `ROOT` = `client/electron`,所以是 `../..` 再进 `.tmp`。
|
||||
// 两处各拼一次是"同一个事实多份实现"——但这边**只读**、写的那边是唯一权威;
|
||||
// 若哪天路径改了,这条闸会因"读不到记录"而**多报**(保守方向),不会漏报。
|
||||
const ranLedger = JSON.parse(prose(join(ROOT, '..', '..', '.tmp', 'harmony-behavioral-ran.json')));
|
||||
for (const [file, key, where, probe] of SETTLED_FROM_STATIC) {
|
||||
// 只在**本相位会跑**它时才判:构建相位不跑判据文件,不该因此报"没验过"
|
||||
if (skippedByPhase.includes(file)) continue;
|
||||
/*
|
||||
* ★ 只在**到期前提成立**(探针判到期)时才追这条账 —— 与到期闸本身同一条纪律。
|
||||
*
|
||||
* 为什么必须这样收口:没设备/没 SDK 的机器上,行为层**不可能**留下记录
|
||||
* (它只会 skip),那条红就是**天天假红**且**无法行动** —— 没人能在那台机器上
|
||||
* 把它做绿。这正是探针三值设计要避免的东西。
|
||||
* ⇒ 前提不成立时,这条判据的可见性交给**闸 (i) 具名跳过**:它会点名到文件与
|
||||
* 判据、写出原因、并给出连续跳过轮数(且连续超 K 轮会自行变红)。
|
||||
* 也就是说"没跑"这件事**仍然看得见**,只是不以这条清不掉的账的形式。
|
||||
* 前提成立而记录仍缺 ⇒ 那才真异常:"设备明明在,却从没跑绿过"。
|
||||
*/
|
||||
if (!probeIsDue(probeResults[probe])) continue;
|
||||
if (!ranLedger[key]) unsettled.push([file, key, where]);
|
||||
}
|
||||
} catch {
|
||||
// 记录文件不存在/坏了 ⇒ **前提成立**的那些算"没验过"(保守方向:宁可多报)
|
||||
unsettled = SETTLED_FROM_STATIC
|
||||
.filter(([file, , , probe]) => !skippedByPhase.includes(file) && probeIsDue(probeResults[probe]));
|
||||
}
|
||||
if (unsettled.length > 0) {
|
||||
console.error('\n✗ 有判据**被移出 STATIC_ONLY,但行为层从未真跑过**:');
|
||||
for (const [file, key, where] of unsettled) {
|
||||
console.error(` - ${file}(行为记录 key:${key})`);
|
||||
console.error(` 它已不在到期闸的点名范围里,等于"已升级"—— 但没有任何证据表明`);
|
||||
console.error(` 行为条真的执行过。${where}`);
|
||||
}
|
||||
console.error(
|
||||
' ⇒ 这只是被**挪走**,不是被**升级**。让行为条在本机真跑绿一次即可(记录会自动落下),\n' +
|
||||
' 或者把它放回 STATIC_ONLY(继续被点名,好过静静滑过去)。\n' +
|
||||
' 为什么要有这条闸(pi 2026-09-18 实测):设备不在时,注入一条真实回归,\n' +
|
||||
' 红清单与基线**逐条一致** —— 判据"已升级"而实际零覆盖,且看不出来。'
|
||||
);
|
||||
dueFailed = true;
|
||||
}
|
||||
if (dueStatic.length > 0) {
|
||||
const firstUnknown = unknownProbes.includes(dueStatic[0][2]);
|
||||
console.error(firstUnknown
|
||||
@ -1138,6 +1278,23 @@ if (reds.length) {
|
||||
console.error(`红的判据(${reds.length}/${SUITE.length}):`);
|
||||
for (const r of reds) console.error(` - ${r}`);
|
||||
}
|
||||
/*
|
||||
* ★★ 跳过的要**具名**(pi 2026-09-18 闸 (i) 前半)—— 与上面的红清单**同级**。
|
||||
*
|
||||
* 为什么必须并列:红的是"判据说 NO",跳过的是"判据没说话"。后者只在余额里有个数时,
|
||||
* **一条真实回归可以零痕迹**(实测:设备不在时注入回归,红清单与基线逐条一致)。
|
||||
* 红清单能点名,跳过清单也必须能点名 —— 否则"没跑"就永久藏在 `skip=1` 里。
|
||||
*/
|
||||
if (totalSkip > 0) {
|
||||
const named = reportedRecords.filter(r => r.skip > 0);
|
||||
console.error(`跳过的判据(${totalSkip} 条,分布在 ${named.length}/${SUITE.length} 个文件)` +
|
||||
`—— **不是通过**,也不等于没问题:`);
|
||||
for (const r of named) {
|
||||
const list = (r.skips || []).map(s => `「${s.name}」${s.why ? `:${s.why}` : ''}`);
|
||||
console.error(` - ${r.file}(跳过 ${r.skip} 条)${list.length ? `\n ${list.join('\n ')}`
|
||||
: '(原因未能从输出里解析出来 —— 这条本身就是个缺口)'}`);
|
||||
}
|
||||
}
|
||||
if (dueFailed && reds.length === 0 && brokens.length === 0) {
|
||||
console.error('另外:静态判据**到期**(报文见上)—— 它现在**不再吃掉**上面的汇总与其它红。');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user