hermes-agent/tests/test_windows_subprocess_no_window_flags.py
Teknium 6b81590c55
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00

374 lines
12 KiB
Python

from __future__ import annotations
import subprocess
from pathlib import Path
from types import SimpleNamespace
_CREATE_NO_WINDOW = 0x08000000
class _Completed:
def __init__(self, stdout: str | bytes = "ok\n", returncode: int = 0):
self.stdout = stdout
self.stderr = ""
self.returncode = returncode
def _spawns(captured, *needles):
"""Captured ``subprocess.run`` calls whose argv contains every needle.
These tests patch ``<module>.subprocess.run``, which is the shared
``subprocess`` module singleton — so the patch is process-wide. Importing
``tui_gateway.server`` kicks off ``prefetch_update_check`` (a daemon thread
that shells out to ``git ... origin`` with ``text=True, timeout=5``), and
that call can land in ``captured`` mid-test. Matching the distinctive argv
tokens of the call under test (e.g. ``--show-toplevel``, ``ls-files``) keeps
each assertion scoped to its own contract and immune to that cross-talk —
otherwise a stray ``git`` spawn trips a bare ``KeyError: 'creationflags'``
or a call-count / full-list mismatch.
"""
return [
(cmd, kwargs)
for cmd, kwargs in captured
if cmd and all(n in cmd for n in needles)
]
def _is_git_spawn(cmd) -> bool:
"""True only for a ``git -C <cwd> ...`` spawn.
``bounded_git_probe`` lives in ``hermes_cli._subprocess_compat`` and both
probe call sites delegate to it, so these tests patch
``_subprocess_compat.subprocess.Popen`` — which is the shared ``subprocess``
module singleton, i.e. a process-wide patch. Any unrelated daemon spawn
(e.g. an import-time update-check thread) must stay benign and out of the
recorded spawns, mirroring the ``_spawns`` scoping the other tests use.
"""
return bool(cmd) and cmd[:2] == ["git", "-C"]
def _make_fake_popen(spawns, *, stdout="ok\n", returncode=0):
"""Fast-path Popen stand-in: git returns within the budget."""
class _FakePopen:
def __init__(self, cmd, **kwargs):
if _is_git_spawn(cmd):
spawns.append((cmd, kwargs))
self.returncode = returncode
def communicate(self, timeout=None):
return (stdout, "")
def kill(self): # pragma: no cover - never reached on the fast path
raise AssertionError("kill() must not run when git returns in time")
return _FakePopen
def test_bounded_git_probe_fast_path_spawn_contract_windows(monkeypatch):
"""The normal-path spawn contract survives the run()->Popen rewrite:
PIPE/PIPE/DEVNULL, text + utf-8/replace, hidden-window flags on Windows."""
from hermes_cli import _subprocess_compat
spawns = []
monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
monkeypatch.setattr(_subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
monkeypatch.setattr(_subprocess_compat.subprocess, "Popen", _make_fake_popen(spawns, stdout="main\n"))
out = _subprocess_compat.bounded_git_probe(
["git", "-C", "C:/repo", "branch", "--show-current"], timeout=1.5
)
assert out == "main"
assert len(spawns) == 1, spawns
cmd, kwargs = spawns[0]
assert cmd == ["git", "-C", "C:/repo", "branch", "--show-current"]
assert kwargs["stdout"] == subprocess.PIPE
assert kwargs["stderr"] == subprocess.PIPE
assert kwargs["stdin"] == subprocess.DEVNULL
assert kwargs["text"] is True
assert kwargs["encoding"] == "utf-8"
assert kwargs["errors"] == "replace"
assert kwargs["creationflags"] == _CREATE_NO_WINDOW
def test_bounded_git_probe_nonzero_returncode_returns_empty(monkeypatch):
from hermes_cli import _subprocess_compat
spawns = []
monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", False)
monkeypatch.setattr(
_subprocess_compat.subprocess,
"Popen",
_make_fake_popen(spawns, stdout="garbage-should-not-leak\n", returncode=1),
)
assert _subprocess_compat.bounded_git_probe(["git", "-C", "/repo", "status"], timeout=1.5) == ""
def test_bounded_git_probe_spawn_failure_returns_empty(monkeypatch):
"""A spawn failure (git not on PATH) fails open to ""."""
from hermes_cli import _subprocess_compat
def boom(cmd, **kwargs):
raise FileNotFoundError("git not found")
monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", False)
monkeypatch.setattr(_subprocess_compat.subprocess, "Popen", boom)
assert _subprocess_compat.bounded_git_probe(["git", "-C", "/repo", "status"], timeout=1.5) == ""
def test_shell_hooks_hide_hook_command_windows(monkeypatch):
from agent import shell_hooks
captured = []
def fake_run(cmd, **kwargs):
captured.append((cmd, kwargs))
return SimpleNamespace(returncode=0, stdout="{}", stderr="")
monkeypatch.setattr(shell_hooks, "IS_WINDOWS", True)
monkeypatch.setattr(shell_hooks, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
monkeypatch.setattr(shell_hooks.subprocess, "run", fake_run)
result = shell_hooks._spawn(
shell_hooks.ShellHookSpec(event="post_tool_call", command="hook-bin --flag"),
"{}",
)
assert result["returncode"] == 0
assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
# ── #56747 GUI-reachable exec paths + provider transports (PR #56877) ──────
#
# These six sites are the desktop-GUI-reachable spawns that still flashed a
# console on Windows after the #54220 sweep: the TUI gateway's cli.exec /
# shell.exec / quick-command exec RPCs, the interactive CLI's quick-command
# exec handler, and the Copilot ACP + Codex app-server stdio transports.
# All are hide-only (creationflags) — PIPE stdio must stay intact.
def _patch_hide_flags(monkeypatch):
import hermes_cli._subprocess_compat as subprocess_compat
monkeypatch.setattr(subprocess_compat, "IS_WINDOWS", True)
monkeypatch.setattr(subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
def test_tui_shell_exec_rpc_hides_console_window(monkeypatch):
from tui_gateway import server
captured = []
def fake_run(cmd, **kwargs):
captured.append((cmd, kwargs))
return _Completed(stdout="ok\n")
_patch_hide_flags(monkeypatch)
monkeypatch.setattr(server.subprocess, "run", fake_run)
resp = server.handle_request(
{"id": "2", "method": "shell.exec", "params": {"command": "echo shellexec-56747"}}
)
assert resp["result"]["code"] == 0
spawns = _spawns(captured, "shellexec-56747")
assert len(spawns) == 1, captured
assert spawns[0][1]["creationflags"] == _CREATE_NO_WINDOW
# ── #47971 LSP spawn + installer paths (salvage) ────────────────────────────
#
# The LSP language-server spawn (agent/lsp/client.py::_spawn) and the
# npm/go LSP auto-installers (agent/lsp/install.py) are reachable from
# console-less parents — a VS Code/Zed extension host running the ACP
# adapter — where a .cmd-wrapped server (pyright-langserver.CMD via
# cmd.exe /c) or an npm/go console app flashes a window on Windows.
# All are hide-only (creationflags); PIPE stdio must stay intact and the
# POSIX start_new_session detach must be preserved on the client spawn.
def test_lsp_client_spawn_hides_console_window(monkeypatch):
import asyncio
from agent.lsp import client as lsp_client
captured = []
class _FakeProc:
stdin = None
stdout = None
stderr = None
async def fake_exec(*cmd, **kwargs):
captured.append((list(cmd), kwargs))
return _FakeProc()
monkeypatch.setattr(lsp_client, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
monkeypatch.setattr(
lsp_client.asyncio, "create_subprocess_exec", fake_exec
)
client = lsp_client.LSPClient(
server_id="test-server",
workspace_root="/tmp/ws",
command=["fake-langserver", "--stdio"],
)
asyncio.run(client._spawn())
assert len(captured) == 1, captured
cmd, kwargs = captured[0]
assert cmd == ["fake-langserver", "--stdio"]
assert kwargs["creationflags"] == _CREATE_NO_WINDOW
# Hide-only: the LSP wire still needs its pipes, and the POSIX
# process-group detach (mcp orphan-sweep guard) must survive.
assert kwargs["stdin"] == asyncio.subprocess.PIPE
assert kwargs["stdout"] == asyncio.subprocess.PIPE
assert kwargs["start_new_session"] is True
# ── #67690 env probes, lazy installs, platform.win32_ver() (@m4r13y) ───────
#
# Windowless processes (pythonw gateway + kanban workers) flashed consoles
# from three more spawn families: tools/env_probe._run's interpreter/pip
# probes, tools/lazy_deps' uv→pip→ensurepip install ladder, and CPython
# 3.11/3.12's platform.win32_ver() which shells out `cmd /c ver` with
# shell=True and no CREATE_NO_WINDOW. All are hide-only (creationflags);
# win32_ver is neutralized by stubbing platform._syscmd_ver so the
# documented ValueError fallback reads sys.getwindowsversion() instead.
def test_env_probe_run_hides_console_window(monkeypatch):
from tools import env_probe
captured = []
def fake_run(cmd, **kwargs):
captured.append((cmd, kwargs))
return _Completed(stdout="", returncode=0)
monkeypatch.setattr(env_probe, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
monkeypatch.setattr(env_probe.subprocess, "run", fake_run)
rc, out, err = env_probe._run(["python3", "--version"], timeout=1.0)
assert rc == 0
assert len(captured) == 1, captured
cmd, kwargs = captured[0]
assert cmd == ["python3", "--version"]
assert kwargs["creationflags"] == _CREATE_NO_WINDOW
# The temp-file capture contract (#67964) must survive: stdout/stderr are
# file objects (not PIPE) so a lingering grandchild can't wedge the probe.
assert kwargs["stdout"] is not None and kwargs["stdout"] != subprocess.PIPE
assert kwargs["stderr"] is not None and kwargs["stderr"] != subprocess.PIPE
assert kwargs["stdin"] == subprocess.DEVNULL
def test_lazy_deps_uv_install_hides_console_window(monkeypatch):
from tools import lazy_deps
captured = []
def fake_run(cmd, **kwargs):
captured.append((cmd, kwargs))
return _Completed(stdout="installed", returncode=0)
monkeypatch.delenv(lazy_deps._LAZY_TARGET_ENV, raising=False)
monkeypatch.setattr(lazy_deps, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
monkeypatch.setattr(lazy_deps.subprocess, "run", fake_run)
monkeypatch.setattr(lazy_deps.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None)
res = lazy_deps._venv_pip_install(("left-pad",))
assert res.success
spawns = _spawns(captured, "pip", "install", "left-pad")
assert len(spawns) == 1, captured
cmd, kwargs = spawns[0]
assert cmd[:3] == ["/usr/bin/uv", "pip", "install"]
assert kwargs["creationflags"] == _CREATE_NO_WINDOW
assert kwargs["stdin"] == subprocess.DEVNULL
def test_suppress_platform_ver_console_stubs_syscmd_ver(monkeypatch):
"""Simulated Windows: _syscmd_ver is replaced by an in-process echo stub
so win32_ver() takes its ValueError fallback instead of `cmd /c ver`."""
import platform
from hermes_cli import _subprocess_compat
monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
# Register the original with monkeypatch so it gets restored after.
monkeypatch.setattr(platform, "_syscmd_ver", platform._syscmd_ver)
_subprocess_compat.suppress_platform_ver_console()
# The stub echoes its inputs — win32_ver() treats the unparseable value
# as the documented ValueError path and falls back to
# sys.getwindowsversion().platform_version (no subprocess, no window).
assert platform._syscmd_ver("s", "r", "v") == ("s", "r", "v")
# Idempotent + never raises on repeat calls.
_subprocess_compat.suppress_platform_ver_console()
assert platform._syscmd_ver() == ("", "", "")