fix: guard STEP-113 empty annotation and expose PBF blockers
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:
@@ -114,7 +114,7 @@ MAPLIBRE_VALIDATION_OUT_DIR=/tmp/my-validation \
|
||||
|
||||
## 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 是否一致;任何不一致即拒绝并说明原因。
|
||||
本测试不使用 STEP-114 的简化 FAST_SMOKE。保留原 Step113PbfGateScene 的 PBF URL、63 个正式基础层与 Native 真实计数。STEP-116 仅对 STATIC_RC 的空 SDK Annotation 辅助层做一次移除及生命周期保护,并增加诊断;其余四场景不做此调整。需要用户在 RDC 独立审计并锁定源码后手工完成标准 APK 编译和安装。工具检查 tracked clean、build-info Git commit 与 HEAD、APK SHA、设备安装 SHA;不一致即拒绝。原 STEP-110/111 等未跟踪任务书可以保留。
|
||||
|
||||
在 Mac 的 MapLibreParticleLab 根目录依序执行:
|
||||
|
||||
@@ -129,3 +129,9 @@ MAPLIBRE_VALIDATION_OUT_DIR=/tmp/my-validation \
|
||||
前两项为用户手工 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。
|
||||
|
||||
## STEP-116 当前交接
|
||||
|
||||
本轮只交源码,缺 `tide` 与 Symbol R 缓存准入继续 fail-closed,不能宣称原 STATIC_RC 已出现 Fast。Native AnnotationManager 在后续真实 Symbol Annotation add/update 与 Style loaded 时同步恢复缺失 points;Test App stop 重加原空层,后续 Annotation/Style 变化撤销空层验收条件,不周期删除新内容。原63层和Source定义保留,内部Annotation Source不删除;源码预期STATIC_RC由66层变65层,须以最新设备manifest核验。
|
||||
|
||||
复测脚本现要求至少75秒,在PID出现后等待完整请求时长;建议 `--scenario STATIC_RC --seconds 90`。同generation/lifecycle、plan/base parity、24Gate、mismatch0、计数单调及blocked撤销均为必要条件。summary新增缺源ID/Placement/commands/Source表;120帧仍需用户手动导出。精确命令、用户一次手工build/install、RDC Native定向测试及阴性结果解释见 [STEP-116 专用APP验收](../../docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md)。历史STEP115记录保留为当时事实,不代表本轮设备已验收。
|
||||
|
||||
@@ -45,6 +45,9 @@ def inspect(apk, adb, serial):
|
||||
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"])
|
||||
tracked_changes = call(["git", "-C", str(ROOT), "status", "--porcelain", "--untracked-files=no"])
|
||||
if tracked_changes:
|
||||
raise RuntimeError("SOURCE_NOT_LOCKED: tracked源码有未提交变更;RDC须先独立审计并scoped提交,用户再构建最新HEAD")
|
||||
if info.get("commit") != source_head:
|
||||
raise RuntimeError(f"STALE_APK: build-info commit={info.get('commit')}, "
|
||||
f"current HEAD={source_head}; 禁止用旧APK验收本轮修复")
|
||||
@@ -76,7 +79,7 @@ def extract(logcat, pid, expected):
|
||||
continue
|
||||
try:
|
||||
item = json.loads(found.group(2))
|
||||
if item.get("encoding") != "base64-utf8" or item.get("kind") != "summary":
|
||||
if item.get("encoding") != "base64-utf8" or item.get("kind") not in ("summary", "blocked"):
|
||||
continue
|
||||
ident = (item["exportId"], item["kind"])
|
||||
record = chunks.setdefault(ident, {"count": item["chunkCount"], "items": {}})
|
||||
@@ -85,13 +88,18 @@ def extract(logcat, pid, expected):
|
||||
except (KeyError, ValueError, TypeError):
|
||||
continue
|
||||
result = []
|
||||
for (ident, _), record in chunks.items():
|
||||
for (ident, kind), 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"):
|
||||
if kind == "blocked":
|
||||
sample["blockedReason"] = sample.get("reason") or "SCENE_BLOCKED"
|
||||
sample["sceneReady"] = False
|
||||
# Keep revoked/blocked summaries as negative evidence too; an
|
||||
# earlier Fast counter must not conceal a later Annotation guard.
|
||||
if sample.get("scenario") == expected:
|
||||
result.append((int(ident.split("-", 1)[0]), sample))
|
||||
except (KeyError, ValueError, TypeError, UnicodeError):
|
||||
continue
|
||||
@@ -107,8 +115,8 @@ def run():
|
||||
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 args.seconds < 75 and not args.preflight_only:
|
||||
parser.error("原STEP-113场景至少75秒;摘要与完整120帧导出仍须分别验收")
|
||||
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)
|
||||
@@ -136,12 +144,17 @@ def run():
|
||||
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))
|
||||
# PID acquisition can take less than 12 seconds. Never subtract the
|
||||
# maximum poll duration from the actual scenario observation period.
|
||||
observation_started = time.monotonic()
|
||||
time.sleep(args.seconds)
|
||||
observation_elapsed = time.monotonic() - observation_started
|
||||
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)
|
||||
all_samples = extract(raw_log, process_id, args.scenario)
|
||||
samples = [s for s in all_samples if s.get("sceneReady") and not s.get("blockedReason")]
|
||||
if not samples:
|
||||
status = "NO_READY_SUMMARY"
|
||||
status = "SCENE_BLOCKED_OR_REVOKED" if any(s.get("blockedReason") for s in all_samples) else "NO_READY_SUMMARY"
|
||||
fast_delta = full_delta = 0
|
||||
else:
|
||||
start, end = samples[0], samples[-1]
|
||||
@@ -149,8 +162,20 @@ def run():
|
||||
full_delta = end["fullRendered"] - start["fullRendered"]
|
||||
if len(samples) < 2:
|
||||
status = "INSUFFICIENT_SAMPLES"
|
||||
elif any(s.get("blockedReason") for s in all_samples):
|
||||
status = "SCENE_BLOCKED_OR_REVOKED"
|
||||
elif any((s.get("sceneGeneration"), s.get("lifecycle")) !=
|
||||
(start.get("sceneGeneration"), start.get("lifecycle")) for s in samples):
|
||||
status = "SUMMARY_LIFECYCLE_CHANGED"
|
||||
elif any(not s.get("planValid") or not s.get("basePreserved") for s in samples):
|
||||
status = "INVALID_PLAN_OR_BASE_PARITY"
|
||||
elif any(len(s.get("gateFailureCounts", [])) != 24 for s in samples):
|
||||
status = "INCOMPLETE_GATE_SUMMARY"
|
||||
elif any(s["decisionMismatch"] != 0 for s in samples):
|
||||
status = "DECISION_MISMATCH"
|
||||
elif any(b["fastRendered"] < a["fastRendered"] or b["fullRendered"] < a["fullRendered"]
|
||||
for a, b in zip(samples, samples[1:])):
|
||||
status = "COUNTER_RESET"
|
||||
elif fast_delta > 0:
|
||||
status = "FAST_OBSERVED"
|
||||
else:
|
||||
@@ -165,7 +190,9 @@ def run():
|
||||
"scenario": args.scenario,
|
||||
"sourceReport": "STEP-113 original PBF test scene; not STEP-114 smoke",
|
||||
"durationSecondsRequested": args.seconds,
|
||||
"durationSecondsObservedAfterPid": observation_elapsed,
|
||||
"completeSummarySamples": len(samples),
|
||||
"blockedSummarySamples": [s for s in all_samples if s.get("blockedReason")],
|
||||
"status": status,
|
||||
"fastDelta": fast_delta,
|
||||
"fullDelta": full_delta,
|
||||
|
||||
Reference in New Issue
Block a user