3535"""
3636from __future__ import annotations
3737
38+ import sys
3839import threading
3940import time
4041from pathlib import Path
4647 _run_bash_with_abort ,
4748)
4849from src .utils .abort_controller import AbortController
50+ from src .utils .shell_platform import bash_argv
51+
52+ # Resolve bash the way production does. A bare ``["bash", ...]`` argv resolves
53+ # via PATH, and on the Windows CI runner PATH's first ``bash.exe`` is the WSL
54+ # launcher at ``C:\Windows\System32\bash.exe`` — which exits immediately with
55+ # no distro installed, so ``sleep``/``echo`` never run and every timing
56+ # assertion below fails on Windows only. ``bash_argv`` resolves Git Bash
57+ # explicitly (the same helper ``_run_bash_with_abort``'s production callers
58+ # use), keeping these supervisor tests on the real shell on every platform.
4959
5060
5161def test_run_bash_with_abort_sets_only_timed_out_on_timeout (tmp_path : Path ) -> None :
@@ -56,7 +66,7 @@ def test_run_bash_with_abort_sets_only_timed_out_on_timeout(tmp_path: Path) -> N
5666 """
5767 start = time .monotonic ()
5868 result = _run_bash_with_abort (
59- [ "bash" , "-lc" , " sleep 3; echo done"] ,
69+ bash_argv ( " sleep 3; echo done") ,
6070 cwd = str (tmp_path ),
6171 timeout_s = 1 ,
6272 abort_signal = None ,
@@ -70,11 +80,27 @@ def test_run_bash_with_abort_sets_only_timed_out_on_timeout(tmp_path: Path) -> N
7080 "timeout). Conflating them makes the model treat timed-out commands "
7181 "as user-cancelled and retry them on resume."
7282 )
73- # Sanity: SIGKILL takes effect near-instantly; the timeout deadline
74- # is 1s + at most one ``_ABORT_POLL_INTERVAL_S`` (50ms) jitter.
75- assert elapsed < 3.0 , (
76- f"timeout supervisor should have killed the process well under "
77- f"the 3s sleep, took { elapsed :.2f} s"
83+ # Sanity: the supervisor must trip the 1s deadline, not wait out the full
84+ # 3s sleep. On POSIX the process-group kill closes the pipe at once, so
85+ # elapsed ~1s and the pre-port ``< 3.0`` bound also proves the kill beat
86+ # the sleep. On Windows, ``taskkill /T`` cannot reach the MSYS2 grandchild
87+ # that ``sleep`` becomes — its Windows parent is not ``bash.exe`` — so that
88+ # orphan keeps the stdout pipe open and ``communicate()`` blocks up to
89+ # ``_KILL_REAP_TIMEOUT_S`` draining it: elapsed lands at ~1s + drain,
90+ # indistinguishable by clock from a natural 3s completion. There the
91+ # ``timed_out``/``interrupted`` assertions above carry the regression
92+ # guard, and this bound only catches a supervisor that hangs past one
93+ # kill-drain.
94+ if sys .platform == "win32" :
95+ from src .tool_system .tools .bash .bash_tool import _KILL_REAP_TIMEOUT_S
96+
97+ max_elapsed = 1.0 + _KILL_REAP_TIMEOUT_S + 1.5
98+ else :
99+ max_elapsed = 3.0
100+ assert elapsed < max_elapsed , (
101+ f"timeout supervisor should have tripped the 1s deadline rather than "
102+ f"waited out the 3s sleep; took { elapsed :.2f} s (budget "
103+ f"{ max_elapsed :.1f} s)"
78104 )
79105
80106
@@ -96,7 +122,7 @@ def _trip_abort() -> None:
96122 threading .Thread (target = _trip_abort , daemon = True ).start ()
97123
98124 result = _run_bash_with_abort (
99- [ "bash" , "-lc" , " sleep 3; echo done"] ,
125+ bash_argv ( " sleep 3; echo done") ,
100126 cwd = str (tmp_path ),
101127 timeout_s = 30 , # well above the abort time
102128 abort_signal = ctrl .signal ,
@@ -236,7 +262,7 @@ def test_run_bash_with_abort_natural_exit_sets_neither_flag(tmp_path: Path) -> N
236262 where a code change accidentally sets one of them on the happy path.
237263 """
238264 result = _run_bash_with_abort (
239- [ "bash" , "-lc" , " echo hello"] ,
265+ bash_argv ( " echo hello") ,
240266 cwd = str (tmp_path ),
241267 timeout_s = 10 ,
242268 abort_signal = None ,
@@ -251,11 +277,7 @@ def test_run_bash_with_abort_natural_exit_sets_neither_flag(tmp_path: Path) -> N
251277def test_detached_descendant_does_not_discard_captured_stdout (tmp_path : Path ) -> None :
252278 """A detached child may keep the output pipe open after Bash exits."""
253279 result = _run_bash_with_abort (
254- [
255- "bash" ,
256- "-lc" ,
257- "nohup sh -c 'sleep 4' >/dev/null 2>&1 & echo server-started" ,
258- ],
280+ bash_argv ("nohup sh -c 'sleep 4' >/dev/null 2>&1 & echo server-started" ),
259281 cwd = str (tmp_path ),
260282 timeout_s = 10 ,
261283 abort_signal = None ,
0 commit comments