diff --git a/scripts/tests/deploy/act/down_act_outer.sh b/scripts/tests/deploy/act/down_act_outer.sh index af792d57f2..f8d0340ae2 100755 --- a/scripts/tests/deploy/act/down_act_outer.sh +++ b/scripts/tests/deploy/act/down_act_outer.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash set +e -# Match act's outer-runner name prefix, not the image: ACT_PLATFORM_IMAGE varies the ancestor. -ids="$(docker ps -aq --filter "name=^act-")" +: "${INFINITO_CONTAINER:?INFINITO_CONTAINER is not set; source scripts/meta/env/load.sh or invoke via make}" + +ids="$(docker ps -aq --filter "name=^act-" --filter "label=infinito.worktree=${INFINITO_CONTAINER}")" if [ -n "${ids}" ]; then - echo "→ removing act outer container(s): ${ids}" + echo "→ removing act outer container(s) of ${INFINITO_CONTAINER}: ${ids}" # shellcheck disable=SC2086 docker rm -f ${ids} >/dev/null 2>&1 else diff --git a/scripts/tests/deploy/act/workflow.sh b/scripts/tests/deploy/act/workflow.sh index 2d5e126a9a..7be72c21a6 100755 --- a/scripts/tests/deploy/act/workflow.sh +++ b/scripts/tests/deploy/act/workflow.sh @@ -5,7 +5,8 @@ set -euo pipefail : "${ACT_EVENT:=workflow_dispatch}" : "${ACT_JOB:=}" : "${ACT_MATRIX:=}" -: "${ACT_CONTAINER_OPTIONS:=--privileged}" +: "${INFINITO_CONTAINER:?INFINITO_CONTAINER is not set; source scripts/meta/env/load.sh or invoke via make}" +: "${ACT_CONTAINER_OPTIONS:=--privileged --label infinito.worktree=${INFINITO_CONTAINER}}" : "${ACT_NETWORK:=host}" : "${ACT_PULL:=false}" : "${ACT_RM:=true}" diff --git a/tests/unit/python/scripts/tests/deploy/act/__init__.py b/tests/unit/python/scripts/tests/deploy/act/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/python/scripts/tests/deploy/act/test_down_act_outer.py b/tests/unit/python/scripts/tests/deploy/act/test_down_act_outer.py new file mode 100644 index 0000000000..425e7566c2 --- /dev/null +++ b/tests/unit/python/scripts/tests/deploy/act/test_down_act_outer.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + +from utils.cache.files import PROJECT_ROOT + +SCRIPT = PROJECT_ROOT / "scripts" / "tests" / "deploy" / "act" / "down_act_outer.sh" +DOCKER_STUB = ( + "#!/usr/bin/env bash\n" + 'printf "%s\\n" "$*" >>"${STUB_LOG}"\n' + 'if [ "$1" = ps ]; then\n' + ' case "$*" in *"label=infinito.worktree=own"*) echo own-id ;; esac\n' + "fi\n" +) + + +def teardown(container: str | None) -> tuple[subprocess.CompletedProcess, list[str]]: + """Run the teardown against a ``docker`` stub that only knows ``own-id``. + + Args: + container: value of ``INFINITO_CONTAINER``, or None to leave it unset. + + Returns: + The finished process and the docker invocations it made. + """ + with tempfile.TemporaryDirectory() as tmp: + stub = Path(tmp) / "docker" + stub.write_text(DOCKER_STUB) + stub.chmod(0o755) + log = Path(tmp) / "docker.log" + log.touch() + env = { + k: v + for k, v in os.environ.items() + if k not in ("BASH_ENV", "INFINITO_CONTAINER") + } + env.update(PATH=f"{tmp}{os.pathsep}{os.environ['PATH']}", STUB_LOG=str(log)) + if container is not None: + env["INFINITO_CONTAINER"] = container + done = subprocess.run( + ["bash", str(SCRIPT)], + env=env, + capture_output=True, + text=True, + check=False, + ) + return done, log.read_text().splitlines() + + +class TestDownActOuter(unittest.TestCase): + def test_only_the_own_worktrees_act_containers_are_listed(self) -> None: + _, calls = teardown("own") + self.assertIn("label=infinito.worktree=own", calls[0]) + + def test_the_own_act_container_is_removed(self) -> None: + _, calls = teardown("own") + self.assertEqual(calls[1], "rm -f own-id") + + def test_a_foreign_worktrees_act_container_is_left_alone(self) -> None: + _, calls = teardown("foreign") + self.assertEqual(len(calls), 1) + + def test_an_unset_container_name_removes_nothing(self) -> None: + done, calls = teardown(None) + self.assertNotEqual(done.returncode, 0) + self.assertEqual(calls, []) + + +if __name__ == "__main__": + unittest.main()