Review: the fork mode spread its server state, lock, process creation and
process-group rules through the runner, so _run_one_file_once had to know that
a forked child calls setsid() while a subprocess needs getpgid(). The runner
now calls start_pytest() and gets the process and its group back; the fork
server, its lock and the Popen shim live with it. Behaviour is unchanged.
classify_changes lists the launch module and the fork child as test harness
next to run_tests_parallel.py, so editing either starts the lanes a runner
edit starts (the fork child was missing since it was added).
Under the fork server multiprocessing still saw each file as the server's
child: current_process() was ForkServerProcess-N with a parent, every file
after the first inherited the runner's /tmp/pymp-* temp dir, and __main__ was
this runner, re-run in every child as __mp_main__ (~7-20ms per file). A
test's own Process, Pool or spawn context now sees what python -m pytest gives
it: a parentless MainProcess, a temp dir under its TMPDIR, and pytest as
__main__ and __mp_main__; the server preloads the runner once. The fork probe
compares these against a fresh pytest process, from the server's second child.
Also from review: run_tests_fork_child.run takes keyword arguments and one
argv; the start-under-lock is one helper the race test calls, and the test
starts 2000 children (unlocked, ~1 status in 400 comes back 255), so dropping
either lock fails it; kill() leaves a reaped pid alone; the output file is
removed when start() fails; the exit-status note skips exit 1 (tests failed,
already reported) and crashes; the 2.0s default duration is one constant; the
new tests reuse the file's runner helpers. The docstring records the gap left:
a daemon a test forks without exec escapes the leftover sweep.
main() is over the code-health ratchet's complexity and length budgets, and
starting the fork server there grew both. The first file now starts it under
the fork lock, before it copies os.environ, so the knob still never reaches a
test. Also: a timeout on the sys.path probe (HX006), a utf-8-sig read of the
child's output file, an unquoted annotation (UP037), and the Linux-only setsid
marked for the Windows footgun check.
A fresh python -m pytest <file> spends about half a second starting the
interpreter and importing pytest and the third-party libraries nearly every
test file loads; over ~7000 files that is most of an hour of CPU per run.
With HERMES_TEST_FORKSERVER=1 (Linux CI) the runner starts one
multiprocessing fork server that imports those once and forks a child per
file. Each child still starts from a clean state: its own session, the
file's environment, python -m pytest's sys.path and argv, combined output and
pytest's exit code. Product code and tests/conftest.py are never preloaded.