012d038025
Assets (CC0 license): - 3D models: Blaster gun, walls, platforms, grass, clouds, enemy - Sounds: blaster fire, impacts, footsteps, jumps, weapon change - Sprites: crosshair, hit marker, muzzle flash, skybox, blob shadow - Font: Lilita One Code changes: - weapon_manager.gd: load blaster.glb as gun model (replaces box mesh) - player.tscn: use Kenney sounds (blaster.ogg, enemy_hurt/destroy) and crosshair Test suite (100 unit tests + 4 integration scenarios): - weapon_data.gd: 27 tests — all 6 weapons every stat verified - economy.gd: 19 tests — constants, loss streak, buy thresholds - bomb.gd: 15 tests — state machine, timing constants - round_manager.gd: 10 tests — win conditions, elimination logic - team_manager.gd: 8 tests — auto-balance, team assignment - headless_test_bot.gd: integration bot with movement/idle/rounds scenarios - run_multi_bot.sh: multi-client launcher
119 lines
3.0 KiB
GDScript
119 lines
3.0 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", "headless_test_bot.gd", "bot_test_scene.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
|