Files
hermes-agent/tests/hermes_cli/test_gateway_multiplex_status.py
T
teknium1 9188e708b3 fix(gateway): served_profiles bind to a verified gateway identity, not bare PID existence
`live_default_gateway_pid()` trusted `gateway.pid` + `_pid_exists`, so a stale
default record whose PID an unrelated process had recycled kept its old
`served_profiles` authoritative: `hermes -p X gateway start` exited 78 and
`status` said "running via multiplexer" for a gateway long gone (review of
#108352, finding D). The salvaged #110167 fallback inherited the same bare
check for the pid-file branch.

One helper now answers "which live gateway owns this home?" for every reader:
`gateway.status.live_gateway_pid_for_home` = scoped `get_running_pid` (pid file
+ runtime lock, start-time reuse guard, live gateway command line, home match)
then `get_runtime_status_running_pid(..., expected_home=home)` (honours
`gateway_state` stopped/startup_failed). `gateway_multiplex_served`,
`gateway_migrate._live_gateway_pid` and the `hermes update` inventory's
gateway_state.json fallback (#109680: a `stopped` record + recycled PID
fabricated a phantom runtime, so the update exited partial) all route through
it. Tests that impersonated a gateway with this pytest PID now wear a gateway
command line instead of stubbing `_pid_exists`.
2026-09-13 15:41:01 -07:00

85 lines
3.4 KiB
Python

"""PR #69118: a named profile served by the default multiplexer reports as running.
``hermes gateway status`` / ``gateway list`` / ``profile list`` keyed liveness
off the profile's own gateway.pid, so a satellite profile served by the default
multiplexer showed "not running" even though the multiplexer was its live
inbound process. All three now consult the same
``named_profile_served_by_running_multiplexer()`` lookup the start guard and
cron liveness use.
"""
from __future__ import annotations
import io
import os
from contextlib import redirect_stdout
from types import SimpleNamespace
def _fake_multiplexer(monkeypatch, tmp_path, *, multiplex: bool, pid_file: bool = True):
"""A live default gateway at ``tmp_path`` whose runtime record names this process; the process passes
the identity check because its command line reads as a gateway's. ``pid_file=False`` models a
launch-service gateway whose ``gateway.pid`` was unlinked while it kept serving."""
import json
import hermes_constants
import gateway.status as status
(tmp_path / "profiles" / "beta").mkdir(parents=True)
(tmp_path / "config.yaml").write_text(
f"gateway:\n multiplex_profiles: {'true' if multiplex else 'false'}\n"
)
if pid_file:
(tmp_path / "gateway.pid").write_text(str(os.getpid()))
(tmp_path / "gateway_state.json").write_text(json.dumps({
"pid": os.getpid(), "kind": "hermes-gateway", "gateway_state": "running",
"start_time": status._get_process_start_time(os.getpid()), "hermes_home": str(tmp_path),
}))
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "profiles" / "beta"))
monkeypatch.setattr(hermes_constants, "_default_hermes_root_memo", None)
monkeypatch.setattr(
status, "_read_process_cmdline", lambda pid: "python -m hermes_cli.main gateway run --replace"
)
def _run_status():
from hermes_cli import gateway as gw
buf = io.StringIO()
with redirect_stdout(buf):
gw._gateway_command_inner(
SimpleNamespace(gateway_command="status", deep=False, full=False, system=False)
)
return buf.getvalue().splitlines()[0]
def test_served_named_profile_reports_running(monkeypatch, tmp_path):
from hermes_cli.profiles import list_profiles
_fake_multiplexer(monkeypatch, tmp_path, multiplex=True)
beta = next(p for p in list_profiles() if p.name == "beta")
assert beta.gateway_running is True
assert _run_status().startswith("✓ Gateway is running via the default-profile multiplexer")
def test_unserved_named_profile_still_reports_stopped(monkeypatch, tmp_path):
from hermes_cli.profiles import list_profiles
_fake_multiplexer(monkeypatch, tmp_path, multiplex=False)
beta = next(p for p in list_profiles() if p.name == "beta")
assert beta.gateway_running is False
assert _run_status().startswith("✗ Gateway is not running")
def test_served_named_profile_reports_running_without_default_pid_file(monkeypatch, tmp_path):
"""A live multiplexer whose PID file is missing still serves the profile it ticks (#110166)."""
from hermes_cli.profiles import list_profiles
_fake_multiplexer(monkeypatch, tmp_path, multiplex=True, pid_file=False)
beta = next(p for p in list_profiles() if p.name == "beta")
assert beta.gateway_running is True
assert _run_status().startswith("✓ Gateway is running via the default-profile multiplexer")