"""Tests for the bounded, metadata-only G7 topology sampler.""" from __future__ import annotations import importlib.util import json import sys import unittest from pathlib import Path from typing import Sequence RUNNER_PATH = Path(__file__).with_name("capture-g7-topology-evidence.py") def load_runner(): spec = importlib.util.spec_from_file_location("g7_topology_evidence", RUNNER_PATH) if spec is None or spec.loader is None: raise RuntimeError("G7 topology evidence sampler could not be loaded") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module API_ID = "a" * 64 CADDY_ID = "c" * 64 API_DIGEST = "sha256:" + "1" * 64 CADDY_DIGEST = "sha256:" + "2" * 64 def inspect_item( *, container_id: str, image_digest: str, service: str, pid: int, restart_count: int = 0, started_at: str = "2026-08-07T00:00:00Z", ) -> dict[str, object]: return { "Id": container_id, "Name": f"/vignette-{service}-1", "Image": image_digest, "RestartCount": restart_count, "Config": { "Labels": { "com.docker.compose.project": "vignette-preview-20260807", "com.docker.compose.service": service, } }, "State": {"Running": True, "Pid": pid, "StartedAt": started_at}, } def stable_inspect() -> str: return json.dumps( [ inspect_item( container_id=API_ID, image_digest=API_DIGEST, service="api", pid=101, ), inspect_item( container_id=CADDY_ID, image_digest=CADDY_DIGEST, service="caddy", pid=202, ), ] ) def stats_output() -> str: return "\n".join( [ json.dumps( { "ID": API_ID[:12], "CPUPerc": "1.25%", "MemUsage": "128MiB / 2GiB", "MemPerc": "6.25%", "NetIO": "1.5MB / 2MB", "BlockIO": "4KiB / 8KiB", "PIDs": "9", } ), json.dumps( { "ID": CADDY_ID[:12], "CPUPerc": "0.50%", "MemUsage": "32MiB / 1GiB", "MemPerc": "3.12%", "NetIO": "3MB / 4MB", "BlockIO": "0B / 1KiB", "PIDs": "4", } ), ] ) SS_OUTPUT = """State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 3 5 10.0.0.1:443 10.0.0.2:50000 cubic wscale:7,7 rto:204 retrans:1/4 cwnd:10 LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* cubic cwnd:10 """ class FixtureRunner: def __init__( self, *, inspect_outputs: list[str] | None = None, unavailable: set[str] | None = None, ) -> None: self.inspect_outputs = list(inspect_outputs or [stable_inspect()]) self.unavailable = unavailable or set() self.calls: list[tuple[str, ...]] = [] def available(self, command: str) -> bool: return command not in self.unavailable def run(self, args: Sequence[str]) -> str: command = tuple(args) self.calls.append(command) if command[:3] == ("docker", "inspect", "--type"): if len(self.inspect_outputs) > 1: return self.inspect_outputs.pop(0) return self.inspect_outputs[0] if command[:3] == ("docker", "stats", "--no-stream"): return stats_output() if command == ("ss", "-tinm"): return SS_OUTPUT raise AssertionError(f"unexpected command: {command!r}") class FixtureSource: def __init__(self) -> None: self.files = { "/proc/101/cgroup": "0::/docker/api.scope\n", "/proc/202/cgroup": "0::/docker/caddy.scope\n", "/proc/101/status": "VmRSS:\t100 kB\nVmHWM:\t120 kB\nThreads:\t3\n", "/proc/202/status": "VmRSS:\t50 kB\nVmHWM:\t70 kB\nThreads:\t2\n", "/sys/fs/cgroup/docker/api.scope/memory.current": "104857600\n", "/sys/fs/cgroup/docker/api.scope/memory.peak": "125829120\n", "/sys/fs/cgroup/docker/api.scope/cpu.stat": ( "usage_usec 1000\nuser_usec 700\nsystem_usec 300\n" "nr_periods 10\nnr_throttled 1\nthrottled_usec 20\n" ), "/sys/fs/cgroup/docker/api.scope/pids.current": "9\n", "/sys/fs/cgroup/docker/caddy.scope/memory.current": "33554432\n", "/sys/fs/cgroup/docker/caddy.scope/memory.peak": "41943040\n", "/sys/fs/cgroup/docker/caddy.scope/cpu.stat": ( "usage_usec 500\nuser_usec 300\nsystem_usec 200\n" ), "/sys/fs/cgroup/docker/caddy.scope/pids.current": "4\n", } self.directories = { "/proc/101/fd": ["0", "1", "2", "3"], "/proc/202/fd": ["0", "1", "2"], } def read_text(self, path: str) -> str: return self.files[path] def list_names(self, path: str) -> list[str]: return list(self.directories[path]) class G7TopologyEvidenceTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.runner_module = load_runner() def config(self, **overrides): values = { "compose_project": "vignette-preview-20260807", "public_host": "api.example.test", "api_container": "vignette-api-1", "api_service": "api", "api_image_digest": API_DIGEST, "caddy_container": "vignette-caddy-1", "caddy_service": "caddy", "caddy_image_digest": CADDY_DIGEST, "samples": 1, "interval_seconds": 1.0, } values.update(overrides) return self.runner_module.CaptureConfig(**values) def test_parsers_emit_numeric_metadata_without_endpoints(self) -> None: source = FixtureSource() targets = self.runner_module.pin_targets(self.config(), FixtureRunner(), source) stats = self.runner_module.parse_docker_stats(stats_output(), targets) tcp = self.runner_module.parse_ss_tcp(SS_OUTPUT) proc = self.runner_module.read_proc_metrics( source, proc_root="/proc", target=targets["api"], ) cgroup = self.runner_module.read_cgroup_metrics( source, cgroup_root="/sys/fs/cgroup", target=targets["api"], ) self.assertEqual(134_217_728, stats["api"]["memory_usage_bytes"]) self.assertEqual(1_500_000, stats["api"]["network_rx_bytes"]) self.assertEqual(2, tcp["connections"]) self.assertEqual(3, tcp["recv_q_bytes_total"]) self.assertEqual(133, tcp["send_q_bytes_total"]) self.assertEqual(1, tcp["retransmit_current_total"]) self.assertEqual(4, tcp["retransmit_cumulative_total"]) self.assertEqual(102_400, proc["vm_rss_bytes"]) self.assertEqual(4, proc["fd_count"]) self.assertEqual(1_000, cgroup["cpu_stat"]["usage_usec"]) serialized = json.dumps({"stats": stats, "tcp": tcp}) self.assertNotIn("10.0.0.1", serialized) self.assertNotIn("10.0.0.2", serialized) def test_capture_pins_targets_and_builds_high_water_summary(self) -> None: runner = FixtureRunner() evidence = self.runner_module.capture_evidence( self.config(), runner, FixtureSource(), sleep=lambda _seconds: self.fail("single sample must not sleep"), ) self.assertEqual("passed", evidence["status"]) self.assertEqual(1, evidence["samples_completed"]) self.assertEqual(API_ID, evidence["targets"]["api"]["container_id"]) self.assertEqual(API_DIGEST, evidence["targets"]["api"]["image_digest"]) self.assertEqual("api.example.test", evidence["requested"]["public_host"]) self.assertEqual( 125_829_120, evidence["summary"]["containers"]["api"]["cgroup_memory_peak_bytes_max"], ) scope = evidence["scope"] self.assertTrue(scope["metadata_only"]) self.assertFalse(scope["socket_endpoints_retained"]) self.assertFalse(scope["cloudflare_edge"]["internal_queue_measured"]) self.assertEqual( "separate_external_artifact_required", scope["cloudflare_edge"]["evidence_boundary"], ) inspect_calls = [call for call in runner.calls if call[1] == "inspect"] self.assertEqual(3, len(inspect_calls)) def test_pin_validation_fails_on_pid_restart_and_image_drift(self) -> None: cases = { "init_pid_drift:api": inspect_item( container_id=API_ID, image_digest=API_DIGEST, service="api", pid=999, ), "container_restart_drift:api": inspect_item( container_id=API_ID, image_digest=API_DIGEST, service="api", pid=101, restart_count=1, started_at="2026-08-07T00:01:00Z", ), "image_digest_drift:api": inspect_item( container_id=API_ID, image_digest="sha256:" + "9" * 64, service="api", pid=101, ), } for expected_failure, changed_api in cases.items(): with self.subTest(expected_failure=expected_failure): drifted = json.dumps( [ changed_api, inspect_item( container_id=CADDY_ID, image_digest=CADDY_DIGEST, service="caddy", pid=202, ), ] ) runner = FixtureRunner(inspect_outputs=[stable_inspect(), drifted]) with self.assertRaises(self.runner_module.EvidenceFailure) as caught: self.runner_module.capture_evidence( self.config(), runner, FixtureSource(), sleep=lambda _: None ) self.assertEqual(expected_failure, str(caught.exception)) def test_missing_cgroup_source_fails_closed(self) -> None: source = FixtureSource() del source.files["/sys/fs/cgroup/docker/api.scope/memory.peak"] with self.assertRaises(self.runner_module.EvidenceFailure) as caught: self.runner_module.capture_evidence( self.config(), FixtureRunner(), source, sleep=lambda _: None ) self.assertEqual( "cgroup_source_missing:api:memory.peak", str(caught.exception), ) def test_missing_command_fails_before_any_runtime_read(self) -> None: runner = FixtureRunner(unavailable={"ss"}) with self.assertRaises(self.runner_module.EvidenceFailure) as caught: self.runner_module.capture_evidence( self.config(), runner, FixtureSource(), sleep=lambda _: None ) self.assertEqual("command_unavailable:ss", str(caught.exception)) self.assertEqual([], runner.calls) def test_capture_bounds_are_fail_closed(self) -> None: for config, failure in ( (self.config(samples=0), "sample_count_out_of_bounds"), (self.config(interval_seconds=0), "sample_interval_out_of_bounds"), ( self.config(samples=7_200, interval_seconds=2), "capture_window_out_of_bounds", ), ): with self.subTest(failure=failure): with self.assertRaises(self.runner_module.EvidenceFailure) as caught: self.runner_module.validate_config(config) self.assertEqual(failure, str(caught.exception)) if __name__ == "__main__": unittest.main()