diff --git a/tests/test_agent_exec.py b/tests/test_agent_exec.py index 1b0fe01c..fdb3af3a 100644 --- a/tests/test_agent_exec.py +++ b/tests/test_agent_exec.py @@ -41,6 +41,25 @@ def _canonical_snapshot(repo: Path) -> dict[str, str]: return {"head": head, "tree": tree, "status": status, "index": index} +def _pid_alive(pid: int) -> bool: + """Return true only for a running, non-zombie process.""" + try: + text = Path(f"/proc/{pid}/stat").read_text( + encoding="utf-8", errors="replace" + ) + except FileNotFoundError: + return False + rpar = text.rfind(")") + if rpar != -1 and rpar + 2 < len(text) and text[rpar + 2] == "Z": + return False + return ( + subprocess.run( + ["kill", "-0", str(pid)], check=False, capture_output=True + ).returncode + == 0 + ) + + def _write_config( repo: Path, *, @@ -740,13 +759,8 @@ class AgentExecControllerTests(unittest.TestCase): self.assertNotIn("idle timeout", data["reason"]) if child_pid_file.exists(): child = int(child_pid_file.read_text(encoding="utf-8").strip()) - # Child must not still be alive. - still = subprocess.run( - ["kill", "-0", str(child)], - check=False, - capture_output=True, - ) - self.assertNotEqual(still.returncode, 0) + # A zombie is terminated even if a container PID 1 has not reaped it. + self.assertFalse(_pid_alive(child)) self._assert_canonical_untouched() def test_idle_timeout_kills_silent_worker_before_total(self) -> None: @@ -821,12 +835,8 @@ class AgentExecControllerTests(unittest.TestCase): self.assertEqual(cleanup["reason"], "idle timeout") self.assertTrue(child_pid_file.is_file()) child = int(child_pid_file.read_text(encoding="utf-8").strip()) - still = subprocess.run( - ["kill", "-0", str(child)], - check=False, - capture_output=True, - ) - self.assertNotEqual(still.returncode, 0) + # A zombie is terminated even if a container PID 1 has not reaped it. + self.assertFalse(_pid_alive(child)) self._assert_canonical_untouched() def test_progress_keeps_worker_alive_past_one_idle_interval(self) -> None: