From 9a6bcb2f3461dcb3e536fac3d1f5ca9dcf69dc8a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 5 Jun 2026 18:21:24 -0400 Subject: [PATCH] docs(testing): add Known Gotchas section (live_gui non-determinism + early-render C crash) --- docs/guide_testing.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/guide_testing.md b/docs/guide_testing.md index ef1a3183..3abfeff6 100644 --- a/docs/guide_testing.md +++ b/docs/guide_testing.md @@ -579,6 +579,43 @@ The `live_gui` session fixture runs once at the start of the test session and te --- +## Known Gotchas (2026-06-05) + +### Live_gui Non-Determinism + +`live_gui` is a **session-scoped** fixture. All tests in a session share the same `sloppy.py` subprocess. As a result: + +- A test that triggers deep render paths early in the session "warms up" ImGui state (Fonts, DisplaySize, internal caches) for later tests. The same test run in a different order or in isolation may fail with a crash that wouldn't happen in the full run. +- `time.sleep(1)` between test operations is **not** enough for ImGui to stabilize in the first few render frames. Reproducers that need stable render state should sleep 3+ seconds before triggering the call. +- Bisect by running the failing test both **with** and **without** the rest of the suite to distinguish "needs warmup" from "real bug". +- When writing a new live_gui test, do NOT assume the first render frame is "ready". Use a `wait_for_event` or multi-poll pattern that tolerates the first few frames being broken. + +### Early-Render C-Level Crashes (Defer-Not-Catch Pattern) + +`imgui.save_ini_settings_to_memory()` (and similar raw imgui calls that read internal state) will **crash the Python process at the C level** (`0xc0000005` access violation) if called before ImGui's internal state is fully initialized. This is **not catchable from Python** — `try/except Exception` cannot intercept native access violations. + +Symptoms: +- The `sloppy.py` subprocess disappears without a Python traceback. +- The pytest output shows `pytest.fail("Hook server did not start in 15s")` (the subprocess died during startup). +- Windows Event Viewer shows `Faulting module: _imgui_bundle.cp311-win_amd64.pyd` with exception code `0xc0000005`. + +**Fix pattern: defer-not-catch.** Track a one-shot "ready" flag in the instance state; return early on the first call, only invoking the C function on subsequent calls: + +```python +def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile: + if not getattr(self, "_ini_capture_ready", False): + self._ini_capture_ready = True + return models.WorkspaceProfile(name=name, docking_layout=b"", ...) + ini = imgui.save_ini_settings_to_memory() + return models.WorkspaceProfile(name=name, docking_layout=ini.encode("utf-8") if isinstance(ini, str) else ini, ...) +``` + +The first call (during initial startup) returns a safe empty profile and flips the flag; subsequent calls (when the user actually clicks "Save Profile") invoke the C function. The user's workflow is unaffected because the first call is non-blocking and the user cannot have clicked "Save Profile" before the GUI was fully rendered. + +See `src/gui_2.py:601-606` for the canonical implementation. This pattern unblocks 4-5 live_gui tests that were crashing the GUI subprocess during the first render frames after `_capture_workspace_profile` was invoked by the test (typically via a `save_workspace_profile` Hook API callback). + +--- + ## See Also - **[guide_simulations.md](guide_simulations.md)** — Older guide focused on the Puppeteer pattern; still relevant for the test scenarios it documents