Files
gplan-2/tools/probe_gate.sh
T
DKChan 8c82d3bf56 feat(迭代29 T1): 本地闸三硬闸落地——退出码/脚本错误扫描/断言数下限
①退出码为唯一权威判据:probe_gate.sh 八件探针(轻闸 4 + full 4)判定统一走 verdict 函数,
  退出码≠0 即红,PASS 串降级为附加判据(ui_click_probe 本就走退出码语义,签名标记传它自己的
  click_probe 串,不强加 PASS)。
②脚本错误扫描闸件 tools/error_scan.py(标记 import 自 run_smoke.py,禁第二份清单):
  probe_gate 逐探针段扫日志;run_all 侧因 --script 子进程的 stdout 管道恒空(OS.execute 在本机
  Godot/Windows 组合下拿不到输出,且 GUI 子系统 exe 的 stdout/stderr 句柄无效),改为整条命令
  交给系统命令解释器重定向落盘再读,退出码原样回传——引擎二进制取 *_console 版(GODOT_BIN 优先)。
③断言数下限:Kit.finish 在 checks==0 时返回专用退出码 2(不是 1——致命类套件期望 1,返回 1 会被
  expect 协议放过);run_all 汇总校验实跑套件数 == manifest 条目数。
④example_test.gd 退出生产 manifest(协议活样板,文件保留;同类先例 fog_lantern 摘除)。

变异验证(三反例 T0 判绿、T1 后全部判红):零断言套件 exit=2 判红/脚本错误套件命中 SCRIPT ERROR
判红/探针打印 PASS 后 quit(1) 判红「PASS 串不抵」。

本卡附带裁决落地(均已用户裁准):
· fx_grant_gp 补进行来源断言豁免表(grant_segment 的专长 GP 入账行无棋盘来源格,每局触发 7 次
  断言失败却因脚本错误不改退出码长期漏过;迭代 21 同款修过 fx_reveal)。
· manifest 增 negative 字段,仅 reward_dangling_treasure 一件(坏表是其测试目标,错误扫描误伤)。
· config_db_test 的 rows 定型数组赋值改 rows.assign——原写法触发 Invalid assignment 中断整个函数,
  敌人表行集三条纪律 10 条断言从未执行过(31 处 check 实跑 21 条);修复后 26 条全跑,其中两条正向
  断言见红,根因是夹具缺 depth-0 锚行(validate 的 anchor 规则),补锚行后全绿。
· system_contracts_test 的 RngStub/UnitContainerStub 空桩换真实实例(定型成员赋值报 type 空)。
2026-10-16 15:08:13 +08:00

156 lines
8.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 轻闸探针正主(2026-10-02 挂死灾害批落籍)——headless 安全段固化,禁再手拼同款 Godot 命令。
# 三闸:①每段 timeout 守卫(挂死=FAIL 不静默)②段前预清+段后清本 game 目录孤儿 Godot
# (按命令行精确匹配本 game 路径,不误杀其他 worktree 会话的合法进程)③末尾残留清点=批末闸(必须 0)。
# 段:ui_input_probe=默认必跑;ui_struct_probe=默认必跑(ui-struct-probe mini-loop 2026-10-05 注册——
# 全 UI 屏结构断言:按钮互压/等宽/出屏/零尺寸/标题屏双列);ui_contrast_probe=默认必跑(UI 设计对齐批
# T2 2026-10-06 注册——UI 文字对比度机检 ≥4.5:1 无区域例外,测点表随批维护);--click=加跑 ui_click_probe 行为走查
# (自退出+退出码闸;超时多半=探针未适配新停点层,归 probe适配批)。
# layout_probe=默认必跑(2026-10-15 建——分辨率几何断言,档位表是数据/断言是一份函数,
# 增删档位只改表内一行;用 content_scale_size 改逻辑视口,headless 下真生效——
# 旧 ui_layout_probe 用 get_window().size 在 headless 下不改变视口,其「1920 档恒定」
# 断言是永远为真的假断言,随该探针退役)。
# 判定口径(迭代 29 T1 三硬闸):每段走 verdict 函数——①退出码为唯一权威判据(PASS 串降级为日志)
# ②脚本错误扫描命中即红(tools/error_scan.py,标记与 run_smoke.py 同源)③PASS 串保留为附加判据。
# --full 段(迭代 19 卡① T3 收编 2026-10-08——重闸件):ui_probe/ui_tune_probe/t17_defer_probe/fx_probe
# 四件带断言探针收进(此前游离在一切闸外只能手敲);轻闸默认段断言面零删减零改动(--full 只增不改)。
# 渲染面归 render_gate.sh 正主(窗口化:shot_probe 七屏+fx_shot/board_shot 取证+棋盘 golden 逐像素)——
# 旧头注「shot_probe 禁入本闸……归真人目检」纪律句随三闸口径扩容改写退役(2026-10-07 用户授权=历史偷懒标记)。
# 用法: tools/probe_gate.sh [game目录] [--click] [--full] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree)
# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell;Godot 路径可用环境变量 GODOT_BIN 覆盖。
set -u
G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
REPO="$(cd "$(dirname "$0")/.." && pwd)"
GAME=""; WITH_CLICK=0; WITH_FULL=0
for a in "$@"; do
case "$a" in
--click) WITH_CLICK=1 ;;
--full) WITH_FULL=1 ;;
*) GAME="$a" ;;
esac
done
GAME="${GAME:-$REPO/game}"
GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式
TMP="$(mktemp -d /tmp/probegate_XXXXXX)"
trap 'rm -rf "$TMP"' EXIT
_rc=0
# 错误扫描闸件用纯标准库 python(不依赖 PIL),优先带 PIL 的 gamedev venv 以保持与重闸同解释器。
PY=""
for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "python" "python3"; do
if "$cand" -c "import sys" >/dev/null 2>&1; then PY="$cand"; break; fi
done
if [ -z "$PY" ]; then
echo "[probe_gate] 警告:未找到 python,脚本错误扫描闸不生效(三硬闸缺一)"
fi
# 判定三硬闸——各探针段统一走本函数,禁再手拼判定:
# ①退出码为唯一权威判据(PASS 串降级为日志,不得再当判据)
# ②脚本错误扫描命中即红(SCRIPT ERROR/Parse Error 不改退出码,只能外部扫)
# ③签名标记保留为附加判据(退出码 0 却无该探针的签名行=静默退出,仍判红)。
# 标记默认 PASS;ui_click_probe 本就走退出码语义、日志不写 PASS,传它自己的摘要标记。
verdict() { # verdict <探针名> <日志路径> <退出码> [签名标记]
local name="$1" log="$2" st="$3" mark="${4:-PASS}"
if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
echo "[probe_gate] FAIL — $name 超时挂死(exit=$st),孤儿已清"; return 1
fi
if [ "$st" -ne 0 ]; then
echo "[probe_gate] FAIL — $name 退出码=$st(退出码为唯一权威判据,PASS 串不抵)"; return 1
fi
if [ -n "$PY" ] && ! "$PY" "$REPO/tools/error_scan.py" "$log" >"$TMP/err.txt" 2>&1; then
echo "[probe_gate] FAIL — $name 脚本错误扫描命中:"
sed -n '1,6p' "$TMP/err.txt" | sed 's/^/ /'
return 1
fi
if ! grep -q "$mark" "$log"; then
echo "[probe_gate] FAIL — $name 未见签名标记「$mark」(exit=$st——退出码 0 但零输出亦判红)"; return 1
fi
return 0
}
orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(console wrapper+真引擎都算)
printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$GAME_WIN" > "$TMP/k.ps1"
powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$TMP/k.ps1")" 2>/dev/null | tr -d '\r'
}
kill_orphans() {
local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done
}
# 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付)
if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then
echo "[probe_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)"
timeout 300 "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1
kill_orphans
fi
pre="$(orphans | wc -l | tr -d ' ')"
if [ "$pre" -gt 0 ]; then echo "[probe_gate] 预清本闸残留 Godot×$pre"; kill_orphans; fi
echo "======== ui_input_probe ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_input_probe.tscn >"$TMP/uip.log" 2>&1
st=$?; kill_orphans
grep -E "PASS|FAIL|问题" "$TMP/uip.log" | head -12
verdict "ui_input_probe" "$TMP/uip.log" "$st" || _rc=1
echo "======== ui_struct_probe ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_struct_probe.tscn >"$TMP/stp.log" 2>&1
st=$?; kill_orphans
grep -E "PASS|FAIL" "$TMP/stp.log" | head -12
verdict "ui_struct_probe" "$TMP/stp.log" "$st" || _rc=1
echo "======== layout_probe(分辨率几何·两档) ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/layout_probe.tscn >"$TMP/lay.log" 2>&1
st=$?; kill_orphans
grep -E "PASS|FAIL" "$TMP/lay.log" | head -12
verdict "layout_probe" "$TMP/lay.log" "$st" || _rc=1
echo "======== ui_contrast_probe ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_contrast_probe.tscn >"$TMP/ctp.log" 2>&1
st=$?; kill_orphans
grep -E "PASS|FAIL" "$TMP/ctp.log" | head -12
verdict "ui_contrast_probe" "$TMP/ctp.log" "$st" || _rc=1
if [ "$WITH_CLICK" -eq 1 ]; then
echo "======== ui_click_probe 行为走查 ========"
timeout 240 "$G" --headless --path "$GAME_WIN" res://smoke/ui_click_probe.tscn >"$TMP/clk.log" 2>&1
st=$?; kill_orphans
grep -E "click_probe" "$TMP/clk.log" | tail -4
verdict "ui_click_probe" "$TMP/clk.log" "$st" "click_probe" || _rc=1
fi
# ── --full 收编段(迭代 19 卡① T3 2026-10-08——四件带断言探针入闸;实测时长 ui_probe 17.4s/
# ui_tune_probe 49.6s/t17_defer_probe 63.0s/fx_probe 21.9s,timeout 120=2-7×余量) ──
if [ "$WITH_FULL" -eq 1 ]; then
echo "======== ui_probe(--full 收编) ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_probe.tscn >"$TMP/flp.log" 2>&1
st=$?; kill_orphans
grep -E "\[ui_probe\]" "$TMP/flp.log" | tail -2
verdict "ui_probe" "$TMP/flp.log" "$st" || _rc=1
echo "======== ui_tune_probe(--full 收编) ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_tune_probe.tscn >"$TMP/flt.log" 2>&1
st=$?; kill_orphans
grep -E "\[tune_probe\]" "$TMP/flt.log" | tail -2
verdict "ui_tune_probe" "$TMP/flt.log" "$st" || _rc=1
echo "======== t17_defer_probe(--full 收编) ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/t17_defer_probe.tscn >"$TMP/fld.log" 2>&1
st=$?; kill_orphans
grep -E "\[t17_defer\]" "$TMP/fld.log" | tail -2
verdict "t17_defer_probe" "$TMP/fld.log" "$st" || _rc=1
echo "======== fx_probe(--full 收编) ========"
timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/fx_probe.tscn >"$TMP/flx.log" 2>&1
st=$?; kill_orphans
grep -E "\[fx_probe\]" "$TMP/flx.log" | tail -2
verdict "fx_probe" "$TMP/flx.log" "$st" || _rc=1
fi
echo "======== 残留检查(批末闸) ========"
left="$(orphans | wc -l | tr -d ' ')"
total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)"
echo "本闸残留 Godot=$left(必须0) 全局 Godot 进程=$total(其他会话的仅报数不算账)"
if [ "$left" -ne 0 ]; then echo "[probe_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi
if [ "$_rc" -eq 0 ]; then echo "[probe_gate] 全绿"; else echo "[probe_gate] 存在 FAIL,见上"; fi
exit "$_rc"