Files
aiturk-hermes-ide/tests/hermes_cli/test_update_gateway_restart_fallback.py

121 lines
4.3 KiB
Python

"""Regression coverage for #88654.
``hermes update`` relaunches manually-run profile gateways through
``_prepare_profile_gateway_update_restart``. When the profile-derived
relaunch could not be armed the helper returned ``None``, and the update
path's response to ``None`` was a bare ``continue`` -- so the gateway was
neither relaunched, nor stopped, nor mentioned. It kept serving from
pre-update modules while the new code sat on disk, and every lazy import
from that point mixed versions.
The helper now falls back to replaying the process's own captured command
line via ``launch_detached_gateway_restart_by_cmdline`` -- the companion
that already exists for gateways with no profile mapping, and that the
Windows post-update path already uses for exactly this case.
"""
import pytest
import hermes_cli.gateway as gateway
_ARGV = ["python", "-m", "hermes_cli.main", "gateway", "run"]
def _stub_argv(monkeypatch, argv):
monkeypatch.setattr(gateway, "_capture_gateway_argv", lambda _pid: argv)
def test_profile_relaunch_wins_and_skips_the_cmdline_replay(monkeypatch):
"""The existing path is unchanged: a profile relaunch short-circuits."""
_stub_argv(monkeypatch, list(_ARGV))
monkeypatch.setattr(
gateway, "launch_detached_profile_gateway_restart", lambda *_a: True
)
monkeypatch.setattr(
gateway,
"launch_detached_gateway_restart_by_cmdline",
lambda *_a: pytest.fail("cmdline replay must not run when the profile path works"),
)
assert gateway._prepare_profile_gateway_update_restart("fitness", 4242) == "detached"
def test_falls_back_to_cmdline_replay_when_profile_relaunch_fails(monkeypatch):
"""The #88654 fix: an unarmable profile relaunch still gets the gateway back."""
_stub_argv(monkeypatch, list(_ARGV))
monkeypatch.setattr(
gateway, "launch_detached_profile_gateway_restart", lambda *_a: False
)
seen = []
def _by_cmdline(pid, argv):
seen.append((pid, argv))
return True
monkeypatch.setattr(
gateway, "launch_detached_gateway_restart_by_cmdline", _by_cmdline
)
assert (
gateway._prepare_profile_gateway_update_restart("fitness", 4242)
== "detached-cmdline"
)
# Replays the process's OWN argv, which is the whole point: the profile
# could not be mapped back to a run argv, so the captured one is the only
# faithful description of how to restart it.
assert seen == [(4242, _ARGV)]
def test_returns_none_when_there_is_no_argv_to_replay(monkeypatch):
"""No captured argv means no honest way to relaunch; caller must be told."""
_stub_argv(monkeypatch, [])
monkeypatch.setattr(
gateway, "launch_detached_profile_gateway_restart", lambda *_a: False
)
monkeypatch.setattr(
gateway,
"launch_detached_gateway_restart_by_cmdline",
lambda *_a: pytest.fail("must not replay an empty argv"),
)
assert gateway._prepare_profile_gateway_update_restart("fitness", 4242) is None
def test_returns_none_when_both_relaunch_paths_fail(monkeypatch):
"""Both mechanisms failing is still reported as None, not a false success."""
_stub_argv(monkeypatch, list(_ARGV))
monkeypatch.setattr(
gateway, "launch_detached_profile_gateway_restart", lambda *_a: False
)
monkeypatch.setattr(
gateway, "launch_detached_gateway_restart_by_cmdline", lambda *_a: False
)
assert gateway._prepare_profile_gateway_update_restart("fitness", 4242) is None
def test_external_supervisor_still_short_circuits_before_any_replay(monkeypatch):
"""Guardrail: the supervisor hand-back must not gain a replay behind it.
Replaying the argv for an externally supervised gateway would escape the
manager and race its replacement process, which is the exact hazard the
supervisor branch exists to avoid.
"""
_stub_argv(monkeypatch, _ARGV + ["--external-supervisor"])
monkeypatch.setattr(
gateway,
"launch_detached_profile_gateway_restart",
lambda *_a: pytest.fail("detached watcher must not be launched"),
)
monkeypatch.setattr(
gateway,
"launch_detached_gateway_restart_by_cmdline",
lambda *_a: pytest.fail("cmdline replay must not be launched"),
)
assert (
gateway._prepare_profile_gateway_update_restart("fitness", 4242)
== "external-supervisor"
)