Skip to content

Commit 0d6c126

Browse files
BabyChrist666claude
andcommitted
fix: replace fixed sleeps with polling in TestChildProcessCleanup (#1775)
The three flaky tests in TestChildProcessCleanup fail intermittently on macOS CI because fixed-duration sleeps (0.5s-1.0s) aren't enough for child processes to start writing on slow CI machines. Replace the fixed `anyio.sleep()` + size assertion pattern with a `_wait_for_file_growth()` helper that polls until the file exists, has content, and is actively growing, with a 10s timeout. This eliminates the race condition where assertions fire before processes have started. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2fe56e5 commit 0d6c126

File tree

1 file changed

+35
-32
lines changed

1 file changed

+35
-32
lines changed

tests/client/test_stdio.py

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,32 @@ def sigint_handler(signum, frame):
221221
raise
222222

223223

224+
async def _wait_for_file_growth(
225+
file_path: str,
226+
description: str = "process",
227+
timeout: float = 10.0,
228+
poll_interval: float = 0.2,
229+
) -> None:
230+
"""Wait until a file exists, has content, and is actively growing.
231+
232+
Polls the file size at regular intervals until it observes growth,
233+
raising AssertionError if the timeout is exceeded. This replaces
234+
fixed-duration sleeps that cause flaky tests on slow CI machines.
235+
"""
236+
deadline = time.monotonic() + timeout
237+
prev_size = -1
238+
239+
while time.monotonic() < deadline:
240+
if os.path.exists(file_path):
241+
size = os.path.getsize(file_path)
242+
if size > 0 and prev_size >= 0 and size > prev_size:
243+
return # File is growing
244+
prev_size = size
245+
await anyio.sleep(poll_interval)
246+
247+
raise AssertionError(f"{description} did not start writing within {timeout}s")
248+
249+
224250
class TestChildProcessCleanup:
225251
"""Tests for child process cleanup functionality using _terminate_process_tree.
226252
@@ -296,19 +322,9 @@ async def test_basic_child_process_cleanup(self):
296322
# Start the parent process
297323
proc = await _create_platform_compatible_process(sys.executable, ["-c", parent_script])
298324

299-
# Wait for processes to start
300-
await anyio.sleep(0.5)
301-
302-
# Verify parent started
303-
assert os.path.exists(parent_marker), "Parent process didn't start"
304-
305-
# Verify child is writing
306-
if os.path.exists(marker_file): # pragma: no branch
307-
initial_size = os.path.getsize(marker_file)
308-
await anyio.sleep(0.3)
309-
size_after_wait = os.path.getsize(marker_file)
310-
assert size_after_wait > initial_size, "Child process should be writing"
311-
print(f"Child is writing (file grew from {initial_size} to {size_after_wait} bytes)")
325+
# Wait for parent and child to start (poll instead of fixed sleep)
326+
await _wait_for_file_growth(parent_marker, "parent process")
327+
await _wait_for_file_growth(marker_file, "child process")
312328

313329
# Terminate using our function
314330
print("Terminating process and children...")
@@ -398,16 +414,10 @@ async def test_nested_process_tree(self):
398414
# Start the parent process
399415
proc = await _create_platform_compatible_process(sys.executable, ["-c", parent_script])
400416

401-
# Let all processes start
402-
await anyio.sleep(1.0)
403-
404-
# Verify all are writing
405-
for file_path, name in [(parent_file, "parent"), (child_file, "child"), (grandchild_file, "grandchild")]:
406-
if os.path.exists(file_path): # pragma: no branch
407-
initial_size = os.path.getsize(file_path)
408-
await anyio.sleep(0.3)
409-
new_size = os.path.getsize(file_path)
410-
assert new_size > initial_size, f"{name} process should be writing"
417+
# Wait for all processes to start writing (poll instead of fixed sleep)
418+
await _wait_for_file_growth(parent_file, "parent process")
419+
await _wait_for_file_growth(child_file, "child process")
420+
await _wait_for_file_growth(grandchild_file, "grandchild process")
411421

412422
# Terminate the whole tree
413423
await _terminate_process_tree(proc)
@@ -477,15 +487,8 @@ def handle_term(sig, frame):
477487
# Start the parent process
478488
proc = await _create_platform_compatible_process(sys.executable, ["-c", parent_script])
479489

480-
# Let child start writing
481-
await anyio.sleep(0.5)
482-
483-
# Verify child is writing
484-
if os.path.exists(marker_file): # pragma: no branch
485-
size1 = os.path.getsize(marker_file)
486-
await anyio.sleep(0.3)
487-
size2 = os.path.getsize(marker_file)
488-
assert size2 > size1, "Child should be writing"
490+
# Wait for child to start writing (poll instead of fixed sleep)
491+
await _wait_for_file_growth(marker_file, "child process")
489492

490493
# Terminate - this will kill the process group even if parent exits first
491494
await _terminate_process_tree(proc)

0 commit comments

Comments
 (0)