diff --git a/.claude/skills/run-tests/test-harness-reference.md b/.claude/skills/run-tests/test-harness-reference.md index d4c9b225..ae1c26af 100644 --- a/.claude/skills/run-tests/test-harness-reference.md +++ b/.claude/skills/run-tests/test-harness-reference.md @@ -172,9 +172,12 @@ whole suite supports UDS merely because the Tractor plugin exposes it. `tests/_inputs/account.binance.paper.toml` are used in place. Accounting contexts can write them on exit. Inspect `git diff -- tests/_inputs` after any selected accounting case. -- `tests/conftest.py` selects offscreen PyQt6 defaults before test modules - import Qt, and its `qapp_args` fixture isolates `XDG_CONFIG_HOME` before - pytest-qt creates the application. +- `tests/conftest.py` selects PyQt6 defaults and owns temporary + `XDG_CONFIG_HOME` and `XDG_CONFIG_DIRS` trees before importing Piker or Qt. + `--headless` force-selects Qt's `offscreen` platform at that same early + point, overriding compositor values inherited from a development shell. + A pytest config cleanup restores the caller environment even when only + collection runs. The `qapp_args` fixture only supplies `piker-tests`. - Piker and the installed Tractor pytest plugin do not provide a repository-local process or socket reaper. Never apply historical broad `pkill -f tractor._child` guidance automatically. @@ -186,26 +189,41 @@ whole suite supports UDS merely because the Tractor plugin exposes it. ## Qt/UI Tier -The default pytest process environment is `QT_QPA_PLATFORM=offscreen` and -`PYTEST_QT_API=pyqt6`. `tests/conftest.py` installs both with `setdefault` -before Qt imports, so an explicit caller environment still wins. The -authoritative pytest config also selects `qt_api = "pyqt6"`, and +The pytest process defaults to `QT_QPA_PLATFORM=offscreen` only when the +caller has not selected a platform. Development shells can select a real +compositor such as Wayland. Pass `--headless` on every automated Qt run to +force `offscreen` before imports and avoid opening desktop windows. Without +`--headless`, an explicit caller platform wins. `PYTEST_QT_API` defaults to +PyQt6, the authoritative pytest config selects `qt_api = "pyqt6"`, and `pytest-qt==4.5.0` is locked. -The session `qapp_args` fixture assigns a temporary `XDG_CONFIG_HOME` before -pytest-qt creates its `QApplication` and uses `piker-tests` as the application -argument. Deterministic offscreen Qt tests are normal local targets; they are -not opt-in merely because they import Qt or might otherwise read user config. +Root conftest setup assigns temporary `XDG_CONFIG_HOME` and `XDG_CONFIG_DIRS` +trees before importing Piker or Qt. This keeps import-cached Piker paths and +Qt user/system settings below test-owned storage. The session `qapp_args` +fixture uses `piker-tests` as the application argument. Deterministic +offscreen Qt tests are normal local targets; they are not opt-in merely +because they import Qt or might otherwise read user config. + +`tests/ui/conftest.py` reuses pytest-qt's session `QApplication` and applies +an autouse function guard. After pytest-qt closes registered widgets, the +guard checks and restores top-level widgets, `ViewBox.AllViews`, +`ViewBox.NamedViews`, `pyqtgraph.CONFIG_OPTIONS`, Piker config paths, +`QSettings`, and `quitOnLastWindowClosed()`. Register every test-owned widget +with `qtbot.addWidget()` so a surviving object is reported as a leak. Exact verified gap-overlay commands: ```text -.venv/bin/python -m pytest -q --collect-only tests/test_gap_overlays.py -.venv/bin/python -m pytest -q -x --tb=short --no-header tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q --collect-only tests/ui/test_harness.py tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q -x --tb=short --no-header tests/ui/test_harness.py tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q -x --tb=short --no-header tests/ui/test_harness.py +.venv/bin/python -m pytest --headless -q --collect-only tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q -x --tb=short --no-header tests/test_gap_overlays.py ``` -The first command collected 9 tests. The second passed all 9 in 2.00s with -one Tractor `trio.Event` boolean-use deprecation warning. +The combined collection command collected 11 tests. The combined headless +command passed all 11 in 2.00s, including the UI platform assertion, with one +upstream Tractor `trio.Event` boolean-use deprecation warning. - Let `qtbot` own every widget registered with `qtbot.addWidget()` through teardown. Do not manually close or delete the same widget a second time. @@ -218,9 +236,10 @@ QT_QPA_PLATFORM=wayland .venv/bin/python -m pytest -q -x --tb=short --no-header QT_QPA_PLATFORM=xcb .venv/bin/python -m pytest -q -x --tb=short --no-header tests/test_gap_overlays.py ``` -Real-compositor, visual, manual, and live UI tests remain opt-in. Do not -replace the offscreen default or infer compositor availability without -explicit authorization. +Real-compositor, visual, manual, and live UI tests remain opt-in. Announce +them before execution and obtain explicit authorization. Never omit +`--headless` from an automated Qt run merely because a compositor is +available. ## Test Layout @@ -245,6 +264,7 @@ tests/ test_storage_audit.py read-only NativeDB audit and JSON CLI test_backfill_audit_snippet.py disposable xonsh qualification helpers + ui/ repeated-session Qt isolation and leak proofs test_watchlists.py deterministic watchlist JSON operations ``` @@ -262,6 +282,7 @@ tests/ | `piker/storage/cli.py` SHM null-slot guard | `tests/test_ldshm.py` | synthetic timestamps, no SHM mutation | | `piker/config.py` | `test_account_file_default_empty` | root-network test has a known mismatch | | `piker/accounting/` | targeted accounting node | some cases use live/configured state | +| root/UI test fixtures | `tests/ui/test_harness.py` | same-process Qt and config restoration | | `piker/ui/_gaps.py`, `_annotate.py`, `_display.py`, `_interaction.py`, `_remote_ctl.py` | `tests/test_gap_overlays.py` | offscreen PyQt6 plus one local Tractor actor | | `piker/ui/_style.py`, `piker/ui/qt.py` | `tests/test_dpi_font.py` | deterministic offscreen Qt | | `piker/service/_actor_runtime.py`, `_registry.py`, `_mngr.py` | `test_runtime_boot` | then `test_datad_spawn` | @@ -282,8 +303,8 @@ container coverage. ```text .venv/bin/python -c 'import importlib.util, pathlib, sys; root = pathlib.Path.cwd().resolve(); spec = importlib.util.find_spec("piker"); mod = pathlib.Path(spec.origin).resolve(); print(sys.executable); print(mod); assert mod.is_relative_to(root)' .venv/bin/python -c 'from importlib.metadata import version; import pathlib, piker, pytestqt, sys; root = pathlib.Path.cwd().resolve(); mod = pathlib.Path(piker.__file__).resolve(); qt_ver = version("pytest-qt"); print(sys.executable); print(mod); print(qt_ver); assert mod.is_relative_to(root); assert qt_ver == "4.5.0"' -.venv/bin/python -m pytest -q --collect-only tests/test_gap_overlays.py -.venv/bin/python -m pytest -q -x --tb=short --no-header tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q --collect-only tests/test_gap_overlays.py +.venv/bin/python -m pytest --headless -q -x --tb=short --no-header tests/test_gap_overlays.py .venv/bin/python -m pytest -q tests/test_watchlists.py .venv/bin/python -m pytest -q tests/test_accounting.py::test_account_file_default_empty timeout -k 5 300 .venv/bin/python -m pytest -q tests/test_services.py::test_runtime_boot diff --git a/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.md b/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.md new file mode 100644 index 00000000..1501e8de --- /dev/null +++ b/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.md @@ -0,0 +1,64 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +session: d3e773b1-63ab-44da-a30f-33bc8a05e778 +timestamp: 2026-08-31T21:56:40Z +git_ref: 233fa590 +scope: code +substantive: true +raw_file: 20260831T215640Z_233fa590_prompt_io.raw.md +--- + +## Prompt + +Continue Phase 1 of the human-facing E2E foundation from the existing +worktree state: establish robust pytest-qt isolation and leak proofs, +use real QtBot Ctrl-G input, replace arbitrary actor sleeps with typed +deterministic synchronization, keep the test-harness reference current, +and verify the work end to end. Preserve the user's staged snapshot, +with permission to unstage if useful. After implementation, unstage +the snapshot and prepare a complete multi-commit plan. + +## Response summary + +Establishes import-time XDG isolation with exact pytest cleanup, adds +same-process QApplication and mutable-state restoration proofs, and +migrates Ctrl-G to real QtBot input. An explicit `--headless` option +forces offscreen execution before imports even inside the Wayland +development shell. The real input sequence exposed and fixed +modifier-only event handling in the production keyboard path. The real +Tractor regression now uses a typed receipt and explicit cancellation +barrier instead of timing and logger instrumentation. + +## Files changed + +- `tests/conftest.py` - own and restore process XDG config roots before + importing Piker or Qt. +- `tests/ui/conftest.py` - guard and restore mutable Qt, PyQtGraph, + QSettings, filesystem, and Piker config state after each UI test. +- `tests/ui/test_harness.py` - prove shared QApplication reuse, + isolation, sentinel preservation, and leak-free teardown. +- `piker/ui/_interaction.py` - ignore modifier-only chord events before + action dispatch. +- `tests/test_gap_overlays.py` - use real QtBot Ctrl-G and a typed, + deterministic real-actor cancellation barrier. +- `.claude/skills/run-tests/test-harness-reference.md` - document the + verified fixture contract, commands, and results. + +## Human edits + +The human directed continuation of the existing Phase 1 scope, +authorized `nix develop` for runtime verification, requested Prompt-IO +capture, and stated that the current staged snapshot may be unstaged if +needed. After an unannounced test run opened real Wayland windows, the +human identified the problem and required an explicit headless mode, +while preserving separately authorized real-window coverage. This +material correction added `--headless`, offscreen platform proof, and +the rule that real-compositor runs must be announced and explicitly +authorized. The staged snapshot was preserved; subsequent fixes and +these provenance files initially remained unstaged. The human later +explicitly requested a mixed reset before commit-plan generation. +During review of the first staged boundary, the human required a TODO +for a public config-path API and detailed rationale around each UI +teardown check. The agent applied both changes and added authorized +responses to the persisted local Tuicr session. diff --git a/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.raw.md b/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.raw.md new file mode 100644 index 00000000..c5066b8c --- /dev/null +++ b/ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.raw.md @@ -0,0 +1,52 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +timestamp: 2026-08-31T21:56:40Z +git_ref: 233fa590 +diff_cmd: git diff HEAD +--- + +## Generated changes + +> `git diff HEAD -- tests/conftest.py tests/ui/conftest.py tests/ui/test_harness.py` + +Moves XDG user and system config isolation ahead of Piker and Qt +imports, guarantees process-environment restoration through pytest +cleanup, and adds repeated-session proofs for one shared +`QApplication`. The function-scoped UI guard restores QSettings, +Piker config paths, PyQtGraph options and view registries, Qt window +state, and exact test-owned files while preserving process sentinels. +An early `--headless` option force-selects `offscreen` even when the +development shell exports Wayland, and the test asserts the effective +Qt platform. + +> `git diff HEAD -- piker/ui/_interaction.py tests/test_gap_overlays.py` + +Routes Ctrl-G through a shown and focused real widget with +`QtBot.keyPress()`. The production input handler now ignores the +modifier-only events emitted by real key chords. Replaces a timed +Tractor cancellation window and patched logger with a typed stream +receipt, explicit task-completion event, and correlated stale-response +proof across a real child actor. + +> `git diff HEAD -- .claude/skills/run-tests/test-harness-reference.md` + +Records the verified early-XDG lifecycle, same-process UI isolation +contract, leak checks, targeted commands, layout, and current results. + +## Verification + +- Local package and pytest-qt 4.5.0 resolve in the approved + `nix develop` environment. +- The combined UI and gap-overlay scope collects 11 tests. +- Both UI isolation tests pass; a duplicated same-process run reports + six passing cases. +- The real QtBot Ctrl-G test passes after exercising Qt's standalone + Control event before G. +- The real Tractor actor test passes with no arbitrary sleep or + monkeypatched synchronization. +- The combined target passes: 11 tests and one upstream Tractor + `trio.Event.__bool__` deprecation warning. +- The final combined run passes with `--headless`, and the harness + proves `QApplication.platformName()` is `offscreen`. +- Python static compilation and `git diff --check` pass.