Repository navigation
Conversation
Tests for a small catalog holding one example of each value shape extraction accepts, published to PostgreSQL on every pull request; for .github/scripts/decide_full_catalog_tests.sh, which decides whether a change can affect the full-catalog tests; and for a separate full-catalog-integration job that runs both of those tests in one process only when the decision says so. Refs #4149 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…them The two tests that load the complete installed PolicyEngine.py catalog move out of catalog-runtime-integration into a new job, full-catalog-integration, which runs them in one pytest process that extracts the catalog once. Its first step, .github/scripts/decide_full_catalog_tests.sh, compares the commit with the pull request's base or the commit before the push, and the tests run only when the change touches the locked packages, the catalog code or its table models, the v2 schema, the tests or the workflow. Scheduled runs, now nightly, and manual runs always run them. The job itself always runs, so "CI complete" and the release run's check of the merge run keep working. Every pull request now publishes a small catalog holding one example of each value shape extraction accepts, and checks that each comes back from PostgreSQL unchanged. Fixes #4149 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4150 +/- ##
===========================================
+ Coverage 50.84% 88.15% +37.30%
===========================================
Files 270 286 +16
Lines 15221 16659 +1438
Branches 2443 2733 +290
===========================================
+ Hits 7739 14685 +6946
+ Misses 6841 1297 -5544
- Partials 641 677 +36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This branch was successfully 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 #4149
Summary
New job
full-catalog-integration("Cross-database integration tests / Full installed PolicyEngine.py catalog") in.github/workflows/v2-integration-check.yml. It takes the two steps that load the complete installed catalog out ofcatalog-runtime-integrationand runs them in one pytest process with--durations=10, so the catalog is extracted once (tests/fixtures/installed_catalog.py, afunctools.cachearoundextract_installed_catalog()). The qualification test still startstracemallocafter extraction andgc.collect(), so the 256 MB limit still measures onlypublish_catalog's additional memory. The job reportsnproc,free -mandlscpu, prepares its own disposable v2 schema (PostgreSQL only; neither test needs MySQL, Redis or the managed roles) and uploads its own coverage file (coverage-v2-full-catalog.xml).Runs only when needed. The job's first step,
.github/scripts/decide_full_catalog_tests.sh, lists what changed since the merge base of the pull request's base commit (or the commit before the push) and setsrun_full_catalog_tests. Every later step hasif: steps.full_catalog.outputs.run_full_catalog_tests == 'true'. The tests run when the change touches:uv.lock,pyproject.toml(the .py and country model package versions)policyengine_api/data/v2/catalog/*, andpolicyengine_api/data/v2/models/metadata.py(the models of the tables publication writes through)migrations/v2/*,alembic-v2.initests/fixtures/installed_catalog.py.github/workflows/v2-integration-check.ymland the script itselfA moved file counts under its old path too (
--no-renames). An empty or all-zero base, or one missing from the clone (a force push), runs the tests.Nightly and manual runs. A
scheduletrigger (04:23 UTC) and the existingworkflow_dispatchalways run them.The job always runs. It has no job-level
if, so it succeeds whether or not the tests are skipped.check_ci_complete.shfails on any job that didn't succeed, and.github/release_commit.pylets a release run skip the check only when everyCross-database integration tests / ...job of the merge run completed and succeeded. Both still hold.Pushes to master use the same rule against
github.event.before. A merge that doesn't touch those paths skips the full-catalog tests, which also shortens the time from merge to the release commit, becauseversioningneeds this check. When the release run can skip its checks (the usual case), it skips this whole check as before. When it has to run them itself, its diff includes the version lines inpyproject.tomlanduv.lock, so it runs the full-catalog tests too, as it does today.Edge-case catalog on every pull request.
tests/fixtures/v2_catalog_value_shapes.pyadds these to the deterministic fixture catalog, as a separate catalog so thatnormalized_catalog()'s counts stay astest_catalog_extraction.pyandtest_v2_catalog_publication.pyassert them:True/False,0and negative integers,0.30000000000000004,12000.0(which must stay a float),±inf(stored as strings), text with tabs, newlines, backslashes, quotes and non-ASCII characters, an enum, a date and a datetime, lists and tuples (including empty), nested dicts (including empty),None, and NumPyfloat64/int64/True_gov.value_shapes.scale[0].rateand similar, with their bracket nodestest_publication_stores_every_value_shape_unchangedpublishes it twice. The second publication compares every stored row with the catalog in both directions. The test then reads every parameter value, variable, parameter, node and region back from PostgreSQL and compares them as strict JSON, which tells1,1.0andtrueapart.tests/unit/v2/test_catalog_value_shapes.pyfails if a type inJSON_VALUE_NORMALIZERShas no example in the catalog.docs/engineering/skills/testing.mdsays where the path list and the edge-case catalog live.Time saved on a pull request that doesn't touch the catalog
Before this change, over the last 13 successful pull request runs (2026-10-29 and 2026-10-30), "Cross-database integration tests / MySQL, PostgreSQL, and Redis integration" took 7.2 to 37.6 minutes (median 11.1). Its two full-catalog steps took 5.0 to 32.8 minutes of that; every other step together took 2.1 to 4.8 minutes (median 3.1).
This PR's first run (41119889262):
On a pull request that doesn't touch the listed paths, the full-catalog job stops after those first steps (about 20 s), so the check finishes when the runtime job does, in about 2 to 5 minutes. That saves about 5 to 33 minutes per run, 8 at the median.
Environment variables and secrets
CODECOV_TOKENpr.ymlandpush.ymlpass it explicitly (secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}) to the reusable workflow, which declares it as an optionalworkflow_callsecret. Scheduled and manual runs start the workflow directly, sosecrets.CODECOV_TOKENreads the secret itself. Unchanged forcatalog-runtime-integration.V2_MIGRATION_DATABASE_URL,V2_ALEMBIC_DISPOSABLE_TEST,UV_PYTHONfull-catalog-integrationenv:; the URL points at the job's ownpostgres:17serviceRUN_V2_CATALOG_COMPATIBILITY,RUN_V2_CATALOG_PUBLICATION_QUALIFICATIONenv:EVENT_NAME,BASE_SHAgithubcontext (github.event_name,github.event.pull_request.base.sha || github.event.before), which in a called workflow is the caller's eventGITHUB_OUTPUTNo other variable is read. Neither job uses
pr.yml's orpush.yml's workflow-levelenv:(OLD_SIMULATION_GATEWAY_URL), which GitHub doesn't pass to called workflows. That was already true before this change.Testing
Run individually, by file or node ID (no full suite):
tests/unit/test_decide_full_catalog_tests_script.py(35 tests: each listed path, similar paths that don't match, a move out of the catalog directory, a deletion, a test merge commit whose base changeduv.lock, a push, scheduled and manual runs, a missing or all-zero base, a base missing from the clone, required variables)tests/unit/test_full_catalog_integration_workflow.py,tests/unit/v2/test_catalog_value_shapes.pytests/unit/test_alembic_workflows.py::test_reusable_v2_integration_check_uses_disposable_databases_and_coverage(coverage counts updated: two jobs, one coverage file each)tests/unit/test_locked_test_environments.py::test_detects_every_known_test_job(EXPECTED_TEST_JOBSnow listsfull-catalog-integration)tests/integration/test_v2_catalog_publication.py::test_publication_stores_every_value_shape_unchangedagainst a localpostgres:17; it also fails when one expected value is changed from12000.0to12000postgres:17on macOS. The qualification test passed in 29.6 s with the shared catalog, staying under the 256 MB limit. The installed-interface test counted 29,361 parameter nodes on macOS instead of the 29,367 it pins. This change only caches the return value ofextract_installed_catalog(), so master counts the same on that machine; the job on Linux in this PR's run is what countspython scripts/run_quality_guards.py(all guards pass, includingci-scriptswith shellcheck) andpython -m scripts.rules render(no changes; the[[rule.ci]]entries forpr.yml:v2-integration-checkandv2-integration-check.yml:catalog-runtime-integrationstill match)publish-git-tag'srequire-checks,pr.ymlandpush.ymlare unchanged: both callers still call one reusable workflow under the job IDv2-integration-check.🤖 Generated with Claude Code