Private
Public Access
0
0
Files
manual_slop/conductor/tracks/test_engine_integration_20260627/plan.md
T
ed ca185235e9 conductor(track): init test_engine_integration_20260627 (Track 1 of 3)
Spec + plan + metadata + state for the ImGui Test Engine integration.
Enables the test engine via --enable-test-engine flag, bridges it through
the existing API hooks layer (4 new /api/test_engine/* endpoints + 4 new
ApiHookClient methods), and proves the full bridge with a smoke test.

The test engine enables high-fidelity simulation of docking, window focus,
panel visibility, drag-and-drop, and keyboard input that the current Hook
API cannot express. The API hooks remain the single communication boundary;
the test engine is integrated behind it.

This is Track 1 of a 3-track campaign:
  Track 1: bridge + smoke test (this track)
  Track 2: migrate docking/focus/panel tests
  Track 3: visual regression via screenshot capture

Key risk: R1 (GIL-transfer crash) mitigated by Phase 1 Task 1.4 manual
verification checkpoint. Parallel-safe against the running tier2 taxonomy
branch and the enforcement_gap_closure track (zero file overlap).
2026-06-26 23:43:56 -04:00

13 KiB

Plan: ImGui Test Engine Integration (Bridge via API Hooks)

Track: test_engine_integration_20260627 Branch: master (parallel-safe; touches sloppy.py, src/gui_2.py, src/app_controller.py, src/api_hooks.py, src/api_hook_client.py, tests/conftest.py, new tests/test_test_engine_smoke.py — zero overlap with the running tier2 taxonomy branch or the enforcement_gap_closure track) Spec: conductor/tracks/test_engine_integration_20260627/spec.md

All Python edits use 1-space indentation. No comments in body. CRLF preserved.


Phase 1: Enable the Test Engine in the App

Focus: Add --enable-test-engine CLI flag, set runner_params.use_imgui_test_engine, add the register_tests callback with a placeholder smoke test.

  • Task 1.1: Write failing test for --enable-test-engine flag + engine activation

    • WHERE: tests/test_test_engine_smoke.py (NEW file)
    • WHAT: Test 1: test_engine_enabled — start live_gui (which will pass --enable-test-engine), verify the engine is active by calling client.get_test_status() (new method, implemented in Phase 3) and asserting queue_empty == True (engine is running, no tests queued). This test will FAIL before Phase 1 + Phase 3 land (the endpoint doesn't exist yet).
    • HOW: Use the live_gui fixture. Call client.get_test_status(). Assert the response has a queue_empty field. (The method is added in Phase 3; the test is written first per TDD.)
    • SAFETY: No live_gui state mutation; just a GET request.
    • COMMIT: test(smoke): add failing test for test engine activation
    • GIT NOTE: Red-phase test for the --enable-test-engine flag + engine activation.
  • Task 1.2: Add --enable-test-engine CLI flag to sloppy.py + AppController

    • WHERE: sloppy.py:35 (add arg), src/app_controller.py:1042 (add test_engine_enabled field)
    • WHAT:
      1. sloppy.py: add parser.add_argument("--enable-test-engine", action="store_true", help="Enable Dear ImGui Test Engine for automated UI testing") after the --enable-test-hooks line.
      2. src/app_controller.py:1042: add self.test_engine_enabled: bool = ("--enable-test-engine" in sys.argv) after the test_hooks_enabled line.
    • HOW: Use manual-slop_edit_file MCP tool. 1-space indent.
    • SAFETY: The flag is opt-in; normal runs are unaffected.
    • COMMIT: feat(cli): add --enable-test-engine flag
    • GIT NOTE: CLI flag for test engine; mirrors the --enable-test-hooks pattern at app_controller.py:1042.
  • Task 1.3: Enable the engine in App.run() + add _register_imgui_tests callback

    • WHERE: src/gui_2.py:641 (after RunnerParams() construction) + src/gui_2.py:~700 (new _register_imgui_tests method)
    • WHAT:
      1. In App.run() between line 641 (self.runner_params = _hi.RunnerParams()) and line 684 (callbacks.show_gui = ...), add:
        if getattr(self.controller, "test_engine_enabled", False):
         self.runner_params.use_imgui_test_engine = True
         self.runner_params.callbacks.register_tests = self._register_imgui_tests
        
      2. Add _register_imgui_tests(self) method on App (after _post_init, ~line 700):
        def _register_imgui_tests(self) -> None:
         from imgui_bundle import hello_imgui
         from imgui_bundle.imgui import test_engine
         engine = hello_imgui.get_imgui_test_engine()
         test = test_engine.register_test(engine, "Smoke Tests", "Tab Switch")
         def smoke_func(ctx) -> None:
          from imgui_bundle.imgui.test_engine_checks import CHECK
          ctx.set_ref("###manual slop")
          ctx.item_click("**/Session")
          CHECK(True)
         test.test_func = smoke_func
        
        The exact set_ref + item_click targets are determined during implementation by inspecting the running GUI's label tree. The smoke test should click a harmless tab (e.g., switch to "Session" tab) and CHECK(True) as a placeholder assertion. The real assertion (verify the tab actually switched) is added once the label path is confirmed.
    • HOW: Use manual-slop_edit_file / manual-slop_py_update_definition MCP tool. 1-space indent.
    • SAFETY: Guarded by test_engine_enabled; normal runs skip this entirely. The register_tests callback is only called by hello_imgui when use_imgui_test_engine = True.
    • COMMIT: feat(gui): enable test engine + register smoke test via callbacks.register_tests
    • GIT NOTE: Activates the test engine when --enable-test-engine is set; registers a placeholder smoke test.
  • Task 1.4: Verify the engine activates (manual)

    • WHAT: Run uv run python sloppy.py --enable-test-hooks --enable-test-engine locally. Verify the app starts without crashing (the GIL-transfer mechanism works). Verify hello_imgui.get_imgui_test_engine() returns a non-None engine. This is a manual checkpoint before proceeding to Phase 2.
    • COMMIT: (no commit; manual verification checkpoint)
    • GIT NOTE: Manual verification that the engine + GIL transfer works with the app's existing thread layout.

Phase 2: Build the API Hooks Bridge

Focus: Add the 4 /api/test_engine/* endpoints to HookServer + the 4 methods to ApiHookClient.

  • Task 2.1: Write failing tests for the 4 new ApiHookClient methods

    • WHERE: tests/test_test_engine_smoke.py (append to the file from Task 1.1)
    • WHAT: 2 more tests:
      • test_queue_and_run_smoke_test: queue the smoke test via client.queue_test("Smoke Tests", "Tab Switch"), poll via client.wait_for_test_results(timeout=30), assert results["count_success"] >= 1 and results["count_tested"] >= 1.
      • test_engine_results_shape: call client.get_test_results(), assert the response dict has keys count_tested, count_success, count_in_queue.
    • HOW: Use live_gui fixture. These tests fail until Phase 2 + Phase 3 land (the client methods + endpoints don't exist yet).
    • SAFETY: The smoke test queues a harmless tab-switch; no destructive state change.
    • COMMIT: test(smoke): add failing tests for queue_test + wait_for_test_results + get_test_results
    • GIT NOTE: Red-phase tests for the 4 new ApiHookClient methods.
  • Task 2.2: Add the 4 /api/test_engine/* endpoints to HookServer

    • WHERE: src/api_hooks.pydo_GET (line 157) + do_POST (line 490)
    • WHAT: Add 4 new elif branches:
      1. do_GET: elif self.path == "/api/test_engine/status": — lazy-import hello_imgui + test_engine; get engine via hello_imgui.get_imgui_test_engine(); call test_engine.is_test_queue_empty(engine); respond {"queue_empty": bool}.
      2. do_GET: elif self.path == "/api/test_engine/results": — get engine; create TestEngineResultSummary(); call test_engine.get_result_summary(engine, out_results); respond {"count_tested": N, "count_success": N, "count_in_queue": N}.
      3. do_POST: elif self.path == "/api/test_engine/queue": — body {"group": "...", "name": "..."}; get engine; find test via test_engine.find_test_by_name(engine, group, name); if found, test_engine.queue_test(engine, test); respond {"status": "queued"} or {"error": "test not found"} (404).
      4. do_POST: elif self.path == "/api/test_engine/abort": — get engine; test_engine.abort_current_test(engine); respond {"status": "aborted"}.
    • HOW: Follow the existing endpoint pattern (lines 499-505 for POST, lines 231-241 for GET). Use _get_app_attr(app, "controller") to check test_engine_enabled; if not enabled, respond 503. Use json.dumps(...) for the response body. 1-space indent.
    • SAFETY: The endpoints run on the HTTP handler thread. hello_imgui.get_imgui_test_engine() is a C++ accessor (thread-safe). queue_test / is_test_queue_empty / get_result_summary are thread-safe C++ engine operations (the engine is designed for cross-thread test scheduling). abort_current_test is also thread-safe.
    • COMMIT: feat(api_hooks): add /api/test_engine/* bridge endpoints
    • GIT NOTE: 4 new endpoints: queue, status, results, abort; bridge the test process to the engine via HTTP.
  • Task 2.3: Add the 4 new methods to ApiHookClient

    • WHERE: src/api_hook_client.py (after the existing methods, ~line 500)
    • WHAT: 4 new methods:
      1. queue_test(self, group: str, name: str) -> dict — POST /api/test_engine/queue with {"group": group, "name": name}; return the response dict.
      2. get_test_status(self) -> dict — GET /api/test_engine/status; return {"queue_empty": bool}.
      3. get_test_results(self) -> dict — GET /api/test_engine/results; return {"count_tested": N, "count_success": N, "count_in_queue": N}.
      4. wait_for_test_results(self, timeout: float = 30.0) -> dict — poll get_test_status() every 0.5s until queue_empty == True or timeout; then return get_test_results(). On timeout, return the last results (with a timed_out: True field).
    • HOW: Follow the existing method pattern (e.g., get_status at line 105, push_event at line 156). Use requests.get/post + retry. 1-space indent.
    • SAFETY: Pure HTTP client; no thread safety concerns.
    • COMMIT: feat(api_hook_client): add queue_test + get_test_status + get_test_results + wait_for_test_results
    • GIT NOTE: 4 new client methods mirroring the 4 new endpoints; wait_for_test_results replaces time.sleep+get_value polling.
  • Task 2.4: Run Phase 2 tests (Green phase)

    • WHAT: uv run pytest tests/test_test_engine_smoke.py -v --timeout=60. All 3 tests must pass. If the smoke test (test_queue_and_run_smoke_test) fails, the most likely cause is the set_ref / item_click label path being wrong — debug by using imgui.show_id_stack_tool_window() or ctx.window_info("manual slop") to find the correct label. If the GIL transfer fails, the app will crash — that's a hard blocker; report to user.
    • COMMIT: conductor(state): Phase 2 green-phase verification (or skip if no code changes)
    • GIT NOTE: Green-phase verification for the 4 new endpoints + 4 new client methods.

Phase 3: Live_gui Fixture + Full Smoke Test

Focus: Pass --enable-test-engine in the live_gui fixture + verify the full bridge works end-to-end.

  • Task 3.1: Update live_gui fixture to pass --enable-test-engine

    • WHERE: tests/conftest.py:792
    • WHAT: Change gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"] to include "--enable-test-engine":
      gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks", "--enable-test-engine"]
      
    • HOW: manual-slop_edit_file MCP tool. 1-space indent.
    • SAFETY: The engine is idle when no tests are queued. Existing live_gui tests that don't use the test engine are unaffected (the engine adds sub-ms per-frame overhead).
    • COMMIT: test(conftest): pass --enable-test-engine in live_gui fixture
    • GIT NOTE: Engine activates on every live_gui run; idle when no tests queued.
  • Task 3.2: Run the full smoke test suite (Green phase)

    • WHAT: uv run pytest tests/test_test_engine_smoke.py -v --timeout=60. All 3 tests pass. Then run a small batch of existing live_gui tests to verify no regression: uv run pytest tests/test_workspace_profiles_restoration.py tests/test_undo_redo_lifecycle.py -v --timeout=120.
    • COMMIT: conductor(state): Phase 3 green-phase verification
    • GIT NOTE: Full bridge verified: pytest → HTTP → HookServer → engine → scenario thread → ctx.item_click → GUI thread → CHECK → results → HTTP → pytest assert.

Phase 4: End-of-Track Report + State Update

  • Task 4.1: Write end-of-track report

    • WHERE: docs/reports/TRACK_COMPLETION_test_engine_integration_20260627.md (NEW file)
    • WHAT: Report following the precedent:
      • TL;DR
      • Phase summary (each phase + commits + status)
      • Verification Criteria status (mapped to spec G1-G7)
      • Threading model verification (did the GIL transfer work? any crashes? any state-access issues from the scenario thread?)
      • The 4 new endpoints + 4 new client methods documented
      • The smoke test result
      • Handoff to Track 2 (docking test migration) — what's now possible that wasn't before
      • Known limitations (engine requires a live window; not headless; the interactive panel is not shown)
    • COMMIT: docs(reports): TRACK_COMPLETION_test_engine_integration_20260627
    • GIT NOTE: End-of-track report; documents the bridge + threading model verification + Track 2 handoff.
  • Task 4.2: Update conductor/tracks.md + conductor/chronology.md + state.toml

    • WHAT:
      1. state.toml: mark all phases "completed" with checkpoint SHA; status = "completed".
      2. conductor/tracks.md: add row for this track (status "shipped").
      3. conductor/chronology.md: prepend row for 2026-06-27 | test_engine_integration_20260627 | shipped | ....
    • COMMIT: conductor(state): test_engine_integration_20260627 SHIPPED + TRACK_COMPLETION
    • GIT NOTE: Track state + chronology + tracks.md closed out.
  • Task 4.3: Conductor - User Manual Verification

    • WHAT: Present the results: the smoke test pass, the threading model verification, the 4 new endpoints, the 4 new client methods. PAUSE for user sign-off.
    • COMMIT: (no commit; user-confirmation gate)
    • GIT NOTE: User sign-off record.