Skip to content

test(execution): start the job-object tests on the real interpreter - #325

Merged
dovvnloading merged 1 commit into
mainfrom
fix/venv-job-object-tests
Oct 17, 2026
Merged

dovvnloading merged 1 commit into
mainfrom
fix/venv-job-object-tests

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Problem

CONTRIBUTING.md and the README tell contributors to work in python -m venv .venv. Inside a Windows virtual environment four job-object tests fail, although they pass on a plain interpreter (which is what CI uses, so CI never saw it):

  • tests/test_brokered_process.py::test_a_brokered_process_reads_back_the_job_it_was_placed_in (reads limit_flags 12288 and active_process_limit 0 instead of the broker's job)
  • tests/test_worker_isolation.py::test_a_real_job_carries_exactly_the_limits_it_was_given
  • tests/test_worker_isolation.py::test_a_real_job_without_limits_is_only_kill_on_close (12288 instead of 8192)
  • tests/test_worker_isolation.py::test_a_real_job_of_one_process_refuses_a_second (stderr: "Unable to create process using '...\Scripts\python.exe ...': Not enough quota is available to process this command.")

Found while verifying the dependency refresh (#324) in a fresh venv.

Root cause

In a Windows venv, sys.executable is the venv redirector (Scripts\python.exe). It starts the base interpreter as its own child, inside a job object it creates (KILL_ON_JOB_CLOSE | SILENT_BREAKAWAY_OK = 0x3000). The tests start sys.executable, attach that PID to the job under test, and then ask the child which job it is in:

  • The code runs in the redirector's child, which is in both jobs. Its innermost job is the redirector's, so the probe reports 0x3000 and no limits.
  • The redirector plus the real interpreter are two processes, so a job limited to one process refuses the interpreter.

Measured in a venv: a child started from sys.executable reported PID 8892 while Popen.pid was 25540; from sys._base_executable the two matched.

The product is not affected. The backend never uses sys.executable. The scratch, image and code workers are started through multiprocessing's spawn context, which already starts the base interpreter directly inside a venv (bpo-35797). The brokered path runs whatever argv the user approved.

Change

  • tests/support.py: real_interpreter() returns sys._base_executable on Windows when it exists, and sys.executable otherwise. Outside a venv the two are the same.
  • _run_in_job (every real-job test in test_worker_isolation.py) and the brokered job-probe test start real_interpreter(). Those children run only standard-library code (job_probe.py imports nothing else, by design), so not seeing the venv's packages costs nothing.
  • New test_the_interpreter_the_job_tests_start_is_the_process_that_runs: the PID the child reports equals Popen.pid. This pins the property the job tests rely on, and it runs on every platform.

The other sys.executable spawns in the suite (test_launcher.py, test_worker_containment.py, the remaining brokered tests) only check kill-on-close or exit codes. All 245 tests in the launcher and containment files pass in a venv unchanged, so they are left alone.

Compatibility and rollback

Test-only change; nothing under backend/, frontend/ or contracts/ moves. Revert the single commit to undo.

Checks

Venv used: C:\Python314\python.exe -m venv --system-site-packages, so Scripts\python.exe is the redirector and the packages are the same as the base run.

  • Baseline, full suite in the venv on main (00b76b0): python -m pytest -q, 4 failed, 3067 passed, 25 subtests passed. The 4 failures are exactly the tests above; nothing else fails only in a venv.
  • After the change, in the venv: python -m pytest tests/test_brokered_process.py tests/test_worker_isolation.py -q, 39 passed.
  • After the change, base interpreter (Python 3.14): the same two files, 39 passed.
  • After the change, Python 3.12 (py -3.12): the same two files, 39 passed.
  • Baseline, tests/test_launcher.py tests/test_worker_containment.py in the venv on main: 245 passed (no change needed there).
  • Each assertion was checked against a deliberate break:
    • Job limit off by one. win_jobs.py was changed to set active_process_limit = limits.active_processes + 1. In the venv, 5 tests fail, including carries_exactly_the_limits and of_one_process_refuses_a_second. Before this change, the venv masked that defect behind the redirector's job.
    • Helper made to return sys.executable. In the venv, the original 4 tests fail plus the new PID test (5 failed, 34 passed).
  • python -m ruff check backend tests tools main.py app_factory.py scripts: all checks passed. python -m mypy: no issues in 98 source files (tests are outside mypy's scope by configuration).
  • Pre-push hook (scripts/check.ps1, quick tier): all 11 checks passed in 471.3 s (backend tests 307.9 s, frontend unit tests 80.5 s).

Security, concurrency, data loss

None. The children run a probe or a short standard-library snippet under the same job limits as before. Passing the venv path to them was ruled out on purpose: the brokered path strips the environment to SystemRoot, and __PYVENV_LAUNCHER__ would send the probe's own children back through the redirector.

Limits

  • Not tested with a Microsoft Store Python, whose sys._base_executable can be an app-execution alias. If that path does not exist as a file, the helper falls back to sys.executable, and the previous behaviour returns.
  • The full suite was run in the venv on the baseline only. After the change, only the two affected files were rerun there: the change touches nothing else, and the other modules that import tests/support.py are covered by the hook's full run on the base interpreter.

🤖 Generated with Claude Code

Inside a Windows virtual environment sys.executable is the venv
redirector, which starts the base interpreter as its own child in a job
object of its own. Four tests put sys.executable in a job and read the
job back from inside the child, so in a venv they read the redirector's
job (limit flags 0x3000) and a one-process job refused the interpreter
as a second process. They pass on a plain interpreter, which is why CI
never saw it; CONTRIBUTING.md tells contributors to work in a venv.

tests/support.py gains real_interpreter() (sys._base_executable on
Windows, else sys.executable), used by _run_in_job and the brokered
job-probe test, plus a test that the process started is the process
that runs. The workers are unaffected: multiprocessing already starts
the base interpreter directly in a venv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dovvnloading added a commit that referenced this pull request Oct 17, 2026
…st (#327)

The test failed intermittently under load ("expected to be called 1
times, but got 2"), on a loaded local machine and in CI on #325. The
streaming bubble delivers text through useThrottledText's timer. The
test clicked "Jump to latest" while the delivery of "more " could still
be pending; when that delivery landed in the same act as the click, it
ran after jumpToLatest had marked the reader as back at the end, so
followStreamedOutput correctly followed it with an instant scroll, and
the test counted two scrolls.

Each step now waits until its text is in the bubble, so nothing is
pending at the click. The test also checks one more thing than before:
no scroll at all happens while the reader is scrolled away. The
assertions on what it protects are unchanged: following is instant and
only the reader's jump is smooth.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit 47ec2c4 into main Oct 17, 2026
17 of 18 checks passed
@dovvnloading
dovvnloading deleted the fix/venv-job-object-tests branch October 17, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant