From b3928f8167be46838df425d2ce6d074b2dd6966d Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 3 Nov 2026 03:07:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(ci=E8=BF=9C=E7=AB=AF?= =?UTF-8?q?=E5=8C=96=C2=B7=E9=98=B6=E6=AE=B5=E4=BA=8C):=20=E9=97=B8?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E8=B7=A8=E5=B9=B3=E5=8F=B0=E5=8C=96=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=96=B0=E5=A2=9Etools/gate=5Fplatform.sh=E5=8D=95?= =?UTF-8?q?=E7=82=B9=E5=B0=81=E8=A3=85(=E5=B9=B3=E5=8F=B0=E6=8E=A2?= =?UTF-8?q?=E6=B5=8B/=E8=B7=AF=E5=BE=84=E5=BD=A2=E5=BC=8F/=E7=BA=AFbash?= =?UTF-8?q?=E5=B8=A6=E8=B6=85=E6=97=B6=E6=89=A7=E8=A1=8C/=E5=AD=A4?= =?UTF-8?q?=E5=84=BF=E6=B8=85=E6=9D=80/=E8=BF=9B=E7=A8=8B=E8=AE=A1?= =?UTF-8?q?=E6=95=B0,=E5=8F=8C=E5=B9=B3=E5=8F=B0=E5=8F=8C=E6=A0=96);=20run?= =?UTF-8?q?=5Fall=5Fgates=20cygpath=C3=972=E6=8D=A2=E5=B0=81=E8=A3=85+PY?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8A=A0=E5=AD=98=E5=9C=A8=E6=80=A7=E5=AE=88?= =?UTF-8?q?=E5=8D=AB(=E4=BF=AE=E9=9D=9EWindows=E8=AF=AF=E9=80=89=E5=B9=BD?= =?UTF-8?q?=E7=81=B5venv=E8=B7=AF=E5=BE=84);=20probe=5Fgate/render=5Fgate/?= =?UTF-8?q?golden=5Fbaseline=5Frefresh=E7=9A=84cygpath/taskkill/PowerShell?= =?UTF-8?q?/tasklist/=E8=A3=B8timeout=E5=85=A8=E9=83=A8=E6=94=B6=E7=BC=96?= =?UTF-8?q?=E5=B0=81=E8=A3=85,Windows=E4=BE=A7=E5=8E=9F=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E8=B5=B0=E5=B9=B3=E5=8F=B0=E5=88=86=E6=94=AF?= =?UTF-8?q?;=20run=5Fsmoke/run=5Fsystem=5Ftest=20DEFAULT=5FGODOT=E6=94=B9G?= =?UTF-8?q?ODOT=5FBIN=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E4=BC=98?= =?UTF-8?q?=E5=85=88;=20gdstyle=5Fgate=20DEFAULT=5FBIN=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=8C=96(macOS=3D/usr/local/bin/gdstyle,=E8=87=AA=E4=B8=BE?= =?UTF-8?q?=E4=BB=B6=E8=A3=85v0.4.0=20darwin=E5=8C=85);=20=E4=BE=9D?= =?UTF-8?q?=E6=8D=AE=3D=E7=AC=AC=E4=B8=83=E8=BD=AE=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E6=96=AD=E7=82=B9=E6=B8=85=E5=8D=95+runner?= =?UTF-8?q?=E8=BF=81=E7=A7=BBMac=20host=E6=A8=A1=E5=BC=8F=E8=A3=81?= =?UTF-8?q?=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/gate_platform.sh | 86 ++++++++++++++++++++++++++++++++ tools/gdstyle_gate.py | 5 +- tools/golden_baseline_refresh.sh | 7 +-- tools/probe_gate.sh | 25 +++++++--- tools/render_gate.sh | 22 ++++---- tools/run_all_gates.sh | 9 ++-- tools/run_smoke.py | 4 +- tools/run_system_test.py | 4 +- 8 files changed, 134 insertions(+), 28 deletions(-) create mode 100644 tools/gate_platform.sh diff --git a/tools/gate_platform.sh b/tools/gate_platform.sh new file mode 100644 index 00000000..cf5828d1 --- /dev/null +++ b/tools/gate_platform.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# 闸件跨平台件——平台探测/路径形式/带超时执行/Godot 孤儿清杀,Git Bash 与 macOS/Linux 双栖。 +# 单点定义:run_all_gates.sh / probe_gate.sh / render_gate.sh / golden_baseline_refresh.sh 共用(source 引入)。 +# 纪律:闸脚本禁裸调 coreutils timeout 与 Windows 专用件(taskkill/PowerShell/ps -W/tasklist/cygpath)—— +# 一律走本件封装;macOS 为 BSD 工具链(无 coreutils timeout,sed/stat 语义不同),裸调必断 +# (2026-11-03 远端 CI 迁移裁决:runner=Mac 本机 host 模式,禁再增 Windows 专用直调)。 +# 平台判定以 uname 为准(Git Bash=MINGW*,macOS=Darwin,Linux=Linux)。 + +gate_is_win() { + case "$(uname -s 2>/dev/null)" in + MINGW*|MSYS*|CYGWIN*|Windows_NT) return 0 ;; + *) return 1 ;; + esac +} + +# Godot --path 接受的项目路径形式:Windows 需 cygpath -m 混合斜杠,其他平台原样。 +gate_game_path() { + if gate_is_win; then cygpath -m "$1"; else printf '%s' "$1"; fi +} + +# 杀整棵进程树()——递归杀子进程后杀本进程。macOS/Linux 走 pgrep -P; +# Windows 侧 MSYS kill 对其可见进程有效,带 wrapper 树的场景仍由调用方原 taskkill /T 逻辑负责。 +gate_kill_tree() { + local p c + kill -9 "$1" 2>/dev/null + for c in $(pgrep -P "$1" 2>/dev/null); do gate_kill_tree "$c"; done +} + +# 带超时执行(coreutils timeout 的纯 bash 替代,双平台同语义): +# gate_run_timeout <秒> <日志路径> <命令...> +# 命令输出重定向到日志;退出码=命令退出码,超时=124(与 coreutils 语义一致,判据与既有闸一致)。 +# 实现=后台启动+守卫落标志文件(probe_gate run_suite 同款手法:taskkill 强杀后 wait 报 0 的 +# 假绿以标志文件纠偏);超时先杀整树再回 124。 +gate_run_timeout() { + local t="$1" log="$2"; shift 2 + local kf + kf="$(mktemp "${TMPDIR:-/tmp}/gatetime_XXXXXX")" + "$@" >"$log" 2>&1 & + local pid=$! + ( sleep "$t" 2>/dev/null + touch "$kf" + gate_kill_tree "$pid" + true ) & + local guard=$! + wait "$pid"; local st=$? + kill "$guard" 2>/dev/null; wait "$guard" 2>/dev/null + if [ -f "$kf" ]; then + gate_kill_tree "$pid" + st=124 + fi + rm -f "$kf" + return "$st" +} + +# 列命令行含 的 Godot 进程 PID(孤儿清杀与批末残留清点共用)。 +# Windows:PowerShell WMI 按进程名+命令行匹配(console wrapper+真引擎都算); +# macOS/Linux:ps 全表按「可执行含 godot+命令行含本 game 路径」过滤(ps 两平台同形)。 +gate_orphans() { + local gp="$1" + if gate_is_win; then + local tmpf + tmpf="$(mktemp "${TMPDIR:-/tmp}/gateorph_XXXXXX.ps1")" + local gp_win + gp_win="$(cygpath -m "$gp")" + printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$gp_win" > "$tmpf" + powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$tmpf")" 2>/dev/null | tr -d '\r' + rm -f "$tmpf" + else + ps -axo pid=,command= 2>/dev/null | awk -v g="$gp" 'tolower($0) ~ /godot/ && index($0, g) {print $1}' + fi +} + +gate_kill_orphans() { # gate_kill_orphans + local p + for p in $(gate_orphans "$1"); do + if gate_is_win; then taskkill //F //PID "$p" >/dev/null 2>&1 + else kill -9 "$p" 2>/dev/null; fi + done +} + +# 全局 Godot 进程计数(其他会话的仅报数不算账——批末残留清点的对照面)。 +gate_godot_count_all() { + if gate_is_win; then tasklist 2>/dev/null | grep -ci '^Godot' || true + else ps -axo comm= 2>/dev/null | grep -ci 'godot' || true + fi +} diff --git a/tools/gdstyle_gate.py b/tools/gdstyle_gate.py index b93d54cb..49c375a5 100644 --- a/tools/gdstyle_gate.py +++ b/tools/gdstyle_gate.py @@ -48,7 +48,10 @@ CONFIG = ROOT / "tools" / "gdstyle.toml" BASELINE = ROOT / "tools" / "gdstyle_baseline.txt" SCAN_DIRS = ["game/core", "game/data", "game/presentation"] PINNED_VERSION = "0.4.0" -DEFAULT_BIN = "D:/code/gdstyle/gdstyle.exe" +# 二进制定位:GDSTYLE_BIN 环境变量优先;Windows 本机固定路径保底,macOS/Linux 走 /usr/local/bin +# (mac_setup.sh 自举件装钉定版 v0.4.0 darwin 包至此,远端 CI 与本机同判据)。 +DEFAULT_BIN = ("D:/code/gdstyle/gdstyle.exe" if sys.platform == "win32" + else "/usr/local/bin/gdstyle") MAX_REPORT = 50 MAX_VANISHED_REPORT = 20 SHRINK_GUARD = 0.9 # 新键数 < 旧键数 * 本系数=骤降,refresh 拒写须 --force diff --git a/tools/golden_baseline_refresh.sh b/tools/golden_baseline_refresh.sh index 93ad7d7f..6bcac2b7 100755 --- a/tools/golden_baseline_refresh.sh +++ b/tools/golden_baseline_refresh.sh @@ -15,6 +15,7 @@ set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行) GAME="" MODE="board" for a in "$@"; do @@ -24,7 +25,7 @@ for a in "$@"; do esac done GAME="${GAME:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)" trap 'rm -rf "$TMP"' EXIT @@ -36,7 +37,7 @@ if [ "$MODE" = "menu" ]; then exit 1 fi done - timeout 240 "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1 + gate_run_timeout 240 "$TMP/run.log" "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$TMP/shots" st=$? grep -E "\[shot_probe\]" "$TMP/run.log" | tail -3 if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/shot_menu.png" ]; then @@ -61,7 +62,7 @@ for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ fi done -timeout 180 "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1 +gate_run_timeout 180 "$TMP/run.log" "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$TMP/shots" st=$? grep -E "\[board_shot\]" "$TMP/run.log" | tail -6 if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/board_meta.json" ]; then diff --git a/tools/probe_gate.sh b/tools/probe_gate.sh index 839caa7d..94c0b03d 100755 --- a/tools/probe_gate.sh +++ b/tools/probe_gate.sh @@ -17,10 +17,12 @@ # 四件带断言探针;默认段断言面零删减零改动(--full 只增不改)。 # 渲染面归 render_gate.sh 正主(窗口化:shot_probe 七屏+fx_shot/board_shot 取证+棋盘 golden 逐像素)。 # 用法: tools/probe_gate.sh [game目录] [--click] [--full] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) -# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell;Godot 路径可用环境变量 GODOT_BIN 覆盖。 +# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell(Windows 侧);macOS/Linux 走 tools/gate_platform.sh +# 共享跨平台件(Godot 路径两平台都可用 GODOT_BIN 环境变量覆盖)。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) GAME=""; WITH_CLICK=0; WITH_FULL=0 for a in "$@"; do case "$a" in @@ -30,7 +32,7 @@ for a in "$@"; do esac done GAME="${GAME:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式 +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) TMP="$(mktemp -d /tmp/probegate_XXXXXX)" trap 'rm -rf "$TMP"' EXIT _rc=0 @@ -68,12 +70,11 @@ verdict() { # verdict <探针名> <日志路径> <退出码> [签名标记] 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' +orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(实现单点在 gate_platform.sh,双平台) + gate_orphans "$GAME_WIN" } kill_orphans() { - local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done + gate_kill_orphans "$GAME_WIN" } # 每段跑批统一走本函数:后台启动记 MSYS PID→WINPID,守卫进程到点先 taskkill /T 整树回收 @@ -81,6 +82,14 @@ kill_orphans() { # 2026-10-25 在册挂死根源),后 kill -9 收 MSYS 侧。正常退出不动进程树;段后 kill_orphans 照跑兜底。 run_suite() { # run_suite <日志> <场景 res:// 路径> local t="$1" log="$2" scene="$3" + if ! gate_is_win; then + # macOS/Linux:无 console wrapper(Godot 单进程),走共享带超时执行+孤儿清杀兜底。 + local st + gate_run_timeout "$t" "$log" "$G" --headless --path "$GAME_WIN" "$scene" + st=$? + gate_kill_orphans "$GAME_WIN" + return "$st" + fi local kf="$TMP/killed.$RANDOM" rm -f "$kf" "$G" --headless --path "$GAME_WIN" "$scene" >"$log" 2>&1 & @@ -108,7 +117,7 @@ run_suite() { # run_suite <日志> <场景 res:// 路径> # 冷检出刷导入(.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 + gate_run_timeout 300 "$TMP/refresh.log" "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 kill_orphans fi @@ -183,7 +192,7 @@ fi echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" -total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" +total="$(gate_godot_count_all)" 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 diff --git a/tools/render_gate.sh b/tools/render_gate.sh index d6443f25..ee2722f5 100755 --- a/tools/render_gate.sh +++ b/tools/render_gate.sh @@ -27,22 +27,22 @@ # 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径);禁改 --headless 跑本闸 # (headless 无渲染面,探针内 fail-fast 护栏会红——那是护栏生效不是闸红)。 # 用法: tools/render_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) -# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell + python(PIL);Godot 路径可用环境变量 GODOT_BIN 覆盖。 +# 依赖: tools/gate_platform.sh(双平台封装)+ python(PIL);Windows 侧另用 taskkill/PowerShell,macOS/Linux 走共享件;Godot 路径可用环境变量 GODOT_BIN 覆盖。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) GAME="${1:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式 +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) TMP="$(mktemp -d /tmp/rendergate_XXXXXX)" trap 'rm -rf "$TMP"' EXIT _rc=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' +orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(实现单点在 gate_platform.sh,双平台) + gate_orphans "$GAME_WIN" } kill_orphans() { - local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done + gate_kill_orphans "$GAME_WIN" } scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不可读(error_scan exit 2)同判红 @@ -57,7 +57,7 @@ scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不 # 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付) if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)" - timeout 300 "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 + gate_run_timeout 300 "$TMP/refresh.log" "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 kill_orphans fi @@ -66,7 +66,7 @@ if [ "$pre" -gt 0 ]; then echo "[render_gate] 预清本闸残留 Godot×$pre"; k echo "======== shot_probe 七屏截图 → shot_checker A-D ========" SHOT="$TMP/shots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$SHOT" >"$TMP/shot.log" 2>&1 +gate_run_timeout 180 "$TMP/shot.log" "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$SHOT" st=$?; kill_orphans grep -E "\[shot_probe\]" "$TMP/shot.log" | tail -12 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then @@ -83,7 +83,7 @@ scan_log "shot_probe" "$TMP/shot.log" echo "======== fx_shot_probe 三时刻取证(Q7) ========" FX="$TMP/fxshots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/fx_shot_probe.tscn -- "$FX" >"$TMP/fx.log" 2>&1 +gate_run_timeout 180 "$TMP/fx.log" "$G" --path "$GAME_WIN" res://probe/fx_shot_probe.tscn -- "$FX" st=$?; kill_orphans grep -E "\[fx_shot_probe\]" "$TMP/fx.log" | tail -4 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then @@ -108,7 +108,7 @@ fx_evidence "fx_final.png" 1 echo "======== board_shot_probe L1-L3 取证(Q7) ========" BD="$TMP/boardshots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$BD" >"$TMP/bd.log" 2>&1 +gate_run_timeout 180 "$TMP/bd.log" "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$BD" st=$?; kill_orphans grep -E "\[board_shot\]" "$TMP/bd.log" | tail -6 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then @@ -172,7 +172,7 @@ fi echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" -total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" +total="$(gate_godot_count_all)" echo "本闸残留 Godot=$left(必须0) 全局 Godot 进程=$total(其他会话的仅报数不算账)" if [ "$left" -ne 0 ]; then echo "[render_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi if [ "$_rc" -eq 0 ]; then echo "[render_gate] 全绿"; else echo "[render_gate] 存在 FAIL,见上"; fi diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index 341b4efe..399c0b51 100755 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -44,6 +44,7 @@ GAME="$REPO/game" STAMP_DIR="$GAME/tests/.gates" RECEIPT="$STAMP_DIR/receipt.json" BASELINE="$STAMP_DIR/baseline.json" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行) QUICK=0 DOMAIN="full" @@ -93,8 +94,10 @@ fi # ── python 解释器解析:优先带 PIL 的 gamedev venv(渲染闸的 shot_checker/golden_compare 依赖), # 回落 PATH 上的 python。渲染闸的三个脚本要 PIL,用错解释器会假红。 PY="" -for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "$(command -v python 2>/dev/null)" "python"; do +for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "$(command -v python 2>/dev/null)" "python3" "python"; do [ -z "$cand" ] && continue + # 存在性守卫先行:Windows venv 路径在非 Windows 平台不存在,误选会让 PY 指向幽灵路径整批崩。 + command -v "$cand" >/dev/null 2>&1 || [ -x "$cand" ] || continue if "$cand" -c "import PIL" >/dev/null 2>&1; then PY="$cand"; break; fi [ -z "$PY" ] && PY="$cand" done @@ -348,7 +351,7 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分 echo "════════════════════════════════════════════" local rc=0 if [ "$_shards" -le 1 ]; then - local -a _cmd=(--headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd) + local -a _cmd=(--headless --path "$(gate_game_path "$GAME")" --script res://tests/run_all.gd) [ ${#_user_args[@]} -gt 0 ] && _cmd+=(-- "${_user_args[@]}") ( cd "$REPO" && "$GODOT" "${_cmd[@]}" ) \ >"$STAMP_DIR/unit.log" 2>&1 @@ -360,7 +363,7 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分 local -a _pids=() local k for k in $(seq 1 "$_shards"); do - ( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd \ + ( cd "$REPO" && "$GODOT" --headless --path "$(gate_game_path "$GAME")" --script res://tests/run_all.gd \ -- "${_user_args[@]}" "--shard=$k" "--shard-count=$_shards" ) \ >"$STAMP_DIR/unit_shard_$k.log" 2>&1 & _pids+=($!) diff --git a/tools/run_smoke.py b/tools/run_smoke.py index f6b756cd..cbf34fa8 100644 --- a/tools/run_smoke.py +++ b/tools/run_smoke.py @@ -18,13 +18,15 @@ r"""run_smoke.py — 冒烟测试运行器(脚本错误外部门禁) ③ 输出无「SCRIPT ERROR」「Parse Error」标记行(标记清单供 tools/error_scan.py 复用,禁另写一份)。 """ import argparse +import os import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -DEFAULT_GODOT = r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" +# 引擎定位:GODOT_BIN 环境变量优先(远端 CI 与非 Windows 平台的注入口),Windows 本机固定路径保底。 +DEFAULT_GODOT = os.environ.get("GODOT_BIN") or r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" DEFAULT_SLICE = ROOT / "game" DEFAULT_SCENE = "res://tests/smoke_boot.tscn" SUMMARY_RE = re.compile(r"\[smoke\] 摘要:(\d+) 项检查,(\d+) 项失败") diff --git a/tools/run_system_test.py b/tools/run_system_test.py index 9d49bbe2..43711a3c 100644 --- a/tools/run_system_test.py +++ b/tools/run_system_test.py @@ -19,13 +19,15 @@ r"""run_system_test.py — 系统测试运行器(脚本错误外部门禁) ③ 输出无「SCRIPT ERROR」「Parse Error」标记行(标记清单经 tools/error_scan.py 复用,禁另写一份)。 """ import argparse +import os import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -DEFAULT_GODOT = r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" +# 引擎定位:GODOT_BIN 环境变量优先(远端 CI 与非 Windows 平台的注入口),Windows 本机固定路径保底。 +DEFAULT_GODOT = os.environ.get("GODOT_BIN") or r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" DEFAULT_SLICE = ROOT / "game" DEFAULT_SCRIPT = "res://tests/system_test.gd" # 系统测试宿主 SUMMARY_RE = re.compile(r"(?:冒烟完成|\[system_test\] 摘要):(\d+) 项检查,(\d+) 项失败")