Files
Bullosseum/tests
richard 9458085abb Add typed DamageEvent hit pipeline
A typed DamageEvent contract (systems/combat/damage_event.gd) as a single
entry point for every attacker, plus a characterization test locking the bull's
damage reception. Attacker/receiver wiring rides along with the moved actor
scripts in the reorg commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCyaJPTHCH9EDthpf4A5Sj
2026-09-08 14:55:46 +03:00
..
2026-08-23 13:16:04 +03:00
2026-08-23 20:32:52 +03:00
2026-07-24 14:51:57 +03:00
2026-06-10 18:56:17 +03:00
2026-08-23 20:32:52 +03:00
2026-08-23 20:32:52 +03:00
2026-08-23 20:32:52 +03:00

Tests

Headless, deterministic tests that run in CI (.gitea/workflows/ci.yml) and locally via bash run_tests.sh. Nothing here needs a GPU — even the UI checks assert computed geometry instead of comparing pixels, so the whole suite runs in plain headless Godot.

Run

bash run_tests.sh                 # lint + every headless test (+ gameplay assertions)
bash run_tests.sh --no-gameplay   # skip the slower full-scene gameplay pass (what CI runs)

# a single test:
godot --headless --script tests/<name>.gd

Set GODOT to point at a specific binary: GODOT=/path/to/godot bash run_tests.sh.

The six layers

Order is cheapest first. The middle layers are where "don't break interactions/UI by accident" is actually won — none of them need a screenshot.

Layer What it drives Example here
Scene smoke Instantiate a scene headless, assert no errors level_switch_test.gd (level modules load, no geometry leaks)
Unit / logic Pure functions, no scene tree logic_test.gd; harness_selftest.gd (proves test_helpers.gd)
Signal contract Emit a signal, assert args/count to add: died / health_changed / all_defeated fire correctly
Interaction Feed synthetic input, step frames, assert state touch_controls_test.gd, matador_charge_test.gd
Scenario / replay Scripted inputs + fixed seed over a whole fight difficulty_sim.gd (a report), bear_boss_test.gd
UI layout Resize the viewport, query Control rects ui_layout_test.gd (on-screen, text fits, tap size, scroll)

Shared harness

test_helpers.gd (class_name TestHelpers) holds the primitives every test used to re-implement:

  • TestHelpers.make_rng(seed) — a seeded RandomNumberGenerator. Determinism is the precondition for the interaction/scenario layers; seed the RNG here instead of the global randf()/randi() so a test replays identically. (Reworking gameplay code to accept an injected RNG is tracked in the refactor plan's Workstream 2.)
  • TestHelpers.approx(a, b, eps) / vec_approx(a, b, eps) — float/vector comparisons with tolerance, because computed positions never compare exactly equal.
  • A pass/fail tally: t.ok(cond, msg) and t.report(), which prints Results: X passed, Y failed and returns the process exit code.

New tests follow the existing idiom: extends SceneTree, _init() → _run.call_deferred(), print PASS: / FAIL:, and quit(t.report()).

Next step: GdUnit4

The hand-rolled harness is enough for CI today. The plan's Workstream 5 adopts GdUnit4 for a fluent assertion API, its scene runner (synthetic input + frame stepping — purpose-built for the interaction layer), and HTML/JUnit reports. Adoption is incremental, so the current tests keep running throughout:

  1. Add it under addons/gdUnit4 (AssetLib in-editor, or a git submodule) and enable the plugin in Project Settings → Plugins.
  2. Point CI at GdUnit4's own GitHub Action (it publishes the JUnit report to the PR) alongside the existing run_tests.sh step.
  3. Port suites one at a time — test_helpers.gd's primitives map directly onto GdUnit4 assertions, so a ported test is smaller, not larger.

Do not vendor GdUnit4 into this repo by hand; install it through AssetLib or a submodule.