30da5d0a89
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.
169 lines
5.3 KiB
Python
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
|