# tests/run_all.gd — 单元测试统一入口(ADR-0004 双载体的逻辑测试调用点,套件 manifest 协议 D2 AC-2b) # 用法:godot --headless --path game --script res://tests/run_all.gd (CI 侧包装外部超时:timeout 120s …) # 协议:套件清单逐条"套件 → 期望退出码";致命类独立进程(expect=1)。 # 执行形态(单元测试与契约层批 2026-09-28):逐套件起独立 Godot 子进程实跑 #(OS.get_executable_path() 解析本引擎二进制——--script 模式无 autoload/全局标识符, # 每个测试文件自带显式实例化夹具,见 tests/README 协议要点)。 # 场景探针(.tscn 载体)由 CI/轻闸单独步骤调用,不经本入口。 extends SceneTree func _init() -> void: 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 for suite in _suites(): failures += _run_suite(suite) if failures > 0: push_error("[run_all] %d 个套件失败" % failures) quit(1) return print("[run_all] all green (%d suites)" % _suites().size()) quit(0) # 套件清单:{ "path": String, "expect": int };致命类(期望非 0)独立进程断言退出码。 func _suites() -> Array: return [ {"path": "res://tests/unit/example_test.gd", "expect": 0}, {"path": "res://tests/unit/data_config/config_db_test.gd", "expect": 0}, {"path": "res://tests/unit/data_config/config_db_fatal_oob_test.gd", "expect": 1}, {"path": "res://tests/unit/data_config/aux_target_violation_test.gd", "expect": 1}, # 迭代 7 T1——真视窄域护栏 {"path": "res://tests/unit/data_config/reward_dangling_aux_test.gd", "expect": 1}, # 迭代 7 T1——aux 双表校验 {"path": "res://tests/unit/data_config/rng_service_test.gd", "expect": 0}, {"path": "res://tests/unit/core/aux_items_test.gd", "expect": 0}, # 迭代 7 T7——秘宝结构冻结断言面 {"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 迭代 8 卡①/T9——断点往返/续玩确定性/幂等/退出清断点 {"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # 迭代 8 卡⑦ AC⑤/T9——排空零分叉/零丢行/延迟双态 {"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 迭代 7 hotfix——奖励节点 UI 直验(盲区补漏) {"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0}, {"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0}, {"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0}, {"path": "res://tests/unit/presentation/terrain_tile_sets_test.gd", "expect": 0}, ] func _run_suite(suite: Dictionary) -> int: var path: String = suite.get("path", "") var expect: int = int(suite.get("expect", 0)) if path.is_empty(): push_error("[run_all] suite missing path") return 1 var args := PackedStringArray(["--headless", "--path", ProjectSettings.globalize_path("res://"), "--script", path]) var out := PackedStringArray() var code: int = OS.execute(OS.get_executable_path(), args, out, true, false) var text := "\n".join(out) print(text) if code != expect: push_error("[run_all] FAIL %s — 退出码 %d ≠ 期望 %d" % [path, code, expect]) return 1 print("[run_all] PASS %s (exit=%d)" % [path, code]) return 0