pi 邮件 `6f902e1e` 指出两件,都对: 1. **我那条正例是自相矛盾的**:我用脚本把 `state #FOREGROUND` 对调,却漏了同一块的 `app state #X` ⇒ 造出 `state FG` + `app state BG` 这种**真机上不会出现**的 dump。于是"闸能放行"这条正例 建在**非法输入**上 —— 它绿,但它没证明任何真机会发生的事。 现在:正例改用 12:08 **真机实采**的 `fixtures/aa-dump-l-ours-foreground.txt`(两个 mission 都在、 `state`/`app state` 一致、FG 是我们); 2. 顺手把"矛盾怎么办"钉成规则:同一块里 `state` 与 `app state` 打架 ⇒ **`unverified`**, **不许"挑一个信"**(那是把互相打脸的证据当成证据)。`app state` 缺失时不因此判未验 (否则老格式 dump 会一律未验 —— 那是"更安静的失效")。 判据 8 条全绿;登记同步为 8。
135 lines
5.7 KiB
TypeScript
135 lines
5.7 KiB
TypeScript
/**
|
||
* 设备读回的**前置闸**(纯逻辑,无 `@ohos`)—— 判据能用 node 直接跑。
|
||
*
|
||
* 为什么需要它(pi 邮件 `971c58fa` §4):别的 agent 在同一个模拟器上跑它的 app,
|
||
* 它的 `aa start` 会把前台抢走。如果我在那之后读控件树,**读到的是它的窗口**,
|
||
* 而我的断言就会拿**别的 app 的布局**去判 —— 可能"通过",也可能红,**两者都不是在讲我们的界面**。
|
||
* 后者是**假绿**:比"被打断"严重得多。
|
||
*
|
||
* 处置规则(pi 邮件 `82ae145b` 认可,`bf583b0f` 补样本):
|
||
* 前台不是我们 ⇒ **不下断言**;而且**不许静默跳过**(跳过会在下一轮被读成"验过了")。
|
||
* 正确状态是 **`未验`**,余额里那笔到期欠账**继续开着**。
|
||
* 一句话:**"没验"和"验过了"之间不许有斜坡。**
|
||
*/
|
||
|
||
/** 闸的判决:**只有三种**,没有第四种"大概是我们" */
|
||
export const VERDICT_OURS: string = 'ours';
|
||
export const VERDICT_OTHER: string = 'other';
|
||
export const VERDICT_UNVERIFIED: string = 'unverified';
|
||
|
||
/** 一个 mission 块里我们要的两样 */
|
||
export interface MissionInfo {
|
||
bundle: string;
|
||
state: string;
|
||
/** 同一块里的 `app state #X` —— 真机 dump 两个都在。**两者矛盾 ⇒ 拿不准**(pi 邮件 `6f902e1e`) */
|
||
appState: string;
|
||
}
|
||
|
||
/** `bundle name [com.x]` —— 方括号形状是**真机实测**的(样本 fixtures/aa-dump-l-real.txt) */
|
||
const RE_BUNDLE = new RegExp('^\\s*(?:bundle|app)\\s+name\\s+\\[([^\\]]+)\\]');
|
||
/** 只认行首的 `state`;`app state #FOREGROUND` 是同一块里的另一行,不算错 */
|
||
const RE_STATE = new RegExp('^\\s*state\\s+#(\\w+)');
|
||
/** `app state #FOREGROUND` —— 与 `state` 交叉验证:**矛盾就是拿不准**,不是"挑一个信" */
|
||
const RE_APPSTATE = new RegExp('^\\s*app\\s+state\\s+#(\\w+)');
|
||
|
||
/**
|
||
* 解析所有 mission 块。取不到就返回**空数组**(空 dump / 截断 / 格式变了)—— 不猜、不兜底。
|
||
*
|
||
* ⚠️ 第一版是**猜的**格式(`bundleName: com.x`),真机上根本没有那种写法;改"取第一个 bundle name"
|
||
* 又会在多 mission 时取到**别人的**(真机上对方可能排在我们前面)⇒ 假绿。真机形状见文件头。
|
||
*/
|
||
export function parseMissions(dump: string): MissionInfo[] {
|
||
const out: MissionInfo[] = [];
|
||
if (dump.length === 0) {
|
||
return out;
|
||
}
|
||
const lines: string[] = dump.split('\n');
|
||
let bundle: string = '';
|
||
let state: string = '';
|
||
let appState: string = '';
|
||
for (const raw of lines) {
|
||
const line: string = raw.replace('\r', '');
|
||
if (line.indexOf('Mission ID') >= 0) {
|
||
if (bundle.length > 0) {
|
||
out.push({ bundle: bundle, state: state, appState: appState });
|
||
}
|
||
bundle = '';
|
||
state = '';
|
||
appState = '';
|
||
continue;
|
||
}
|
||
const mb = RE_BUNDLE.exec(line);
|
||
if (mb !== null && mb[1].length > 0 && bundle.length === 0) {
|
||
bundle = mb[1];
|
||
continue;
|
||
}
|
||
const ms = RE_STATE.exec(line);
|
||
if (ms !== null && state.length === 0) {
|
||
state = ms[1];
|
||
continue;
|
||
}
|
||
const ma = RE_APPSTATE.exec(line);
|
||
if (ma !== null && appState.length === 0) {
|
||
appState = ma[1];
|
||
}
|
||
}
|
||
if (bundle.length > 0) {
|
||
out.push({ bundle: bundle, state: state, appState: appState });
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 前台是哪个 bundle —— **只认带 `state #FOREGROUND` 的那个 mission 块**。
|
||
* 取不到返回空串(没有 mission / 没有 FOREGROUND / 空 dump):
|
||
* **空 dump 最容易被误读**(窗口没起来时它最常出现,而"没读到"极易被当成"坏现象没有出现")。
|
||
*/
|
||
export function foregroundBundle(dump: string): string {
|
||
const missions: MissionInfo[] = parseMissions(dump);
|
||
for (const m of missions) {
|
||
if (m.state !== 'FOREGROUND') {
|
||
continue;
|
||
}
|
||
// 真机 dump 里 `state` 与 `app state` 是一致的;**两者矛盾 ⇒ 这份 dump 自相矛盾 ⇒ 拿不准**
|
||
// (不许"挑一个信":那是把互相打脸的证据当成证据。pi 邮件 `6f902e1e` 抓到我的对调样本正是这种形状)
|
||
if (m.appState.length > 0 && m.appState !== 'FOREGROUND') {
|
||
return '';
|
||
}
|
||
return m.bundle;
|
||
}
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* 判决"这份 dump 是不是我们的" —— **只许看 bundle,不许看标题/控件名/文案**。
|
||
*
|
||
* 理由(pi 邮件 `bf583b0f` §2):别人的合法 dump 里**完全可能**有与我们要判的控件同名、
|
||
* 文案相同的项("邮件"这种通用词尤其容易)。按文案判 ⇒ 别人的 dump 会被当成我们的 ⇒ 静默放行。
|
||
*/
|
||
export function foregroundVerdict(dump: string, ourBundle: string): string {
|
||
const fg: string = foregroundBundle(dump);
|
||
if (fg.length === 0) {
|
||
return VERDICT_UNVERIFIED;
|
||
}
|
||
return fg === ourBundle ? VERDICT_OURS : VERDICT_OTHER;
|
||
}
|
||
|
||
/**
|
||
* 能不能在这份 dump 上做断言。
|
||
* `false` ⇒ 调用侧**必须**把这条判据记成 `未验`(欠账继续开着),**不许**记成通过、也不许静默跳过。
|
||
*/
|
||
export function mayAssertOn(dump: string, ourBundle: string): boolean {
|
||
return foregroundVerdict(dump, ourBundle) === VERDICT_OURS;
|
||
}
|
||
|
||
/** 拿不到独占时的状态词 —— 统一到这里,免得各处自己编(编出来的词会互相读不懂) */
|
||
export function unverifiedReason(verdict: string): string {
|
||
if (verdict === VERDICT_OURS) {
|
||
return '';
|
||
}
|
||
if (verdict === VERDICT_OTHER) {
|
||
return '未验:前台是别的 app(拿不到独占窗口)—— 读到的界面不是我们的,断言会讲错对象';
|
||
}
|
||
return '未验:没读到前台是谁(空 dump / 截断 / 格式变了 / 没有 FOREGROUND 状态)—— **缺证据不等于没有那个现象**';
|
||
}
|