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.
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
"""Tests for /proc-based gateway PID detection in Docker environments.
|
|
|
|
Verifies that _scan_gateway_pids() uses /proc/*/cmdline when available
|
|
(Docker without procps) and falls back to ps only when /proc is absent.
|
|
|
|
See: NousResearch/hermes-agent#7622
|
|
"""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
import hermes_cli.gateway as gateway_mod
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_GATEWAY_CMD = "python -m hermes_cli.main gateway run"
|
|
_OTHER_CMD = "python -m some_other_thing"
|
|
|
|
|
|
def _fake_proc_dir(entries: dict):
|
|
"""Return side_effects that simulate /proc: isdir → True, listdir → pids,
|
|
open(cmdline) → null-delimited command bytes."""
|
|
def _isdir(path):
|
|
return str(path) == "/proc"
|
|
|
|
def _listdir(path):
|
|
if str(path) == "/proc":
|
|
return [str(pid) for pid in entries] + ["self", "version"]
|
|
raise FileNotFoundError(path)
|
|
|
|
def _open(path, mode="r", **kwargs):
|
|
path_str = str(path)
|
|
if "/cmdline" in path_str:
|
|
pid = int(path_str.split("/proc/")[1].split("/")[0])
|
|
raw = entries.get(pid, "").encode("utf-8").replace(b" ", b"\x00")
|
|
m = MagicMock()
|
|
m.read.return_value = raw
|
|
m.__enter__ = lambda s: s
|
|
m.__exit__ = MagicMock(return_value=False)
|
|
return m
|
|
raise FileNotFoundError(path)
|
|
|
|
return _isdir, _listdir, _open
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.linux_only
|
|
class TestProcFallback:
|
|
"""_scan_gateway_pids reads /proc when available, skips ps.
|
|
|
|
Linux-only: ``/proc/<pid>/cmdline`` is the subject. The non-Windows arm of
|
|
``_scan_gateway_pids`` is selected by the real host here, so the previous
|
|
``is_windows`` stub is gone — only the /proc filesystem itself is faked so
|
|
the scan sees deterministic PIDs.
|
|
"""
|
|
|
|
def test_detects_gateway_pid_via_proc(self):
|
|
my_pid = os.getpid()
|
|
entries = {
|
|
my_pid: "python -m hermes_cli.main", # own process — excluded
|
|
12345: _GATEWAY_CMD,
|
|
99999: _OTHER_CMD,
|
|
}
|
|
_isdir, _listdir, _open = _fake_proc_dir(entries)
|
|
|
|
with (
|
|
patch("os.path.isdir", side_effect=_isdir),
|
|
patch("os.listdir", side_effect=_listdir),
|
|
patch("builtins.open", side_effect=_open),
|
|
patch("hermes_cli.gateway._get_ancestor_pids", return_value=set()),
|
|
patch("subprocess.run") as mock_ps,
|
|
):
|
|
pids = gateway_mod._scan_gateway_pids(set(), all_profiles=True)
|
|
|
|
assert 12345 in pids
|
|
assert 99999 not in pids
|
|
mock_ps.assert_not_called() # ps must NOT be called when /proc worked
|
|
|
|
|
|
|
|
|
|
def test_proc_permission_error_skips_pid(self):
|
|
def _isdir(path):
|
|
return str(path) == "/proc"
|
|
|
|
def _listdir(path):
|
|
if str(path) == "/proc":
|
|
return ["12345", "self"]
|
|
raise FileNotFoundError
|
|
|
|
def _open(path, mode="r", **kwargs):
|
|
raise PermissionError("no access")
|
|
|
|
with (
|
|
patch("os.path.isdir", side_effect=_isdir),
|
|
patch("os.listdir", side_effect=_listdir),
|
|
patch("builtins.open", side_effect=_open),
|
|
patch("hermes_cli.gateway._get_ancestor_pids", return_value=set()),
|
|
patch("subprocess.run") as mock_ps,
|
|
):
|
|
pids = gateway_mod._scan_gateway_pids(set(), all_profiles=True)
|
|
|
|
# PermissionError swallowed — empty result, no crash
|
|
assert 12345 not in pids
|
|
mock_ps.assert_not_called() # /proc dir existed, so ps not called
|