fix(判据脚本): --walk 存证不进工作树;真机动作需显式持锁确认

票 = task-26(自报缺陷,Lead 批准开树自修)。两处都是我上一笔(导航器入库)留下的:
一条会让工作树变脏,另一条更严重——动到了不属于我的台架。

一、--walk 的存证落点
- 症状:outdir = os.path.join(HERE, "walk"),HERE = <仓库根>/scripts → 真机跑完在
  scripts/walk/ 落 12 个帧:git status 立刻脏、done 的并集残留审计会把它当任务遗留物,
  下一个窗口还盖掉上一个窗口的证据。我自己刚立过「轨迹只在 --trace 给了路径才落盘」的
  规矩,这条路径上被自己破了。
- 改法:默认落 <仓库根>/ci-artifacts/uinav/walk/(已 gitignore,本仓台架产物一贯在此),
  --outdir 可改;并加**反向拒绝**——解析后的目录若落在版本控制内且不在 ci-artifacts 之下,
  直接 [FAIL] 退出 3、连目录都不建。「跑完记得删」只防得住记得的人(同 bom-guard 立项
  理由,也同今天 .map / 缺 lvgl_sim.exe 那一族:事实源不存在就大声失败)。

二、未持锁就注入了按键(实测踩到,已做成参数闸)
- 我给「默认落点会打印成什么」做验收时跑了 --walk,脚本在没有任何拦截的情况下连上 7003
  并开始注键;而那时台架属于验收窗的准备方,板子停在主干 mega 镜像上。**板端 UI 状态可能
  已被推到别处**:只注入了方向/进入/退出类按键,未烧录、未碰串口、未触发任何写操作
  (网口设置的保存行走不到——导航器从不在保存行按 ENTER,过密码默认也只走只读查看密码)。
- 根因不是手滑,是「必须持 bench 锁」只写在文档与 print 里:跑的人自认为在跑一次离线
  检查,脚本就真去连了。于是把确认做成参数 --i-hold-bench-lock:不给就拒绝(退出码 3,
  不碰网络);脚本不代取锁,也不替人判断「应该没人在用」。
- 给窗口方的处置建议:验收窗前先复位一次,或直接以 --walk 起步(导航器第一步就是回根,
  且回根后还补收一次 l 防被裁弹框漏计)——别把我注入后的残留状态当成验收现象。

验收(本机、无板,全部实跑)
- --walk --outdir <仓库内目录> → rc=3 + [FAIL] + 目录未被创建;
- --walk / --board 未给确认位 → rc=3 且**不触网**(瞬时返回,不再有任何连板可能);
- --selftest-quick rc=0(黄金参考 18 帧 0 错);check_doc_links 通过;
- 一笔提交,未用 --no-verify;工作树仅这 2 个文件改动,无多余产物。

文档同步:docs/UI设计/UI_自动化测试流程.md §7.3 补「存证不进工作树」「真机动作需显式
确认位」两条并把用法示例改对(同一事实只在一处,正文仍在那一节)。
This commit is contained in:
2026-10-25 18:30:41 +08:00
parent e771a8ea21
commit 621421f3ca
2 changed files with 67 additions and 9 deletions
+45 -4
View File
@@ -22,6 +22,10 @@
python scripts/board_ui_nav.py --walk # 台架窗口:走全部绑定页并存帧
python scripts/board_ui_nav.py --board --to yx --grab /tmp/yx
可选:--trace 路径(步迹落盘) --dump-fail 目录(把认错的那一屏存下来)
--board / --walk 属真机动作:必须另加 --i-hold-bench-lock 显式确认已持 bench 锁,
否则直接拒绝(退出码 3,不碰网络)——台架是独占资源,脚本不替人判断有没有在用。
--walk 的存证目录默认在 ci-artifacts/uinav/walk(版本控制之外),--outdir 可改;
落在版本控制内会被直接拒绝——台架证据不进工作树,工作树才能保持干净可比对。
退出码: 0 = 成功;1 = 分类或导航失败;2 = 环境错误(exe 缺失 / 板子连不上)
创建日期:2026-10-25(道E,Lead 大窗反馈后的判据脚本升级)
"""
@@ -69,6 +73,13 @@ COL_W = 66 # 一级子列宽(板端与模拟器同源)
BLANK_INK = 900 # "几乎空白"屏的墨量上限(待机/密码页形态,实测 512)
MAX_STEPS = 60 # 单次导航按键上限,到顶即失败而不是继续猜
# 直连真机的显式确认位:命令行里没有它,--board / --walk 一律不碰网络。
# 为什么需要(2026-10-25 实测踩过):--walk 在没有任何拦截的情况下就连上板子注入了按键
# ——跑的人只想“看一下默认存证目录打印成什么”,可台架是独占资源,别人可能正停在验收态。
# 文档里那句“必须持 bench 锁”防不住“我只是跑一下”,所以把确认做成命令行参数:要连真机,
# 就得在命令行里留下“我持锁”的字面证据。脚本不代取锁,也不接受“应该没人在用”的判断。
BENCH_CONFIRM = "--i-hold-bench-lock"
def load_harness():
"""载入库内黄金回归脚本,复用它的字体解析与字形渲染(不复制第二套规则)。"""
@@ -848,6 +859,17 @@ def selftest_closed_loop(clf, want):
return len(want), bad, log
def bench_gate(argv, what):
"""真机动作前的闸:没给显式确认位就拒绝(退出码 3,且不碰网络)。"""
if BENCH_CONFIRM in argv:
return 0
print("[拒绝] %s 会直连真机(注 KEY + 取 LCDMEM 帧),台架是独占资源。" % what)
print(" 先取 bench 锁,再由人在同一命令行加 %s 明确确认已持锁:" % BENCH_CONFIRM)
print(" .venv/Scripts/python.exe .dsh/skills/task-worktree/scripts/task_worktree.py lock acquire bench --task <ID>")
print(" 本脚本不代取锁,也不接受“应该没人在用”——那种判断只有持锁的人能做。")
return 3
def _dump_trace(log):
"""轨迹只在 --trace 给了路径时落盘(默认不往仓库里写东西)。"""
if not TRACE:
@@ -918,12 +940,29 @@ def main(argv):
# 但黄金参考与闭环必须真跑过:任一为 0 说明这次"自检"其实什么都没检查
return 1 if (badr or bad1 or bad2 or nr == 0 or m == 0) else 0
if "--walk" in argv:
rc = bench_gate(argv, "--walk")
if rc:
return rc
# 台架窗口用:一条命令走完菜单树上**所有绑定页**,每页存一帧 + 一份轨迹。
# 之所以要"一条命令",是因为 5 分钟窗口里没时间让人逐页手按;跑完只需把
# uinav/walk/ 与 trace.json 交给 Lead,判据失败在哪一屏、当时认出的是什么,
# 都在文件里,不用现场再猜。
# 存证目录(默认 ci-artifacts/uinav/walk/)与 --trace 落下的步迹交给 Lead,
# 判据失败在哪一屏、当时认出的是什么都在文件里,不用现场再猜。
pid = None
print("⚠ --walk 直连真机:调用方必须已持 bench 锁")
# 存证**必须落在版本控制之外**。第一版写 HERE/walk = <仓库根>/scripts/walk/,等于
# 把台架产物写进工作树:git status 立刻脏、done 的并集残留审计会把它当成任务遗留
# 物,下一个窗口还盖掉上一个窗口的证据。默认落 ci-artifacts/uinav/walk/(已
# gitignore,本仓台架产物一贯在此),--outdir 可改。
# 反向拒绝:落在版本控制内且不在 ci-artifacts 之下就**直接不跑**——规矩一旦依赖
# "跑完记得删",它就只防得住记得的人(这条道理同 bom-guard 的立项理由)。
outdir = argv[argv.index("--outdir") + 1] if "--outdir" in argv else \
os.path.join(REPO, "ci-artifacts", "uinav", "walk")
rp = os.path.abspath(outdir)
if rp.lower().startswith(os.path.abspath(REPO).lower() + os.sep) \
and ("ci-artifacts" not in rp.lower()):
print("[FAIL] --walk 的存证目录不能落在版本控制内:%s" % rp)
print(" 默认 ci-artifacts/uinav/walk/;换目录请用 --outdir <忽略区里的路径>")
return 3
print("⚠ --walk 直连真机:调用方必须已持 bench 锁;存证目录 %s" % rp)
dev = BoardDevice()
ok, bad = 0, []
try:
@@ -932,7 +971,6 @@ def main(argv):
nav = Navigator(dev, clf)
try:
fr, st = nav.go_page(pid)
outdir = os.path.join(HERE, "walk")
if not os.path.isdir(outdir):
os.makedirs(outdir)
with open(os.path.join(outdir, pid + ".bin"), "wb") as fp:
@@ -948,6 +986,9 @@ def main(argv):
print("走完 %d 页:成功 %d,失败 %d;轨迹 %s" % (ok + len(bad), ok, len(bad), TRACE))
return 1 if bad else 0
if "--board" in argv:
rc = bench_gate(argv, "--board")
if rc:
return rc
if "--to" not in argv:
print("--board 需要 --to <页面 id>(见 --list)")
return 3