From 46373c1ba32bdfd39d36c096373ff620dabf980e Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Wed, 14 Oct 2026 13:50:07 +0200 Subject: [PATCH] fix(act): remove only the own worktree's act containers down_act_outer.sh removed every container named act-*, so starting or releasing a swarm drill in one worktree killed the act container of a drill another worktree had running. workflow.sh now labels act's job containers with infinito.worktree=${INFINITO_CONTAINER} through the default ACT_CONTAINER_OPTIONS, and down_act_outer.sh removes only containers carrying the own label. Both scripts refuse to run without INFINITO_CONTAINER, so the filter never widens to every act container; make exports BASH_ENV, which loads it from .env. Worktrees without a custom.env share the default container name and therefore still share one label among themselves. Closes #933 Co-Authored-By: Claude Opus 5.5 (1M context) --- scripts/tests/deploy/act/down_act_outer.sh | 7 +- scripts/tests/deploy/act/workflow.sh | 3 +- .../scripts/tests/deploy/act/__init__.py | 0 .../tests/deploy/act/test_down_act_outer.py | 74 +++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 tests/unit/python/scripts/tests/deploy/act/__init__.py create mode 100644 tests/unit/python/scripts/tests/deploy/act/test_down_act_outer.py 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()