Files
graphify/tests/test_cli_broken_pipe.py
T
90b545c609 fix(cli): survive early pipe close (#1807); skip .nox venvs (#1804)
#1807 — piping graphify into a reader that stops early (head,
Select-Object -First N, sed q) disconnected stdout mid-write, raising an
unhandled BrokenPipeError (OSError(EINVAL) on Windows) and exiting 255,
so CI wrappers and agent harnesses read a successful query as a failure.
The console entry point now wraps the CLI body: a closed-pipe reader is
treated as success — stdout is redirected to devnull so shutdown flush
can't raise again, and the process exits 0. Adds a subprocess regression
test.

#1804 — .nox/ (nox virtualenvs, tox's successor, same .nox/ tree shape)
was missing from _SKIP_DIRS while .tox was present, so nox site-packages
got fully indexed (one repo came out 91% venv noise). Added next to .tox
with a regression test.

Reported by @varuntej07 (#1807) and @igorregoir-lgtm (#1804).

Co-Authored-By: varuntej07 <varuntej07@users.noreply.github.com>
Co-Authored-By: igorregoir-lgtm <igorregoir-lgtm@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:24:42 +01:00

32 lines
1.2 KiB
Python

"""CLI must not crash when a downstream reader closes the pipe early (#1807).
Truncating a command's output (`head`, PowerShell `Select-Object -First N`,
`sed q`) is routine. graphify used to keep writing after the reader disconnected,
hit an unhandled BrokenPipeError, and exit 255 — so CI wrappers and agent
harnesses that both trim output and check the exit code read a successful query
as a failure. An early-closing reader is now treated as success (exit 0).
"""
from __future__ import annotations
import subprocess
import sys
PYTHON = sys.executable
def test_help_survives_reader_closing_pipe_early():
"""`graphify --help | head -n1` must leave graphify exiting 0, not 255."""
producer = subprocess.Popen(
[PYTHON, "-m", "graphify", "--help"], stdout=subprocess.PIPE
)
reader = subprocess.Popen(
[PYTHON, "-c", "import sys; sys.stdin.readline()"],
stdin=producer.stdout,
stdout=subprocess.DEVNULL,
)
producer.stdout.close() # let the producer see EPIPE when the reader exits
reader.wait()
rc = producer.wait()
# 0 (our handled-and-succeed convention). Never the 255 unhandled-exception code.
assert rc == 0, f"expected clean exit after early pipe close, got {rc}"