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
parent
61181ea54e
commit
aadfd1cfcc
|
|
@ -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
|
`tests/_inputs/account.binance.paper.toml` are used in place. Accounting
|
||||||
contexts can write them on exit. Inspect `git diff -- tests/_inputs` after
|
contexts can write them on exit. Inspect `git diff -- tests/_inputs` after
|
||||||
any selected accounting case.
|
any selected accounting case.
|
||||||
- `tests/conftest.py` selects offscreen PyQt6 defaults before test modules
|
- `tests/conftest.py` selects PyQt6 defaults and owns temporary
|
||||||
import Qt, and its `qapp_args` fixture isolates `XDG_CONFIG_HOME` before
|
`XDG_CONFIG_HOME` and `XDG_CONFIG_DIRS` trees before importing Piker or Qt.
|
||||||
pytest-qt creates the application.
|
`--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
|
- Piker and the installed Tractor pytest plugin do not provide a
|
||||||
repository-local process or socket reaper. Never apply historical broad
|
repository-local process or socket reaper. Never apply historical broad
|
||||||
`pkill -f tractor._child` guidance automatically.
|
`pkill -f tractor._child` guidance automatically.
|
||||||
|
|
@ -186,26 +189,41 @@ whole suite supports UDS merely because the Tractor plugin exposes it.
|
||||||
|
|
||||||
## Qt/UI Tier
|
## Qt/UI Tier
|
||||||
|
|
||||||
The default pytest process environment is `QT_QPA_PLATFORM=offscreen` and
|
The pytest process defaults to `QT_QPA_PLATFORM=offscreen` only when the
|
||||||
`PYTEST_QT_API=pyqt6`. `tests/conftest.py` installs both with `setdefault`
|
caller has not selected a platform. Development shells can select a real
|
||||||
before Qt imports, so an explicit caller environment still wins. The
|
compositor such as Wayland. Pass `--headless` on every automated Qt run to
|
||||||
authoritative pytest config also selects `qt_api = "pyqt6"`, and
|
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.
|
`pytest-qt==4.5.0` is locked.
|
||||||
|
|
||||||
The session `qapp_args` fixture assigns a temporary `XDG_CONFIG_HOME` before
|
Root conftest setup assigns temporary `XDG_CONFIG_HOME` and `XDG_CONFIG_DIRS`
|
||||||
pytest-qt creates its `QApplication` and uses `piker-tests` as the application
|
trees before importing Piker or Qt. This keeps import-cached Piker paths and
|
||||||
argument. Deterministic offscreen Qt tests are normal local targets; they are
|
Qt user/system settings below test-owned storage. The session `qapp_args`
|
||||||
not opt-in merely because they import Qt or might otherwise read user config.
|
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:
|
Exact verified gap-overlay commands:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.venv/bin/python -m pytest -q --collect-only 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 -q -x --tb=short --no-header 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
|
The combined collection command collected 11 tests. The combined headless
|
||||||
one Tractor `trio.Event` boolean-use deprecation warning.
|
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
|
- Let `qtbot` own every widget registered with `qtbot.addWidget()` through
|
||||||
teardown. Do not manually close or delete the same widget a second time.
|
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
|
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
|
Real-compositor, visual, manual, and live UI tests remain opt-in. Announce
|
||||||
replace the offscreen default or infer compositor availability without
|
them before execution and obtain explicit authorization. Never omit
|
||||||
explicit authorization.
|
`--headless` from an automated Qt run merely because a compositor is
|
||||||
|
available.
|
||||||
|
|
||||||
## Test Layout
|
## Test Layout
|
||||||
|
|
||||||
|
|
@ -245,6 +264,7 @@ tests/
|
||||||
test_storage_audit.py read-only NativeDB audit and JSON CLI
|
test_storage_audit.py read-only NativeDB audit and JSON CLI
|
||||||
test_backfill_audit_snippet.py
|
test_backfill_audit_snippet.py
|
||||||
disposable xonsh qualification helpers
|
disposable xonsh qualification helpers
|
||||||
|
ui/ repeated-session Qt isolation and leak proofs
|
||||||
test_watchlists.py deterministic watchlist JSON operations
|
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/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/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 |
|
| `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/_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/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` |
|
| `piker/service/_actor_runtime.py`, `_registry.py`, `_mngr.py` | `test_runtime_boot` | then `test_datad_spawn` |
|
||||||
|
|
@ -282,8 +303,8 @@ container coverage.
|
||||||
```text
|
```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 '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 -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 --headless -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 -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_watchlists.py
|
||||||
.venv/bin/python -m pytest -q tests/test_accounting.py::test_account_file_default_empty
|
.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
|
timeout -k 5 300 .venv/bin/python -m pytest -q tests/test_services.py::test_runtime_boot
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
Loading…
Reference in New Issue