README advertised `uv run pytest -m live --live`, but there was no --live option and no `live` marker registered — the command errored out, so the one workflow that catches an upstream renaming a series did not exist. - conftest adds --live and skips `live`-marked tests without it, so the default run stays hermetic (258 passed, 6 skipped, no network). - tests/test_live_fleet.py drives the real code paths (collectors.*, NutMonitor.collect) against the fleet, both engines, and upsd. Each skips when its source is unset, and none of them may write — a battery test on live hardware is not a unit test. The host test polls twice because a rate has no second point on the first scrape. - The agent's real-/proc test stays in the default run and is explicitly not `live`: it touches only the box running pytest. - dev-tunnels.sh grows DEV_FORWARDS for services already listening remotely, which is how upsd gets reachable without opening 3493; pidfiles are now host+port so one host can carry two tunnels. - requires-python, ruff and pyright were still on 3.13 while the venv and Containerfile are 3.14, so the lock resolved for a stack nobody deploys. LLM-Generated: true
95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
"""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 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.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 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)
|