The detached watcher runs as `<python> -c <program>`, so `hermes_cli` resolved only because update_completion happened to spawn it with cwd=<checkout>. The program now puts the checkout on sys.path itself, and the bare-Python test runs it from an unrelated cwd (red without the sys.path line). Also drops the two unused re-export aliases in gateway.status (`_posix_is_zombie`, `_pid_exists_win32_ctypes`): nothing imports them and neither is in the old-updater compat surface.
59 lines
2.3 KiB
Python
59 lines
2.3 KiB
Python
"""The detached gateway restart watcher must survive on an interpreter without the dependency env.
|
|
|
|
``hermes update`` finishes on the bare store Python (dependencies come from ``hermes_bootstrap``,
|
|
not site-packages) and spawns the watcher as ``sys.executable -c <watcher>``. A watcher that imports
|
|
a third-party-backed Hermes module (``gateway.status`` -> ``utils`` -> ``ruamel``) dies before it
|
|
relaunches the gateway, leaving a manually started gateway down after every update.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import gateway
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
|
|
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="POSIX shell wrapper stands in for the bare interpreter")
|
|
|
|
|
|
def _dead_pid() -> int:
|
|
proc = subprocess.Popen([sys.executable, "-c", "pass"])
|
|
proc.wait()
|
|
return proc.pid
|
|
|
|
|
|
def test_restart_watcher_relaunches_from_an_interpreter_without_site_packages(tmp_path, monkeypatch):
|
|
# A python that sees the checkout but no third-party packages, like the bare store Python.
|
|
bare = tmp_path / "bare-python"
|
|
bare.write_text(f'#!/bin/sh\nunset PYTHONPATH\nexec "{sys.executable}" -S "$@"\n', encoding="utf-8")
|
|
bare.chmod(0o755)
|
|
probe = subprocess.run([str(bare), "-c", "import ruamel.yaml"], cwd=REPO, capture_output=True, text=True)
|
|
assert probe.returncode != 0, "premise: the stand-in interpreter must not see site-packages"
|
|
|
|
marker = tmp_path / "respawned"
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
# The watcher must find the checkout on its own, not through an inherited cwd.
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setattr(sys, "executable", str(bare))
|
|
relaunch = [str(bare), "-c", f"open({str(marker)!r}, 'w').write('ok')"]
|
|
|
|
assert gateway._spawn_gateway_restart_watcher(_dead_pid(), relaunch, host=False)
|
|
|
|
deadline = time.monotonic() + 30
|
|
while time.monotonic() < deadline and not marker.exists():
|
|
time.sleep(0.1)
|
|
stdio = home / "logs" / "gateway-stdio.log"
|
|
assert marker.exists(), (
|
|
"the restart watcher never relaunched the gateway command"
|
|
+ (f"; stdio log:\n{stdio.read_text(errors='replace')}" if stdio.exists() else ""))
|
|
assert os.path.getsize(marker) == 2
|