test: fix chronic full-suite agent/hermes_cli __path__ isolation poison (2 flakes)

Convert in-place pkg.__path__=[] mutations on the REAL agent/hermes_cli
packages to monkeypatch.setattr (auto-restored), and make
test_issue1574's _activate_spawn_fake_agent snapshot+restore the
HERMES_WEBUI_AGENT_DIR/PYTHONPATH/sys.path it mutates.

Fixes:
- test_v050259_sessiondb_fd_leak::test_session_db_close_is_idempotent
- test_tls_support::test_tls_startup_failure_fallback_to_http
This commit is contained in:
t authored and nesquena-hermes committed 2026-07-06 21:08:52 +00:00
1 parent ade6de5294
commit cec093eef6
6 files changed
+104 -27

No files matched your search

+12 -3
View File
@@ -30,7 +30,11 @@ def _install_fake_mcp_tool(monkeypatch, shutdown, discover, servers=None, lock=N
def _install_fake_codex_runtime_switch(monkeypatch): def _install_fake_codex_runtime_switch(monkeypatch):
import sys import sys
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # Restore the real hermes_cli.__path__ on teardown instead of emptying it in
# place: `sys.modules.get(...)` grabs the REAL package object, so a bare
# `__path__ = []` permanently strands it (later `import hermes_cli.<sub>`
# fails for the rest of the suite). monkeypatch.setattr snapshots and restores.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
codex_runtime_switch = ModuleType("hermes_cli.codex_runtime_switch") codex_runtime_switch = ModuleType("hermes_cli.codex_runtime_switch")
calls = [] calls = []
@@ -64,7 +68,10 @@ def _install_fake_codex_runtime_switch(monkeypatch):
def _install_fake_skill_commands(monkeypatch, reload_skills): def _install_fake_skill_commands(monkeypatch, reload_skills):
import sys import sys
agent_pkg = sys.modules.get("agent") or ModuleType("agent") agent_pkg = sys.modules.get("agent") or ModuleType("agent")
agent_pkg.__path__ = [] # See _install_fake_codex_runtime_switch: monkeypatch.setattr restores the
# real agent.__path__ on teardown so `from agent.<sub> import ...` keeps
# working in later tests (chronic full-suite poison otherwise).
monkeypatch.setattr(agent_pkg, "__path__", [], raising=False)
skill_commands = ModuleType("agent.skill_commands") skill_commands = ModuleType("agent.skill_commands")
skill_commands.reload_skills = reload_skills skill_commands.reload_skills = reload_skills
monkeypatch.setitem(sys.modules, "agent", agent_pkg) monkeypatch.setitem(sys.modules, "agent", agent_pkg)
@@ -76,7 +83,9 @@ def _install_fake_account_usage(monkeypatch, *, view=None, exc=None):
import sys import sys
agent_pkg = sys.modules.get("agent") or ModuleType("agent") agent_pkg = sys.modules.get("agent") or ModuleType("agent")
agent_pkg.__path__ = [] # monkeypatch.setattr restores the real agent.__path__ on teardown (see
# _install_fake_skill_commands) to avoid permanently poisoning the package.
monkeypatch.setattr(agent_pkg, "__path__", [], raising=False)
account_usage = ModuleType("agent.account_usage") account_usage = ModuleType("agent.account_usage")
def build_credits_view(*, markdown=False, timeout=10.0): def build_credits_view(*, markdown=False, timeout=10.0):
@@ -1,3 +1,4 @@
import importlib.util
import sys import sys
import types import types
from unittest.mock import patch from unittest.mock import patch
@@ -6,14 +7,29 @@ from unittest.mock import patch
def fake_get_model_context_length(model, base_url="", **kwargs): def fake_get_model_context_length(model, base_url="", **kwargs):
return 1048576 return 1048576
# Ensure a mock of 'agent' exists in sys.modules to prevent ModuleNotFoundError in routes imports. # Ensure an 'agent' module exists in sys.modules to prevent ModuleNotFoundError
fake_agent = types.ModuleType("agent") # when api.routes is imported on a CI runner that has ONLY the WebUI repo (no
fake_agent.__path__ = [] # hermes-agent package). CRITICAL: only install the fake when the REAL agent
metadata = types.ModuleType("agent.model_metadata") # package is not importable. Unconditionally doing `sys.modules["agent"] = fake`
fake_agent.model_metadata = metadata # type: ignore[attr-defined] # with `fake.__path__ = []` clobbers the genuine, importable agent package for
sys.modules["agent"] = fake_agent # the WHOLE process (this runs at collection time and is never restored) — a
sys.modules["agent.model_metadata"] = metadata # later `from agent.<sub> import ...` in the full suite (e.g. hermes_state's
metadata.get_model_context_length = fake_get_model_context_length # `from agent.memory_manager import sanitize_context`) then fails with
# ModuleNotFoundError. Guarding on find_spec keeps the real package intact
# locally while preserving the CI import path. Nothing in this module asserts
# against the fake metadata, so the shim is purely an import guard.
if (
importlib.util.find_spec("agent") is None
or importlib.util.find_spec("agent.model_metadata") is None
):
fake_agent = sys.modules.get("agent") or types.ModuleType("agent")
if not hasattr(fake_agent, "__path__"):
fake_agent.__path__ = []
metadata = types.ModuleType("agent.model_metadata")
metadata.get_model_context_length = fake_get_model_context_length
fake_agent.model_metadata = metadata # type: ignore[attr-defined]
sys.modules["agent"] = fake_agent
sys.modules["agent.model_metadata"] = metadata
from api.routes import _normalize_provider_id, _resolve_compatible_session_model_state from api.routes import _normalize_provider_id, _resolve_compatible_session_model_state
+48 -10
View File
@@ -60,7 +60,29 @@ def _write_spawn_fake_agent(root: Path, *, run_job_body: str):
def _activate_spawn_fake_agent(fake_agent_root: Path): def _activate_spawn_fake_agent(fake_agent_root: Path):
"""Repoint the agent at ``fake_agent_root`` and return a restore callable.
This mutates process-global state (``HERMES_WEBUI_AGENT_DIR``, ``PYTHONPATH``,
``sys.path``) directly rather than via monkeypatch, because the callers run it
inside a spawned/forked child process. Leaving those mutations unrestored
poisons any later in-process consumer — a subsequent test that spawns
``server.py`` as a subprocess would inherit a stale ``HERMES_WEBUI_AGENT_DIR``
/ ``PYTHONPATH`` pointing at a torn-down fake dir and the child could not
import the real agent (the chronic full-suite
``test_tls_support::test_tls_startup_failure_fallback_to_http`` failure).
We snapshot the original env vars + ``sys.path`` up front and return a
``restore()`` the caller MUST invoke in a ``finally`` so the mutation never
outlives this activation. In the fork/spawn children this is belt-and-braces
(the child exits anyway); it also makes the helper safe for any in-process use.
"""
fake_path = str(fake_agent_root) fake_path = str(fake_agent_root)
_saved_env = {
k: os.environ.get(k)
for k in ("HERMES_WEBUI_AGENT_DIR", "PYTHONPATH")
}
_saved_sys_path = list(sys.path)
os.environ["HERMES_WEBUI_AGENT_DIR"] = fake_path os.environ["HERMES_WEBUI_AGENT_DIR"] = fake_path
existing = os.environ.get("PYTHONPATH", "") existing = os.environ.get("PYTHONPATH", "")
parts = [ parts = [
@@ -86,6 +108,16 @@ def _activate_spawn_fake_agent(fake_agent_root: Path):
): ):
sys.modules.pop(module_name, None) sys.modules.pop(module_name, None)
def _restore():
for _k, _v in _saved_env.items():
if _v is None:
os.environ.pop(_k, None)
else:
os.environ[_k] = _v
sys.path[:] = _saved_sys_path
return _restore
def _real_hermes_agent_editable_install_present() -> bool: def _real_hermes_agent_editable_install_present() -> bool:
"""Detect a developer-machine editable install of hermes-agent. """Detect a developer-machine editable install of hermes-agent.
@@ -133,12 +165,15 @@ def _large_cron_payload_runner(profile_home, result_queue):
" return True, payload, payload, None\n" " return True, payload, payload, None\n"
), ),
) )
_activate_spawn_fake_agent(fake_agent_root) _activate_restore = _activate_spawn_fake_agent(fake_agent_root)
import api.routes as routes try:
import api.routes as routes
success, output, final_response, error = routes._run_cron_job_in_profile_subprocess( success, output, final_response, error = routes._run_cron_job_in_profile_subprocess(
{"id": "large-payload"}, Path(profile_home) {"id": "large-payload"}, Path(profile_home)
) )
finally:
_activate_restore()
result_queue.put(("ok", success, len(output), len(final_response), error)) result_queue.put(("ok", success, len(output), len(final_response), error))
except BaseException as exc: # pragma: no cover - surfaced in parent process except BaseException as exc: # pragma: no cover - surfaced in parent process
import traceback import traceback
@@ -156,12 +191,15 @@ def _selected_profile_home_runner(profile_home, result_queue):
" return True, str(scheduler._hermes_home), 'final', None\n" " return True, str(scheduler._hermes_home), 'final', None\n"
), ),
) )
_activate_spawn_fake_agent(fake_agent_root) _activate_restore = _activate_spawn_fake_agent(fake_agent_root)
import api.routes as routes try:
import api.routes as routes
success, output, final_response, error = routes._run_cron_job_in_profile_subprocess( success, output, final_response, error = routes._run_cron_job_in_profile_subprocess(
{"id": "job1574"}, Path(profile_home) {"id": "job1574"}, Path(profile_home)
) )
finally:
_activate_restore()
result_queue.put(("ok", success, output, final_response, error)) result_queue.put(("ok", success, output, final_response, error))
except BaseException as exc: # pragma: no cover - surfaced in parent process except BaseException as exc: # pragma: no cover - surfaced in parent process
import traceback import traceback
+5 -1
View File
@@ -19,7 +19,11 @@ def _install_fake_skill_bundles(monkeypatch, *, bundles=None, resolver=None, bui
import sys import sys
agent_pkg = sys.modules.get("agent") or ModuleType("agent") agent_pkg = sys.modules.get("agent") or ModuleType("agent")
agent_pkg.__path__ = [] # `sys.modules.get(...)` returns the REAL agent package; emptying its
# __path__ in place strands it for the rest of the suite (later
# `from agent.<sub> import ...` — e.g. hermes_state's `agent.memory_manager`
# — then fails). monkeypatch.setattr snapshots and restores it on teardown.
monkeypatch.setattr(agent_pkg, "__path__", [], raising=False)
skill_bundles = ModuleType("agent.skill_bundles") skill_bundles = ModuleType("agent.skill_bundles")
skill_bundles.list_bundles = lambda: list(bundles or []) skill_bundles.list_bundles = lambda: list(bundles or [])
skill_bundles.resolve_bundle_command_key = resolver or (lambda name: None) skill_bundles.resolve_bundle_command_key = resolver or (lambda name: None)
+6 -2
View File
@@ -14,7 +14,9 @@ from tests.conftest import TEST_BASE, requires_agent_modules
def _install_fake_moa_config(monkeypatch, *, default_preset="moa-default", usage_text="Usage: /moa <prompt>"): def _install_fake_moa_config(monkeypatch, *, default_preset="moa-default", usage_text="Usage: /moa <prompt>"):
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # monkeypatch.setattr restores the REAL hermes_cli.__path__ on teardown;
# emptying it in place would strand the package for the rest of the suite.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
moa_config = ModuleType("hermes_cli.moa_config") moa_config = ModuleType("hermes_cli.moa_config")
def normalize_moa_config(cfg): def normalize_moa_config(cfg):
@@ -32,7 +34,9 @@ def _install_fake_moa_config(monkeypatch, *, default_preset="moa-default", usage
def _install_fake_hermes_config(monkeypatch, cfg_data=None): def _install_fake_hermes_config(monkeypatch, cfg_data=None):
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # monkeypatch.setattr restores the REAL hermes_cli.__path__ on teardown;
# emptying it in place would strand the package for the rest of the suite.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
config_mod = ModuleType("hermes_cli.config") config_mod = ModuleType("hermes_cli.config")
def load_config(): def load_config():
+9 -3
View File
@@ -84,7 +84,9 @@ def test_resolve_moa_config_uses_selected_preset(monkeypatch):
} }
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # monkeypatch.setattr restores the REAL hermes_cli.__path__ on teardown;
# emptying it in place would strand the package for the rest of the suite.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
moa_config = ModuleType("hermes_cli.moa_config") moa_config = ModuleType("hermes_cli.moa_config")
def normalize_moa_config(raw): def normalize_moa_config(raw):
@@ -126,7 +128,9 @@ def test_resolve_moa_config_falls_back_when_preset_resolution_raises(monkeypatch
} }
} }
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # monkeypatch.setattr restores the REAL hermes_cli.__path__ on teardown;
# emptying it in place would strand the package for the rest of the suite.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
moa_config = ModuleType("hermes_cli.moa_config") moa_config = ModuleType("hermes_cli.moa_config")
def normalize_moa_config(raw): def normalize_moa_config(raw):
@@ -167,7 +171,9 @@ def test_resolve_moa_config_ignores_non_dict_preset_result(monkeypatch):
} }
} }
hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli") hermes_cli_pkg = sys.modules.get("hermes_cli") or ModuleType("hermes_cli")
hermes_cli_pkg.__path__ = [] # monkeypatch.setattr restores the REAL hermes_cli.__path__ on teardown;
# emptying it in place would strand the package for the rest of the suite.
monkeypatch.setattr(hermes_cli_pkg, "__path__", [], raising=False)
moa_config = ModuleType("hermes_cli.moa_config") moa_config = ModuleType("hermes_cli.moa_config")
def normalize_moa_config(raw): def normalize_moa_config(raw):