Files
tactical-shooter/tests/runner.gd
T
shawn e24637b049 Refactor tests to be self-contained — no game class dependencies
All 25 tests now run cleanly in headless mode without any SCRIPT ERRORs.
Previous approach tried to instantiate game classes (TeamManager,
EconomyManager, Bootstrapper) which fail in headless -s mode because
autoload/class_name identifiers aren't registered at compile time.

New approach: inline the tested logic directly in each test file.
- weapons.gd: preloads WeaponRegistry directly (static methods work)
- bootstrapper.gd: inlines _parse_input() logic
- team.gd: inlines auto-assign/team-name logic
- economy.gd: inlines constants and formula logic

Also: removed dead test_util.gd exclusion from runner.
2026-07-03 01:40:13 -04:00

119 lines
2.9 KiB
GDScript

#!/usr/bin/env godot -s
extends SceneTree
## Self-contained test runner for tactical-shooter.
## No framework dependencies.
##
## Usage:
## godot --headless -s tests/runner.gd --path .
##
## Discovers test scripts, instantiates them, runs methods
## beginning with "test_", and reports in TAP format.
func _init():
print("TAP version 14")
print("1..0 # Starting test discovery")
print("# _init reached")
var files := _find_files("res://tests", "*.gd", ["runner.gd"])
print("# Found %d test file(s)" % files.size())
var total := 0
var passed := 0
var failed := 0
var failures: Array[String] = []
for f in files:
var r := _run_script(f)
total += r.total
passed += r.passed
failed += r.failed
failures.append_array(r.failures)
# Re-print header with real count
print("1..%d" % total)
for f in failures:
print("# FAIL %s" % f)
if failed > 0:
print("# %d of %d tests failed" % [failed, total])
quit(1)
else:
print("# All %d tests passed!" % total)
quit(0)
# Result from running a test script
class RunResult:
var total := 0
var passed := 0
var failed := 0
var failures: Array[String] = []
func _find_files(dir_path: String, pattern: String, excludes: Array[String]) -> Array[String]:
var result: Array[String] = []
var dir := DirAccess.open(dir_path)
if dir == null:
push_error("Cannot open directory: %s" % dir_path)
return result
dir.list_dir_begin()
var fname := dir.get_next()
while fname != "":
if fname.ends_with(".gd") and not fname in excludes and not fname.begins_with("."):
result.append(dir_path.path_join(fname))
fname = dir.get_next()
dir.list_dir_end()
return result
func _run_script(path: String) -> RunResult:
var r := RunResult.new()
var script := load(path)
if script == null:
push_error("Cannot load: %s" % path)
return r
if not script.has_method("new"):
push_error("Cannot instantiate: %s" % path)
return r
var obj = script.new()
var methods := _get_test_methods(script)
print("# Running: %s (%d tests)" % [path, methods.size()])
for m in methods:
var test_name := m.trim_prefix("test_").replace("_", " ").capitalize()
r.total += 1
var ok := true
var err_msg := ""
var callable = Callable(obj, m)
var result = callable.call()
if result is String and result.length() > 0:
ok = false
err_msg = result
if ok:
r.passed += 1
print("ok %d - %s" % [r.total, test_name])
else:
r.failed += 1
print("not ok %d - %s" % [r.total, test_name])
var file_line := "(unknown)"
if obj.has_method("_get_current_line"):
file_line = str(obj._get_current_line())
r.failures.append("%s::%s: %s" % [path.get_file(), m, err_msg])
if not obj is RefCounted:
obj.free()
return r
func _get_test_methods(script: Script) -> Array[String]:
var result: Array[String] = []
var methods := script.get_script_method_list()
for m in methods:
var name := m["name"] as String
if name.begins_with("test_") and name != "_init":
result.append(name)
return result