test(判据): 到期闸第一条按 (a) 升级 —— harmony-nav 加**行为层**(真 dumpLayout),并把"跳过"变成可数余额
pi 2026-09-18 报"静态判据到期闸开了"是真阳性:探针的前提"本工作区能装、能点 设备"现在成立(我实测:签名 HAP 装上 install bundle successfully、aa start start ability successfully、uitest uiInput click 返回 No Error、uitest dumpLayout 出真 UI 树)。闸门要求 (a) 改行为判据 或 (b) 改换更准的前提 —— (b) 救不了: 它建议拆的"目标存在 ↔ 能装能点"两层我两层都实测为真,拆开照样红。所以走 (a)。 ## 1. 新增行为层(harmony-nav,17 条) `lib/harmony-device.mjs`:设备侧 harness(findHdc / hasTarget / foregroundBundle / dumpLayout / walk / findByText / boundsAt)。**只读不抢** —— 模拟器是共享的, 应用不在前台就跳过,不启动、不点。 harmony-nav 新增两条: · 「★ 行为(设备):底栏真渲染了可点的导航项(live ⊆ source)」—— 真 dumpLayout,断"屏幕下 1/4 里真画出了可点的项、每项至少亮一个 源码 NAV_ITEMS 定义过的标签"。**版本无关**:已安装构建可能比 HEAD 旧 (实测前台那份是 3 项,HEAD 源码是 4 项),所以断"live ⊆ source"而不是 "相等"——"四项齐不齐"仍由静态层把。 · 「★ 判据自检:底栏取值逻辑」——纯函数 `navItemsOf` 上的合成树断言。 它**不替**静态那批(点击配对/挂载映射/命中区 ≥44vp/让位派生)——那些读源码更准。 它补的是源码读不到的那半:真渲染出来了吗、真可点吗、标签对吗。 ## 2. 把"跳过"变成可数余额(run-all.mjs) ⚠️ 这是本轮**我先写错、再查出来**的地方,记在这里:升级之前套件里**没有任何 in-file skip**(全仓 grep 零命中)。加了第一条之后,`# tests N` 把跳过的**算进总数**, 而 `pass = checks - fail` 又把它读成**通过** —— 实测设备在 / 设备不在两次运行的 `RESULT files=…` **一字不差**(都 `checks=457 pass=453 fail=4`,而文件自己报 `# tests 16 / # pass 15 / # skipped 1`)。**"看不到 ⇒ 绿"长在总数行上。** 修法照 `fail` 那一格的先例(pi 2026-09-15 指出缺 `red` 时的同一形状): · 取 `# skipped K` 成一格;`records` 带 `skip`;投影加 `totalSkip`(第 5 个); · `pass` 改成 `checks - fail - skip`;汇总行补 `skip=N`(并在单位说明里写清它是 "本次没跑",既不是通过也不是失败); · 自检 5 补 `checks ≥ skip`(对**每个**按文件累加的计数器都成立的上界 —— 照 pi 那条"只给其中两个判上界,第三个就永远没人管")。 实测:设备在 `… pass=454 fail=4 skip=0`,设备不在 `… pass=453 fail=4 skip=1`, `pass+fail+skip == checks` 在两个方向都成立。 ## 3. 到期债务结算一笔:7 → 6 STATIC_ONLY 去掉 harmony-nav;`docs/DEBTS.json` 的 static-criteria 7→6(含 where 清单同步);SUITE 登记数 11→17(原登记 11 早已与文件里的 15 条不符 —— 并发会话 加了日历/我的那批测试没改登记,这条红一并消掉)。 ## 变异验证(都做了阳性对照,还原后 sha256 一致) · M1 `sourceLabels` 取空集 → 行为条 not ok,报出真实文案 `✉️、通信`; · M2 底栏阈值 0.75→2.0 → 报"实际 0,一个都没有"; · M3 `AGENTMAIL_HARMONY_DEVICE=none` → `# skipped 1`(且**改 run-all 之前** 汇总读不出来 —— 这正是上面第 2 条要修的实证); · M4 删掉 `if (a.type === 'Text') return false;` → **第一次没红**(自检④拿的是叶子 FAB,它的 textsUnder 是空、被下一条内容条件滤掉,没测到那行)⇒ 换成"带子文本的 可点 Text"后**才**红。自检本身也会空跑,这是同一族病的又一个实例; · M5 阈值 0.75→0.10 → 自检②红;M6 `clickable` 判定取反 → 自检①红。 ## 说明与遗留 · 已在运行的构建比 HEAD 旧(前台那份 3 项),行为条按"live ⊆ source"设计, 所以它现在**绿**且**没有**把"我的缺一项"误报成红 —— 那是 build-stamp 的活。 · 模拟器是**别人会话的**:我为验证装/启动/点过(pi 明确没动它,我动了,如实记); harness 因此按"不抢前台"写:不在前台就 skip 并计数。 · 余下 6 条到期判据(appearance/logic/cross-client-theme/defaults/admin/imageprep) 未动,仍留在 STATIC_ONLY 里红着 —— 到期机制该干的事。harness 已就位,可按 可观测性逐条升级。
This commit is contained in:
@ -75,7 +75,7 @@ const SUITE = [
|
||||
// P4 外观同步:跑 model/Appearance.ts(纯逻辑),所以也要 strip-types
|
||||
['test/harmony-appearance.test.mjs', ['--experimental-strip-types', '--no-warnings'], 25],
|
||||
// P5 悬浮玻璃导航:点击配对 / index 决定挂载 / 命中区 ≥44vp / 悬浮与让位
|
||||
['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 11],
|
||||
['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 17],
|
||||
// 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号
|
||||
['test/appearance-defaults.test.mjs', [], 4],
|
||||
['test/build-stamp.test.mjs', [], 7],
|
||||
@ -251,6 +251,7 @@ const records = [];
|
||||
/* eslint-disable no-unused-vars */
|
||||
let totalTests;
|
||||
let totalFail;
|
||||
let totalSkip;
|
||||
let unreportedFiles;
|
||||
let ranFilesCount;
|
||||
/* eslint-enable no-unused-vars */
|
||||
@ -392,15 +393,33 @@ for (const [file, flags, expected] of SUITE) {
|
||||
const nodeTests = /^# tests (\d+)/m.exec(out); // node:test:总条数
|
||||
const nodeFail = /^# fail (\d+)/m.exec(out);
|
||||
/*
|
||||
* `# tests` 缺失时**回退**到 `# pass + # fail`(不是 `# pass`)——
|
||||
* ★ `# skipped K`(node:test):**没跑**的条数,必须单独取一格(dsh 2026-09-18)。
|
||||
*
|
||||
* 起因:给到期判据加行为层时,第一条行为判据在"设备不在"时走 `t.skip` ——
|
||||
* 而 `# tests N` **把跳过的也算进去**(实测 `# tests 16 / # pass 15 / # skipped 1`,
|
||||
* 16 = 15 + 0 + 1)。不取这一格,下面的 `pass = checks - fail` 就把那 1 条读成"通过":
|
||||
* 设备在与设备不在两次运行的 `RESULT files=…` **一字不差**(实测两行完全相同)。
|
||||
* 这就是"**看不到 ⇒ 绿**"—— 本仓反复消的那条,这次长在总数行上。
|
||||
*
|
||||
* ⚠️ 为什么不能靠"跳过的判据会自己打印 SKIP 字样"来兜:那是**文件内的输出**,
|
||||
* 而这里要的是**可数的余额**。余额的定义就是"汇总行里读得出来的那个数" ——
|
||||
* 读不出来的跳过等于没有跳过(同 CRITERIA.md 里"跳过若不可数就等于不显形")。
|
||||
* `check()` 风格的文件没有跳过机制 ⇒ 恒 0。
|
||||
*/
|
||||
const nodeSkip = /^# skipped (\d+)/m.exec(out);
|
||||
/*
|
||||
* `# tests` 缺失时**回退**到 `# pass + # fail + # skipped`(不是 `# pass`)——
|
||||
* 老版本 node 或输出被截断时,和才等于总条数;单取 `# pass` 正是上面那个 bug。
|
||||
* 两者都没有 ⇒ null("没自报条数",走 unreported 那条红)。
|
||||
*/
|
||||
const nodePass = /^# pass (\d+)/m.exec(out);
|
||||
const nodeTotal = nodeTests ? Number(nodeTests[1])
|
||||
: (nodePass ? Number(nodePass[1]) + (nodeFail ? Number(nodeFail[1]) : 0) : null);
|
||||
: (nodePass ? Number(nodePass[1]) + (nodeFail ? Number(nodeFail[1]) : 0)
|
||||
+ (nodeSkip ? Number(nodeSkip[1]) : 0) : null);
|
||||
const fileChecks = marker ? Number(marker[1]) + Number(marker[2]) : nodeTotal;
|
||||
const fileFail = marker ? Number(marker[2]) : (nodeFail ? Number(nodeFail[1]) : 0);
|
||||
/* 跳过的条数:`check()` 风格没有这个机制 ⇒ 0(marker 存在即非 node:test 输出) */
|
||||
const fileSkip = marker ? 0 : (nodeSkip ? Number(nodeSkip[1]) : 0);
|
||||
|
||||
/*
|
||||
* ★★ 唯一的信息来源:**这个文件只 push 一条记录**(pi 2026-09-15 的建议,我采纳)。
|
||||
@ -422,6 +441,12 @@ for (const [file, flags, expected] of SUITE) {
|
||||
file,
|
||||
checks: fileChecks, // null = 跑了但没能自报条数
|
||||
fail: fileChecks === null ? null : fileFail,
|
||||
/*
|
||||
* ★ 跳过条数(dsh 2026-09-18):与 `fail` 一样是 `checks` 的**子集**,
|
||||
* 一起随记录走。它不是"失败",但**也不是通过** —— 所以既不能并进 `fail`,
|
||||
* 也不能让它落在 `pass = checks - fail` 里冒充通过。
|
||||
*/
|
||||
skip: fileChecks === null ? null : fileSkip,
|
||||
exitCode: r.status,
|
||||
});
|
||||
/*
|
||||
@ -549,6 +574,15 @@ for (const [file, flags, expected] of SUITE) {
|
||||
const reportedRecords = records.filter(r => r.checks !== null);
|
||||
totalTests = reportedRecords.reduce((n, r) => n + r.checks, 0);
|
||||
totalFail = reportedRecords.reduce((n, r) => n + r.fail, 0);
|
||||
/*
|
||||
* ★ 跳过余额(dsh 2026-09-18)——与 `totalFail` 同源、同层,第 5 个投影。
|
||||
*
|
||||
* 它与 `totalFail` 是**兄弟**:都从 `records` 里取、都是 `checks` 的子集、
|
||||
* 都要在汇总行上可读。区别是语义 —— `fail` 是"判据说这不成立",`skip` 是
|
||||
* "这条本次没跑"(设备不在 / 相位跳过)。**混进任一边都是假读数**:
|
||||
* 并进 `fail` 会把"没跑"报成"坏了";留在 `pass` 里会把"没跑"报成"过了"。
|
||||
*/
|
||||
totalSkip = reportedRecords.reduce((n, r) => n + r.skip, 0);
|
||||
ranFilesCount = reportedRecords.length;
|
||||
unreportedFiles = records.length - reportedRecords.length;
|
||||
|
||||
@ -712,7 +746,6 @@ if (process.argv.includes('--probe-selftest')) {
|
||||
process.exit(bad ? 1 : 0);
|
||||
}
|
||||
const STATIC_ONLY = [
|
||||
['test/harmony-nav.test.mjs', '底栏结构/命中区常量/挂载关系:`.ets` 要 hvigorw 才能编译、要设备才能点', 'device'],
|
||||
['test/harmony-appearance.test.mjs', '壁纸/令牌/遮罩渲染:观感与运行期换肤要设备', 'device'],
|
||||
['test/harmony-logic.test.mjs', '页面状态机与文案:`.ets` 状态要跑起来才算数', 'device'],
|
||||
['test/cross-client-theme.test.mjs', '跨端令牌与玻璃分工:一端是 `.ets`,只能静态对齐', 'device'],
|
||||
@ -836,7 +869,16 @@ try {
|
||||
probs.push(`checks=${totalTests} < fail=${totalFail} —— fail 是 checks 的子集,这不可能成立`);
|
||||
}
|
||||
/*
|
||||
* ★★ 这里原来是一条**源码文本扫描**("四个计数器各只许一处写"),连同它的自检一起删掉了。
|
||||
* ★ `skip` 是 `checks` 的**子集**(dsh 2026-09-18),与 `fail` 同一形状:
|
||||
* "跳过的条数"当然属于"自报的总条数"。这条上界对**每个按文件累加的计数器**都成立
|
||||
* (pi 2026-09-15 的教训:只给其中两个判上界,第三个就永远没人管)——
|
||||
* 所以 `skip` 一进来就把上界补上,别等它出事。
|
||||
*/
|
||||
if (totalTests < totalSkip) {
|
||||
probs.push(`checks=${totalTests} < skip=${totalSkip} —— skip 是 checks 的子集,这不可能成立`);
|
||||
}
|
||||
/*
|
||||
* ★★ 这里曾经有一条**源码文本扫描**("四个计数器各只许一处写"),连同它的自检一起删掉了。
|
||||
*
|
||||
* 为什么删(pi 2026-09-15 建议,我采纳):那条扫描器自己长出了**五轮**毛病 ——
|
||||
* 注释 → 字符串 → `= X + n`/`X++` → 锚钉行首 vs 语句起点 →
|
||||
@ -1053,18 +1095,29 @@ console.log(`\n========== 判据汇总 ==========`);
|
||||
* 单位取成 `# pass` 时 `checks` 与 `pass` 一起少算,那个等式照样成立。
|
||||
*/
|
||||
/*
|
||||
* ★ 这一行的**三个"坏"计数单位不同**,别混着读(pi 2026-09-15 指出缺一格):
|
||||
* ★ 这一行的**四个"坏/没跑"计数单位不同**,别混着读(pi 2026-09-15 指出缺一格;
|
||||
* dsh 2026-09-18 补 `skip`):
|
||||
*
|
||||
* · `fail=N` —— **文件自报的失败条数**(`# fail N` / `RESULT fail=N` 累加)。
|
||||
* · `skip=N` —— **文件自报的跳过条数**(`# skipped N` 累加)。`check()` 风格没有
|
||||
* 跳过机制 ⇒ 那些文件恒 0。语义是"**这条本次没跑**"(设备不在 /
|
||||
* 相位不判),**既不是通过也不是失败** —— 所以它从 `pass` 里减掉。
|
||||
* · `red=N` —— **红的判据文件数**(`reds`,含**运行器自己**推的那些,见下)。
|
||||
* · `broken=N` —— 跑不起来(exit 非 0 / 无输出)的文件数。
|
||||
*
|
||||
* 为什么必须把 `red` 显式打出来:那 3 条"自报条数 ≠ 登记数"的判据是**运行器自己**推的
|
||||
* (`reds.push`),根本不经过文件 ⇒ **进不了 `fail`**。实测过 `fail=1` 而"红的判据(4/25)",
|
||||
* 于是 `fail=1` 被读成"只有一处坏了"。两格都在,读的人才不用自己猜单位。
|
||||
*
|
||||
* ★ 为什么必须有 `skip`(dsh 2026-09-18,实测):给到期判据加行为层时,第一条行为判据
|
||||
* 在"设备不在"时 `t.skip`。补这一格之前,设备在与设备不在两次运行的这一行**一字不差**
|
||||
* (`checks=457 pass=453 fail=4 …`)—— 因为 `# tests` 把跳过的算进总数,而
|
||||
* `pass = checks - fail` 又把它读成通过。**"看不到 ⇒ 绿"长在总数行上**,
|
||||
* 与 `red` 那一格当初缺的理由完全同源:**余额里读不出来的东西等于不存在**。
|
||||
*/
|
||||
const totalsLine = (verdict) => `RESULT files=${SUITE.length} ran=${ranFilesCount} ` +
|
||||
`checks=${totalTests} pass=${totalTests - totalFail} fail=${totalFail} ` +
|
||||
`checks=${totalTests} pass=${totalTests - totalFail - totalSkip} fail=${totalFail} ` +
|
||||
`skip=${totalSkip} ` +
|
||||
`red=${reds.length} broken=${brokens.length} unreported=${unreportedFiles} verdict=${verdict}`;
|
||||
if (reds.length === 0 && brokens.length === 0 && !dueFailed && !selfCheckFailed) {
|
||||
// 报**本相位实际跑了**几条,不报清单长度 —— 上面刚写完"不能声称自己做过的事",
|
||||
|
||||
Reference in New Issue
Block a user