Files
hermes-agent/tests/tools/test_drain_fd_windows.py
ethernet 9f837d298b Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Conflicts resolved toward the PM model: main's lazy_deps/update_cmd_deps/npm
stamp machinery stays deleted (PM + scripts/build/node-deps.mjs own it), the
systemd ExecStop stop-mark rides the installation launcher, legacy
linux_only/macos_only/windows_only markers are rewritten to platforms(), and
finalize_update_receipt carries pending manual-serve obligations forward
again (lost when the ContextVar receipt rewrite crossed c0aa3ce354).

Test harness: the real-home I/O guard exempts /proc/<pid>/fd metadata reads
(deleted-WAL holder scans) and run_tests.sh drops ~/.hermes PATH entries so
shutil.which() cannot trip the tripwire.
2026-09-20 10:07:50 -04:00

62 lines
2.3 KiB
Python

"""Windows stdout drain: bounded when a grandchild keeps the pipe's write handle open.
``select()`` cannot poll pipe fds on Windows, so the drain used a blocking
``os.read`` loop that waited for true EOF — a backgrounded grandchild that
inherited the write end kept the terminal tool hung for its whole lifetime.
The ``PeekNamedPipe`` drain mirrors the POSIX ``select`` loop's post-exit bound.
"""
import codecs
import os
import time
from unittest.mock import MagicMock
import pytest
from tools.environments.base_output import _BoundedOutputCollector, _drain_fd_windows
@pytest.mark.platforms("windows")
def test_drain_fd_windows_returns_promptly_when_writer_remains_open():
"""Data already in the pipe is captured; the drain stops shortly after the
process reports exit even though the write end is still open."""
r, w = os.pipe()
try:
os.write(w, b"hello from child process\n")
proc = MagicMock()
proc.poll.return_value = 0 # bash exited; ``w`` (the grandchild's copy) is still open
output = _BoundedOutputCollector(1000)
decoder = codecs.getincrementaldecoder("utf-8")("replace")
t0 = time.monotonic()
_drain_fd_windows(proc, r, output, decoder)
elapsed = time.monotonic() - t0
assert "hello from child process" in output.render()
assert elapsed < 2.0, f"drain hung for {elapsed:.2f}s instead of bounding after exit"
finally:
os.close(r)
os.close(w)
@pytest.mark.platforms("windows")
def test_local_environment_returns_while_background_grandchild_holds_pipe(tmp_path):
"""End-to-end on Git Bash: ``execute()`` returns promptly with the marker while a
backgrounded grandchild still holds the stdout pipe (issues #105865 / #67362)."""
from tools.environments.local import LocalEnvironment
env = LocalEnvironment(cwd=str(tmp_path))
try:
marker = "windows_drain_e2e_marker"
cmd = f'python -c "import time; time.sleep(30)" & echo {marker}'
t0 = time.monotonic()
result = env.execute(cmd, timeout=15)
elapsed = time.monotonic() - t0
assert elapsed < 10.0, f"LocalEnvironment.execute hung for {elapsed:.2f}s on Windows"
assert result["returncode"] == 0
assert marker in result["output"]
finally:
env.cleanup()