Give every pwsh call in the suite the private startup cache - #10900
Conversation
tests/_shared/unsloth_pwsh_runner.py exists because a `pwsh -NonInteractive` startup reads and rewrites $XDG_CACHE_HOME/powershell/StartupProfileData- NonInteractive, and under xdist every worker shares one $HOME, so the whole job races on one file. A startup that deserialises a half-written one dies before it reaches our script. That protection is opt-in, and tests/studio/install/test_installed_release_ backend_line.py was calling pwsh directly through subprocess.run, so all 498 of its parametrised PowerShell cases opted out. Measured on this box at -n 16 over `pytest tests/python tests/kaggle tests/studio/install`, three runs of origin/main: 0, 14 and 0 failures in that file, every one of them returncode -6 with `Stack overflow.` on stderr, reported by the assertions as the printer emitting the wrong line. Five runs with it routed through run_pwsh: 0, 0, 0, 0, 0. Five other call sites were spawning PowerShell directly too. One more could take run_pwsh as-is; the rest could not, because run_pwsh is a subprocess.run wrapper and they are a long-lived Popen holder written to over stdin, a deliberate control that has to invoke pwsh the old way, and a caller with its own crash policy that needs the crashed CompletedProcess back rather than an exception. Rewriting those around run_pwsh would change what they test, so the private cache directory is exposed on its own as pwsh_env() and they take that instead. run_pwsh now calls pwsh_env for its own env, so there is one implementation. Adds tests/studio/test_pwsh_calls_use_the_shared_runner.py, which walks the AST of every Python file under tests/ and fails on a subprocess spawn of pwsh or powershell that neither goes through run_pwsh nor passes pwsh_env. Grep cannot do this: the interpreter name sits in an argv list, and every file that already uses the runner mentions pwsh in prose. tests/test_windows_amd_gpu_scan_ fallback.py is allowlisted with its reason, as it hands the child a hermetic env whose HOME is tmp_path and so was never in the race.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
# Conflicts: # tests/studio/install/test_installed_release_backend_line.py
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f7c785edda
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| offenders = { | ||
| rel: calls | ||
| for rel, calls in scan_tests().items() | ||
| if rel not in _ALLOWED_DIRECT_PWSH_CALLS |
There was a problem hiding this comment.
Scope direct-call exemptions to individual call sites
When a new direct PowerShell spawn is added to test_windows_amd_gpu_scan_fallback.py without the existing hermetic HOME, scan_tests() detects it but this comprehension discards every finding from the file because the path is allowlisted. The stale-entry test only verifies that at least one direct call remains, so CI would stay green while the new call reintroduces the shared-cache race; key exemptions by call location or validate the environment of each detected call instead.
Useful? React with 👍 / 👎.
What this is, after a correction
I opened this against
d49efcfe7with a measured flake:tests/studio/install/test_installed_release_backend_line.pycalledpwshthroughsubprocess.runrather than throughtests/_shared/unsloth_pwsh_runner.py, so it never got the per-worker startup cache, and at-n 16it failed 1 run in 3, 14 cases at a time,returncode=-6/Stack overflow.That file has since been fixed on
mainby an equivalent change. Merging main in produced a conflict where upstream's side already routes it throughrun_pwsh; I took upstream's side. So the numbers this PR opened with describe a defectmainhas already closed, and they are not evidence for what is left here.Re-measured on current
main, same command, three runs:No pwsh deaths, no host-fault skips. The two failures are the pre-existing
tests/python/test_torchcodec_torch_compat.pypair on this box. So there is no failing test left for this PR to fix, and it should be judged as prevention plus a residue, not as a bug fix.What is left, and why it is still worth having
Five call sites in five files still start PowerShell outside the private cache. They are in the same race, at lower volume than the file that was fixed: two
subprocess.Popenholders intests/python/test_windows_installer_concurrency_guard.pyplus onesubprocess.runthere, one intests/studio/test_installer_av_shapes.py, one intests/studio/test_install_phase_timing.py, one intests/python/test_pwsh_runner_encoding.py.Three of those are
skipif(os.name != "nt"), so they cannot show up on Linux at all, and one of the Linux ones —test_installer_av_shapes.py— converts an interpreter crash into a skip through its_PWSH_HOST_FAULTbanner, which namesSystem.IO.FileLoadExceptionoutright. That is the honest reason there is nothing to measure: that file has been quietly skipping past this bug rather than reporting it.pwsh_env(env = None)in the runner returnsenv(defaultos.environ) withXDG_CACHE_HOMEpointed at the existing per-worker cache dir;run_pwshnow calls it instead of doing the same six lines inline. Pure extract-method, behaviour unchanged, its own test file 27/27. It exists because three of those sites cannot becomerun_pwshcallers without changing what they test: aPopenholder written to over stdin while a second shell races it for a named mutex, a call site whose policy needs the crashedCompletedProcessback rather than an exception, and a deliberate control that must invoke pwsh the pre-fix way to show the fix changes something. Rewriting those aroundrun_pwshwould bend the tests; handing them the cache directory removes them from the race and leaves their control flow alone.tests/studio/test_pwsh_calls_use_the_shared_runner.pyis the part that lasts. It walks the AST of every*.pyundertests/— not justtest_*.py, so a conftest or a_sharedhelper cannot hide — resolvessubprocessaliases and from-imports acrossrun/Popen/call/check_call/check_output, and resolves the interpreter as a literal, as an.exepath, or through a module constant bound to one. A call passes if it goes throughrun_pwshor passesenv = pwsh_env(...), matched on the AST, so the exemption is a checkable property of the call and not a comment._ALLOWED_DIRECT_PWSH_CALLSmirrors_EXPECTED_CI_SKIPSintest_ci_shell_suite_coverage.py. One file is allowlisted,tests/test_windows_amd_gpu_scan_fallback.py(3 sites), because it hands its child a hermetic{"PATH": ..., "HOME": tmp_path}soXDG_CACHE_HOMEalready resolves insidetmp_path— which is why the runner's own header names it as the one pwsh-heavy file with zero failures in the CI run that prompted that module.Without the guard, the fix that just landed on
mainis onesubprocess.runaway from being undone, in a file nobody would think to check.Checks
.exepath, from-import,Popen([PWSH, ...]),args=kwarg) and seven that must not flag.subprocess.run(["pwsh", "-NoProfile", "-Command", "Write-Output hi"])into another test file fails it withtests/studio/test_ci_shell_suite_coverage.py:31: subprocess.run(...), naming file and line. Reverted after.ruff check --select E9,F63,F7,F82clean on all of them; the repo's fullruff checkpasses;scripts/run_ruff_format.pyis a fixed point by md5 across two passes.Not verified here: the three Windows sites are
skipif(os.name != "nt")and never execute on Linux. Their change is reasoned and lint-clean, and only Windows CI can exercise it.