test: prepare STEP-113 PBF Fast Path APK retest
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
22
docs/50_Step记录/STEP-115_STEP113原PBF专用App复测准备_v1.md
Normal file
22
docs/50_Step记录/STEP-115_STEP113原PBF专用App复测准备_v1.md
Normal 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 仅测试工具准备完成。
|
||||
@@ -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。
|
||||
|
||||
191
tools/particle_android_validation/verify_step113_fast_path.py
Executable file
191
tools/particle_android_validation/verify_step113_fast_path.py
Executable 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)
|
||||
@@ -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。
|
||||
|
||||
Reference in New Issue
Block a user