Files
hermes-agent/tests/agent/test_deadline_fork_race.py

67 lines
2.3 KiB
Python

"""Real-process deadline regression; the probe owns and reaps its whole subtree."""
import os
from pathlib import Path
import subprocess
import signal
import sys
import time
import pytest
@pytest.mark.linux_only
def test_cron_timeout_closes_the_descendant_snapshot_fork_window(tmp_path):
probe = Path(__file__).resolve().parents[2] / "evals" / "cron_timeout_fork_race.py"
result = subprocess.run(
[sys.executable, str(probe)],
env={**os.environ, "HOME": str(tmp_path), "HERMES_HOME": str(tmp_path)},
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=35,
)
assert result.returncode == 0, result.stdout + result.stderr
@pytest.mark.linux_only
@pytest.mark.parametrize("initially_stopped", [False, True])
def test_refused_hard_kill_preserves_the_targets_original_run_state(monkeypatch, initially_stopped):
import psutil
from agent.deadline import kill_process_tree
proc = subprocess.Popen(
[sys.executable, "-c", "import time; time.sleep(30)"],
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
start_new_session=True,
)
process = psutil.Process(proc.pid)
real_kill, real_killpg = os.kill, os.killpg
def refuse_owned_kill(pid, sig):
assert pid == proc.pid
if sig == signal.SIGKILL:
raise PermissionError("owned probe: hard kill refused")
return real_kill(pid, sig)
def refuse_owned_group(pgid, sig):
assert pgid == proc.pid
if sig == signal.SIGKILL:
raise PermissionError("owned probe: hard kill refused")
return real_killpg(pgid, sig)
def wait_for_state(stopped):
deadline = time.monotonic() + 5
while (process.status() == psutil.STATUS_STOPPED) != stopped and time.monotonic() < deadline:
time.sleep(0.005)
assert (process.status() == psutil.STATUS_STOPPED) == stopped
try:
if initially_stopped:
process.suspend()
wait_for_state(True)
with monkeypatch.context() as patcher:
patcher.setattr(os, "kill", refuse_owned_kill)
patcher.setattr(os, "killpg", refuse_owned_group)
assert kill_process_tree(proc.pid) is False
wait_for_state(initially_stopped)
finally:
proc.kill()
proc.wait(timeout=5)