pyro-mcp/tests/test_daily_loop_smoke.py
Thales Maciel 663241d5d2 Add daily-loop prepare and readiness checks
Make the local chat-host loop explicit and cheap so users can warm the machine once instead of rediscovering environment and guest setup on every session.

Add cache-backed daily-loop manifests plus the new `pyro prepare` flow, extend `pyro doctor --environment` with warm/cold/stale readiness reporting, and add `make smoke-daily-loop` to prove the warmed repro-fix reset path end to end.

Also fix `python -m pyro_mcp.cli` to invoke `main()` so the new smoke and `dist-check` actually exercise the CLI module, and update the docs/roadmap to present `doctor -> prepare -> connect host -> reset` as the recommended daily path.

Validation: `uv lock`, `UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make check`, `UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make dist-check`, and `UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make smoke-daily-loop`.
2026-03-13 21:17:59 -03:00

138 lines
4.3 KiB
Python

from __future__ import annotations
import json
import subprocess
from pathlib import Path
from types import SimpleNamespace
import pytest
import pyro_mcp.daily_loop_smoke as smoke_module
class _FakePyro:
def __init__(self) -> None:
self.workspace_id = "ws-1"
self.message = "broken\n"
self.deleted = False
def create_workspace(
self,
*,
environment: str,
seed_path: Path,
name: str | None = None,
labels: dict[str, str] | None = None,
) -> dict[str, object]:
assert environment == "debian:12"
assert seed_path.is_dir()
assert name == "daily-loop"
assert labels == {"suite": "daily-loop-smoke"}
return {"workspace_id": self.workspace_id}
def exec_workspace(self, workspace_id: str, *, command: str) -> dict[str, object]:
assert workspace_id == self.workspace_id
if command != "sh check.sh":
raise AssertionError(f"unexpected command: {command}")
if self.message == "fixed\n":
return {"exit_code": 0, "stdout": "fixed\n"}
return {"exit_code": 1, "stderr": "expected fixed got broken\n"}
def apply_workspace_patch(self, workspace_id: str, *, patch: str) -> dict[str, object]:
assert workspace_id == self.workspace_id
assert "+fixed" in patch
self.message = "fixed\n"
return {"changed": True}
def export_workspace(
self,
workspace_id: str,
path: str,
*,
output_path: Path,
) -> dict[str, object]:
assert workspace_id == self.workspace_id
assert path == "message.txt"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(self.message, encoding="utf-8")
return {"artifact_type": "file"}
def reset_workspace(self, workspace_id: str) -> dict[str, object]:
assert workspace_id == self.workspace_id
self.message = "broken\n"
return {"reset_count": 1}
def read_workspace_file(self, workspace_id: str, path: str) -> dict[str, object]:
assert workspace_id == self.workspace_id
assert path == "message.txt"
return {"content": self.message}
def delete_workspace(self, workspace_id: str) -> dict[str, object]:
assert workspace_id == self.workspace_id
self.deleted = True
return {"workspace_id": workspace_id, "deleted": True}
def test_run_prepare_parses_json(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
subprocess,
"run",
lambda *args, **kwargs: SimpleNamespace(
returncode=0,
stdout=json.dumps({"prepared": True}),
stderr="",
),
)
payload = smoke_module._run_prepare("debian:12")
assert payload == {"prepared": True}
def test_run_prepare_raises_on_failure(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
subprocess,
"run",
lambda *args, **kwargs: SimpleNamespace(
returncode=1,
stdout="",
stderr="prepare failed",
),
)
with pytest.raises(RuntimeError, match="prepare failed"):
smoke_module._run_prepare("debian:12")
def test_run_daily_loop_smoke_happy_path(monkeypatch: pytest.MonkeyPatch) -> None:
prepare_calls: list[str] = []
fake_pyro = _FakePyro()
def fake_run_prepare(environment: str) -> dict[str, object]:
prepare_calls.append(environment)
return {"prepared": True, "reused": len(prepare_calls) > 1}
monkeypatch.setattr(smoke_module, "_run_prepare", fake_run_prepare)
monkeypatch.setattr(smoke_module, "Pyro", lambda: fake_pyro)
smoke_module.run_daily_loop_smoke(environment="debian:12")
assert prepare_calls == ["debian:12", "debian:12"]
assert fake_pyro.deleted is True
def test_main_runs_selected_environment(
monkeypatch: pytest.MonkeyPatch,
) -> None:
observed: list[str] = []
monkeypatch.setattr(
smoke_module,
"run_daily_loop_smoke",
lambda *, environment: observed.append(environment),
)
monkeypatch.setattr(
smoke_module,
"build_arg_parser",
lambda: SimpleNamespace(
parse_args=lambda: SimpleNamespace(environment="debian:12-build")
),
)
smoke_module.main()
assert observed == ["debian:12-build"]