fix(state): count the test runner only when a Python interpreter runs it

The ancestry guard matched run_tests_parallel.py against every argv token's
basename, so a process that merely named the file looked like a test run:
'hermes chat -q "fix the timeout in scripts/run_tests_parallel.py"' or
'nvim scripts/run_tests_parallel.py', and every hermes their descendants start
would then refuse the production state.db. Only the script a Python
interpreter runs counts now.
This commit is contained in:
kshitijk4poor authored and kshitij committed 2026-11-04 02:01:54 +05:30
1 parent 22207eb936
commit 4273f3822d
2 files changed
+22 -11

No files matched your search

+16 -11
View File
@@ -61,12 +61,13 @@ def _running_under_pytest() -> bool:
#: pytest launcher names, matched against each argv token's *basename* so
#: ``/tmp/pytest-of-dev/...`` paths cannot false-positive. ``run_tests_parallel.py``
#: is the repo's test runner: under its fork server (HERMES_TEST_FORKSERVER) a test
#: process has no ``python -m pytest`` ancestor, only the runner.
_PYTEST_LAUNCHER_NAMES = frozenset(
{"pytest", "py.test", "pytest.exe", "py.test.exe", "run_tests_parallel.py"}
)
#: ``/tmp/pytest-of-dev/...`` paths cannot false-positive.
_PYTEST_LAUNCHER_NAMES = frozenset({"pytest", "py.test", "pytest.exe", "py.test.exe"})
#: The repo's test runner: under its fork server (HERMES_TEST_FORKSERVER) a test
#: process has no ``python -m pytest`` ancestor, only ``python .../run_tests_parallel.py``.
#: It counts only as the script a Python interpreter runs: a prompt or an editor
#: naming the file (``hermes chat -q "fix scripts/run_tests_parallel.py"``) is no test.
_TEST_RUNNER_SCRIPT = "run_tests_parallel.py"
#: Memoised ancestry answer: the tree above us doesn't change; keep the hot path free.
_PYTEST_ANCESTOR: Optional[bool] = None
@@ -79,15 +80,19 @@ def _process_looks_like_pytest(proc: Any) -> bool:
cmdline = proc.cmdline() or []
except Exception:
return False
names = []
for arg in cmdline:
try:
# Split on both separators on every host so the answer is platform-independent.
name = str(arg).strip('"').strip("'").replace("\\", "/").rsplit("/", 1)[-1].lower()
names.append(str(arg).strip('"').strip("'").replace("\\", "/").rsplit("/", 1)[-1].lower())
except Exception:
continue
if name in _PYTEST_LAUNCHER_NAMES:
return True
return False
names.append("")
if any(name in _PYTEST_LAUNCHER_NAMES for name in names):
return True
if not names or not names[0].startswith("python"):
return False
script = next((name for name in names[1:] if not name.startswith("-")), "")
return script == _TEST_RUNNER_SCRIPT
def _has_pytest_ancestor() -> bool:
@@ -137,6 +137,7 @@ class TestPytestProcessRecognition:
[r"C:\venv\Scripts\pytest.exe", "-q"],
["/usr/bin/py.test", "tests/"],
["/venv/bin/python", "/repo/scripts/run_tests_parallel.py", "-q"],
["python3.14", "-u", "scripts/run_tests_parallel.py"],
],
)
def test_recognises_pytest_invocations(self, cmdline):
@@ -150,6 +151,11 @@ class TestPytestProcessRecognition:
# A path that merely *contains* "pytest" is not a pytest process:
# tmp paths like /tmp/pytest-of-dev/... show up in real argv.
["hermes", "run", "--file", "/tmp/pytest-of-dev/test0/input.txt"],
# Naming the test runner is not running it: only the script a
# Python interpreter runs counts.
["hermes", "chat", "-q", "fix the timeout in scripts/run_tests_parallel.py"],
["nvim", "scripts/run_tests_parallel.py"],
["/usr/bin/python", "-m", "hermes_cli.main", "chat", "-q", "scripts/run_tests_parallel.py"],
],
)
def test_ignores_non_pytest_invocations(self, cmdline):