"""CI orchestration must isolate host resources and clean up on failure.""" import importlib.util import subprocess from pathlib import Path from types import SimpleNamespace import pytest @pytest.fixture def ci(monkeypatch, tmp_path): spec = importlib.util.spec_from_file_location( "ci_compose", Path(__file__).parents[1] / "scripts/ci-compose-verify.py" ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) monkeypatch.setenv("GITHUB_ENV", str(tmp_path / "job-env")) return module def test_ci_config_preserves_runtime_and_isolates_resources(ci): config = ci.isolated_compose("ffci-fixture") production = ci.yaml.safe_load((ci.ROOT / "docker-compose.yml").read_text()) service = config["services"]["pigway-frame-flow"] assert not any(key in service for key in ("ports", "container_name", "env_file")) assert service["volumes"] == ["data:/data", "config:/config"] assert config["volumes"] == {"data": {}, "config": {}} assert config["name"] == "ffci-fixture" assert service["image"] != "pigway-frame-flow:latest" assert service["environment"]["KOKONNA_MOCK"] == "true" assert service["environment"]["KOKONNA_WRITE_ENABLED"] == "false" for key in ( "user", "read_only", "healthcheck", "restart", "cap_drop", "security_opt", ): assert service[key] == production["services"]["pigway-frame-flow"][key] assert ( production["services"]["pigway-frame-flow"]["container_name"] == "pigway-frame-flow" ) @pytest.mark.parametrize("fail_smoke", [False, True]) def test_verification_cleanup_and_container_side_smoke(ci, monkeypatch, fail_smoke): calls = [] def run(command, **kwargs): calls.append((command, kwargs)) if command[-2:] == ["python", "-"] and fail_smoke: raise subprocess.CalledProcessError(1, command) return SimpleNamespace(returncode=0, stdout="fixture-container\n") monkeypatch.setattr(ci.subprocess, "run", run) if fail_smoke: with pytest.raises(subprocess.CalledProcessError): ci.verify() else: ci.verify() assert any("down" in cmd and "--volumes" in cmd for cmd, _ in calls) assert any(cmd[1:3] == ["image", "rm"] for cmd, _ in calls) smoke = [(cmd, kw) for cmd, kw in calls if cmd[-2:] == ["python", "-"]] assert len(smoke) == (1 if fail_smoke else 2) assert all("exec" in cmd and "-T" in cmd and kw["input"] for cmd, kw in smoke) if not fail_smoke: assert any("restart" in cmd for cmd, _ in calls) build_env = calls[0][1]["env"] assert build_env["FRAME_FLOW_IMAGE"].startswith("pigway-frame-flow-ci:ffci-") assert not Path(build_env["COMPOSE_FILE"]).exists() def test_cleanup_failure_keeps_manifest_for_always_step(ci, monkeypatch, tmp_path): path = tmp_path / "compose.yml" path.write_text(ci.yaml.safe_dump(ci.isolated_compose("ffci-fixture"))) def fail(*args, **kwargs): raise subprocess.CalledProcessError(1, args[0]) monkeypatch.setattr(ci.subprocess, "run", fail) with pytest.raises(subprocess.CalledProcessError): ci.cleanup(path) assert path.exists()