Repository navigation
Conversation
…nditions A release run repeats lint, the quality guards, both Alembic checks and the cross-database integration tests that its merge run passed on the same code, and runs the release test suite in one job. These tests describe the next step: - release_commit.py decides whether the release commit changes only release files (the [project] version, uv.lock's own version entry, CHANGELOG.md, removed fragments) and whether the merge commit's Push run passed every check; check_release_commit.sh gathers what it reads. require-checks stops publish-git-tag unless the run skipped the checks for that reason or passed them itself. - The release tests run in six shards, split by file (scripts/release_test_shards.py, tests/release_test_shard_plugin.py), and check-release-test-shards fails unless each collected test ran in exactly one shard. - Every release job calls !cancelled() and checks each job it needs, and so do the jobs of the reusable workflows they call, so skipped checks never skip a deploy job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge run passed A release run repeated lint, the quality guards, both Alembic checks and the cross-database integration tests (about 10 minutes before the tag and the staging database jobs), and ran the release suite in one process. The check-release-commit job runs .github/scripts/check_release_commit.sh, which gathers the release commit's changes, the parent's pyproject.toml and uv.lock, and the parent's Push runs and jobs (gh api, with contents: read and actions: read), and .github/release_commit.py decides: the five checks are skipped only when the release commit changes nothing but the [project] version, uv.lock's own version entry, CHANGELOG.md and removed fragments, and the merge commit's Push run passed all five. Otherwise they run as before, and publish-git-tag's require-checks step requires lint, both Alembic checks and the integration tests to pass, as its needs did. The job runs on every push, doing nothing on merge commits, so the checks never need a skipped job. GitHub skips a job whose needs include a skipped job, even through other jobs, unless its if: calls a status function. Every release job after the checks therefore calls !cancelled() and checks each job it needs, and the migrate and seed reusable workflows' jobs run unless cancelled. release-tests runs as a six-shard matrix. run_release_tests.sh loads tests/release_test_shard_plugin.py, which keeps the tests in the files scripts/release_test_shards.py assigns to its shard (heaviest first, by measured seconds or test count) and uploads what it collected and ran. check-release-test-shards fails, and the staging deploy with it, unless every shard collected the same tests and each ran in exactly one. testing.md, ci-workflows.md and alembic-migrations.md describe the new behaviour and the !cancelled() rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
split-release-tests
branch
from
October 29, 2026 21:01
dde97a9 to
6664d5e
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4133
Stacked on #4130.
Summary
The release run (the run for the "Update PolicyEngine API" commit) stops repeating the checks its merge run passed on the same code, and runs the release test suite on six runners. The two only help together: with either one alone, the other remains the longer branch before the staging deploy.
check-release-commit(new job):.github/scripts/check_release_commit.shgathers what.github/release_commit.pyreads, and that module decides whether this run may skip the checks. It writesmerge_run_checks_passed(trueorfalse) andmerge_run_urlas job outputs. The job runs on every push; on a merge commit its steps do nothing, so the checks that need it never need a skipped job.lint,quality-guards,alembic-v1-check,alembic-v2-check,v2-integration-check) needcheck-release-commitand are skipped only whenmerge_run_checks_passedistrue.!cancelled()runs them when the check job fails.publish-git-tagalso needscheck-release-commit, runs whatever the checks ended, and its new steppython .github/release_commit.py require-checks(withJOB_RESULTS: ${{ toJSON(needs) }}) fails unless either the checks were skipped becausecheck-release-commitsaid so and none of them failed, or lint, both Alembic checks and the integration tests passed in this run. These are the checks it needed before; the quality guards still block no deploy job, as before.if:with!cancelled()and checks the result of each job it needs, and the jobs inmigrate-v1-cloud-sql.ymlandseed-v2-database.ymlhaveif: ${{ !cancelled() }}. GitHub skips a job whose needs include a skipped job, even through the jobs between them, unless itsif:calls a status function (actions/runner#2205). Without this, skipping the checks would skip every deploy job after them, and the run would still finish green.release-testsis a matrix of six shards (fail-fast: false).run_release_tests.shrequiresRELEASE_TEST_SHARD_INDEX,RELEASE_TEST_SHARD_COUNTandRELEASE_TEST_SHARD_REPORT_DIRECTORYand loads the pytest plugintests/release_test_shard_plugin.py(throughPYTEST_ADDOPTS). Each shard uploads what it collected and ran as the artifactrelease-test-shard-<n>.check-release-test-shards(new job) downloads every shard's report and runspython -m scripts.release_test_shards check-coverage --shard-count 6.deploy-cloud-run-stagingneeds it.docs/engineering/skills/testing.md,ci-workflows.mdandalembic-migrations.mddescribe the shards, the check, and the!cancelled()rule for release jobs.No new dependency, and no inline ruff suppressions are added.
The release commit check
release_commit.py checklets the release run skip the five checks only when both hold:git diff --raw --no-renamesagainst its single parent:pyproject.tomlanduv.lockare edited regular files (statusM, mode100644), and each differs from the parent's copy in exactly one line of the file: theversion = "..."line in the[project]table, and theversionline of the[[package]]entry named after the project (policyengine-api). Any other change, such as auv lockthat re-resolved a dependency, fails the check.CHANGELOG.mdis an edited regular file.[tool.towncrier] directory, read through.github/changelog_fragments.py) are deleted, never added or edited;.gitkeepand anything in a subfolder count as other files.GET /repos/{repo}/actions/workflows/push.yml/runs?head_sha=<parent>&event=pushand each run's jobs: every job namedLint,Quality guards, or<check name> / ...for the three reusable checks, endedcompletedandsuccess.When either fails, or the script cannot read git or the API, it prints the reasons as notices and writes
false, and the checks run as before. Run against this repository's history, the change check accepts each of the 15 most recent release commits (b5cf71dc8tob93b0b3b6). Run read-only against the real API, the whole check returnstruefor 5.14.3 (merge run #2614) andfalsefor release #2590, whose merge run failed the quality guards.How the release tests are split
make testpaths, about 3,980 tests), so all of them see the same tests.tests/release_test_shard_plugin.py(pytest_collection_modifyitems,trylast) keeps only the tests in the filesscripts/release_test_shards.pyassigns to its shard, and reports the rest as deselected. A file's tests stay together, so module- and class-scoped fixtures are set up once.MEASURED_FILE_SECONDS(the 20 files over 10 seconds in a local run with--durations=0), or else its test count times 0.04 seconds. The assignment depends only on the collected files and their test counts, so it is the same on every runner, and each test runs in exactly one shard.check-release-test-shardsfails unless all six shards reported, every shard collected the same node IDs, and each collected test ran in exactly one shard. It lists any test that ran in no shard, in two shards, or ran without being collected.Locally, the six shards of the real suite ran side by side: all 3,984 tests passed, each in exactly one shard (the check said so), in 98 to 175 seconds of test time per shard. The heaviest shards hold
test_country_spm.pyalone andtest_import_side_effects.pywith one small file.Job dependencies on a release run
Before (with #4125, #4127 and #4130; minutes from the start of the run):
After:
Everything after
deploy-cloud-run-stagingkeeps its order. Expected saving: about 10 to 12 minutes per release beyond the three pull requests below (estimate). The merge run's checks start about 10 to 15 seconds later, aftercheck-release-commitfinishes doing nothing.When the check finds a reason to run the checks, the release run behaves as before except for the shards: the tag and the staging database jobs wait for the checks (about 10 minutes), and the staging deploy starts at about 16.4.
Environment variables
check-release-commitGH_TOKENGITHUB_TOKEN(GitHub)env:fromgithub.token; jobpermissions: contents: read, actions: readcheck-release-commitGITHUB_REPOSITORY,GITHUB_OUTPUTcheck-release-commitPYTHONpython3fromactions/setup-pythonpublish-git-tagJOB_RESULTSneedscontext: each needed job's result, andcheck-release-commit's outputsenv:fromtoJSON(needs)release-testsRELEASE_TEST_SHARD_INDEXenv:frommatrix.shard_indexrelease-testsRELEASE_TEST_SHARD_COUNT"6"env:release-testsRELEASE_TEST_SHARD_REPORT_DIRECTORYrunner.temp(runner context)env:release-testsPYTEST_ADDOPTSrun_release_tests.shenvbeforemake testrelease-testsstagingenvironment variables and secrets, repository and organization secrets, literalsenv:,environment: stagingrelease-tests,check-release-test-shardsactions/upload-artifactandactions/download-artifactuse for the same runcheck-release-test-shardshaspermissions: contents: readv2-integration-checkCODECOV_TOKENsecrets:, unchangedSecret and variable names were confirmed with read-only
gh apicalls that list names only. No new secret or variable is needed. Every other job keeps itsenvironment:,env:,secrets:andpermissions:; only theirif:changes.Google Cloud sign-in: no job that signs in to Google Cloud moves or changes environment. Neither new job signs in.
Unverified until the first release after merge
push.ymlruns only on pushes tomaster, so this pull request's CI runs none of these jobs.!cancelled()and the explicitneeds.<job>.resultconditions on every release job, and on the jobs insidemigrate-v1-cloud-sql.ymlandseed-v2-database.yml. If any of them were skipped, the run would finish without deploying. Check the first release's job list.GITHUB_TOKENwithactions: readcan list the parent's Push runs and their jobs. The same calls worked read-only with a personal token.check-release-commitsucceeds on merge runs with every step skipped, so the checks start.actions/upload-artifact@v4andactions/download-artifact@v4, used here for the first time in this repository.make test(coverage run -m pytest) on the runner; it does locally.check-release-test-shardschecks this on each release.check-release-commititself fails, the checks run, andpublish-git-tagrequires them. Whether GitHub then runs the jobs inside the three reusable check workflows is unverified; if it skips them,publish-git-tagfails and the release stops before the tag.Release commits whose checks are skipped no longer upload integration coverage to Codecov.
Testing
pytest --continue-on-collection-errors tests/unit/test_release_commit.py tests/unit/test_release_test_shards.py tests/unit/test_release_run_checks.py tests/unit/test_deploy_pipeline_scripts.py tests/unit/test_cloud_run_push_workflow.py: 11 failed, 26 passed, 2 errors (the two new modules do not exist yet).make testsuite, run side by side locally with a Redis each: 3,984 passed, andcheck-coveragepassed.ruff format --check .andruff check .with the ruff versionuv.lockpins (0.15.12): clean.python scripts/run_quality_guards.py: every guard passed.python -m scripts.rules guard --base-ref origin/master: passed. The rules tool's tests: OK.python -m scripts.rules render: no diff.uv lock --check: passed.shellcheckoncheck_release_commit.shandrun_release_tests.sh: clean.push.yml,migrate-v1-cloud-sql.ymlandseed-v2-database.ymlparse as YAML and passactionlint.🤖 Generated with Claude Code