extends SceneTree ## Minimal headless test runner for CI (used in place of an editor-based addon ## until a Godot editor workflow is introduced). ## ## Usage: ## godot --headless --path . --script res://src/test/test_runner.gd ## Exit code 0 = all passed, 1 = failures. const TestUtils := preload("res://src/test/test_utils.gd") const TEST_DIR := "res://src/test/tests" func _initialize() -> void: var test_files := _list_gd_files(TEST_DIR) var total_passed := 0 var total_failed := 0 var failures: Array[String] = [] for path in test_files: var script: GDScript = load(path) if script == null: failures.append("Could not load %s" % path) total_failed += 1 continue var instance: Object = script.new() TestUtils.reset() var suite_passed := 0 var suite_failed := 0 for method_info in instance.get_method_list(): var method: StringName = method_info["name"] if not String(method).begins_with("test_"): continue if method_info["flags"] & METHOD_FLAG_STATIC: continue TestUtils.reset() var result: Variant = instance.call(method) var errored: bool = TestUtils._errors.size() > 0 if result == true and not errored: suite_passed += 1 else: suite_failed += 1 var detail := "" if errored: detail = " :: " + TestUtils.report() failures.append("%s.%s%s" % [path.get_file(), method, detail]) total_passed += suite_passed total_failed += suite_failed print("Suite %-42s pass=%d fail=%d" % [path.get_file(), suite_passed, suite_failed]) print("") print("==================== TEST SUMMARY ====================") print("Passed: %d Failed: %d" % [total_passed, total_failed]) if failures.size() > 0: print("--------------------------------------------------") for f in failures: print("FAIL: %s" % f) print("======================================================") quit(0 if total_failed == 0 else 1) func _list_gd_files(dir_path: String) -> Array[String]: var out: Array[String] = [] var dir := DirAccess.open(dir_path) if dir == null: push_error("Cannot open test dir %s" % dir_path) return out dir.list_dir_begin() var file_name := dir.get_next() while file_name != "": if not dir.current_is_dir() and file_name.ends_with(".gd"): out.append(dir_path.path_join(file_name)) file_name = dir.get_next() dir.list_dir_end() out.sort() return out