Files
hermes-webui/tests/test_pytest_execv_guard.py
T
Hermes Agent 20bd845416 fix(tests): permanent os.execv guard to stop pytest self-restart loop
api.updates._schedule_restart() spawns a daemon thread that calls
os.execv() after a short sleep. Tests in test_update_banner_fixes.py
monkeypatch os.execv to a no-op, but monkeypatch teardown can win the
race against the daemon thread — when the thread wakes up after
teardown, the real os.execv is back, and it re-execs pytest with the
original argv. From the outside this looked like pytest hanging at 99%
and then restarting the entire suite from 0% in a loop.

The fix shadows os.execv with a permanent no-op wrapper at conftest
module-import time, so late-firing daemon threads can't escape. Tests
that need to verify execv was called still patch it themselves; their
patches sit on top of the wrapper for their lifetime.

Also adds tests/test_pytest_execv_guard.py to pin the guard against
future conftest refactors.
2026-05-16 19:32:49 +00:00

36 lines
1.6 KiB
Python

"""Regression guard for the pytest "hangs at 99% then restarts from 0%" loop.
Root cause documented in tests/conftest.py — daemon threads spawned by
api.updates._schedule_restart() can fire os.execv() AFTER monkeypatch
teardown restores the real os.execv, which re-execs the entire pytest
process. The conftest installs a permanent no-op wrapper on os.execv that
shadows any late-firing daemon thread.
This test pins the guard so a future conftest refactor can't silently
remove it.
"""
import os
def test_conftest_installs_permanent_execv_guard():
"""os.execv must be replaced by the conftest's safe no-op wrapper."""
# The wrapper is named `_pytest_session_safe_execv` in conftest.py.
# Verify the module attribute now points to that wrapper, not the real
# libc-bound function.
assert os.execv.__name__ == '_pytest_session_safe_execv', (
f"os.execv must be the conftest-installed pytest-safe no-op, but "
f"resolves to {os.execv!r}. Did a recent conftest refactor remove "
f"the guard? See conftest.py § 'Permanent os.execv guard for the "
f"pytest session' — without it, late-firing _schedule_restart "
f"daemon threads re-exec pytest and the suite loops forever."
)
def test_safe_execv_returns_none_does_not_exec():
"""The wrapper must be a true no-op — it must not raise, exec, or block."""
# Pass deliberately bogus args to confirm the wrapper drops them rather
# than passing them through to the real execv.
result = os.execv('/nonexistent/binary/path/that/should/not/be/executed',
['/nonexistent/binary/path/that/should/not/be/executed'])
assert result is None