[tests] Fix flaky pty log probe test on macOS (#18681)

This commit is contained in:
J. Nick Koston
2026-08-23 11:26:55 -05:00
committed by GitHub
parent f741c274d5
commit 4efd308345
+22 -26
View File
@@ -1,5 +1,4 @@
from collections.abc import Generator
import errno
import io
import logging
import os
@@ -178,37 +177,34 @@ def _run_probe_on_pty(
output = b""
deadline = time.monotonic() + 60
try:
try:
proc = subprocess.Popen(
_probe_command(fixture_path),
stdout=follower,
stderr=follower if stderr_to_pty else subprocess.PIPE,
stdin=follower,
env=probe_env,
)
finally:
os.close(follower)
while True:
timeout = deadline - time.monotonic()
if timeout <= 0 or not select.select([controller], [], [], timeout)[0]:
pytest.fail(f"pty probe produced no EOF in time; got {output!r}")
try:
chunk = os.read(controller, 1024)
except OSError as err:
# macOS raises EIO once the child closes its end of the pty;
# anything else is a real failure, not end-of-stream.
if err.errno != errno.EIO:
raise
break
if not chunk:
break
proc = subprocess.Popen(
_probe_command(fixture_path),
stdout=follower,
stderr=follower if stderr_to_pty else subprocess.PIPE,
stdin=follower,
env=probe_env,
)
# The parent keeps the follower open until the child has exited and
# the controller is drained: macOS discards buffered pty output once
# the last follower closes, so closing it early loses the probe's
# output whenever the child finishes before the first read.
while proc.poll() is None:
if time.monotonic() > deadline:
pytest.fail(f"pty probe did not exit in time; got {output!r}")
if select.select([controller], [], [], 0.01)[0]:
output += os.read(controller, 4096)
# Everything the child wrote is already buffered, so drain without waiting.
while select.select([controller], [], [], 0)[0] and (
chunk := os.read(controller, 4096)
):
output += chunk
stderr_text = ""
if proc.stderr is not None:
stderr_text = proc.stderr.read().decode(errors="replace")
proc.stderr.close()
assert proc.wait(60) == 0, stderr_text
assert proc.returncode == 0, stderr_text
finally:
os.close(follower)
os.close(controller)
if proc is not None and proc.poll() is None:
proc.kill()