test: prepare STEP-113 PBF Fast Path APK retest
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-10-11 10:34:08 +09:00
parent 21a1081643
commit e2d202b560
4 changed files with 233 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
# STEP-115:STEP-113 原 PBF 专用 APP Fast Path 复测准备 v1
日期:2026-10-11(JST)。状态:TEST-PREPARATION COMPLETE / MANUAL ANDROID BUILD REQUIRED / FAST NOT VERIFIED。
## 任务意图
原 STEP-113 专用 Android Test App 的 PBF STATIC_RC 是 Fast Path 正式验证主体,不以 STEP-114 FAST_SMOKE 或删掉原正式图层的测试代替。STEP-113 的五个场景不修改,不要求 NavSea 113 层完整复刻,但每个结果必须基于真实 Native 的 Fast/Full、缓存 Build/Reuse 以及 Gate 记录。
## 本次已核实事实
1. Integration main 已 fetch 并确认 80667d6c343411d03a760425438e3d292f452118。Particle 当前 main 为 21a10816435a82de3471bdb55ed83223101f5689。
2. 现有 APK 的 build-info 显示构建源码 c6d13e1059d464d312ac33acac87967300fa5dc4;APK SHA-256 为 0054248bc3872e8670df4e9a10d11f1d65c0912a0482586cd7a247e880a6eaef,与设备 192.168.200.139:5555 的当前安装包 SHA 一致。这是 STEP-114 修改以前的旧 APK,不能拿来验收新修复。
3. STEP-114 未解决原 PBF 缺 tide Source、R Symbol Placement 首次 Build 及 C 内建 Annotation Symbol 拒绝,因此不能提前承诺原 STATIC_RC 会进入 Fast。
4. 新增工具 tools/particle_android_validation/verify_step113_fast_path.py;不修改原 STEP-113 Test App、Native 渲染逻辑、RN 公共 API。工具对版本/安装包 SHA fail-closed,按当前进程 PID 解码现有 STEP113Gate Base64 低频摘要并比较帧计数。
5. 使用 python3 -B 解码旧设备 Logcat 的 STEP113 SOURCE_CHANGE 摘要成功,历史累计 Fast=0、decisionMismatch=0。旧 APK 执行 --preflight-only 返回 STALE_APK,exit=3,属于**预期的旧包拒绝**,不是“修复后测试失败”。
6. 没有执行 Android 编译、安装、启动新 APK、完整 120 帧采集、真实 Fast/性能/像素验收。用户需手工完成最新源码的唯一 Validation APK Pipeline,并手工 install -r,之后才能进行新二进制测试。
## 通过准则与下一步
实际源提交与安装 APK 完全匹配、原 STATIC_RC Native Plan valid、场景 READY、同一进程和场景内 Full/Fast 计数真实变化,FastDelta > 0 方可称 Fast observed。需另外核对 cache Build/Reuse 和 decisionMismatch=0、24 Gate、影像等价及 Source mutation/Annotation fail-closed。若 FastDelta=0,记录具体 Gate/R fallback 后停止将其宣称为 PASS,回到源码定位。原五场景以及 STEP-102 维持保护,非正式发行,不改 Source Lock。
步骤见 tools/particle_android_validation/README.md。没有修复完成或新的 APK 时,保持 STEP-114 PARTIAL,本 STEP 仅测试工具准备完成。

View File

@@ -111,3 +111,21 @@ MAPLIBRE_VALIDATION_OUT_DIR=/tmp/my-validation \
- STEP 场景验收。
这样构建产物与上机验证保持分离,避免脚本意外操作设备。
## STEP-113 原 PBF 专用 Test App Fast Path 复测
本测试不使用 STEP-114 的简化 FAST_SMOKE。保持原 Step113PbfGateScene、PBF URL、63 个正式基础层与原 Native 诊断不变。需要用户手工完成新源码的标准 APK 编译和安装。工具先检查 build-info 中的 Git commit 与当前 HEAD、APK SHA、设备已安装 APK SHA 是否一致;任何不一致即拒绝并说明原因。
在 Mac 的 MapLibreParticleLab 根目录依序执行:
./tools/particle_android_validation/build_validation_apk.sh
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --preflight-only
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC --seconds 75
前两项为用户手工 Android 构建和更新安装,不得在未授权情况下代劳。后两项由 RDC 或用户执行。脚本使用明确 ADB serial 启动原有 Activity 的 STEP-113 原场景,按当前 PID 还原 STEP113Gate 的 Base64 Logcat summary,计算实际 Fast/Full 计数差及 R Build/Reuse,保存到忽略 Git 的 artifacts/maplibre-particle/android-validation/step113-retest-时间/场景/ 目录。
FAST_OBSERVED 仅表示实际 Fast 数字增长,不代表像素等价、性能或所有安全回退已验收;NO_FAST_OBSERVED 表示仍未进入,记录完整 R fallback 和 24 Gate 聚合。STALE_APK / INSUFFICIENT_SAMPLES 不是 PASS。依次需要时用 --scenario PBF63、PARTICLE_OFF、PARTIAL_RCRR、SOURCE_CHANGE。SOURCE_CHANGE 的手动 Source mutation 仍须通过 App 原菜单做独立验证,脚本不会自行修改 Source。阶段事实详见 docs/50_Step记录/STEP-115_STEP113原PBF专用App复测准备_v1.md。

View File

@@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""STEP-113 on-device PBF Fast Path verifier. Does not build, install or alter APKs."""
import argparse
import base64
import datetime
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[2]
ARTIFACTS = ROOT / "artifacts/maplibre-particle/android-validation"
APK = ARTIFACTS / "MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk"
BUILD_INFO = ARTIFACTS / "build-info.txt"
PACKAGE = "org.maplibre.android.testapp"
ACTIVITY = f"{PACKAGE}/{PACKAGE}.activity.particle.ParticleCoreLayerValidationActivity"
SCENARIOS = ("STATIC_RC", "PBF63", "PARTICLE_OFF", "PARTIAL_RCRR", "SOURCE_CHANGE")
LOG_ENTRY = re.compile(r"^[A-Z]/STEP113Gate\(\s*(\d+)\):\s*(\{.+\})\s*$")
def call(args, *, check=True):
result = subprocess.run(args, text=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False)
if check and result.returncode:
raise RuntimeError(f"command returned {result.returncode}: {' '.join(args)}\n"
f"{result.stderr[-1200:]}\n{result.stdout[-1200:]}")
return result.stdout.strip()
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for block in iter(lambda: f.read(1048576), b""):
h.update(block)
return h.hexdigest()
def inspect(apk, adb, serial):
if not apk.is_file() or not BUILD_INFO.is_file():
raise RuntimeError("缺少 Validation APK/build-info.txt,必须先由用户手工运行标准构建脚本")
info = dict(line.split("=", 1) for line in BUILD_INFO.read_text(encoding="utf-8").splitlines()
if "=" in line)
source_head = call(["git", "-C", str(ROOT), "rev-parse", "HEAD"])
if info.get("commit") != source_head:
raise RuntimeError(f"STALE_APK: build-info commit={info.get('commit')}, "
f"current HEAD={source_head}; 禁止用旧APK验收本轮修复")
local_sha = sha256(apk)
if local_sha != info.get("sha256"):
raise RuntimeError(f"APK_SHA_MISMATCH: actual={local_sha}, build-info={info.get('sha256')}")
if info.get("abi") != "x86_64":
raise RuntimeError(f"unexpected ABI: {info.get('abi')}")
device = lambda *a: call([adb, "-s", serial, *a])
abi = device("shell", "getprop", "ro.product.cpu.abi")
if abi != "x86_64":
raise RuntimeError(f"DEVICE_ABI_MISMATCH: {abi}")
paths = device("shell", "pm", "path", PACKAGE).splitlines()
if not paths or not paths[0].startswith("package:"):
raise RuntimeError(f"TEST_APP_NOT_INSTALLED: {PACKAGE}")
remote = paths[0].split("package:", 1)[1].strip()
installed_sha = device("shell", "sha256sum", remote).split()[0]
if installed_sha != local_sha:
raise RuntimeError(f"STALE_INSTALLED_APK: installed={installed_sha} local={local_sha}; "
"用户须核实后手工 adb install -r,不能用旧版本验收")
return info, local_sha, source_head, device
def extract(logcat, pid, expected):
chunks = {}
for line in logcat.splitlines():
found = LOG_ENTRY.match(line.strip())
if not found or int(found.group(1)) != pid:
continue
try:
item = json.loads(found.group(2))
if item.get("encoding") != "base64-utf8" or item.get("kind") != "summary":
continue
ident = (item["exportId"], item["kind"])
record = chunks.setdefault(ident, {"count": item["chunkCount"], "items": {}})
if record["count"] == item["chunkCount"]:
record["items"][int(item["chunkIndex"])] = item["payload"]
except (KeyError, ValueError, TypeError):
continue
result = []
for (ident, _), record in chunks.items():
if len(record["items"]) != record["count"]:
continue
try:
packed = "".join(record["items"][i] for i in range(record["count"]))
sample = json.loads(base64.b64decode(packed, validate=True).decode("utf-8"))
if sample.get("scenario") == expected and sample.get("sceneReady") and not sample.get("blockedReason"):
result.append((int(ident.split("-", 1)[0]), sample))
except (KeyError, ValueError, TypeError, UnicodeError):
continue
return [x[1] for x in sorted(result, key=lambda item: item[0])]
def run():
parser = argparse.ArgumentParser(description="Check actual STEP-113 PBF on-device Fast frames")
parser.add_argument("--serial", default="192.168.200.139:5555")
parser.add_argument("--adb", default=str(Path.home() / "Library/Android/sdk/platform-tools/adb"))
parser.add_argument("--apk", default=str(APK))
parser.add_argument("--scenario", choices=SCENARIOS, default="STATIC_RC")
parser.add_argument("--seconds", type=int, default=75)
parser.add_argument("--preflight-only", action="store_true")
args = parser.parse_args()
if args.seconds < 65 and not args.preflight_only:
parser.error("至少65秒以取得首次及下一次30秒周期摘要;不是FPS测量")
if not Path(args.adb).is_file():
raise RuntimeError(f"adb not found: {args.adb}")
info, local_sha, head, device = inspect(Path(args.apk), args.adb, args.serial)
print(f"APK_SOURCE_HEAD={head}")
print(f"APK_SHA256={local_sha}")
print(f"DEVICE={args.serial} ABI=x86_64 INSTALLED_SHA_MATCH=True")
if args.preflight_only:
print("PREFLIGHT_PASS(仅证明同版本APK已安装,未测试Fast)")
return 0
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output = ARTIFACTS / f"step113-retest-{stamp}" / args.scenario.lower()
output.mkdir(parents=True, exist_ok=False)
command = ["shell", "am", "start", "-S", "-n", ACTIVITY,
"--es", "step113_scenario", args.scenario]
print(f"START_SCENARIO={args.scenario}")
output.joinpath("launch.txt").write_text(device(*command) + "\n", encoding="utf-8")
process_id = None
for _ in range(12):
time.sleep(1)
pid_text = call([args.adb, "-s", args.serial, "shell", "pidof", PACKAGE], check=False)
if pid_text.strip().split():
process_id = int(pid_text.strip().split()[0])
break
if process_id is None:
raise RuntimeError("APP_PROCESS_NOT_RUNNING after launch")
print(f"APP_PID={process_id}")
time.sleep(max(0, args.seconds - 12))
raw_log = device("logcat", "-d", "-v", "brief", "-s", "STEP113Gate:I", "*:S")
output.joinpath("logcat.txt").write_text(raw_log + "\n", encoding="utf-8")
samples = extract(raw_log, process_id, args.scenario)
if not samples:
status = "NO_READY_SUMMARY"
fast_delta = full_delta = 0
else:
start, end = samples[0], samples[-1]
fast_delta = end["fastRendered"] - start["fastRendered"]
full_delta = end["fullRendered"] - start["fullRendered"]
if len(samples) < 2:
status = "INSUFFICIENT_SAMPLES"
elif any(s["decisionMismatch"] != 0 for s in samples):
status = "DECISION_MISMATCH"
elif fast_delta > 0:
status = "FAST_OBSERVED"
else:
status = "NO_FAST_OBSERVED"
report = {
"schema": "step113-fast-path-retest",
"sourceCommit": head,
"apkSha256": local_sha,
"installedShaMatched": True,
"device": args.serial,
"appPid": process_id,
"scenario": args.scenario,
"sourceReport": "STEP-113 original PBF test scene; not STEP-114 smoke",
"durationSecondsRequested": args.seconds,
"completeSummarySamples": len(samples),
"status": status,
"fastDelta": fast_delta,
"fullDelta": full_delta,
"initialSummary": samples[0] if samples else None,
"finalSummary": samples[-1] if samples else None,
}
output.joinpath("result.json").write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8")
print(f"STATUS={status} FastDelta={fast_delta} FullDelta={full_delta} Samples={len(samples)}")
print(f"EVIDENCE={output / 'result.json'}")
# Fail closed: no Fast observed is a meaningful negative result, not PASS.
if status == "FAST_OBSERVED":
print("FAST_OBSERVED仅表示真实Fast计数增长;缓存/像素/安全回退仍须逐帧验收。")
return 0
return 2
if __name__ == "__main__":
try:
sys.exit(run())
except (OSError, RuntimeError, ValueError, KeyError) as exc:
print(f"STEP113_RETEST_BLOCKED: {exc}", file=sys.stderr)
sys.exit(3)

View File

@@ -1155,3 +1155,5 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
- 下一步 RDC 独立审计/执行 Mac Native 定向检查;用户一次手工 Android/设备闭环并保存源码diff/制品SHA/逐帧/截图证据。详见 docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md 与 docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md。
- 2026-10-11 STEP-114 RDC:独立核对安全Gate改动、3项C++测试源码、Android Test App接线及STEP-113/102冻结SHA,git diff --check PASS。Mac缺libuv,单独C++ header检查因Xcode找不到标准头array环境阻塞;Android/真机/Fast未执行,PARTIAL,后续用户一次手工构建验收。
- 2026-10-11 STEP-115:为原STEP-113专用APP增加Fast Path复测工具,严格校验源码HEAD、build-info APK SHA与设备安装SHA;旧包源码为c6d13e1,与现行21a1081不匹配,preflight正确拒绝。已验证旧Logcat可解码STEP113真实摘要,原五场景保持不改。尚无新Android编译/安装/真正Fast,下一步用户手工build/install后以STATIC_RC为先验收,状态PARTIAL。