All checks were successful
Build ISO / build-iso (push) Successful in 18m3s
CI / lint (push) Successful in 27s
CI / test (push) Successful in 1m22s
CI / validate-json (push) Successful in 25s
CI / markdown-links (push) Successful in 13s
Release / release (push) Successful in 12m13s
Whitespace-only — `ruff check` was green when 26.17-alpha shipped but I forgot to run `ruff format`, so the CI format-check job went red on the release commit. Runtime artifacts are unaffected (release.yml doesn't gate on lint); this just re-greens the main baseline going forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
import subprocess
|
|
|
|
import pytest
|
|
|
|
from furtka import dockerops
|
|
|
|
|
|
class FakeProc:
|
|
def __init__(self, stdout=b"", stderr=b"", returncode=0):
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
self.returncode = returncode
|
|
|
|
|
|
def test_compose_exec_builds_command(tmp_path, monkeypatch):
|
|
recorded = {}
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
recorded["cmd"] = cmd
|
|
recorded["kwargs"] = kwargs
|
|
return FakeProc(stdout="ok\n", returncode=0)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
out = dockerops.compose_exec(tmp_path, "myproj", "svc", ["echo", "hi"])
|
|
assert out == "ok\n"
|
|
cmd = recorded["cmd"]
|
|
# docker compose --project-name myproj --file <path>/docker-compose.yaml exec -T svc echo hi
|
|
assert cmd[0] == "docker"
|
|
assert cmd[1] == "compose"
|
|
assert "--project-name" in cmd and "myproj" in cmd
|
|
assert "exec" in cmd
|
|
assert "-T" in cmd
|
|
# -T must come before the service name
|
|
assert cmd.index("-T") < cmd.index("svc")
|
|
# argv appended after service
|
|
assert cmd[-2:] == ["echo", "hi"]
|
|
|
|
|
|
def test_compose_exec_propagates_env(tmp_path, monkeypatch):
|
|
recorded = {}
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
recorded["cmd"] = cmd
|
|
return FakeProc()
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
dockerops.compose_exec(tmp_path, "p", "s", ["true"], env={"A": "1", "B": "two"})
|
|
cmd = recorded["cmd"]
|
|
# `--env A=1 --env B=two` should appear before the service name.
|
|
s_idx = cmd.index("s")
|
|
env_args = cmd[:s_idx]
|
|
assert env_args.count("--env") == 2
|
|
assert "A=1" in env_args
|
|
assert "B=two" in env_args
|
|
|
|
|
|
def test_compose_exec_raises_on_nonzero(tmp_path, monkeypatch):
|
|
def fake_run(cmd, **kwargs):
|
|
return FakeProc(stdout="", stderr="boom", returncode=2)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
with pytest.raises(dockerops.DockerError, match="exited 2"):
|
|
dockerops.compose_exec(tmp_path, "p", "s", ["fail"])
|
|
|
|
|
|
def test_compose_exec_raises_on_timeout(tmp_path, monkeypatch):
|
|
def fake_run(cmd, **kwargs):
|
|
raise subprocess.TimeoutExpired(cmd, timeout=kwargs.get("timeout"))
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
with pytest.raises(dockerops.DockerError, match="timed out"):
|
|
dockerops.compose_exec(tmp_path, "p", "s", ["sleep", "9999"], timeout=1)
|
|
|
|
|
|
def test_compose_exec_script_streams_via_stdin(tmp_path, monkeypatch):
|
|
script = tmp_path / "hook.sh"
|
|
body = b"#!/bin/sh\necho hello\n"
|
|
script.write_bytes(body)
|
|
recorded = {}
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
recorded["cmd"] = cmd
|
|
recorded["input"] = kwargs["input"]
|
|
return FakeProc(stdout=b"hello\n", returncode=0)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
out = dockerops.compose_exec_script(tmp_path, "p", "s", script)
|
|
assert out == "hello\n"
|
|
# exec ... s sh -s (script body comes in on stdin)
|
|
cmd = recorded["cmd"]
|
|
assert cmd[-3:] == ["s", "sh", "-s"]
|
|
assert recorded["input"] == body
|
|
|
|
|
|
def test_compose_exec_script_raises_on_nonzero(tmp_path, monkeypatch):
|
|
script = tmp_path / "fail.sh"
|
|
script.write_bytes(b"exit 1\n")
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
return FakeProc(stdout=b"", stderr=b"hook says no", returncode=1)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
with pytest.raises(dockerops.DockerError, match="hook fail.sh exited 1"):
|
|
dockerops.compose_exec_script(tmp_path, "p", "s", script)
|
|
|
|
|
|
def test_compose_exec_script_raises_on_timeout(tmp_path, monkeypatch):
|
|
script = tmp_path / "slow.sh"
|
|
script.write_bytes(b"sleep 10\n")
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
raise subprocess.TimeoutExpired(cmd, timeout=kwargs.get("timeout"))
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
with pytest.raises(dockerops.DockerError, match="hook slow.sh timed out"):
|
|
dockerops.compose_exec_script(tmp_path, "p", "s", script, timeout=1)
|