Skip to content

Run the Rest job's remaining Python tests in six pytest processes - #9787

Merged
MaxGhenis merged 3 commits into
mainfrom
ci-split-rest-job
Oct 4, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
ci-split-rest-job

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Summary

Runs the Full Suite - Rest job's remaining Python tests as six pytest processes in sequence, one per group. A process's exit releases its heap before the next group starts.

Why

  • Memory. On a passing run (37198184683, artifact rest-test-timings), the single process peaked at 15.7 GB RSS on the 16 GB runner, with 1.48M major page faults.
  • Runner deaths. Four runs on 2026-10-04 (37201566995, 37201944922, 37202679009, 37205594473) end with "The runner has received a shutdown signal" and make … Killed. Each died 1,424–1,454 tests into the step, inside test_formulas_do_not_write_into_cached_arrays or at the start of test_md_poverty_line_credit_invariants.
  • Scale. Since Fix read-only cache tests with Core 3.32.16 #9782 merged, about 7 of 16 PR Rest runs died this way.

Change

  • Makefile. test-other-python-rest runs six groups:

    Group Contents Test time
    cached-arrays test_formulas_do_not_write_into_cached_arrays 403 s
    heavy-a four of the slowest modules 350 s
    heavy-b four of the slowest modules 345 s
    core everything else in core/ 142 s
    policy policy/ 185 s
    remaining everything else, including new files 239 s

    Times are at the measured run's speed; the two newer heavy modules were timed in later runs. Every group runs even if an earlier one fails, and the target fails, naming the groups, if any did.

  • Reports. With REST_REPORT_DIR set (both pr.yaml and push.yaml set it), each group writes rest-python-<group>.xml and rest-python-<group>-resources.txt (GNU time), and the Rest job uploads them all. The first CI run on this PR reports per-group peak memory.

  • Guard test (new). policyengine_us/tests/code_health/test_rest_python_groups.py checks that:

    • the groups together collect each of the old single command's 87 files exactly once, so no file is dropped or run twice;
    • both workflows upload every group's reports;
    • the target's failure handling works. It runs the real target with a stub pytest: one group fails and another is killed, all fail, all pass.

    The guard was mutation-checked: dropping the policy group, || true, stopping at the first failure, mismatched report paths and more were all caught.

  • Unchanged: the test set, the --ignores (contrib, microsimulation, SPM files), the SPM step and timeout-minutes.

Verification

  • Guard: uv run --no-sync pytest policyengine_us/tests/code_health/test_rest_python_groups.py: 10 passed.
  • Dry run: make -n test-other-python-rest prints the six group commands.
  • Lint: make format is clean.
  • Reviews: two independent review rounds during the build. The hub's independent review runs on this head.

Before merge: read the per-group resources.txt from this PR's CI run, to confirm each group's peak memory is well under 16 GB.

Related:

axiom: n/a: CI configuration only.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 4, 2026 11:38
As one pytest process, the remaining Python tests peaked at 15.7 GB RSS on
the 16 GB runner (run 37198184683), with 1.48M major page faults, and since
#9782 the step has been cancelled partway through on several runs, with no
log retained.

test-other-python-rest now runs six groups in sequence, one pytest process
each. cached-arrays, heavy-a and heavy-b hold the seven slowest modules;
then come core/, policy/, and remaining, which takes every other file,
including new ones. The target runs every group even after one fails, and
fails if any did. With REST_REPORT_DIR set, as both workflows set it, each
group writes its own JUnit XML and GNU time report, and the Rest job
uploads them all. The SPM step, the --ignores and the test set are
unchanged.

code_health/test_rest_python_groups.py takes the old single command and
each group's command from `make -n`, lists the files pytest collects for
each without importing them, and checks that the groups collect every file
of the old command exactly once. It also checks that every group's reports
are uploaded by both workflows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of the split found that two core modules added after run
37198184683 are heavy too. Scaled to that run's speed, the timings from
runs 37198179476 and 37196338557 put test_marginal_tax_rate_coverage at
about 124 s and test_dependent_net_investment_income_invariants at about
64 s. The core group would have reached about 330 s. Move the first into
heavy-b and the second into heavy-a. That gives 403 / 350 / 345 / 142 /
185 / 239 s across the six groups.

Correction to the previous commit's message: the failed runs did keep
logs. Four runs on 2026-10-04 (37201566995, 37201944922, 37202679009,
37205594473) end with "The runner has received a shutdown signal" and
make's "test-other-python-rest] Killed". That came after 1,530-1,560
passed tests, inside test_formulas_do_not_write_into_cached_arrays or at
the start of test_md_poverty_line_credit_invariants. The Makefile comment
now cites them.

The guard gains two checks:
- It runs the real target with a stub pytest, failing one group and
  SIGKILLing another. Every group must still run, with the dry run's
  arguments, and the target must fail, naming both groups.
- Test file names must stay unique outside packages. The old single
  process failed on a clash, and separate groups would not.

The changelog now states the memory bound as the aim, since the first CI
run will be the first to measure each group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second-round review of the split:
- The stub-pytest test failed only heavy-a and core. A `-` prefix on
  any other group's recipe would have hidden that group's failures.
  An all-fail case now exercises every recipe. A mutation check on
  cached-arrays, heavy-b, policy and remaining failed it, as it should.
- The duplicate-name check now compares the module name that pytest's
  default import mode gives each file: its dotted path inside a
  package, or its stem outside one.
- Correction to the previous commit's message and the Makefile comment:
  the four runner losses came 1,424-1,454 tests into the
  test-other-python-rest step. The 1,530-1,560 figure counted the SPM
  step's 106 tests as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

First CI run on this head (run 37219969714). Full Suite - Rest passed in 58 minutes. Per-process peak memory, from each group's /usr/bin/time -v report in the rest-test-timings artifact:

Process Wall time Peak RSS Major page faults
spm (unchanged) 15:51 5.08 GB 37
cached-arrays 5:44 3.24 GB 0
heavy-a 6:58 4.47 GB 0
heavy-b 9:55 8.54 GB 0
core 4:23 4.54 GB 0
policy 6:29 10.93 GB 3
remaining 7:09 5.81 GB 2
variables (unchanged) 0:56 2.40 GB 0

Before the split, the remaining Python tests ran as one process that peaked at 15.67 GB, with 1.48M major page faults (run 37198184683). The largest group now peaks at 10.93 GB, with no paging.

The job's wall time is 58 minutes, close to the 60-minute limit, so #9785 (limit raised to 90) is still worth landing after this.

@MaxGhenis
MaxGhenis merged commit c836731 into main Oct 4, 2026
37 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged by the US + core hub on the merge gates:

  • Head: 9f77f57.
  • CI: all check runs successful on this head. Full Suite - Rest passed in 58 min.
  • Memory: each group peaked at 3.2–10.9 GB with no paging, measured from this head's artifact (see the comment above). The single process before peaked at 15.67 GB.
  • Independent review: GPT-6.1 Sol APPROVE on this head (~/reviews/us-hub/ci-split-rest/review-r1.md). One non-blocking note: the conftest scan checks only top-level statements.
  • Built by: a Subfleet Opus lane (job 20261004-102602-hub-ci-split-rest), with two internal review rounds.
  • Admin merge: used only for the required-review rule, at the reviewed head.

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