-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
PoolWatch is divided into collection, target discovery, analysis, and rendering. The core report model is independent of pytest's terminal and file formats.
pytest public hooks
│
▼
SessionCollector ──► TestInterval + PhaseInterval
│
├──► target discovery
│
▼
sweep-line analysis ──► PoolWatchReport
│
┌─────────────┼─────────────┐
▼ ▼ ▼
terminal JSON HTML
The package declares:
[project.entry-points.pytest11]
poolwatch = "pytest_poolwatch.plugin"Pytest discovers the plugin after installation. pytest_configure registers a
per-session runtime only when PoolWatch is enabled and the process is not an
xdist worker.
This keeps controller-side state isolated and prevents each xdist worker from writing competing artifacts.
The runtime observes:
pytest_sessionstartpytest_collection_finish-
pytest_xdist_node_collection_finishedwhen available pytest_runtest_logstartpytest_runtest_logreportpytest_runtest_logfinishpytest_sessionfinishpytest_terminal_summary
No private scheduler API is required.
SessionCollector groups phase reports by worker and node ID. Repeated test
protocols become numbered attempts.
For a complete attempt, report phase timestamps are authoritative:
started_at = min(phase.started_at)
finished_at = max(phase.finished_at)
Lifecycle timestamps are fallback data for an attempt that never emitted phase reports. Such a session-ended interval is marked incomplete.
Outcome precedence is:
- any failed phase;
- call-phase outcome;
- skipped phase;
- last observed phase;
- unknown.
Explicit configuration always wins. Without it, the discovery layer recognizes:
- serial pytest and pytest-asyncio;
- numeric or observed xdist workers;
-
max_asyncio_tasksfor standalone pytest-asyncio-cooperative; - dynamic asyncio grouping as an observed-peak fallback.
The xdist and cooperative schedulers are not combined. If both plugins are installed, xdist takes precedence because their runtest-loop implementations cannot be used together reliably.
The analyzer sorts interval boundaries and performs a sweep-line pass. Its main
complexity is O(n log n) for n attempts because of sorting; the sweep itself
is linear in the number of unique boundaries.
The result is an immutable PoolWatchReport containing:
- target metadata;
- summary metrics;
- timeline points;
- merged underfill windows;
- finalized test attempts.
See Metrics and Diagnosis for formulas.
Renderers receive only PoolWatchReport and do not inspect pytest state.
Produces compact summary lines plus diagnosis guidance.
Maps the immutable model into a schema-versioned dictionary and serializes it as UTF-8 JSON.
Builds a self-contained document with embedded CSS and SVG step charts. Dynamic node IDs and labels are escaped before interpolation.
Both file renderers use a temporary sibling and atomic replacement.
src/pytest_poolwatch/
├── analysis.py # sweep-line metrics and underfill windows
├── collector.py # pytest report normalization
├── models.py # immutable report data
├── plugin.py # pytest hooks and runtime
├── reporting.py # terminal, JSON, and HTML renderers
├── settings.py # CLI and ini configuration
└── targets.py # scheduler capacity discovery
Tests are separated into analysis, collector, reporting, target-discovery, and subprocess integration coverage.
pytest-poolwatch documentation · Source · PyPI · MIT License