Skip to content

Replace the full-catalog integration tests with selected folders of the installed models - #4150

Merged
anth-volk merged 6 commits into
masterfrom
ci/split-full-catalog-integration
Nov 2, 2026
Merged

anth-volk merged 6 commits into
masterfrom
ci/split-full-catalog-integration

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #4149

Summary

CI no longer loads the complete installed PolicyEngine.py catalog (about 1.2 million parameter values). Two tests replace the two full-catalog tests, and both run on every run of the cross-database check, with no conditions.

  • Removed: tests/integration/test_v2_catalog_installed.py, tests/integration/test_v2_catalog_publication_qualification.py, their steps in catalog-runtime-integration, and the RUN_V2_CATALOG_COMPATIBILITY and RUN_V2_CATALOG_PUBLICATION_QUALIFICATION switches, which nothing else read. "Cross-database integration tests" keeps its single job, catalog-runtime-integration ("MySQL, PostgreSQL, and Redis integration"); pr.yml, push.yml, "CI complete" and .github/release_commit.py are unchanged.
  • New step, "Publish selected parameter folders of the installed PolicyEngine.py models" (tests/integration/test_v2_catalog_installed_folders.py, with --durations=10):
    • It loads the real installed US and UK models, as extract_installed_catalog() does, and gives extract_catalog a reduced view of each. The view keeps the real model identity, package and release manifest, only the national region, no variables, and only the parameters and parameter nodes under the folders below.
    • It publishes the result to the disposable PostgreSQL twice; the second publication compares every stored row with the catalog in both directions. It then reads every parameter value, parameter, node, variable and region back and compares them with the extracted catalog as strict JSON, which tells 1, 1.0 and true apart.
    • It fails with the folder's name when a listed folder no longer exists in the installed models. A second check fails when the folders together stop holding any of the value shapes listed below.
  • Edge-case catalog: tests/fixtures/v2_catalog_value_shapes.py covers the shapes extraction accepts that the installed models don't hold (text, mappings, enums as values, NumPy scalars and others). It is published and read back by test_v2_catalog_publication.py::test_publication_stores_every_value_shape_unchanged. The row comparison helpers both tests use moved to tests/integration/catalog_rows.py.

The selected folders

Model Folder Why it's there
US gov.irs.income.bracket income tax rates and thresholds by bracket number and by filing status (SINGLE, JOINT, SEPARATE, HEAD_OF_HOUSEHOLD, SURVIVING_SPOUSE); the top bracket's thresholds are infinite
US gov.irs.payroll.social_security plain rates and a cap, with a long history of values
US gov.aca.family_tier_states booleans by state, some starting in year 1 (.py reads Core's 0000-01-01 as year 1)
US gov.aca.ineligible_immigration_statuses a list of enum names
US gov.irs.credits.premium_tax_credit.phase_out bracket scales (ending_rate[0].amount, ...) whose upper brackets hold null values
US gov.states.ne.dhhs.child_care_subsidy.fpg_fraction values in force for a single day, the only ones in the installed models
UK gov.hmrc.income_tax.rates.uk the UK income tax bracket scale: a rate and a threshold per bracket
UK gov.dwp.state_pension.triple_lock UK booleans and a null value

Together that is 150 parameters and 2,913 parameter values (US 136 and 2,870, UK 14 and 43). The installed models hold integers, fractions, booleans, lists, infinities and nulls, and no plain text or mapping values. Enum names appear as breakdown keys and inside lists. Every value is open-ended or ends the day before the next one starts.

What this gives up

  • The 256 MB limit on publish_catalog's additional memory while it publishes the complete catalog, which test_v2_catalog_publication_qualification.py measured with tracemalloc.
  • Publishing the complete catalog before merge. Each release still publishes and validates the complete catalog against staging, then production (seed-v2-database.yml, scripts/publish_v2_metadata_catalog.py), before each deploy job. That compares the full catalog with the stored rows on every release, but inserts the full catalog only when the .py version is new.
  • The exact per-bundle catalog counts that test_v2_catalog_installed.py pinned (7,438 variables, 29,367 parameter nodes, 104,673 parameters, 1,196,139 parameter values, 4 datasets, 1,837 regions for the 6.11.1 bundle), and its other checks on the complete catalog: the dataset each US and UK region type defaults to, England's region filter, the US place fallback count, and that every model version is .py's version.

Cost of loading the models

Importing policyengine builds both country models; after that, policyengine.us.model and policyengine.uk.model are attribute reads. The test's first test uses only the loaded models, so --durations reports their loading on its own.

Where Import of .py, building both models Extracting the selected folders Publishing and reading back Step
Local (macOS, warm) 12.2–14.7 s (a standalone import policyengine took 14.6 s) 0.15 s 0.22 s 13.6–16.4 s for the test file
CI, run 41457077599 running running running running

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).

Environment variables and secrets

Variable Read by Provider Wiring
CODECOV_TOKEN the Codecov upload GitHub Actions secret, both a repository secret and an organization secret (visibility: all repositories); the repository secret takes precedence pr.yml (job v2-integration-check) and push.yml (job v2-integration-check) pass it explicitly as secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}; the reusable workflow declares it as an optional workflow_call secret. A workflow_dispatch run reads the repository secret directly. Unchanged.
V2_MIGRATION_DATABASE_URL, V2_ALEMBIC_DISPOSABLE_TEST the new test, through tests/integration/conftest.py's disposable_v2_database_url literals in the job's env:; the URL points at the job's own postgres:17 service job scope, unchanged
UV_PYTHON, ALEMBIC_DATABASE_URL, DEPLOYMENT_ENVIRONMENT, RUNTIME_CACHE_TEST_URL the other steps literals in the job's env: job scope, unchanged
POLICYENGINE_SKIP_COUNTRY_IMPORTS import policyengine (country models load unless it is "1") not set anywhere in the workflow none; when unset, both models load, which the test needs

No other variable is read on the new test's path. The full-catalog switches RUN_V2_CATALOG_COMPATIBILITY and RUN_V2_CATALOG_PUBLICATION_QUALIFICATION are gone.

Testing

Run individually, by file or node ID (no full suite):

  • tests/integration/test_v2_catalog_installed_folders.py against a local postgres:17 (four tests, including a missing-folder failure)
  • tests/integration/test_v2_catalog_publication.py::test_publication_stores_every_value_shape_unchanged; it fails when one expected value is changed from 12000.0 to 12000
  • tests/unit/test_v2_integration_workflow.py (replaces test_full_catalog_integration_workflow.py): one job, no conditional step, no schedule, no full-catalog test, file or switch, and the new step after the role checks
  • tests/unit/v2/test_catalog_value_shapes.py
  • tests/unit/test_alembic_workflows.py::test_reusable_v2_integration_check_uses_disposable_databases_and_coverage (one coverage run --branch, seven coverage run -a --branch)
  • tests/unit/test_locked_test_environments.py::test_detects_every_known_test_job (EXPECTED_TEST_JOBS back to catalog-runtime-integration only) and test_test_job_selects_the_production_python_version[v2-integration-check.yml:catalog-runtime-integration]
  • python scripts/run_quality_guards.py (all guards pass) and python -m scripts.rules render (no changes; rules/registry.toml names neither deleted test)

🤖 Generated with Claude Code

anth-volk and others added 2 commits October 30, 2026 22:03
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

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.14%. Comparing base (c4e3a92) to head (18f4c89).
⚠️ Report is 11 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #4150       +/-   ##
===========================================
+ Coverage   50.84%   88.14%   +37.30%     
===========================================
  Files         270      286       +16     
  Lines       15221    16659     +1438     
  Branches     2443     2733      +290     
===========================================
+ Hits         7739    14684     +6945     
+ Misses       6841     1300     -5541     
- Partials      641      675       +34     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

anth-volk and others added 2 commits November 2, 2026 17:02
The workflow tests now require that full-catalog-integration has no
conditional step and no decision step, checks out only the commit it
tests, and that the workflow has no schedule.

Refs #4149

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
full-catalog-integration no longer decides whether to run its tests:
the decision step, .github/scripts/decide_full_catalog_tests.sh and its
tests, every step condition, the full-history checkout and the nightly
schedule are gone. The job still runs beside catalog-runtime-integration,
with both full-catalog tests in one pytest process sharing one catalog
extraction, so the cross-database check takes as long as the slower of
the two jobs.

Refs #4149

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk anth-volk changed the title Run the full-catalog integration tests only when a change can affect them Run the full-catalog integration tests in their own job, with one catalog extraction Nov 2, 2026
anth-volk and others added 2 commits November 2, 2026 17:52
A new integration test runs the real US and UK models through extraction
and publication, reduced to eight parameter folders that between them
hold every value shape the installed models hold, and checks that every
extracted value comes back from PostgreSQL unchanged. It fails by name
when a listed folder no longer exists.

The workflow tests now require that the cross-database check has one
job, catalog-runtime-integration, which runs that test and no
full-catalog test, with no conditional step.

The row comparison helpers move to tests/integration/catalog_rows.py so
both publication tests share them.

Refs #4149

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cross-database check goes back to one job,
catalog-runtime-integration. The full-catalog-integration job, both
full-catalog tests (test_v2_catalog_installed.py and
test_v2_catalog_publication_qualification.py), their shared extraction
helper and the RUN_V2_CATALOG_COMPATIBILITY and
RUN_V2_CATALOG_PUBLICATION_QUALIFICATION switches are removed. A new step
publishes selected parameter folders of the installed US and UK models
on every run (test_v2_catalog_installed_folders.py), beside the
edge-case value catalog test.

This gives up the 256 MB memory limit on publication, publishing the
full catalog before merge, and the exact per-bundle catalog counts and
region dataset routing that test_v2_catalog_installed.py pinned. Each
release still publishes and validates the complete catalog against
staging, then production.

Fixes #4149

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk anth-volk changed the title Run the full-catalog integration tests in their own job, with one catalog extraction Replace the full-catalog integration tests with selected folders of the installed models Nov 2, 2026
@anth-volk
anth-volk marked this pull request as ready for review November 2, 2026 14:26
@anth-volk
anth-volk merged commit 0f24772 into master Nov 2, 2026
15 checks passed
@anth-volk
anth-volk deleted the ci/split-full-catalog-integration branch November 2, 2026 14:27

This branch was previously deployed

1 inactive deployment
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.

Replace the full-catalog integration tests with selected folders of the installed models

1 participant