docs(testing): add Known Gotchas section (live_gui non-determinism + early-render C crash)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user