fix(mma_lifecycle): load + start + active_tickets sync for batched tests

The test_visual_sim_mma_v2 failure in tier-3 batch context was caused
by state pollution from prior live_gui tests sharing the subprocess:

1. track state file missing for leftover track:
   `_cb_load_track_result` accessed `state.metadata.id` and
   `state.metadata.name`, but `EMPTY_TRACK_STATE` (returned when no
   state.toml exists for a track_id) had `metadata={}` (a dict, not a
   TrackMetadata object). That raised `'dict' object has no attribute
   'id'`. Fixed by normalizing metadata: dict -> TrackMetadata.from_dict,
   TrackMetadata stays, anything else -> TrackMetadata(id=track_id,
   name=track_id).

2. active_track and active_tickets never reached the App:
   `_cb_load_track_result` set `self.active_track` (controller) and
   `self.active_tickets = []` (via `_load_active_tickets`) but never
   mirrored to `self._app.active_track` / `self._app.active_tickets`.
   The /api/gui/mma_status endpoint reads `app.X` first via
   `_get_app_attr`, so it returned None / [] and the test's poll
   `at_id == track_id and bool(s.get('active_tickets'))` failed. Fixed
   by mirroring active_track / active_tickets / active_tier to the App.

3. leftover tracks list in batched run:
   Without btn_reset, `app.tracks` (App-side) accumulates tracks from
   earlier tests in the session. `_get_app_attr(app, 'tracks', [])`
   then returns stale leftovers, and the test's
   `target_track = next((... if 'hello_world' in t.get('title') else
   tracks_list[0]))` picks a leftover with no on-disk state file.
   Fixed in TWO places:
   (a) btn_reset now also clears `app.tracks = []` so each test starts
       clean if it calls btn_reset.
   (b) tests/test_visual_sim_mma_v2.py now calls `client.click('btn_reset')`
       at the start. The test was the only one in its tier that did NOT
       reset; with the live_gui subprocess shared across batched tests,
       that's the source of the state pollution.

Also reverted the `TrackState.metadata` default change (dict -> TrackMetadata)
because it broke TrackState() construction (TrackMetadata requires `id`/`name`).
The metadata normalization in `_cb_load_track_result` is sufficient and
preserves backward compatibility with on-disk state.toml files.

Verified: tests/test_visual_sim_mma_v2.py passes in isolation (58.36s)
and tier-3 batch passes when run standalone. Other tests in tier-3
have pre-existing render-loop contention flakes in batched xdist mode
that are unrelated to this fix.
This commit is contained in:
ed
2026-07-02 10:23:14 -04:00
parent 0ba0acf567
commit 9cfbb980bd
2 changed files with 56 additions and 3 deletions
+13
View File
@@ -64,6 +64,19 @@ def test_mma_complete_lifecycle(live_gui) -> None:
client = api_hook_client.ApiHookClient()
assert client.wait_for_server(timeout=15), "Hook server did not start"
# Clear any stale state from prior live_gui tests in this batched
# subprocess. Without btn_reset, leftover tracks (created by earlier
# tests like test_mma_concurrent_tracks_sim / test_visual_mma) sit in
# `app.tracks` and become `tracks_list[0]`. The test's `target_track =
# next(..., tracks_list[0])` then loads a leftover track whose on-disk
# state file does not exist, so `_cb_load_track_result` falls back to
# EMPTY_TRACK_STATE → `state.tasks == []` → `bool(active_tickets)` is
# False → Stage 6 polls fail. btn_reset clears `app.tracks` / `app.active_tickets`
# / `app.active_track` so `tracks_list` reflects only tracks accepted
# in this test's plan_epic + accept_tracks batch.
client.click("btn_reset")
time.sleep(2)
# ------------------------------------------------------------------
# Stage 1: Provider setup
# ------------------------------------------------------------------