piker/plans/opencode/live-backend-chart-e2e-revi...

15 KiB
Raw Blame History

Live-backend chart E2E plan review

Decision

Reorient the human-facing test effort around the production Qt chart running against real public backends. Put replay implementation, scenario design, provider failure injection, and replay-backed CI on hold until a real application or backend failure demonstrates a need for controlled data.

The first valuable target is an in-process application journey using pytest-qt, Trio guest mode, an isolated Piker actor tree, and public Kraken data. Installed-process and paper-order journeys follow after that path can start, report readiness, and stop without process-wide signals.

Entry-point backend discovery remains independently useful, but it is not a prerequisite for chart E2E. Treat it as a short standalone patch or defer it with replay.

Review of the original plan

The original plan correctly identifies the end goal: user-facing QtBot journeys paired with installed-process tests. It also correctly requires real Tractor, IPC, SHM, feed, search, and EMS boundaries.

Its central ordering assumption is now rejected. It makes a deterministic offline provider the organizing dependency for chart boot, search, paper orders, public APIs, process tests, and failure handling. It schedules that provider and a general subprocess harness before the production Qt/Trio lifecycle seam.

That ordering optimizes for deterministic default CI. It does not optimize for finding failures users currently encounter with real venues, real history payloads, real quote timing, real symbology, and the composed chart.

The replay phase is not technically required by the later lifecycle work:

  1. Qt/Trio startup and shutdown are provider-independent.
  2. A chart can use Krakens public history and quote APIs without credentials.
  3. Search can use the real Kraken symbol service.
  4. Paper EMS can consume a real public feed without spawning live brokerd.
  5. Installed chart startup can use an isolated registry and real provider.
  6. Guest errors, child failures, SIGINT behavior, and repeated-session leaks do not require synthetic provider failures.

Replay remains potentially useful for forcing exact races, reproducing a captured venue payload, denying network in required CI, or proving a fix for a specific data-dependent regression. Those are follow-up uses, not current prerequisites.

What replay would and would not audit

A replay-backed chart journey would prove that the composed application can consume one protocol-conforming source, render known bars, process known quotes, and shut down. It would be useful interface and lifecycle coverage.

It would not prove the behavior most likely to drift outside the repository:

  • exchange REST and WebSocket availability;
  • TLS, DNS, geo-routing, and endpoint changes;
  • authentication behavior accidentally applied to public requests;
  • real symbology and market metadata changes;
  • incomplete, duplicated, delayed, or out-of-order history;
  • backend normalization against current venue payloads;
  • reconnect and rate-limit behavior;
  • the timing and volume of actual chart updates.

The current priority is the second list. Therefore real-provider application qualification should precede further replay work.

Current practical blockers

Blocking application host

run_qtractor() currently creates or reuses QApplication, constructs the production MainWindow and GodWidget, starts Trio guest mode, shows the window, and then blocks in app.exec_().

This prevents a synchronous pytest-qt test from receiving a session handle while Qt owns the host loop.

Unobservable Trio completion

The Trio guest done_callback prints unexpected errors and unconditionally calls app.quit(). It does not retain the outcome.Outcome, expose a Qt completion signal, or let pytest fail on the original exception.

Calling app.quit() also violates pytest-qt ownership of its session-scoped application.

Process-wide chart shutdown

MainWindow.closeEvent() saves geometry and sends SIGINT to the entire test process. A QtBot-owned window cannot exercise normal close behavior without interrupting pytest.

No application readiness contract

_async_main() has a meaningful readiness point after the initial chart, paper order mode, search handlers, and status cleanup are active, but it does not publish that state outside its Trio task.

Incomplete child-resource accounting

The test harness isolates XDG paths, config paths, registry addresses, and current-process SHM. A chart application test must additionally record the specific child actor IDs and SHM names created by datad so teardown can prove those exact resources disappeared without broad cleanup.

Existing foundation to keep

The completed harness foundation already provides:

  • pytest-qt ownership of one QApplication;
  • import-time PyQt6 and offscreen selection with --headless;
  • process-level XDG isolation before Piker and Qt imports;
  • function-scoped restoration of Piker, Qt, QSettings, and PyQtGraph state;
  • real QtBot key delivery and widget registration;
  • unique Tractor registry addresses from the Tractor pytest plugin;
  • test-owned config directories propagated to child actors through piker_test_dir runtime variables;
  • exact current-process SHM ownership checks.

This is sufficient foundation for the lifecycle extraction. Replay is not needed to begin it.

Revised test model

Keep independent dimensions instead of defining application coverage by its data source:

Dimension Initial selection Later selections
Boundary In-process production application Installed chart process
Data source Live Kraken public data Binance, other qualified backends
Display Qt offscreen through pytest-qt Real Wayland/X11 compositor
Clearing Real paper EMS Credentialed live broker qualification
Cadence Explicit local/CI qualification Repeated soak and release gate

The first test is therefore a headless, live-provider, in-process application journey. It is not a deterministic default unit test, and it should not be presented as one.

Isolation contract

Real provider access does not require attaching to a developers production Piker runtime. Each application test must:

  1. Use the Tractor pytest plugins unique registry address.
  2. Pass that address explicitly through run_qtractor() into maybe_open_pikerd(); never allow fallback to the default registry.
  3. Propagate the test-owned config directory through tractor_runtime_overrides['piker_test_dir'].
  4. Start a fresh local pikerd, datad.kraken, and samplerd under the tests registry.
  5. Use public Kraken data only; do not load user credentials.
  6. Record every actor identity and each chart feeds exact SHM names.
  7. Request structured shutdown through the application session.
  8. Assert those actors, streams, registry connections, and SHM names are gone.
  9. Never use process-name matching, broad SHM scans, pkill, or global ports.
  10. Treat the external network as intentionally shared qualification input.

An explicitly selected live test must fail on connection or provider errors, not silently skip after startup. Tests skip only when live qualification was not requested.

Immediate vertical slice

Production lifecycle seam

Extract a nonblocking start_qtractor() from run_qtractor().

It should return a QtractorSession containing:

  • the production MainWindow and GodWidget;
  • a Trio token and owned cancellation scope;
  • an application-ready Qt signal;
  • a completion Qt signal;
  • the final outcome.Outcome;
  • a structured request_shutdown() method;
  • whether the adapter or caller owns QApplication.exec().

start_qtractor() must configure and show the production objects but must not call app.exec() or app.quit(). run_qtractor() remains the blocking CLI adapter: it calls start_qtractor(), owns app.exec(), and unwraps the final outcome into a process result.

Change MainWindow.closeEvent() to request session shutdown through an injected callback. Keep geometry persistence. Remove process-wide SIGINT from normal window closure; terminal SIGINT remains an outer CLI concern.

Publish application readiness from _async_main() only after the initial feed, charts, paper order mode, search handlers, and startup status have all entered their live scopes.

First live chart journey

Add an explicitly selected test such as:

tests/app/test_live_chart.py::test_kraken_chart_boots_and_closes

Use xbtusd.spot.kraken, because Kraken public REST/WebSocket data is credential-free and the pair is continuously active.

The test should:

  1. Start QtractorSession with pytest-qts QApplication.
  2. Pass the unique test registry and child config root.
  3. Register session.window with qtbot immediately.
  4. Wait for the application-ready signal with a bounded timeout.
  5. Assert the displayed FQME and production window title.
  6. Assert both historical and real-time chart visualizations have nonempty data from their real SHM arrays.
  7. Observe at least one post-start quote/display update from Kraken.
  8. Drive Ctrl-L with QtBot and assert the real search bar receives focus.
  9. Drive the existing search-dismiss interaction and assert chart focus.
  10. Request structured close and wait for the completion signal.
  11. Unwrap a successful Trio outcome.
  12. Prove exact actor and SHM teardown.

Do not assert pixels, exact prices, exact bar counts, result ordering, actor PIDs, generated SHM names, or quote arrival within an unrealistically short interval. Those values vary while the semantic behavior remains correct.

Live-provider selection

Use Kraken as the initial baseline:

  • public market metadata, OHLC, and WebSocket data need no credentials;
  • xbtusd.spot.kraken and ethusdt.spot.kraken already appear in live feed tests;
  • the existing suite allows Kraken feed coverage in CI;
  • crypto trading is continuous outside venue maintenance and outages.

Use Binance spot as a second qualification only where network and geographic access are known. Keep Kucoin informational until its public configuration and startup behavior receive direct repair. Exclude IB, Questrade, and current Deribit from credential-free chart qualification.

Add one explicit selection mechanism, preferably --live-provider=kraken. Without it, live application tests skip. With it, backend startup failure is a test failure and retains the original Trio/Tractor exception and diagnostics.

Do not add a blanket retry. Record cold-start duration and failure signatures. A separate repeated-run or soak command can measure operational reliability after the first journey is stable.

Reordered implementation phases

Phase A: Qt/Trio lifecycle

Implement QtractorSession, nonblocking startup, structured close, observable outcome, and repeated-session cleanup. Verify first with a provider-independent guest coroutine so failures are localized to lifecycle ownership.

Phase B: real Kraken chart

Boot the production _async_main() against xbtusd.spot.kraken using the isolated runtime contract. Prove chart readiness, live data, one QtBot focus journey, and complete teardown.

Phase C: real chart behavior

Add A-to-B-to-A Kraken symbol switching, chart navigation, timeframe changes, and paper order open/cancel behavior. Prefer stable semantic assertions and record real failures before extracting narrow regression tests.

Phase D: installed-process coverage

Run the installed piker chart command with the same isolated registry/config contract. Prove cold startup, readiness, graceful close, exit status, and exact descendant cleanup. Add standalone pikerd attachment only after cold start works.

Phase E: qualification breadth

Add Binance where available, real compositor runs, repeated cold starts, and bounded soak sessions. Keep provider outages visible as qualification results rather than rewriting them into synthetic success.

Deferred work

Defer replay scenarios, provider-conformance tests, deterministic quote sequencing, synthetic provider failures, and replay-backed required CI.

Reconsider replay only when one of these concrete needs appears:

  • a real payload must be preserved as a regression fixture;
  • an ordering race cannot be reproduced reliably against a live venue;
  • required network-free CI needs a minimal known-good chart source;
  • a reconnect or malformed-data defect needs deterministic fault control.

Backend entry-point discovery may land separately if its implementation stays small and useful to external providers. Do not make it block Phase A or B, and do not use plugin support as a reason to continue replay work.

Replay resumption record

If replay work resumes, first move its implementation out of the production backend namespace and use it to introduce general external backend discovery:

  1. Move the implementation to piker.testing.brokers.replay.
  2. Add a piker.brokers Python entry-point group which resolves installed external backend modules after conventional built-in imports.
  3. Register replay through that entry point in Pikers package metadata.
  4. Preserve a backends actual module path when constructing datad and brokerd enable_modules lists so Tractor authorizes the external endpoint module.
  5. Reject duplicate names, built-in shadowing, non-module entry points, name mismatches, and unsupported backend API versions.
  6. Preserve dependency import failures from built-in backends instead of misclassifying them as absent modules.
  7. Cover resolver behavior with focused unit tests and prove one installed entry-point backend through a spawned datad actor.

Do not use pytest-only sys.modules or package-attribute monkeypatching: those registrations do not reliably cross spawn or forkserver actor boundaries. External backends under active development can use editable installation in the same Python environment as Piker.

Practical sequence from the current worktree

The replay worktree contains the completed replay experiment. The unfinished entry-point resolver experiment was deliberately excluded. Preserve this branch as parked work rather than mixing it into the live chart patch.

Create the live-chart work on a fresh branch/worktree from the completed Phase 1 head. The first implementation boundary should touch only the Qt/Trio lifecycle and its provider-independent lifecycle tests. The second boundary should add the opt-in real Kraken chart journey and any defects that journey demonstrates.

If entry-point discovery is finished first, cap it at one independent resolver commit with focused unit tests. Do not move or expand replay as part of that commit.

Effort estimate

  • Entry-point resolver and unit tests: roughly half a day if kept independent.
  • Qt/Trio lifecycle extraction and tests: one to two focused days.
  • First isolated real Kraken chart journey: one to two days, depending on defects revealed during startup and teardown.
  • First QtBot search/focus behavior journey: roughly one additional day after lifecycle stability.
  • Installed-process chart coverage: one to two days after the in-process path.

The first real chart should therefore be reachable in roughly two to four focused days without further replay work.