mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(test): hermetic env-detection tests — pin container/supervisor/HOME probes (#422)
The restart-routing, systemd-support, and subprocess-HOME tests asserted
branch behavior but left part of the real probe surface unmocked, so they
fail when the suite itself runs inside a container (self-hosted CI) or a
launchd-descended shell:
- /restart routing tests: the handler also consults the real /.dockerenv —
extract the inline probe to gateway.restart.is_container_restart_context()
(patchable seam, no behavior change) and pin it False; scrub ALL four
supervisor env markers (ambient XPC_SERVICE_NAME on macOS flipped one).
- supports_systemd_services tests: pin shutil.which('systemctl') and
is_container() so the test asserts the branch, not the host.
- copilot ACP real-HOME test: pin is_container() (auto mode prefers profile
home in containers) and scrub ambient HERMES_REAL_HOME/TERMINAL_HOME_MODE.
97 tests green on macOS dev box AND inside a docker CI runner container.
Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
This commit is contained in:
parent
2c37b1af25
commit
bcec6c8d39
6 changed files with 46 additions and 3 deletions
|
|
@ -45,6 +45,18 @@ def is_gateway_supervisor_process(
|
|||
}
|
||||
|
||||
|
||||
def is_container_restart_context() -> bool:
|
||||
"""Return whether the gateway is running inside a container for restart
|
||||
routing purposes (Docker/Podman ⇒ the detached setsid path dies with the
|
||||
cgroup; exit-75 service restart is the only viable path).
|
||||
|
||||
Extracted from the inline probe in the /restart handler so tests can mock
|
||||
container detection hermetically — a real ``/.dockerenv`` on a
|
||||
containerized CI runner otherwise flips the routing under the test.
|
||||
"""
|
||||
return os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")
|
||||
|
||||
|
||||
def parse_restart_drain_timeout(raw: object) -> float:
|
||||
"""Parse a configured drain timeout, falling back to the shared default."""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1616,10 +1616,13 @@ class GatewaySlashCommandsMixin:
|
|||
# Native supervisor markers cover direct systemd/launchd starts. The
|
||||
# explicit marker covers wrappers such as ``sudo env -i`` that strip
|
||||
# those markers before execing the foreground gateway.
|
||||
from gateway.restart import is_gateway_supervisor_process
|
||||
from gateway.restart import (
|
||||
is_container_restart_context,
|
||||
is_gateway_supervisor_process,
|
||||
)
|
||||
|
||||
_under_service = is_gateway_supervisor_process()
|
||||
_in_container = os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")
|
||||
_in_container = is_container_restart_context()
|
||||
if _under_service or _in_container:
|
||||
self.request_restart(detached=False, via_service=True)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -197,6 +197,16 @@ def test_run_prompt_preserves_real_home_when_profile_home_available(monkeypatch,
|
|||
|
||||
monkeypatch.setenv("HOME", str(real_home))
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
# Hermeticity: an ambient HERMES_REAL_HOME (exported by Hermes' own
|
||||
# terminal contract on dev boxes) outranks HOME in the candidate ladder,
|
||||
# and an ambient TERMINAL_HOME_MODE would change the policy under test.
|
||||
monkeypatch.delenv("HERMES_REAL_HOME", raising=False)
|
||||
monkeypatch.delenv("TERMINAL_HOME_MODE", raising=False)
|
||||
# Hermeticity: get_subprocess_home()'s auto mode prefers the profile home
|
||||
# when is_container() is True — on a containerized CI runner that real
|
||||
# probe flips the resolution this test asserts. The host/VM branch is the
|
||||
# contract under test; pin containment off.
|
||||
monkeypatch.setattr("hermes_constants.is_container", lambda: False)
|
||||
|
||||
captured = {}
|
||||
client = _make_home_client(tmp_path)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ async def test_restart_command_while_busy_requests_drain_without_interrupt(monke
|
|||
# Ensure INVOCATION_ID is NOT set — systemd sets this in service mode,
|
||||
# which changes the restart call signature.
|
||||
monkeypatch.delenv("INVOCATION_ID", raising=False)
|
||||
monkeypatch.delenv("XPC_SERVICE_NAME", raising=False)
|
||||
monkeypatch.delenv("HERMES_S6_SUPERVISED_CHILD", raising=False)
|
||||
monkeypatch.delenv("HERMES_GATEWAY_EXTERNAL_SUPERVISOR", raising=False)
|
||||
# Hermeticity: neutralize the real container probe (see
|
||||
# test_restart_service_detection.py) — /.dockerenv on a containerized CI
|
||||
# runner would otherwise route via_service=True under this test.
|
||||
monkeypatch.setattr(
|
||||
"gateway.restart.is_container_restart_context", lambda: False
|
||||
)
|
||||
runner, _adapter = make_restart_runner()
|
||||
runner.request_restart = MagicMock(return_value=True)
|
||||
event = MessageEvent(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ def _make_runner_with_mock_restart(tmp_path, monkeypatch):
|
|||
monkeypatch.delenv("XPC_SERVICE_NAME", raising=False)
|
||||
monkeypatch.delenv("HERMES_S6_SUPERVISED_CHILD", raising=False)
|
||||
monkeypatch.delenv(EXTERNAL_GATEWAY_SUPERVISOR_ENV, raising=False)
|
||||
# Hermeticity: neutralize the real container probe — on a containerized
|
||||
# CI runner /.dockerenv exists and would route every case via_service=True
|
||||
# regardless of the env markers under test (the detection under test is
|
||||
# the SUPERVISOR markers, not the runner's own containment).
|
||||
monkeypatch.setattr(
|
||||
"gateway.restart.is_container_restart_context", lambda: False
|
||||
)
|
||||
runner, _adapter = make_restart_runner()
|
||||
runner.request_restart = MagicMock(return_value=True)
|
||||
return runner
|
||||
|
|
|
|||
|
|
@ -63,11 +63,13 @@ class TestSupportsSystemdServicesWSL:
|
|||
"""WSL + working systemd → True."""
|
||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||
monkeypatch.setattr(
|
||||
gateway.shutil, "which", lambda _name: "/usr/bin/systemctl"
|
||||
)
|
||||
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
|
||||
monkeypatch.setattr(gateway, "_wsl_systemd_operational", lambda: True)
|
||||
assert gateway.supports_systemd_services() is True
|
||||
|
||||
|
||||
def test_termux_still_excluded(self, monkeypatch):
|
||||
"""Termux → False regardless of WSL status."""
|
||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue