feat(测试代码): 批 A 新冒烟测试与系统测试宿主——smoke_boot.tscn/.gd 极小启动场景(项目运行目标拉起=真实开机路径,17 项存活断言零逻辑判定,摘要+退出码收口);run_smoke.py 重写为冒烟场景运行器(三闸判定,ERROR_MARKERS 仍供 error_scan 复用);run_system_test.py 系统测试运行器(过渡期宿主 smoke_all.gd);system_test.gd SceneTree+构造注入宿主薄壳(零断言,迁移归批 B)——两件均孤立验证 PASS(run_smoke 17/0、system_test 0/0)
This commit is contained in:
1 parent
3b8c6bc3e7
commit
c1f4f80a4f
7 files changed
+215
-16
No files matched your search
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""run_system_test.py — 系统测试运行器(脚本错误外部门禁)
|
||||
|
||||
用法:
|
||||
python tools/run_system_test.py # 跑系统测试(过渡期宿主 res://smoke/smoke_all.gd)
|
||||
python tools/run_system_test.py --script res://tests/system_test.gd
|
||||
python tools/run_system_test.py --godot <引擎路径> --path <切片目录> --timeout 300
|
||||
|
||||
系统测试 = 启动真实工程走真实玩法路径的断言面(标准四级测试层之一)。
|
||||
宿主过渡态:断言现全部住在 res://smoke/smoke_all.gd,按「只剪切禁改写」纪律迁往
|
||||
res://tests/system_test.gd(宿主薄壳已在位)——迁移完成后本件缺省 --script 改指新宿主,
|
||||
闸 01 系统测试随迁。
|
||||
|
||||
背景: GDScript 运行时脚本错误(SCRIPT ERROR)不改变进程退出码、不计入失败数,
|
||||
且会静默中断所在断言函数的剩余断言——摘要零失败也可能断言未跑全。
|
||||
进程内无脚本错误陷阱 API(GDScript 无 try/catch),故闸设在外部:本包装除退出码与摘要行外,
|
||||
另扫输出中的 SCRIPT ERROR / Parse Error,命中即判失败。
|
||||
|
||||
三闸: ① 进程退出码 = 0;② 摘要行(「冒烟完成:N 项检查,0 项失败」或
|
||||
「[system_test] 摘要:N 项检查,0 项失败」)在案且失败数为 0;
|
||||
③ 输出无「SCRIPT ERROR」「Parse Error」标记行(标记清单经 tools/error_scan.py 复用,禁另写一份)。
|
||||
"""
|
||||
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" # 过渡期宿主;断言迁移批后改 res://tests/system_test.gd
|
||||
SUMMARY_RE = re.compile(r"(?:冒烟完成|\[system_test\] 摘要):(\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, help="系统测试宿主脚本")
|
||||
ap.add_argument("--timeout", type=int, default=300, 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_system_test] 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_system_test] 摘要: {m.group(0)}")
|
||||
if gates:
|
||||
print("[run_system_test] FAIL——")
|
||||
for g in gates:
|
||||
print(f" - {g}")
|
||||
return 1
|
||||
print("[run_system_test] PASS(退出码 / 摘要失败数 / 脚本错误扫描 三闸全过)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user