extends SceneTree # ============================================================================ # test_runner.gd —— core 层无头单测入口(GUT 未装前的自包含方案;W3 换 GUT) # # 运行:godot --headless --path . -s res://tests/unit/test_runner.gd # 退出码:0 = 全过;1 = 有失败;2 = 数据/环境错误。 # 每个用例文件 = tests/unit/cases/*.gd,契约:func run(a) -> void(a = TestAssert)。 # ============================================================================ class TestAssert extends RefCounted: var failures: Array[String] = [] var checks := 0 func check(cond: bool, msg: String) -> void: checks += 1 if not cond: failures.append(msg) func eq(got: Variant, want: Variant, msg: String) -> void: checks += 1 if got != want: failures.append("%s:got %s want %s" % [msg, str(got), str(want)]) func near(got: float, want: float, tol: float, msg: String) -> void: checks += 1 if absf(float(got) - float(want)) > tol: failures.append("%s:got %.6g want %.6g(tol %g)" % [msg, got, want, tol]) func true_(cond: bool, msg: String) -> void: check(cond, msg) func false_(cond: bool, msg: String) -> void: check(not cond, msg) const CASE_FILES: Array[String] = [ "res://tests/unit/cases/test_number_format.gd", "res://tests/unit/cases/test_economy_formulas.gd", "res://tests/unit/cases/test_balance_data.gd", "res://tests/unit/cases/test_economy_state.gd", # W3 新增:相机 rig 数据行与纯数学 / 事件系统运行时 / 殿王战状态机 "res://tests/unit/cases/test_camera_data.gd", "res://tests/unit/cases/test_event_director.gd", "res://tests/unit/cases/test_boss_battle.gd", ] func _initialize() -> void: var data := BalanceData.load_from_dir("res://data/build/") if not data.load_errors.is_empty(): for e in data.load_errors: printerr("test_runner:数据错误:%s" % e) quit(2) return var total_checks := 0 var total_fail: Array[String] = [] for path in CASE_FILES: var script: Variant = load(path) if script == null: printerr("test_runner:用例缺失 %s" % path) quit(2) return var case_obj: Variant = script.new() if not (case_obj is RefCounted) or not case_obj.has_method("run"): printerr("test_runner:用例契约不符 %s" % path) quit(2) return var a := TestAssert.new() case_obj.run(a, data) total_checks += a.checks for f in a.failures: total_fail.append("%s → %s" % [path.get_file(), f]) print("test_runner:%s:%d 检查,%d 失败" % [path.get_file(), a.checks, a.failures.size()]) if total_fail.is_empty(): print("test_runner:PASS(%d 检查全过)" % total_checks) quit(0) return print("test_runner:FAIL(%d/%d)" % [total_fail.size(), total_checks]) for f in total_fail: printerr(" ✗ %s" % f) quit(1)