Use `pytest-qt` ownership for gap overlay tests

Let `pytest-qt` own each real `PlotWidget` and input source so
fixture teardown closes them exactly once.

Also,
- select `PyQt6` and offscreen `Qt` before widget imports
- isolate `QSettings` and activate `pyproject.toml` config
- lock `pytest-qt` 4.5.0 and record verified harness commands
- retain real `tractor` dialog coverage beside `Qt` regressions

Prompt-IO: ai/prompt-io/opencode/20260831T005712Z_09ddcf50_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-08-31 16:18:35 -04:00
parent 46ee5a8c4c
commit 233fa590f9
8 changed files with 249 additions and 106 deletions

View File

@ -10,30 +10,34 @@ here.
- Project/import: `piker`
- Test root: `tests/`
- Supported Python: `>=3.12,<3.14`
- Preferred complete environment: worktree-local `py313` inside the current
`nix develop` shell
- The flake shell pins CPython 3.13 and sets
`UV_PROJECT_ENVIRONMENT=py313`.
- Verify the interpreter, package resolution, and dependency import before
running tests. A bare `py313` may lack the Qt binding supplied by Nix.
- Preferred verified environment: worktree-local `.venv`.
- The testing group includes pytest and the locked `pytest-qt==4.5.0`.
- Verify the interpreter, local package resolution, and dependency versions
before running tests.
Use an already-provisioned `py313` only when `import piker` succeeds:
This worktree's environment was provisioned with:
```text
py313/bin/python -m pytest -p no:xonsh
env UV_PROJECT_ENVIRONMENT=.venv uv sync --group testing --frozen
```
If direct environment paths are unavailable, an existing uv environment can
be used without changing it, subject to the same import check:
Do not rerun provisioning without approval. Use the already-provisioned
environment directly:
```text
UV_PROJECT_ENVIRONMENT=py313 uv run --frozen --no-sync python -m pytest -p no:xonsh
.venv/bin/python -m pytest
```
Ask before running provisioning commands such as:
If direct environment paths are unavailable, use uv without changing the
environment, subject to the same import check:
```text
env UV_PROJECT_ENVIRONMENT=.venv uv run --frozen --no-sync python -m pytest
```
Ask before entering or provisioning alternate environments such as:
```text
UV_PROJECT_ENVIRONMENT=py313 uv sync --dev --all-extras --no-group lint
nix develop
nix-shell default.nix
```
@ -42,43 +46,41 @@ nix-shell default.nix
X11 shell. Do not use `develop.nix` for current testing; it retains the old
Python 3.11, Poetry, and Qt 5 stack.
The current root-checkout `py313` resolves `piker` locally but fails
`import piker` outside Nix because PyQtGraph cannot import PyQt or PySide. Do
not treat that environment as test-ready and do not enter `nix develop`
without approval: its shell hook may recreate and sync `py313`.
Plain `uv sync` does not include the testing group. The `dbs` dependency group
is also absent from normal dev-shell provisioning.
Do not enter `nix develop` without approval: its shell hook may provision its
own configured environment. Plain `uv sync` does not include the testing
group; use `--group testing` when provisioning is explicitly authorized. The
`dbs` dependency group is also absent from normal dev-shell provisioning.
## Commands
Base command in the preferred environment:
```text
py313/bin/python -m pytest -p no:xonsh
.venv/bin/python -m pytest
```
The explicit `-p no:xonsh` is required. The tracked comments-only
`pytest.ini` takes precedence over `pyproject.toml`, so the intended
`addopts = "-p no:xonsh"` and `testpaths = ["tests"]` are inactive.
Always pass a test path or node ID explicitly.
The comments-only `pytest.ini` has been deleted. The authoritative
`[tool.pytest.ini_options]` in `pyproject.toml` sets `testpaths = ["tests"]`,
`addopts = "-p no:xonsh"`, and `qt_api = "pyqt6"`; do not repeat
`-p no:xonsh` in ordinary commands. Still pass a test path or node ID when a
deterministic scope is required.
Package-resolution check that does not import Piker's dependencies:
```text
py313/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)'
```
Dependency import check, required before collection or execution:
```text
py313/bin/python -c 'import pathlib, piker, sys; root = pathlib.Path.cwd().resolve(); mod = pathlib.Path(piker.__file__).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"'
```
Safe core collection check:
```text
py313/bin/python -m pytest -p no:xonsh -q --collect-only tests/test_watchlists.py tests/test_accounting.py tests/test_services.py tests/test_ems.py tests/test_feeds.py tests/test_cli.py
.venv/bin/python -m pytest -q --collect-only tests/test_watchlists.py tests/test_accounting.py tests/test_services.py tests/test_ems.py tests/test_feeds.py tests/test_cli.py
```
Default first-pass flags are `-q -x --tb=short --no-header` unless the user
@ -86,7 +88,7 @@ requests otherwise. For actor-heavy tests, use one file or node per process
with an outer timeout:
```text
timeout -k 5 300 py313/bin/python -m pytest -p no:xonsh -q <one-file-or-node>
timeout -k 5 300 .venv/bin/python -m pytest -q <one-file-or-node>
```
If an actor-heavy command exits `124` or `143`, retry that exact command once
@ -111,6 +113,8 @@ Deterministic or local first-pass targets:
- `tests/test_ib_method_proxy.py`
- `tests/test_history_backfill.py`
- `tests/test_ldshm.py`
- `tests/test_dpi_font.py`
- `tests/test_gap_overlays.py`
- `tests/test_accounting.py::test_account_file_default_empty`
- `tests/test_services.py::test_runtime_boot`
- `tests/test_services.py::test_datad_spawn`
@ -128,7 +132,6 @@ Require explicit authorization before running:
fixtures plus possible live symcache generation;
- `tests/test_accounting.py::test_ib_account_with_duplicated_mktids` - active
broker/account configuration and state writes;
- `tests/test_dpi_font.py` - Qt/UI import and user-config side effects;
- `tests/test_docker_services.py` - optional dependencies and containers;
- `tests/test_questrade.py` - obsolete credentialed imports.
@ -169,9 +172,9 @@ 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.
- Importing `tests/test_dpi_font.py` constructs module-global font objects
before fixtures can isolate config. If explicitly requested, isolate
`XDG_CONFIG_HOME` before Python starts and use the proper Qt/Nix shell.
- `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.
- 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.
@ -181,6 +184,44 @@ whole suite supports UDS merely because the Tractor plugin exposes it.
unlinks exact surviving names before failing the leaking test. It never
scans `/dev/shm` or unlinks attachments created by another process.
## 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
`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.
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
```
The first command collected 9 tests. The second passed all 9 in 2.00s with
one 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.
- Keep production `MainWindow` out of this tier: its `closeEvent()` sends
`SIGINT` to the pytest process.
- A real compositor is an explicit process override, for example:
```text
QT_QPA_PLATFORM=wayland .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
replace the offscreen default or infer compositor availability without
explicit authorization.
## Test Layout
```text
@ -193,6 +234,7 @@ tests/
test_dpi_font.py Qt DPI/font behavior
test_ems.py actor, EMS, and paper-position behavior
test_feeds.py live Binance/Kraken feeds and shared memory
test_gap_overlays.py typed gap logic, offscreen Qt, and Tractor IPC
test_ib_history.py deterministic IB history request formatting
test_ib_method_proxy.py deterministic IB asyncio proxy routing
test_history_backfill.py deterministic history/SHM orchestration
@ -220,7 +262,8 @@ 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 |
| `piker/ui/_style.py`, `piker/ui/qt.py` | `tests/test_dpi_font.py` | GUI/config-isolated opt-in |
| `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` |
| `piker/service/`, `piker/data/_daemon.py` | `test_datad_spawn` | feed lifecycle cases are live |
| `piker/data/feed.py`, `flows.py`, `_sharedmem.py`, `_sampling.py` | collect first | feed execution needs live permission |
@ -230,26 +273,28 @@ tests/
| Docker/service adapters | `tests/test_docker_services.py` | optional deps and containers |
| project, lock, or Nix files | import check and safe collection | full collection is not safe by default |
Prefer deterministic filesystem/config tests, then local actor-runtime nodes,
then explicitly approved live broker, GUI, or container coverage.
Prefer deterministic filesystem/config and offscreen Qt tests, then local
actor-runtime nodes, then explicitly approved live broker, visual UI, or
container coverage.
## Quick Checks
```text
py313/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)'
py313/bin/python -c 'import pathlib, piker, sys; root = pathlib.Path.cwd().resolve(); mod = pathlib.Path(piker.__file__).resolve(); print(sys.executable); print(mod); assert mod.is_relative_to(root)'
py313/bin/python -m pytest -p no:xonsh -q tests/test_watchlists.py
py313/bin/python -m pytest -p no:xonsh -q tests/test_accounting.py::test_account_file_default_empty
timeout -k 5 300 py313/bin/python -m pytest -p no:xonsh -q tests/test_services.py::test_runtime_boot
timeout -k 5 300 py313/bin/python -m pytest -p no:xonsh -q tests/test_services.py::test_datad_spawn
timeout -k 5 300 py313/bin/python -m pytest -p no:xonsh -q tests/test_ems.py::test_ems_err_on_bad_broker
.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 -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
timeout -k 5 300 .venv/bin/python -m pytest -q tests/test_services.py::test_datad_spawn
timeout -k 5 300 .venv/bin/python -m pytest -q tests/test_ems.py::test_ems_err_on_bad_broker
```
## Known Outcomes
- The current root-checkout `py313` fails `import piker` outside the Nix shell
with `ImportError: PyQtGraph requires one of PyQt5, PyQt6, PySide2 or
PySide6`. This is an incomplete environment, not an application regression.
- The verified `.venv` import check resolves `piker` beneath this worktree and
imports the locked `pytest-qt` 4.5.0.
- `tests/test_accounting.py::test_root_conf_networking_section` currently
expects `network.tsdb`, which is absent from the tracked config template.
Match the current `KeyError: 'tsdb'` before classifying it as the known

View File

@ -0,0 +1,44 @@
---
model: openai/gpt-5.6-sol
service: opencode
session: unavailable
timestamp: 2026-08-31T00:57:12Z
git_ref: 09ddcf50
scope: tests
substantive: true
raw_file: 20260831T005712Z_09ddcf50_prompt_io.raw.md
---
## Prompt
Deeply evaluate Piker's lacking suites and build a comprehensive plan for
non-mocked, human-driven E2E coverage, beginning with the pytest-qt plan on
`chart_local_gapper`. Work in `/open-wkt`, build the first feasible GUI test
slice, add minimal adjacent CLI and Qt guide docs, keep the `/run-tests`
reference current, and stop before the stated Eastern deadline.
## Response summary
Audits the existing suite and human-facing surfaces, creates a risk-ranked
cross-interface E2E plan, activates pytest-qt, migrates the real-Qt gap suite
to pytest-qt widget ownership, and records verified commands and minimal user
guides. Full `MainWindow` E2E remains deferred behind safe shutdown, guest
outcome, symbol-session ownership, and deterministic provider seams.
## Files changed
- `pyproject.toml`, `uv.lock`, `pytest.ini` - activate pytest-qt config.
- `tests/conftest.py` - establish the Qt process and settings contract.
- `tests/test_gap_overlays.py` - migrate real widgets to `qtbot` ownership.
- `piker/cli/README.rst` - add the minimal command-line guide.
- `piker/ui/README.rst` - add the minimal chart UI guide.
- `plans/opencode/human-facing-e2e-coverage.md` - persist the suite plan.
- `.claude/skills/run-tests/test-harness-reference.md` - record verified
test commands and boundaries.
## Human edits
The human requires isolated worktree execution, adjacent guide-style docs,
continuous `/run-tests` reference maintenance, non-mocked Qt interactions,
and a hard time cutoff. These constraints materially determine the patch
scope and defer unsafe full-window work.

View File

@ -0,0 +1,45 @@
---
model: openai/gpt-5.6-sol
service: opencode
timestamp: 2026-08-31T00:57:12Z
git_ref: 09ddcf50
diff_cmd: git diff HEAD
---
## Generated changes
> `git diff HEAD -- pyproject.toml pytest.ini uv.lock`
Activates pytest-qt with PyQt6 through the repository's authoritative
pytest configuration and locks pytest-qt 4.5.0.
> `git diff HEAD -- tests/conftest.py tests/test_gap_overlays.py`
Selects the offscreen PyQt6 test process before Qt imports, isolates Qt
configuration, migrates real chart-gap widgets to pytest-qt ownership,
and removes the conflicting manual widget teardown path.
> `git diff HEAD -- piker/cli/README.rst piker/ui/README.rst`
Adds minimal adjacent guides for the installed command and Qt chart
interfaces without freezing volatile subsystem details.
> `git diff HEAD -- plans/opencode/human-facing-e2e-coverage.md`
Records the evidence-based test-suite audit and phased human-facing E2E
strategy, distinguishing component, application, system, and live
qualification boundaries.
> `git diff HEAD -- .claude/skills/run-tests/test-harness-reference.md`
Records the verified environment, commands, Qt ownership rules, current
tier boundaries, and test outcomes.
## Verification
- Local package and pytest-qt 4.5.0 import check passes.
- Nine gap-overlay tests collect.
- The targeted file passes: 9 tests, one upstream Tractor warning.
- A duplicated same-process run passes: 18 tests, two equivalent
upstream Tractor warnings.
- `git diff --check` passes.

View File

@ -149,6 +149,7 @@ repl = [
]
testing = [
"pytest",
"pytest-qt",
]
de = [ # (linux) specific DEs
"i3ipc>=2.2.1",
@ -177,6 +178,7 @@ console_output_style = 'progress'
# https://docs.pytest.org/en/stable/how-to/plugins.html#disabling-plugins-from-autoloading
# https://docs.pytest.org/en/stable/how-to/plugins.html#deactivating-unregistering-a-plugin-by-name
addopts = '-p no:xonsh'
qt_api = 'pyqt6'
# ------ tool.pytest ------

View File

@ -1,3 +0,0 @@
#[pytest]
#trio_mode=True
#log_cli=1

View File

@ -9,6 +9,10 @@ from weakref import (
ref,
)
# These must be selected before test modules import Qt.
os.environ.setdefault('QT_QPA_PLATFORM', 'offscreen')
os.environ.setdefault('PYTEST_QT_API', 'pyqt6')
import pytest
import tractor
from piker import (
@ -26,6 +30,20 @@ pytest_plugins: tuple[str] = (
)
@pytest.fixture(scope='session')
def qapp_args(
tmp_path_factory: pytest.TempPathFactory,
) -> list[str]:
'''
Isolate Qt settings before pytest-qt creates `QApplication`.
'''
config_home: Path = tmp_path_factory.mktemp('qt-config')
os.environ['XDG_CONFIG_HOME'] = str(config_home)
return ['piker-tests']
def pytest_addoption(parser):
parser.addoption("--ll", action="store", dest='loglevel',
default=None, help="logging level to set when testing")

View File

@ -4,20 +4,17 @@ Typed chart-local gap-overlay regressions.
'''
from collections.abc import (
Callable,
Iterator,
)
from contextlib import AsyncExitStack
import os
from types import SimpleNamespace
os.environ['QT_QPA_PLATFORM'] = 'offscreen'
import msgspec
import numpy as np
from PyQt6.QtGui import QKeyEvent
from PyQt6.QtWidgets import QGraphicsScene
import pyqtgraph as pg
import pytest
from pytestqt.qtbot import QtBot
import tractor
from tractor._testing import tractor_test
import trio
@ -59,10 +56,12 @@ class _ChartStub:
self,
fqme: str,
array: np.ndarray,
qtbot: QtBot,
) -> None:
self.fqme: str = fqme
self.widget: pg.PlotWidget = pg.PlotWidget()
qtbot.addWidget(self.widget)
self.viz: SimpleNamespace = SimpleNamespace(
plot=self.widget.plotItem,
shm=SimpleNamespace(array=array),
@ -115,6 +114,7 @@ def _ohlcv_array(
def _display_state(
fqme: str,
qtbot: QtBot,
) -> tuple[
SimpleNamespace,
@ -127,10 +127,12 @@ def _display_state(
rt_chart: _ChartStub = _ChartStub(
fqme,
_ohlcv_array((1, 2, 4, 5)),
qtbot,
)
hist_chart: _ChartStub = _ChartStub(
fqme,
_ohlcv_array((60, 120, 300, 360)),
qtbot,
)
ds: SimpleNamespace = SimpleNamespace(
fqme=fqme,
@ -142,22 +144,6 @@ def _display_state(
return ds, (rt_chart, hist_chart)
@pytest.fixture(scope='session')
def qapp() -> Iterator[QApplication]:
'''
Keep one offscreen Qt application alive for graphics tests.
'''
app: QApplication|None = QApplication.instance()
if app is None:
app = QApplication(['piker-gap-tests'])
app.setQuitOnLastWindowClosed(False)
yield app
app.processEvents()
def test_gap_specs_and_wire_roundtrip() -> None:
'''
Local detection and remote IPC share one typed request model.
@ -291,7 +277,7 @@ def test_gap_overlay_unknown_fqme_returns_typed_error() -> None:
def test_gap_manager_real_qt_lifecycle(
qapp: QApplication,
qtbot: QtBot,
) -> None:
'''
@ -307,7 +293,7 @@ def test_gap_manager_real_qt_lifecycle(
'''
ds: SimpleNamespace
charts: tuple[_ChartStub, _ChartStub]
ds, charts = _display_state(FQME)
ds, charts = _display_state(FQME, qtbot)
annots: dict[int, GapAnnotations] = {}
gapman: GapOverlayMngr = GapOverlayMngr(
dss={FQME: ds},
@ -403,14 +389,11 @@ def test_gap_manager_real_qt_lifecycle(
gapman.remove_owner('chart-local')
assert annots == {}
finally:
chart: _ChartStub
for chart in charts:
chart.close()
qapp.processEvents()
gapman.remove_owner('chart-local')
def test_gap_annotations_reposition_after_prepend(
qapp: QApplication,
qtbot: QtBot,
) -> None:
'''
@ -426,7 +409,7 @@ def test_gap_annotations_reposition_after_prepend(
'''
ds: SimpleNamespace
charts: tuple[_ChartStub, _ChartStub]
ds, charts = _display_state(FQME)
ds, charts = _display_state(FQME, qtbot)
annots: dict[int, GapAnnotations] = {}
gapman: GapOverlayMngr = GapOverlayMngr(
dss={FQME: ds},
@ -445,7 +428,7 @@ def test_gap_annotations_reposition_after_prepend(
old_scene_point: QPointF = item.mapToScene(
old_br.center()
)
qapp.processEvents()
QApplication.processEvents()
assert item in scene.items(old_scene_point)
old_rects: np.ndarray = (
item._rectarray.ndarray().copy()
@ -458,7 +441,7 @@ def test_gap_annotations_reposition_after_prepend(
fqme=FQME,
timeframe=60,
)
qapp.processEvents()
QApplication.processEvents()
new_rects: np.ndarray = item._rectarray.ndarray()
np.testing.assert_allclose(
@ -476,14 +459,10 @@ def test_gap_annotations_reposition_after_prepend(
assert item.scene() is charts[1].widget.scene()
finally:
gapman.remove_owner('chart-local')
chart: _ChartStub
for chart in charts:
chart.close()
qapp.processEvents()
def test_duplicate_fqme_layers_use_local_chart_identity(
qapp: QApplication,
qtbot: QtBot,
) -> None:
'''
@ -499,10 +478,10 @@ def test_duplicate_fqme_layers_use_local_chart_identity(
'''
first_ds: SimpleNamespace
first_charts: tuple[_ChartStub, _ChartStub]
first_ds, first_charts = _display_state(FQME)
first_ds, first_charts = _display_state(FQME, qtbot)
second_ds: SimpleNamespace
second_charts: tuple[_ChartStub, _ChartStub]
second_ds, second_charts = _display_state(FQME)
second_ds, second_charts = _display_state(FQME, qtbot)
annots: dict[int, GapAnnotations] = {}
gapman: GapOverlayMngr = GapOverlayMngr(
dss={FQME: second_ds},
@ -534,14 +513,10 @@ def test_duplicate_fqme_layers_use_local_chart_identity(
assert annots[second.aid].scene() is not None
finally:
gapman.remove_owner('chart-local')
chart: _ChartStub
for chart in (*first_charts, *second_charts):
chart.close()
qapp.processEvents()
def test_startup_and_focused_toggle_use_real_qt(
qapp: QApplication,
qtbot: QtBot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
@ -560,10 +535,16 @@ def test_startup_and_focused_toggle_use_real_qt(
first_ds: SimpleNamespace
first_charts: tuple[_ChartStub, _ChartStub]
first_ds, first_charts = _display_state('first.test')
first_ds, first_charts = _display_state(
'first.test',
qtbot,
)
second_ds: SimpleNamespace
second_charts: tuple[_ChartStub, _ChartStub]
second_ds, second_charts = _display_state('second.test')
second_ds, second_charts = _display_state(
'second.test',
qtbot,
)
dss: dict[str, SimpleNamespace] = {
first_ds.fqme: first_ds,
second_ds.fqme: second_ds,
@ -642,14 +623,10 @@ def test_startup_and_focused_toggle_use_real_qt(
assert unrelated == []
finally:
gapman.remove_owner('chart-local')
chart: _ChartStub
for chart in (*first_charts, *second_charts):
chart.close()
qapp.processEvents()
def test_ctrl_g_event_renders_real_history_overlay(
qapp: QApplication,
qtbot: QtBot,
) -> None:
'''
@ -665,7 +642,7 @@ def test_ctrl_g_event_renders_real_history_overlay(
'''
ds: SimpleNamespace
charts: tuple[_ChartStub, _ChartStub]
ds, charts = _display_state(FQME)
ds, charts = _display_state(FQME, qtbot)
annots: dict[int, GapAnnotations] = {}
gapman: GapOverlayMngr = GapOverlayMngr(
dss={FQME: ds},
@ -686,6 +663,7 @@ def test_ctrl_g_event_renders_real_history_overlay(
active=False,
)
source: QWidget = QWidget()
qtbot.addWidget(source)
source.order_mode = order_mode
source._chart = ds.hist_chart
source.linked = SimpleNamespace(
@ -727,12 +705,6 @@ def test_ctrl_g_event_renders_real_history_overlay(
assert annots[aid].scene() is charts[1].widget.scene()
finally:
gapman.remove_owner('chart-local')
source.close()
source.deleteLater()
chart: _ChartStub
for chart in charts:
chart.close()
qapp.processEvents()
_actor_requests: list[dict] = []

22
uv.lock
View File

@ -1132,6 +1132,7 @@ dev = [
{ name = "pyqt6" },
{ name = "pyqtgraph" },
{ name = "pytest" },
{ name = "pytest-qt" },
{ name = "qdarkstyle" },
{ name = "rapidfuzz" },
{ name = "xonsh" },
@ -1149,6 +1150,7 @@ repl = [
]
testing = [
{ name = "pytest" },
{ name = "pytest-qt" },
]
uis = [
{ name = "pyqt6" },
@ -1206,6 +1208,7 @@ dev = [
{ name = "pyqt6", specifier = ">=6.7.0,<7.0.0" },
{ name = "pyqtgraph", specifier = ">=0.14.0" },
{ name = "pytest" },
{ name = "pytest-qt" },
{ name = "qdarkstyle", specifier = ">=3.0.2,<4.0.0" },
{ name = "rapidfuzz", specifier = ">=3.2.0,<4.0.0" },
{ name = "xonsh", specifier = ">=0.23.0" },
@ -1219,7 +1222,10 @@ repl = [
{ name = "pyperclip", specifier = ">=1.9.0" },
{ name = "xonsh", specifier = ">=0.23.0" },
]
testing = [{ name = "pytest" }]
testing = [
{ name = "pytest" },
{ name = "pytest-qt" },
]
uis = [
{ name = "pyqt6", specifier = ">=6.7.0,<7.0.0" },
{ name = "pyqtgraph", specifier = ">=0.14.0" },
@ -1629,6 +1635,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]]
name = "pytest-qt"
version = "4.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pluggy" },
{ name = "pytest" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/61/8bdec02663c18bf5016709b909411dce04a868710477dc9b9844ffcf8dd2/pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f", size = 128702, upload-time = "2025-07-01T17:24:39.889Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/d0/8339b888ad64a3d4e508fed8245a402b503846e1972c10ad60955883dcbb/pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff", size = 37214, upload-time = "2025-07-01T17:24:38.226Z" },
]
[[package]]
name = "python-baseconv"
version = "1.2.2"