Every screen-targeted intent now routes through one _require_current_screen() check and returns the structured error shape instead of silently mutating an off-screen viewer (hoshe's finding: scroll_rung from the reach screen fired real IPC and reported ok). The reference driver's fixed 4-frame settle becomes is_pending()-aware with a 600-frame bound, the keep-waiting decision extracted as a pure testable function — restoring the proven eyeball-driver discipline. The terrain_reference guard moves into AtlasApp._on_body_selected(), the shared tail for double-click, Enter, AND the intent path — closing a pre-existing click/Enter divergence hoshe caught this PR formalizing; the intent layer pre-checks via the new SystemScreen.find_body() and reports structured errors for unknown ids and terrain-less bodies. after_test() resets AtlasAgentBridge.current_app (tyre's freed-pending footgun). Suites 58/58 + 14/14; full suite 3,638. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
107 lines
4.7 KiB
GDScript
107 lines
4.7 KiB
GDScript
## T-971 smoke test for atlas_agent_driver.gd — the reference `-s` SceneTree
|
|
## driver for AtlasAgentInterface. A gdUnit suite cannot boot a SECOND full
|
|
## Godot process (the driver's real usage mode: `godot -s
|
|
## res://tests/atlas_agent_driver.gd -- --jobs ... --output ...`) without
|
|
## spawning a nested engine instance, which is slow/fragile and inconsistent
|
|
## with how this project already runs its OTHER `-s` drivers
|
|
## (visual_capture.gd/atlas_shots.json — manual `make` targets, never inside
|
|
## the gdUnit suite tests/run-godot invokes). The feasible headless smoke
|
|
## here is the driver's own PURE helper logic: job-file loading and
|
|
## result-array writing, both `static func`s with no SceneTree dependency —
|
|
## exercising the exact code path a real run's job-list parsing and
|
|
## output-writing go through, without booting a second engine.
|
|
class_name TestAtlasAgentDriver
|
|
extends GdUnitTestSuite
|
|
|
|
const DriverScript := preload("res://tests/atlas_agent_driver.gd")
|
|
|
|
var _tmp_dir: String = ""
|
|
|
|
|
|
func before_test() -> void:
|
|
_tmp_dir = "user://test_atlas_agent_driver/%d/" % Time.get_ticks_usec()
|
|
DirAccess.make_dir_recursive_absolute(_tmp_dir)
|
|
|
|
|
|
func after_test() -> void:
|
|
var d := DirAccess.open(_tmp_dir)
|
|
if d == null:
|
|
return
|
|
for f: String in d.get_files():
|
|
d.remove(f)
|
|
DirAccess.remove_absolute(_tmp_dir.rstrip("/"))
|
|
|
|
|
|
func test_load_jobs_reads_the_shipped_smoke_fixture() -> void:
|
|
var jobs: Array = DriverScript._load_jobs(
|
|
"res://tests/fixtures/atlas_agent_smoke_jobs.json"
|
|
)
|
|
assert_int(jobs.size()).is_equal(4)
|
|
assert_str((jobs[0] as Dictionary).get("intent", "")).is_equal("open_atlas")
|
|
assert_str((jobs[1] as Dictionary).get("intent", "")).is_equal("observe")
|
|
|
|
|
|
func test_load_jobs_returns_empty_array_for_a_missing_file() -> void:
|
|
var jobs: Array = DriverScript._load_jobs("res://tests/fixtures/does_not_exist.json")
|
|
assert_int(jobs.size()).is_equal(0)
|
|
|
|
|
|
func test_write_output_round_trips_through_json() -> void:
|
|
var out_path: String = _tmp_dir + "results.json"
|
|
var results: Array = [
|
|
{"intent": "observe", "params": {}, "result": {"screen": "reach"}},
|
|
{"intent": "select_system", "params": {"system_id": "GJ1"}, "result": {"ok": true}},
|
|
]
|
|
|
|
DriverScript._write_output(out_path, results)
|
|
|
|
var f := FileAccess.open(out_path, FileAccess.READ)
|
|
assert_object(f).is_not_null()
|
|
var parsed: Variant = JSON.parse_string(f.get_as_text())
|
|
f.close()
|
|
assert_that(parsed).is_equal(results)
|
|
|
|
|
|
# =============================================================================
|
|
# PR #209 review (Hoshe finding 2) — should_keep_waiting()'s bounded-fallback
|
|
# logic. Pure/static, no SceneTree dependency, so the boundary conditions are
|
|
# directly testable without a real frame loop or a real StepCanvasRequest.
|
|
# =============================================================================
|
|
|
|
|
|
func test_should_keep_waiting_true_while_pending_and_under_the_frame_budget() -> void:
|
|
assert_bool(DriverScript.should_keep_waiting(true, 0, 600)).is_true()
|
|
assert_bool(DriverScript.should_keep_waiting(true, 599, 600)).is_true()
|
|
|
|
|
|
func test_should_keep_waiting_false_once_no_longer_pending() -> void:
|
|
assert_bool(DriverScript.should_keep_waiting(false, 0, 600)).is_false()
|
|
|
|
|
|
func test_should_keep_waiting_false_once_the_frame_budget_is_exhausted() -> void:
|
|
# Still pending, but waited has reached max_frames — the bounded fallback
|
|
# must stop here rather than hanging indefinitely on a genuinely-stuck
|
|
# request (the whole reason SETTLE_MAX_FRAMES exists).
|
|
assert_bool(DriverScript.should_keep_waiting(true, 600, 600)).is_false()
|
|
assert_bool(DriverScript.should_keep_waiting(true, 601, 600)).is_false()
|
|
|
|
|
|
## The InputSwallower inner class (T-1157 inventory item 1) — a plain Node
|
|
## subclass with no SceneTree/window dependency for its OWN logic
|
|
## (set_input_as_handled() requires a live viewport to call meaningfully, but
|
|
## the class must at least instantiate and expose the two input hooks by
|
|
## name — this is the structural smoke check; the swallowing BEHAVIOR itself
|
|
## is exercised implicitly every time atlas_agent_driver.gd actually runs,
|
|
## per its own header doc).
|
|
func test_input_swallower_inner_class_instantiates_and_has_both_input_hooks() -> void:
|
|
# A nested `class X: extends Y` inside a script is exposed as a
|
|
# script-level constant on the OUTER GDScript resource — reachable via
|
|
# plain dot access on the preloaded DriverScript, same as any other
|
|
# nested-class reference in this codebase's own suites (e.g.
|
|
# GdUnitObjectAssertImplTest.gd's MyNode/MyExtendedNode pattern), just
|
|
# via a loaded resource instead of a same-file bare identifier.
|
|
var instance: Node = DriverScript._InputSwallower.new()
|
|
assert_bool(instance.has_method("_input")).is_true()
|
|
assert_bool(instance.has_method("_unhandled_input")).is_true()
|
|
instance.free()
|