fix: scope gateway stop/restart to current profile, --all for global kill
gateway stop and restart previously called kill_gateway_processes() which scans ps aux and kills ALL gateway processes across all profiles. Starting a profile gateway would nuke the main one (and vice versa). Now: - hermes gateway stop → only kills the current profile's gateway (PID file) - hermes -p work gateway stop → only kills the 'work' profile's gateway - hermes gateway stop --all → kills every gateway process (old behavior) - hermes gateway restart → profile-scoped for manual fallback path - hermes update → discovers and restarts ALL profile gateways (systemctl list-units hermes-gateway*) since the code update is shared Added stop_profile_gateway() which uses the HERMES_HOME-scoped PID file instead of global process scanning.
This commit is contained in:
@@ -103,7 +103,9 @@ class TestGeneratedSystemdUnits:
|
||||
|
||||
|
||||
class TestGatewayStopCleanup:
|
||||
def test_stop_sweeps_manual_gateway_processes_after_service_stop(self, tmp_path, monkeypatch):
|
||||
def test_stop_only_kills_current_profile_by_default(self, tmp_path, monkeypatch):
|
||||
"""Without --all, stop uses systemd (if available) and does NOT call
|
||||
the global kill_gateway_processes()."""
|
||||
unit_path = tmp_path / "hermes-gateway.service"
|
||||
unit_path.write_text("unit\n", encoding="utf-8")
|
||||
|
||||
@@ -123,6 +125,31 @@ class TestGatewayStopCleanup:
|
||||
|
||||
gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop"))
|
||||
|
||||
assert service_calls == ["stop"]
|
||||
# Global kill should NOT be called without --all
|
||||
assert kill_calls == []
|
||||
|
||||
def test_stop_all_sweeps_all_gateway_processes(self, tmp_path, monkeypatch):
|
||||
"""With --all, stop uses systemd AND calls the global kill_gateway_processes()."""
|
||||
unit_path = tmp_path / "hermes-gateway.service"
|
||||
unit_path.write_text("unit\n", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
|
||||
|
||||
service_calls = []
|
||||
kill_calls = []
|
||||
|
||||
monkeypatch.setattr(gateway_cli, "systemd_stop", lambda system=False: service_calls.append("stop"))
|
||||
monkeypatch.setattr(
|
||||
gateway_cli,
|
||||
"kill_gateway_processes",
|
||||
lambda force=False: kill_calls.append(force) or 2,
|
||||
)
|
||||
|
||||
gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop", **{"all": True}))
|
||||
|
||||
assert service_calls == ["stop"]
|
||||
assert kill_calls == [False]
|
||||
|
||||
|
||||
@@ -47,6 +47,22 @@ def _make_run_side_effect(
|
||||
if "rev-list" in joined:
|
||||
return subprocess.CompletedProcess(cmd, 0, stdout=f"{commit_count}\n", stderr="")
|
||||
|
||||
# systemctl list-units hermes-gateway* — discover all gateway services
|
||||
if "systemctl" in joined and "list-units" in joined:
|
||||
if "--user" in joined and systemd_active:
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0,
|
||||
stdout="hermes-gateway.service loaded active running Hermes Gateway\n",
|
||||
stderr="",
|
||||
)
|
||||
elif "--user" not in joined and system_service_active:
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0,
|
||||
stdout="hermes-gateway.service loaded active running Hermes Gateway\n",
|
||||
stderr="",
|
||||
)
|
||||
return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="")
|
||||
|
||||
# systemctl is-active — distinguish --user from system scope
|
||||
if "systemctl" in joined and "is-active" in joined:
|
||||
if "--user" in joined:
|
||||
@@ -305,15 +321,14 @@ class TestCmdUpdateLaunchdRestart:
|
||||
launchctl_loaded=True,
|
||||
)
|
||||
|
||||
# Mock get_running_pid to return a PID
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"), \
|
||||
patch.object(gateway_cli, "launchd_restart") as mock_launchd_restart:
|
||||
# Mock launchd_restart + find_gateway_pids (new code discovers all gateways)
|
||||
with patch.object(gateway_cli, "launchd_restart") as mock_launchd_restart, \
|
||||
patch.object(gateway_cli, "find_gateway_pids", return_value=[]):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
assert "Restarting gateway service" in captured
|
||||
assert "Restart it with: hermes gateway run" not in captured
|
||||
assert "Restarted" in captured
|
||||
assert "Restart manually: hermes gateway run" not in captured
|
||||
mock_launchd_restart.assert_called_once_with()
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@@ -321,7 +336,7 @@ class TestCmdUpdateLaunchdRestart:
|
||||
def test_update_without_launchd_shows_manual_restart(
|
||||
self, mock_run, _mock_which, mock_args, capsys, tmp_path, monkeypatch,
|
||||
):
|
||||
"""When no service manager is running, update should show the manual restart hint."""
|
||||
"""When no service manager is running but manual gateway is found, show manual restart hint."""
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "is_macos", lambda: True,
|
||||
)
|
||||
@@ -336,14 +351,13 @@ class TestCmdUpdateLaunchdRestart:
|
||||
launchctl_loaded=False,
|
||||
)
|
||||
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"), \
|
||||
# Simulate a manual gateway process found by find_gateway_pids
|
||||
with patch.object(gateway_cli, "find_gateway_pids", return_value=[12345]), \
|
||||
patch("os.kill"):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
assert "Restart it with: hermes gateway run" in captured
|
||||
assert "Gateway restarted via launchd" not in captured
|
||||
assert "Restart manually: hermes gateway run" in captured
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
@@ -360,13 +374,11 @@ class TestCmdUpdateLaunchdRestart:
|
||||
systemd_active=True,
|
||||
)
|
||||
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"), \
|
||||
patch("os.kill"):
|
||||
with patch.object(gateway_cli, "find_gateway_pids", return_value=[]):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
assert "Gateway restarted" in captured
|
||||
assert "Restarted hermes-gateway" in captured
|
||||
# Verify systemctl restart was called
|
||||
restart_calls = [
|
||||
c for c in mock_run.call_args_list
|
||||
@@ -422,13 +434,11 @@ class TestCmdUpdateSystemService:
|
||||
system_service_active=True,
|
||||
)
|
||||
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"):
|
||||
with patch.object(gateway_cli, "find_gateway_pids", return_value=[]):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
assert "system gateway service" in captured.lower()
|
||||
assert "Gateway restarted (system service)" in captured
|
||||
assert "Restarted hermes-gateway" in captured
|
||||
# Verify systemctl restart (no --user) was called
|
||||
restart_calls = [
|
||||
c for c in mock_run.call_args_list
|
||||
@@ -440,10 +450,10 @@ class TestCmdUpdateSystemService:
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
def test_update_system_service_restart_failure_shows_sudo_hint(
|
||||
def test_update_system_service_restart_failure_shows_error(
|
||||
self, mock_run, _mock_which, mock_args, capsys, monkeypatch,
|
||||
):
|
||||
"""When system service restart fails (e.g. no root), show sudo hint."""
|
||||
"""When system service restart fails, show the failure message."""
|
||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
||||
|
||||
@@ -454,19 +464,18 @@ class TestCmdUpdateSystemService:
|
||||
system_restart_rc=1,
|
||||
)
|
||||
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"):
|
||||
with patch.object(gateway_cli, "find_gateway_pids", return_value=[]):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
assert "sudo systemctl restart" in captured
|
||||
assert "Failed to restart" in captured
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
def test_user_service_takes_priority_over_system(
|
||||
self, mock_run, _mock_which, mock_args, capsys, monkeypatch,
|
||||
):
|
||||
"""When both user and system services are active, user wins."""
|
||||
"""When both user and system services are active, both are restarted."""
|
||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
||||
|
||||
@@ -476,12 +485,9 @@ class TestCmdUpdateSystemService:
|
||||
system_service_active=True,
|
||||
)
|
||||
|
||||
with patch("gateway.status.get_running_pid", return_value=12345), \
|
||||
patch("gateway.status.remove_pid_file"), \
|
||||
patch("os.kill"):
|
||||
with patch.object(gateway_cli, "find_gateway_pids", return_value=[]):
|
||||
cmd_update(mock_args)
|
||||
|
||||
captured = capsys.readouterr().out
|
||||
# Should restart via user service, not system
|
||||
assert "Gateway restarted." in captured
|
||||
assert "(system service)" not in captured
|
||||
# Both scopes are discovered and restarted
|
||||
assert "Restarted hermes-gateway" in captured
|
||||
|
||||
Reference in New Issue
Block a user