atlas-iac/testing/tests/test_hermes_handoff_support.py

115 lines
2.9 KiB
Python

"""Shared loader and fixtures for the handoff acceptance harness tests.
The harness lives in ``scripts/ops`` as sibling modules rather than an installed
package, so tests import it the way the entry point does: by putting that
directory on the path once and importing by name.
"""
from __future__ import annotations
import importlib
import sys
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[2]
OPS = REPO_ROOT / "scripts" / "ops"
if str(OPS) not in sys.path:
sys.path.insert(0, str(OPS))
def load_handoff_module(name: str) -> Any:
"""Import one harness module by name."""
return importlib.import_module(name)
exec_module = load_handoff_module("hermes_handoff_exec")
model = load_handoff_module("hermes_handoff_model")
def outcome(
stdout: str = "",
stderr: str = "",
returncode: int | None = 0,
error: str | None = None,
truncated: bool = False,
vantage: str = "self",
argv: tuple[str, ...] = ("kubectl", "get", "pods"),
) -> Any:
"""Return a synthetic command outcome for evaluator tests."""
return exec_module.Outcome(
argv=argv,
vantage=vantage,
returncode=returncode,
stdout=stdout,
stderr=stderr,
error=error,
truncated=truncated,
)
def spec(
rule: str,
expect: dict | None = None,
steps: tuple = (),
identifier: str = "test.check",
mandatory: bool = True,
scope: str | None = None,
) -> Any:
"""Return a synthetic check specification."""
return model.CheckSpec(
id=identifier,
title="test check",
group="test",
rule=rule,
steps=steps,
expect=expect or {},
mandatory=mandatory,
scope=scope or model.ALWAYS,
)
def step(
key: str, kind: str | None = None, vantage: str = "self", optional: bool = False
) -> Any:
"""Return a synthetic step."""
return model.Step(
key=key,
vantage=vantage,
argv=("kubectl", "get", "pods", "-o", "name"),
kind=kind or model.READ,
optional=optional,
)
class FakeClock:
"""A monotonic clock the tests advance by hand."""
def __init__(self, start: float = 0.0) -> None:
self.now = start
def __call__(self) -> float:
return self.now
def advance(self, seconds: float) -> None:
"""Move the clock forward."""
self.now += seconds
class FakeSpawn:
"""A ``subprocess.run`` stand-in driven by a queue of scripted results."""
def __init__(self, results: list) -> None:
self.results = list(results)
self.calls: list[dict] = []
def __call__(self, argv, **kwargs):
self.calls.append({"argv": list(argv), **kwargs})
if not self.results:
raise AssertionError(f"unexpected command: {argv}")
outcome_or_error = self.results.pop(0)
if isinstance(outcome_or_error, Exception):
raise outcome_or_error
return outcome_or_error