extends Node class_name TestUtils # ═══════════════════════════════════════════════════════════════════════════ # TestUtils — Minimal assertion framework for headless Godot unit tests # # Usage: # var tu = TestUtils.new() # add_child(tu) # tu.describe("Movement validation") # tu.assert_eq(result.x, 5, "x should be 5") # tu.assert_true(valid, "should be valid") # tu.finish() # prints summary # ═══════════════════════════════════════════════════════════════════════════ var _passed: int = 0 var _failed: int = 0 var _current_suite: String = "" var _assertions_in_suite: int = 0 func describe(name: String) -> void: """Start a named test suite/section.""" _current_suite = name _assertions_in_suite = 0 print("\n === " + name + " ===") func assert_eq(got, expected, msg: String = "") -> void: _assertions_in_suite += 1 if got == expected: _passed += 1 else: _failed += 1 var detail = "got '%s', expected '%s'" % [str(got), str(expected)] print(" [FAIL] " + msg + " — " + detail) func assert_ne(got, unexpected, msg: String = "") -> void: _assertions_in_suite += 1 if got != unexpected: _passed += 1 else: _failed += 1 print(" [FAIL] " + msg + " — got '%s' which matches unexpected value" % str(got)) func assert_true(cond: bool, msg: String = "") -> void: _assertions_in_suite += 1 if cond: _passed += 1 else: _failed += 1 print(" [FAIL] " + msg + " — expected true, got false") func assert_false(cond: bool, msg: String = "") -> void: _assertions_in_suite += 1 if not cond: _passed += 1 else: _failed += 1 print(" [FAIL] " + msg + " — expected false, got true") func assert_almost_eq(got: float, expected: float, tolerance: float = 0.001, msg: String = "") -> void: _assertions_in_suite += 1 if abs(got - expected) <= tolerance: _passed += 1 else: _failed += 1 var detail = "got %.4f, expected %.4f ± %.4f" % [got, expected, tolerance] print(" [FAIL] " + msg + " — " + detail) func assert_violation(result: Dictionary, category: int, msg: String = "") -> void: """Assert the validation result contains at least one violation of the given category.""" _assertions_in_suite += 1 for v in result.get("violations", []): if v.get("category", -1) == category: _passed += 1 return _failed += 1 print(" [FAIL] " + msg + " — expected violation category " + str(category) + ", none found") func assert_no_violation(result: Dictionary, msg: String = "") -> void: """Assert the validation result has zero violations.""" _assertions_in_suite += 1 var count = result.get("violations", []).size() if count == 0: _passed += 1 else: _failed += 1 print(" [FAIL] " + msg + " — expected 0 violations, got " + str(count) + ": " + str(result["violations"])) func assert_corrected(result: Dictionary, field: String, msg: String = "") -> void: """Assert the result's corrected dict has a different value for `field` than the original.""" _assertions_in_suite += 1 if result.get("corrected", {}).has(field): _passed += 1 else: _failed += 1 print(" [FAIL] " + msg + " — expected correction in field '%s', none found" % field) func finish() -> void: """Print test summary and return exit code via OS.""" var total = _passed + _failed print("\n ──────────────────────────────────────") print(" Suite: " + _current_suite) print(" Passed: %d / %d" % [_passed, total]) if _failed > 0: print(" FAILED: %d test(s)" % _failed) else: print(" All passed.") print("") func exit_code() -> int: return 0 if _failed == 0 else 1