合并(工程改动收编): 02 闸选跑过滤器——run_all.gd 加 --only 选跑态(对套件 res:// 路径子串匹配/零命中与空值=用法错 quit(2) fail-closed/FILTERED RUN 横幅+日志另名 run_all_filter_*/实跑数校验对过滤后清单/默认路径逐字节不变)——worktree 批 runall-only-filter-20261028 验收通过(代码域闸回执 all_green: 00 冒烟/01 系统/02 单元与集成 64 套件默认路径全量实跑/03+04 探针/05 渲染/10 熵增 全绿,11 对拍 SKIP 未触数值存档RNG结算面,09 静态扫描存量红对基线零新增,exit 0);开发期零代码验证直跑=用户裁 2026-10-28,选跑分支判红绿正确性首次实证=实际使用;文档同步与登记条目删除随后一笔直提
This commit is contained in:
commit
fe62463bc3
1 file changed
+52
-9
+52
-9
@@ -1,5 +1,8 @@
|
||||
# tests/run_all.gd — 单元测试统一入口(双载体的逻辑测试调用点,套件 manifest 协议 D2 AC-2b)
|
||||
# 用法:godot --headless --path game --script res://tests/run_all.gd (本地闸 02 闸包装外部超时:timeout 120s …)
|
||||
# 选跑态(开发期直跑专用):追加 `-- --only=<词[,词…]>` ——对套件 res:// 路径做子串匹配,只跑命中套件;
|
||||
# 零命中/空值=用法错 quit(2)。选跑态仅供开发期最窄确认,不产闸回执、不作验收凭据;
|
||||
# 批末全量闸不带参数不经选跑分支——默认(无参数)行为逐字节不变。
|
||||
# 协议:套件清单逐条"套件 → 期望退出码";致命类独立进程(expect=1)。
|
||||
# 执行形态:逐套件起独立 Godot 子进程实跑
|
||||
#(OS.get_executable_path() 解析本引擎二进制——--script 模式无 autoload/全局标识符,
|
||||
@@ -13,24 +16,51 @@ const ERROR_MARKERS: Array[String] = ["SCRIPT ERROR", "Parse Error"]
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
print("[run_all] unit/contract suite entry — %d suites" % _suites().size())
|
||||
# 选跑态解析:--only=<词[,词…]>(经 -- 分隔的 user args)——对套件 res:// 路径做子串匹配。
|
||||
# 空值/零命中=用法错 quit(2)(fail-closed:禁把零套件当绿);选跑态仅供开发期最窄确认,
|
||||
# 不产闸回执、不作验收凭据;批末全量闸不带参数,不走此分支。
|
||||
var only_terms := PackedStringArray()
|
||||
var has_only := false
|
||||
for a in OS.get_cmdline_user_args():
|
||||
if a.begins_with("--only="):
|
||||
has_only = true
|
||||
var term := a.trim_prefix("--only=").strip_edges()
|
||||
if term != "":
|
||||
only_terms.append(term)
|
||||
if has_only and only_terms.is_empty():
|
||||
push_error("[run_all] --only= 空值——用法错(选跑态禁把零套件当全量)")
|
||||
quit(2)
|
||||
return
|
||||
var manifest := _suites()
|
||||
var suites := manifest
|
||||
var filtered := false
|
||||
if has_only:
|
||||
suites = _filter_suites(manifest, only_terms)
|
||||
if suites.is_empty():
|
||||
push_error("[run_all] FILTERED RUN — --only=「%s」零命中(manifest 共 %d 套件)——用法错" % [",".join(only_terms), manifest.size()])
|
||||
quit(2)
|
||||
return
|
||||
filtered = true
|
||||
print("[run_all] FILTERED RUN — 选跑 %d/%d 套件(--only=%s)——仅供开发期最窄确认,不产闸回执、不作验收凭据" % [suites.size(), manifest.size(), ",".join(only_terms)])
|
||||
print("[run_all] unit/contract suite entry — %d suites" % suites.size())
|
||||
if DisplayServer.get_name() != "headless":
|
||||
push_warning("[run_all] 建议以 --headless 运行(D2 冒烟协议)")
|
||||
var failures := 0
|
||||
var ran := 0
|
||||
for suite in _suites():
|
||||
failures += _run_suite(suite, ran)
|
||||
for suite in suites:
|
||||
failures += _run_suite(suite, ran, filtered)
|
||||
ran += 1
|
||||
# 实跑套件数校验:manifest 有条目却被静默跳过(早退/continue)必须判红,不能只凭 failures。
|
||||
if ran != _suites().size():
|
||||
push_error("[run_all] 实跑套件数 %d ≠ manifest 条目数 %d——有套件未跑" % [ran, _suites().size()])
|
||||
# 实跑套件数校验:清单有条目却被静默跳过(早退/continue)必须判红,不能只凭 failures。
|
||||
# 选跑态对过滤后清单校验;全量态与原 manifest 条目数同一。
|
||||
if ran != suites.size():
|
||||
push_error("[run_all] 实跑套件数 %d ≠ 清单条目数 %d——有套件未跑" % [ran, suites.size()])
|
||||
quit(1)
|
||||
return
|
||||
if failures > 0:
|
||||
push_error("[run_all] %d 个套件失败" % failures)
|
||||
quit(1)
|
||||
return
|
||||
print("[run_all] all green (%d suites)" % _suites().size())
|
||||
print("[run_all] all green (%d suites%s)" % [suites.size(), ",filtered" if filtered else ""])
|
||||
quit(0)
|
||||
|
||||
|
||||
@@ -111,7 +141,7 @@ func _suites() -> Array:
|
||||
]
|
||||
|
||||
|
||||
func _run_suite(suite: Dictionary, idx: int) -> int:
|
||||
func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> int:
|
||||
var path: String = suite.get("path", "")
|
||||
var expect: int = int(suite.get("expect", 0))
|
||||
if path.is_empty():
|
||||
@@ -125,7 +155,7 @@ func _run_suite(suite: Dictionary, idx: int) -> int:
|
||||
# 子进程输出只能落盘再读:OS.execute 的 output 管道在本机 Godot/Windows 组合下恒为空
|
||||
#(实测 0 行),故整条命令交给 cmd /c 并重定向到日志,退出码由 cmd 原样回传。
|
||||
var proj := ProjectSettings.globalize_path("res://")
|
||||
var log := proj + "/tests/.gates/run_all_%02d.log" % idx
|
||||
var log := proj + "/tests/.gates/run_all%s_%02d.log" % ["_filter" if filtered else "", idx]
|
||||
DirAccess.make_dir_recursive_absolute(proj + "/tests/.gates")
|
||||
var cmd := '"%s" --headless --path "%s" --script "%s" > "%s" 2>&1' % [bin, proj, path, log]
|
||||
var out := PackedStringArray()
|
||||
@@ -148,6 +178,19 @@ func _run_suite(suite: Dictionary, idx: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# 选跑过滤:对套件 res:// 路径做子串匹配,任一词命中即选入,词序无关。
|
||||
# 只在选跑态调用——全量态走原 manifest,条目与顺序零变化。
|
||||
func _filter_suites(manifest: Array, terms: PackedStringArray) -> Array:
|
||||
var out: Array = []
|
||||
for suite in manifest:
|
||||
var path: String = suite.get("path", "")
|
||||
for t in terms:
|
||||
if path.contains(t):
|
||||
out.append(suite)
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
# 引擎二进制必须取 console 版:OS.get_executable_path() 返回的是 GUI 子系统 exe
|
||||
#(Godot_v4.7.2-stable_win64.exe),它起的子进程 stdout/stderr 句柄无效——输出全丢,
|
||||
# 脚本错误扫描就成了扫空气。GODOT_BIN 优先,其次按命名推导 *_console.exe。
|
||||
|
||||
Reference in new issue
Block a user