115 lines
2.9 KiB
Python
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
|