"""Shared fixtures: real captures for shape, fakes for behaviour. Anything that must match an upstream (an agent snapshot, an engine's /metrics text, the NUT wire protocol) is asserted against a verbatim capture in tests/fixtures/. Everything that needs a *failure* — a dead host, a refused command, a counter reset — is a fake here. The `live` marker is the bridge to real hardware: those tests dial the fleet, the engines, or upsd, so they SKIP unless you pass `--live` and have set the env vars they read (see tests/test_live_fleet.py). That keeps `uv run pytest` hermetic and offline while still letting `uv run pytest -m live --live` catch an upstream that renamed something. """ from __future__ import annotations import copy import json import sys from pathlib import Path from typing import Any import pytest def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( "--live", action="store_true", default=False, help="run tests marked `live` against the real fleet/engines/upsd (they skip otherwise)", ) def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: if config.getoption("--live"): return skip = pytest.mark.skip(reason="needs --live and a reachable target (see tests/test_live_fleet.py)") for item in items: if "live" in item.keywords: item.add_marker(skip) ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) from dashboards.rates import RateEstimator # noqa: E402 from dashboards.sources.host import HostCollector, parse_host_snapshot # noqa: E402 from dashboards.store import SampleStore # noqa: E402 FIXTURES = Path(__file__).resolve().parent / "fixtures" @pytest.fixture def db(tmp_path: Path) -> Any: store = SampleStore(tmp_path / "t.sqlite3") try: yield store finally: store.close() @pytest.fixture def store(db: Any) -> Any: """Alias: some tests read better with `store`, others with `db`. Same object.""" return db @pytest.fixture def agent_snapshot() -> dict[str, Any]: """A real driveripper snapshot (Threadripper + R9700, 48 logical CPUs).""" return json.loads((FIXTURES / "agent_driveripper.json").read_text()) @pytest.fixture def agent_snapshots() -> dict[str, dict[str, Any]]: """Every captured host, keyed by name — fleet code paths need >1 host to mean anything.""" out: dict[str, dict[str, Any]] = {} for path in sorted(FIXTURES.glob("agent_*.json")): out[path.stem.removeprefix("agent_")] = json.loads(path.read_text()) return out @pytest.fixture def host_scrape() -> Any: """`host_scrape(snapshot, cpu_busy=10.0, mutate=None)` -> one parsed host payload. Two scrapes are needed before any rate exists (the agent reports counters; the dashboard owns the window), and the payload the API serves is the second one. `mutate` edits the snapshot before both scrapes so a test can put a value on a chosen side of `severity.THRESHOLDS`; `cpu_busy` sets the CPU percentage over the window directly. Driven from the real capture so a schema drift breaks here rather than in a browser. """ def scrape(snapshot: dict[str, Any], *, name: str = "driveripper", cpu_busy: float = 10.0, mutate: Any = None) -> dict[str, Any]: snap = copy.deepcopy(snapshot) if mutate is not None: mutate(snap) collector = HostCollector(RateEstimator(span=60.0)) parse_host_snapshot(name, snap, collector, now=1_000.0) second = copy.deepcopy(snap) second["ts"] = 1_010.0 modes = second["cpu"]["modes"] # busy = (Δtotal - Δidle)/Δtotal, and iowait is left untouched so it rates at 0 modes["total"] += 1_000.0 modes["idle"] += 1_000.0 - (cpu_busy * 10.0) return parse_host_snapshot(name, second, collector, now=1_010.0).payload return scrape @pytest.fixture def metrics_text() -> Any: """metrics_text('gufo') / metrics_text('vllm') -> the captured exposition verbatim.""" def load(engine: str) -> str: path = FIXTURES / f"metrics_{engine}.txt" if not path.exists(): pytest.skip(f"missing fixture {path.name} — run scripts/capture-fixtures.sh --with-llm") return path.read_text() return load @pytest.fixture def rates() -> RateEstimator: return RateEstimator(span=10.0)