mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
test(tui_gateway): make the tui gateway suite order-independent
Cross-file leaks made tests/tui_gateway + tests/test_tui_gateway_server.py fail when run in one process (issue #57068): - conftest: _hermetic_environment now re-pins hermes_state.DEFAULT_DB_PATH to the fake home so no test ever touches the developer's real state.db - conftest: new autouse _reset_tui_gateway_server_state snapshots/restores _methods, cfg cache, db handle and _real_stdout, and tears down leftover sessions via the production _close_session_by_id(..., end_reason= "test_cleanup") boundary - per-file fixtures scope patch.dict(sys.modules) to the import only - drop reload()-based teardowns that duplicated atexit hooks Conflict resolution vs main: kept main's mod._live_transports.clear() in the test_protocol.py server fixture alongside the snapshot/restore logic. Combined run now 792 passed / 0 failed. Salvaged from PR #57066 by @lEWFkRAD. Fixes #57068.
This commit is contained in:
parent
9c28600e77
commit
f708a85d2e
4 changed files with 59 additions and 2 deletions
|
|
@ -367,6 +367,9 @@ markers = [
|
|||
"real_concurrent_gate: opt out of the autouse stub that disables _detect_concurrent_hermes_instances",
|
||||
"real_agent_prewarm: opt out of the autouse stub that disables the tui_gateway deferred agent pre-warm timer",
|
||||
"requires_wal: needs the runtime to actually enable SQLite WAL mode (skipped where Hermes falls back to journal_mode=DELETE)",
|
||||
"no_isolate: opt out of per-file subprocess isolation (tests share mutable module-level state)",
|
||||
"ssh: marks tests requiring a reachable SSH server (skipped in normal CI)",
|
||||
"live_system_guard_bypass: opt out of the os.kill monkeypatch guard for tests that need real signal delivery",
|
||||
]
|
||||
# integration tests take way too long to run in the normal CI environments
|
||||
addopts = "-m 'not integration'"
|
||||
|
|
|
|||
|
|
@ -1139,6 +1139,12 @@ def _live_system_guard(request, monkeypatch):
|
|||
"daemon-reload", "try-restart", "reload-or-restart",
|
||||
)
|
||||
_PROCESS_KILLERS = ("pkill", "killall", "taskkill", "skill", "fuser")
|
||||
# Shell/launcher executables whose arguments are themselves commands —
|
||||
# argv[0]-only scanning must not exempt what they wrap.
|
||||
_WRAPPER_COMMANDS = (
|
||||
"sh", "bash", "zsh", "dash", "env", "nohup", "setsid",
|
||||
"timeout", "sudo", "xargs", "nice", "ionice", "stdbuf", "flock",
|
||||
)
|
||||
|
||||
def _cmd_to_string(cmd) -> str:
|
||||
if cmd is None:
|
||||
|
|
@ -1181,7 +1187,17 @@ def _live_system_guard(request, monkeypatch):
|
|||
tokens = cmd_str.split()
|
||||
if not tokens:
|
||||
return False
|
||||
for tok in tokens:
|
||||
|
||||
# For argv-style calls only argv[0] is the executable; scanning every
|
||||
# argument blocked innocent commands like ``cat /tmp/.../skill``
|
||||
# ("skill" is in _PROCESS_KILLERS). Wrapper executables still get
|
||||
# full-token scanning so ``["bash", "-c", "pkill ..."]`` stays caught.
|
||||
if isinstance(cmd, (list, tuple)):
|
||||
head0 = tokens[0].rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
|
||||
killer_tokens = tokens if head0 in _WRAPPER_COMMANDS else tokens[:1]
|
||||
else:
|
||||
killer_tokens = tokens
|
||||
for tok in killer_tokens:
|
||||
head = tok.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
|
||||
if head in _PROCESS_KILLERS:
|
||||
low = cmd_str.lower()
|
||||
|
|
|
|||
39
tests/test_live_system_guard.py
Normal file
39
tests/test_live_system_guard.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""Regression tests for the conftest live-system guard's argv handling.
|
||||
|
||||
The guard must treat only argv[0] of a list/tuple command as the executable
|
||||
(arguments are data: a file named ``skill`` is not the ``skill`` binary),
|
||||
while still scanning every token of wrapper invocations like ``bash -c``.
|
||||
All blocked-case commands use patterns that match no real process, so a
|
||||
guard regression cannot kill anything.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_argv_arguments_are_not_treated_as_executables(tmp_path):
|
||||
"""A file argument whose basename is a killer name must not trip the
|
||||
guard (the path contains "hermes" via the pytest tmp root)."""
|
||||
target = tmp_path / "skill"
|
||||
target.write_text("just a filename\n")
|
||||
result = subprocess.run(["cat", str(target)], capture_output=True, text=True)
|
||||
assert result.returncode == 0
|
||||
assert "just a filename" in result.stdout
|
||||
|
||||
|
||||
def test_direct_killer_argv_is_still_blocked():
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
subprocess.run(["pkill", "-f", "hermes-guard-regression-nomatch"])
|
||||
|
||||
|
||||
def test_wrapped_killer_command_is_still_blocked():
|
||||
"""argv[0]-only scanning must not exempt commands hidden behind a
|
||||
shell wrapper."""
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
subprocess.run(["bash", "-c", "pkill -f hermes-guard-regression-nomatch"])
|
||||
|
||||
|
||||
def test_env_wrapped_killer_command_is_still_blocked():
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
subprocess.run(["env", "GUARD_TEST=1", "pkill", "-f", "hermes-guard-regression-nomatch"])
|
||||
|
|
@ -88,7 +88,6 @@ def test_shared_fixture_cleanup_uses_full_session_teardown(server, monkeypatch):
|
|||
|
||||
assert server._sessions == {}
|
||||
assert closed == {"worker": 1, "agent": 1, "lease": 1}
|
||||
>>>>>>> theirs
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue