Files
hermes-agent/tests/cli/test_cpr_local_leak.py
T
ethernet 30da5d0a89 test: run os-specific tests on their real host, not a faked one
many tests patched sys.platform or a module's _IS_WINDOWS flag, then
ran on linux ci. the patch selects the branch under test, but the host
does not have the behavior the branch exists for. the test proves the
patch, not the platform. some gated assertions never ran on any host.

this commit adds three markers: linux_only, macos_only, windows_only.
a conftest hook skips a marked test on the other hosts, with a clear
reason. no test fakes a host now. two documented fakes remain
(android/termux, freebsd) because no ci runner exists for them.

each fake site got one of four treatments:
- gate it: the real host supplies the platform; mocks cover real
  dependencies only, never host identity
- patch the module's own probe when the subject is the probe's consumer
- assert against the real host when the fake stood in for any non-x host
- delete the patch when it set the value the host already has

bare skipif(sys.platform != ...) guards became markers too. the lane
model skips these on linux and never imports them on windows, so they
ran on no host. platform parametrize tables are now one marked test
per os.

running on real hosts found real errors: a chrome-sandbox failure in
test_gui_command that main hides, and two windows failures fixed here.
the agents.md testing section now documents the policy.
2026-08-09 22:09:49 -04:00

169 lines
5.3 KiB
Python

"""Local CPR leak reproduction + classic-CLI Application output selection.
* Deterministic local-PTY proof that delayed CPR replies leak as
``ESC[row;colR`` / ``^[[row;colR`` when ``enable_cpr=True`` (no SSH).
* Integration-level assertion that, with no SSH env vars, classic CLI
output selection wires a CPR-disabled Output into Application on POSIX.
* Native Windows keeps prompt_toolkit's default output selection.
"""
from __future__ import annotations
import os
import select
import sys
import threading
import time
import pytest
from cli import (
_build_cpr_disabled_output,
_select_classic_cli_pt_output,
_terminal_may_leak_cpr,
)
@pytest.fixture(autouse=True)
def _clear_cpr_env(monkeypatch):
for var in ("SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY", "PROMPT_TOOLKIT_NO_CPR"):
monkeypatch.delenv(var, raising=False)
class TestClassicCliOutputSelection:
@pytest.mark.windows_only
def test_windows_preserves_default_output_selection(self):
assert _terminal_may_leak_cpr() is False
assert _select_classic_cli_pt_output(sys.stdout) is None
@pytest.mark.windows_only
def test_windows_honors_explicit_no_cpr(self, monkeypatch):
monkeypatch.setenv("PROMPT_TOOLKIT_NO_CPR", "1")
assert _terminal_may_leak_cpr() is True
out = _select_classic_cli_pt_output(sys.stdout)
# Build may return None if stdout is not a real tty in CI; if it
# succeeds it must be CPR-disabled.
assert out is None or out.enable_cpr is False
def _openpty_or_skip():
import pty
try:
return pty.openpty()
except OSError as exc:
pytest.skip(f"no PTY devices available: {exc}")
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX PTY harness")
class TestDelayedCprLocalPtyLeak:
def test_delayed_cpr_reply_leaks_when_enable_cpr_true(self):
"""Local (no SSH) delayed ESC[6n reply lands as ESC[39;1R on stdin."""
import tty
from prompt_toolkit.data_structures import Size
from prompt_toolkit.output.vt100 import Vt100_Output
master, slave = _openpty_or_skip()
try:
tty.setraw(slave)
slave_w = os.fdopen(os.dup(slave), "w", buffering=1)
stop = threading.Event()
queries = 0
def terminal() -> None:
nonlocal queries
buf = b""
while not stop.is_set():
try:
r, _, _ = select.select([master], [], [], 0.05)
except OSError:
break
if not r:
continue
try:
chunk = os.read(master, 4096)
except OSError:
break
if not chunk:
break
buf += chunk
while True:
idx = buf.find(b"\x1b[6n")
if idx < 0:
buf = buf[-8:] if len(buf) > 8 else buf
break
buf = buf[idx + 4 :]
queries += 1
time.sleep(0.12)
try:
os.write(master, b"\x1b[39;1R")
except OSError:
pass
threading.Thread(target=terminal, daemon=True).start()
out = Vt100_Output(
slave_w, lambda: Size(rows=40, columns=80), enable_cpr=True
)
out.ask_for_cpr()
out.flush()
for i in range(4):
slave_w.write(f"\rgpt-5.6-sol Q {i}\n")
slave_w.flush()
time.sleep(0.02)
time.sleep(0.3)
data = b""
while True:
r, _, _ = select.select([slave], [], [], 0.05)
if not r:
break
data += os.read(slave, 4096)
stop.set()
slave_w.close()
assert queries >= 1
assert b"\x1b[39;1R" in data
finally:
try:
os.close(slave)
except OSError:
pass
try:
os.close(master)
except OSError:
pass
def test_cpr_disabled_output_sends_no_query(self):
"""Hermes CPR-disabled builder must not emit ESC[6n."""
master, slave = _openpty_or_skip()
try:
slave_w = os.fdopen(slave, "w", buffering=1)
out = _build_cpr_disabled_output(slave_w)
assert out is not None
assert out.enable_cpr is False
seen = b""
def reader() -> None:
nonlocal seen
r, _, _ = select.select([master], [], [], 0.25)
if r:
seen = os.read(master, 4096)
threading.Thread(target=reader, daemon=True).start()
slave_w.write("status ok\n")
slave_w.flush()
# Do not call ask_for_cpr — renderer skips it when NOT_SUPPORTED.
time.sleep(0.3)
slave_w.close()
assert b"\x1b[6n" not in seen
finally:
try:
os.close(master)
except OSError:
pass