Files
gplan-2/tools/run_smoke.py
T

79 lines
3.4 KiB
Python
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 python3
r"""run_smoke.py — 冒烟运行器(脚本错误外部门禁)
用法:
python tools/run_smoke.py # 默认跑切片全量冒烟(本机 Godot 固定路径)
python tools/run_smoke.py --script res://smoke/smoke_all.gd
python tools/run_smoke.py --godot <引擎路径> --path <切片目录> --timeout 180
背景: GDScript 运行时脚本错误(SCRIPT ERROR)不改变进程退出码、
不计入冒烟失败数,且会静默中断所在冒烟函数的剩余断言——摘要零失败也可能断言未跑全。
进程内无脚本错误陷阱 API(GDScript 无 try/catch),故闸设在外部:本包装除退出码与摘要行外,
另扫输出中的 SCRIPT ERROR / Parse Error,命中即判失败。
三闸: ① 进程退出码 = 0;② 摘要行「冒烟完成:N 项检查,0 项失败」在案且失败数为 0;
③ 输出无「SCRIPT ERROR」「Parse Error」标记行。
"""
import argparse
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"
DEFAULT_SLICE = ROOT / "game"
DEFAULT_SCRIPT = "res://smoke/smoke_all.gd"
SUMMARY_RE = re.compile(r"冒烟完成:(\d+) 项检查,(\d+) 项失败")
ERROR_MARKERS = ("SCRIPT ERROR", "Parse Error")
def main():
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
ap = argparse.ArgumentParser(description="冒烟运行器(脚本错误外部门禁)")
ap.add_argument("--godot", default=DEFAULT_GODOT)
ap.add_argument("--path", default=str(DEFAULT_SLICE), help="切片项目目录(含 project.godot)")
ap.add_argument("--script", default=DEFAULT_SCRIPT)
ap.add_argument("--timeout", type=int, default=180, help="外部超时秒数(硬性要求)")
args = ap.parse_args()
cmd = [args.godot, "--headless", "--path", args.path, "--script", args.script]
try:
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
errors="replace", timeout=args.timeout)
except subprocess.TimeoutExpired:
print(f"[run_smoke] FAIL:外部超时(>{args.timeout}s——硬性要求)")
return 1
out = (proc.stdout or "") + (proc.stderr or "")
sys.stdout.write(out)
if out and not out.endswith("\n"):
print()
gates = []
if proc.returncode != 0:
gates.append(f"退出码 = {proc.returncode}(≠0)")
m = SUMMARY_RE.search(out)
if m is None:
gates.append("摘要行缺失(未检出「冒烟完成:N 项检查,M 项失败」——入口跑完前中断?)")
elif int(m.group(2)) != 0:
gates.append(f"冒烟失败 {m.group(2)} 项 / 共 {m.group(1)} 项")
hits = [ln.strip() for ln in out.splitlines() if any(k in ln for k in ERROR_MARKERS)]
if hits:
gates.append(f"脚本错误标记命中 {len(hits)} 行(首行: {hits[0][:140]}——脚本错误不计冒烟失败数,"
"且中断所在函数的剩余断言,必须人工排查)")
if m is not None:
print(f"[run_smoke] 摘要: {m.group(0)}")
if gates:
print("[run_smoke] FAIL——")
for g in gates:
print(f" - {g}")
return 1
print("[run_smoke] PASS(退出码 / 摘要失败数 / 脚本错误扫描 三闸全过)")
return 0
if __name__ == "__main__":
sys.exit(main())