From 6caf56b5c1ce542e7c997b47ac0746116696e5de Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 28 Oct 2026 02:49:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(02=E9=97=B8=E9=80=89?= =?UTF-8?q?=E8=B7=91=E8=BF=87=E6=BB=A4=E5=99=A8):=20run=5Fall.gd=20?= =?UTF-8?q?=E5=8A=A0=20--only=20=E9=80=89=E8=B7=91=E6=80=81=E2=80=94?= =?UTF-8?q?=E2=80=94OS.get=5Fcmdline=5Fuser=5Fargs=20=E8=A7=A3=E6=9E=90=20?= =?UTF-8?q?--only=3D<=E8=AF=8D[,=E8=AF=8D=E2=80=A6]>=20=E5=AF=B9=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=20res://=20=E8=B7=AF=E5=BE=84=E5=AD=90=E4=B8=B2?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=8F=AA=E8=B7=91=E5=91=BD=E4=B8=AD=E5=A5=97?= =?UTF-8?q?=E4=BB=B6;=E9=9B=B6=E5=91=BD=E4=B8=AD/=E7=A9=BA=E5=80=BC=3D?= =?UTF-8?q?=E7=94=A8=E6=B3=95=E9=94=99=20quit(2)=20fail-closed=20=E7=A6=81?= =?UTF-8?q?=E9=9B=B6=E5=A5=97=E4=BB=B6=E5=BD=93=E7=BB=BF;=E9=80=89?= =?UTF-8?q?=E8=B7=91=E6=80=81=E6=89=93=E5=8D=B0=20FILTERED=20RUN=20?= =?UTF-8?q?=E6=A8=AA=E5=B9=85+=E6=97=A5=E5=BF=97=E5=8F=A6=E5=90=8D=20run?= =?UTF-8?q?=5Fall=5Ffilter=5F*(=E4=B8=8D=E8=A6=86=E5=86=99=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E8=B7=91=E6=89=B9=E6=97=A5=E5=BF=97);=E5=AE=9E?= =?UTF-8?q?=E8=B7=91=E6=95=B0=E6=A0=A1=E9=AA=8C=E5=AF=B9=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=90=8E=E6=B8=85=E5=8D=95,=E5=85=A8=E9=87=8F=E6=80=81?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E4=B8=8E=E9=A1=BA=E5=BA=8F=E9=9B=B6=E5=8F=98?= =?UTF-8?q?=E5=8C=96;=E9=BB=98=E8=AE=A4=E8=B7=AF=E5=BE=84(=E6=97=A0?= =?UTF-8?q?=E5=8F=82=E6=95=B0)=E9=80=90=E5=AD=97=E8=8A=82=E7=8E=B0?= =?UTF-8?q?=E8=A1=8C=E8=A1=8C=E4=B8=BA=E2=80=94=E2=80=94=E6=89=B9=E6=9C=AB?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E9=97=B8=E4=B8=8D=E5=B8=A6=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E4=B8=8D=E7=BB=8F=E9=80=89=E8=B7=91=E5=88=86=E6=94=AF=E3=80=82?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=8F=A3=E5=BE=84=3D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=A3=81=202026-10-28:=E5=BC=80=E5=8F=91=E6=9C=9F=E9=9B=B6?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=AA=8C=E8=AF=81=E7=9B=B4=E8=B7=91,?= =?UTF-8?q?=E9=80=89=E8=B7=91=E5=88=86=E6=94=AF=E5=88=A4=E7=BA=A2=E7=BB=BF?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E6=80=A7=E9=A6=96=E6=AC=A1=E5=AE=9E=E8=AF=81?= =?UTF-8?q?=3D=E5=AE=9E=E9=99=85=E4=BD=BF=E7=94=A8;=E6=9C=AC=E6=89=B9?= =?UTF-8?q?=E4=BB=85=E5=81=9A=E7=A7=92=E7=BA=A7=20--check-only=20=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E8=87=AA=E6=A3=80(=E4=B8=8D=E8=B7=91=E5=A5=97?= =?UTF-8?q?=E4=BB=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/run_all.gd | 61 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 20b4ee0c..908b4c44 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -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。