87 lines
3.2 KiB
Python
87 lines
3.2 KiB
Python
"""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()
|