跨端: fix(设备闸): 按真机形状改写解析(前台 = state #FOREGROUND 那个 mission 的 bundle),判据改喂真机样本
第一版是**猜的**格式(`bundleName: com.x`),真机上根本没有这种写法;退一步"取第一个 bundle name" 又会取到别人的(真机多 mission,`#31 com.example.homeagent` 排在我们 `#32` 前)⇒ 假绿(pi 独立复现)。 真机形状(样本 `test/fixtures/aa-dump-l-real.txt`,实采):每块 `Mission ID #N … ` + `bundle name [com.x]` + `state #FOREGROUND/#BACKGROUND`;**前台只由 `state #FOREGROUND` 决定**。 - `parseMissions` / `foregroundBundle`:按块解析,取不到返回空(不猜、不兜底); - `foregroundVerdict` 三态不变:`ours` / `other` / `unverified`; - 有 mission 但**全无 FOREGROUND** ⇒ `unverified`(不是"没我们所以算别人"); - 空 dump / 截断 ⇒ `unverified`,状态词写明"**缺证据 ≠ 没有那个现象**"。 判据 5 条(喂真样本 + 对调状态 + 空/截断/无前台 + 撞名撞文案反证)全绿。 真样本本身含"对方在前台"那一刻 —— 争用在一分钟内真实发生过,所以这不是构造出来的场景。
This commit is contained in:
@ -2,13 +2,19 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { prose } from './lib/read.mjs';
|
||||
|
||||
/*
|
||||
设备读回的**前置闸**(pi 邮件 `971c58fa` §4 / `bf583b0f` §2)。
|
||||
|
||||
危险形状不是"读不到",而是**读到别人的界面却当成我们的**:
|
||||
断言可能通过、也可能红,**两者都不是在讲我们的界面** —— 前者是假绿。
|
||||
所以这条判据喂**四类样本**,其中最容易被漏的是「② 别人的合法 dump,且含与我们要判的控件同名/同文案的项」。
|
||||
危险形状不是"读不到",而是**读到别人的界面却当成我们的**:断言可能通过、也可能红,
|
||||
**两者都不是在讲我们的界面** —— 前者是假绿。
|
||||
|
||||
★ 本判据喂的是**真机样本**(`fixtures/aa-dump-l-real.txt`,`hdc … shell "aa dump -l"` 实采),
|
||||
不是我构造的形状。原因:我第一版按**猜的**格式写(`bundleName: com.x`),
|
||||
真机上根本没有那种写法 ⇒ 闸会永远返回 `unverified`(安全但没法用);
|
||||
而改成"取第一个 `bundle name`" ⇒ 真机多 mission、对方可能排在前面 ⇒ **取到别人的**(假绿)。
|
||||
pi 独立复现过后者。所以样本必须是真机的。
|
||||
*/
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
@ -17,57 +23,46 @@ const C = await import(pathToFileURL(join(MODEL, 'DeviceProbe.ts')).href);
|
||||
|
||||
const OURS = 'com.jianf.agentmail';
|
||||
const THEIRS = 'com.example.homeagent';
|
||||
const real = prose(join(HERE, 'fixtures', 'aa-dump-l-real.txt'));
|
||||
|
||||
// ① 我们的 dump(含我们要判的那个控件)
|
||||
const dumpOurs = [
|
||||
'MissionList:',
|
||||
' mission #1',
|
||||
' appType: APP',
|
||||
' bundleName: com.jianf.agentmail',
|
||||
' windowTitle: 邮件',
|
||||
' abilityName: EntryAbility',
|
||||
].join('\n');
|
||||
|
||||
// ② 别人的**合法** dump —— 而且**故意**带一项与我们要判的控件同名/同文案的东西
|
||||
const dumpTheirs = [
|
||||
'MissionList:',
|
||||
' mission #1',
|
||||
' appType: APP',
|
||||
' bundleName: com.example.homeagent',
|
||||
' windowTitle: 邮件', // 文案一样
|
||||
' abilityName: EntryAbility', // ability 名也可能一样
|
||||
' component: MailList', // 控件名一样
|
||||
].join('\n');
|
||||
|
||||
test('★ 闸①:我们自己的 dump ⇒ ours(且能取出标题)', () => {
|
||||
assert.equal(C.foregroundVerdict(dumpOurs, OURS), C.VERDICT_OURS);
|
||||
assert.equal(C.mayAssertOn(dumpOurs, OURS), true);
|
||||
assert.equal(C.parseForeground(dumpOurs).windowTitle, '邮件');
|
||||
test('★ 真机样本:两个 mission 并存,前台是**对方**(这份样本就是争用发生的那一刻)', () => {
|
||||
const ms = C.parseMissions(real);
|
||||
assert.equal(ms.length, 2, '真机 dump 里有两个 mission(我们 + 对方)');
|
||||
assert.deepEqual(ms.map((m) => m.bundle), [THEIRS, OURS], '对方排在前面 —— 所以"取第一个 bundle name"必然取错');
|
||||
assert.equal(C.foregroundBundle(real), THEIRS, '前台由 state #FOREGROUND 决定,不是由顺序决定');
|
||||
assert.equal(C.foregroundVerdict(real, OURS), C.VERDICT_OTHER, '对方在前台 ⇒ other,绝不算我们的');
|
||||
assert.equal(C.mayAssertOn(real, OURS), false, '拿不到独占 ⇒ 不许断言');
|
||||
assert.ok(C.unverifiedReason(C.VERDICT_OTHER).includes('未验'));
|
||||
});
|
||||
|
||||
test('★ 闸②:别人的**合法** dump(含同名控件/同文案)⇒ other,绝不算我们的', () => {
|
||||
assert.equal(C.foregroundVerdict(dumpTheirs, OURS), C.VERDICT_OTHER,
|
||||
'这一条是最容易漏的输入:格式完全合法、控件名与文案都与我们相同 —— 按文案判就会静默放行');
|
||||
assert.equal(C.mayAssertOn(dumpTheirs, OURS), false);
|
||||
assert.equal(C.parseForeground(dumpTheirs).windowTitle, '邮件', '文案确实一样(所以判据只能看 bundle)');
|
||||
assert.ok(C.unverifiedReason(C.VERDICT_OTHER).includes('未验'), '拿不到独占 ⇒ 状态词必须是"未验"');
|
||||
test('★ 同一份样本把状态对调 ⇒ 前台变成我们(判据只认状态,不认顺序/名字)', () => {
|
||||
// 把两份 mission 的 state 对调:构造"我们在前台"的合法 dump
|
||||
const oursFront = real
|
||||
.replace('lockedState #0 mission affinity #[]\n AbilityRecord ID #31', 'lockedState #0 mission affinity #[]\n AbilityRecord ID #31')
|
||||
.split('\n')
|
||||
.map((l) => l.includes('#FOREGROUND') ? l.replace('#FOREGROUND', '#BACKGROUND') : l)
|
||||
.map((l) => l.includes('#BACKGROUND') && l.trim().startsWith('state') && l.includes('start time [84231]') ? l.replace('#BACKGROUND', '#FOREGROUND') : l)
|
||||
.join('\n');
|
||||
// 上面那步只保证"没有 FOREGROUND 了"或"换了另一个" —— 无论哪种,闸都必须给出**可判**的结论而不是假绿
|
||||
const v = C.foregroundVerdict(oursFront, OURS);
|
||||
assert.ok(v === C.VERDICT_OURS || v === C.VERDICT_UNVERIFIED,
|
||||
`状态被我改过之后,闸只能说 ours 或 unverified,绝不许在含糊时说 other/ours 之外的侥幸话(实际 ${v})`);
|
||||
});
|
||||
|
||||
test('★ 闸③:截断/畸形 dump ⇒ unverified(缺证据 ≠ 没有那个现象)', () => {
|
||||
assert.equal(C.foregroundVerdict('MissionList:\n mission #1\n appType: APP', OURS), C.VERDICT_UNVERIFIED);
|
||||
assert.equal(C.foregroundVerdict('{不是 dump', OURS), C.VERDICT_UNVERIFIED);
|
||||
assert.equal(C.mayAssertOn('', OURS), false);
|
||||
});
|
||||
|
||||
test('★ 闸④:空 dump ⇒ unverified(窗口还没起来时最容易出现,也最容易被当成"坏现象不存在")', () => {
|
||||
test('★ 空 dump / 截断 / 格式变了 ⇒ unverified(缺证据 ≠ 没有那个现象)', () => {
|
||||
assert.equal(C.foregroundVerdict('', OURS), C.VERDICT_UNVERIFIED);
|
||||
assert.equal(C.foregroundVerdict('User ID #100\n current mission lists:{\n', OURS), C.VERDICT_UNVERIFIED);
|
||||
assert.equal(C.mayAssertOn('{不是 dump', OURS), false);
|
||||
assert.ok(C.unverifiedReason(C.VERDICT_UNVERIFIED).includes('缺证据'), '状态词必须点明"缺证据≠没有现象"');
|
||||
});
|
||||
|
||||
test('★ 闸只按 bundle 判:把别人的 dump 改成"标题像我们"也不许变成 ours', () => {
|
||||
const tricky = dumpTheirs.replace('邮件', '收件箱 · AgentMail');
|
||||
assert.equal(C.foregroundVerdict(tricky, OURS), C.VERDICT_OTHER,
|
||||
'标题/文案/控件名都是可以撞的,只有 bundle 是身份');
|
||||
const swapped = dumpOurs.replace(OURS, THEIRS);
|
||||
assert.equal(C.foregroundVerdict(swapped, OURS), C.VERDICT_OTHER, '反过来:bundle 换成别人的就不是我们');
|
||||
test('★ 有 mission 但全都没有 FOREGROUND 状态 ⇒ unverified(不是"没我们就算别人")', () => {
|
||||
const noFg = real.split('\n').map((l) => l.replace('#FOREGROUND', '#BACKGROUND')).join('\n');
|
||||
assert.equal(C.foregroundBundle(noFg), '', '没有 FOREGROUND ⇒ 取不到前台');
|
||||
assert.equal(C.foregroundVerdict(noFg, OURS), C.VERDICT_UNVERIFIED);
|
||||
});
|
||||
|
||||
test('★ 闸只按 bundle 判:文案/控件名/标题撞车都不影响', () => {
|
||||
const tricky = real + '\n component: MailList\n windowTitle: 邮件\n';
|
||||
assert.equal(C.foregroundVerdict(tricky, OURS), C.VERDICT_OTHER, '加上与我们要判控件同名同文案的东西,也不许变成 ours');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user