Document headless `Qt` test harness

Record the import-time XDG lifecycle, same-process isolation guard and
verified UI plus Tractor commands.

Require `--headless` for automated Qt runs and explicit authorization
before selecting a real compositor.

Prompt-IO: ai/prompt-io/opencode/20260831T215640Z_233fa590_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
wkt/replay_provider_e2e
Gud Boi 2026-09-02 16:00:09 -04:00
parent 61181ea54e
commit aadfd1cfcc
3 changed files with 157 additions and 20 deletions

View File

@ -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

View File

@ -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.

View File

@ -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.