Files
hermes-agent/tests/test_session_daemon_reaper.py
kshitijk4poor d0001c5ba2 test: reap session-leaked Hermes daemons on macOS too
The session-end reaper (78eff4628a, #106742) found leftover processes by
reading /proc/<pid>/environ, so on macOS it scanned nothing. There, e2e
tests that make a client start its own gateway (e.g. the C7 two-tenant
Desktop backend) leave a `gateway run --idle-exit` daemon running as a
launchd job, outside the pytest process tree. The test homes configure
an API server, so the gateway's idle exit keeps reporting
"messaging ['api_server']" and the daemon never exits: ~320 MB per run.
One dev Mac had 22 of them, 11-26 h old.

Read each process's exec-time environment through psutil on every POSIX
host instead, and run the reaper's own test on macOS. That test times
out on base on macOS and passes here; a real C7 run leaves no gateway
behind afterwards.
2026-11-05 17:07:37 +05:30

48 lines
2.1 KiB
Python

"""The session teardown stops processes left running against this run's Hermes homes.
A test that runs a real ``hermes chat -q`` child gets a grandchild ``gateway run`` in its own
session; no per-test subprocess patch sees it, and pytest's tmp cleanup removes only directories.
"""
from __future__ import annotations
import os
import subprocess
import sys
from types import SimpleNamespace
import pytest
from tests import conftest as suite_conftest
from tests.waiting import wait_until
def _detached(env_home, marker):
code = "import time\nwhile True: time.sleep(0.2)\n"
env = {**os.environ, "HERMES_HOME": str(env_home), "LIVE_REAP_MARKER": marker}
return subprocess.Popen([sys.executable, "-c", code], env=env, start_new_session=True,
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
@pytest.mark.platforms("linux", "macos")
@pytest.mark.live_system_guard_bypass # signals only the two children this test spawned
def test_session_teardown_terminates_daemons_under_the_session_basetemp_only(tmp_path):
basetemp, elsewhere = tmp_path / "basetemp", tmp_path / "elsewhere"
(basetemp / "t0" / "hermes").mkdir(parents=True)
elsewhere.mkdir()
inside = _detached(basetemp / "t0" / "hermes", "inside")
outside = _detached(elsewhere, "outside")
try:
# Both children have exec'd with their HERMES_HOME (what the reaper reads via psutil environ()).
wait_until(lambda: {inside.pid, outside.pid} <= set(suite_conftest._processes_with_hermes_home_under(
[basetemp, elsewhere])), timeout=10, what='both detached children started')
config = SimpleNamespace(_tmp_path_factory=SimpleNamespace(_basetemp=basetemp))
reaped = suite_conftest._reap_session_hermes_processes(config)
assert inside.pid in reaped and outside.pid not in reaped
assert inside.wait(timeout=5) is not None
assert outside.poll() is None, "a process outside this session's homes was signalled"
finally:
for proc in (inside, outside):
if proc.poll() is None:
proc.kill()
proc.wait(timeout=5)