Skip to content

refactor(regression): collapse path-placeholder clump into PlaceholderMap#759

Closed
lewisjared wants to merge 4 commits into
feat/pmp-baselinesfrom
refactor/placeholder-map
Closed

refactor(regression): collapse path-placeholder clump into PlaceholderMap#759
lewisjared wants to merge 4 commits into
feat/pmp-baselinesfrom
refactor/placeholder-map

Conversation

@lewisjared

@lewisjared lewisjared commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

Behaviour-preserving refactor stacked on #756. It collapses the regression-test path-placeholder data clump into a single PlaceholderMap value object.

Previously the placeholder set (<OUTPUT_DIR> / <TEST_DATA_DIR> / <SOFTWARE_ROOT_DIR>) was expressed three ways: loose output_dir / test_data_dir / software_root_dir parameters threaded through ~8 signatures, a private _placeholder_pairs list, and hand-built {real: token} dicts at the verification call sites. Adding the third placeholder (<SOFTWARE_ROOT_DIR>, in #756) had to be threaded through every layer, and the verification side had already drifted — it only knew two of the three tokens.

PlaceholderMap (in climate_ref_core.output_files) now owns the (token, directory) set. The configuration-stable tokens come from PlaceholderMap.for_baseline(...); the per-execution <OUTPUT_DIR> is late-bound via .with_output(...); and .sanitise() / .hydrate() / .as_replacements() replace the old free functions. The three free functions (to_placeholders, from_placeholders, _placeholder_pairs) are deleted, and every caller now passes a single placeholders argument. The verification side (RegressionValidator, plus the conftest ExecutionRegression / DiagnosticValidator) derives its replacements from the same map, so capture and verification can no longer declare different token sets and drift apart.

No behaviour change: the path rewriting, longest-match-first ordering, and float/provenance handling are identical. Verified with ruff, ruff format, mypy, and the existing unit + integration suites (241 passed, 1 skipped), plus an independent review pass.

Stacked on #756 — until that merges, this PR's base is feat/pmp-baselines, so review it on top of #756 (or retarget to main once #756 lands; the diff stays refactor-only).

Checklist

Please confirm that this pull request has done the following:

  • Tests added
  • Documentation added (where applicable)
  • Changelog item added to changelog/ (759.trivial.md)

Summary by CodeRabbit

  • New Features

    • Regression baseline capture and replay now use a unified placeholder handling flow, making path substitution more consistent across test case workflows.
    • Test-case build, replay, and mint commands now apply the same placeholder bindings throughout the lifecycle.
  • Bug Fixes

    • Improved consistency when sanitising and restoring paths in committed bundles.
    • Placeholder substitution now handles output, test data, and optional software-root paths more reliably across regression runs.

…rMap

Introduce a frozen PlaceholderMap value object in climate_ref_core.output_files that owns the (token, directory) placeholder set. Config-stable tokens come from for_baseline(); the per-execution <OUTPUT_DIR> is late-bound via with_output(); sanitise()/hydrate()/as_replacements() replace the to_placeholders/from_placeholders/_placeholder_pairs free functions, which are removed.

Every caller now passes a single placeholders argument instead of threading output_dir/test_data_dir/software_root_dir through ~8 signatures, and the verification side (RegressionValidator, conftest ExecutionRegression/DiagnosticValidator) derives its replacements from the same map so capture and verification can no longer declare different token sets and drift apart. Behaviour-preserving.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e7aad6c-7050-4317-928f-f95c2e5d5d40

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/placeholder-map

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/climate-ref-core/src/climate_ref_core/regression/capture.py (1)

201-243: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject unbound placeholder maps before writing the committed bundle.

write_committed_bundle() now trusts callers to have already called .with_output(...), but nothing enforces that invariant. Passing PlaceholderMap.for_baseline(...) here will silently leave execution-specific output paths in the committed bundle and digest those machine-specific bytes.

Proposed fix
 def write_committed_bundle(
     source_dir: Path,
     regression_dir: Path,
     *,
     placeholders: PlaceholderMap,
 ) -> dict[str, str]:
@@
     regression_dir.mkdir(parents=True, exist_ok=True)
+
+    if not any(token == PLACEHOLDER_OUTPUT_DIR for token, _ in placeholders.pairs):
+        raise ValueError("placeholders must be bound with .with_output(...) before writing a committed bundle")
 
     for filename in COMMITTED_BUNDLE_FILES:
         source = source_dir / filename
🧹 Nitpick comments (1)
packages/climate-ref/tests/integration/test_native_roundtrip.py (1)

309-309: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for <SOFTWARE_ROOT_DIR> round-trips.

This helper now hard-codes a baseline map without software_root_dir, so both round-trip paths still exercise only <OUTPUT_DIR> and <TEST_DATA_DIR>. That leaves the new third token untested even though the CLI wiring now supplies it. Consider threading a fake software root through one capture/replay scenario and asserting the sanitise/hydrate round-trip end to end.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e41d26ae-f335-4315-a47d-0a33527f677d

📥 Commits

Reviewing files that changed from the base of the PR and between 007f94a and 0726420.

📒 Files selected for processing (11)
  • changelog/759.trivial.md
  • packages/climate-ref-core/src/climate_ref_core/output_files.py
  • packages/climate-ref-core/src/climate_ref_core/regression/capture.py
  • packages/climate-ref-core/src/climate_ref_core/testing.py
  • packages/climate-ref-core/tests/unit/regression/test_capture.py
  • packages/climate-ref-core/tests/unit/test_output_files.py
  • packages/climate-ref/src/climate_ref/cli/test_cases/_stages.py
  • packages/climate-ref/src/climate_ref/cli/test_cases/baselines.py
  • packages/climate-ref/src/climate_ref/cli/test_cases/run.py
  • packages/climate-ref/src/climate_ref/conftest_plugin.py
  • packages/climate-ref/tests/integration/test_native_roundtrip.py

Comment thread packages/climate-ref-core/src/climate_ref_core/output_files.py
@lewisjared lewisjared closed this Jun 29, 2026
@lewisjared lewisjared deleted the refactor/placeholder-map branch June 29, 2026 03:52
lewisjared added a commit to schlunma/climate-ref that referenced this pull request Jun 30, 2026
…pture

Make committed regression baselines reproducible from their stored native
blobs on any machine and provider, so `ref test-cases replay` and the
coupling gate stop reporting spurious drift.

Two layers:

1. Port the PlaceholderMap path machinery (from Climate-REF#759/Climate-REF#756): a single
   bidirectional map (`<OUTPUT_DIR>` / `<TEST_DATA_DIR>` /
   `<SOFTWARE_ROOT_DIR>`) drives capture sanitise, replay hydrate and the
   comparator, sourcing the software root from `config.paths.software`;
   plus `<USER>`/`<DATE>` provenance redaction in the committed bundle.

2. Capture reconstruction inputs and store native portably:
   - `copy_execution_outputs(..., extra_globs=)` persists extra declared
     globs beyond the bundle-referenced files (deduped, directories skipped).
   - new `Diagnostic.reconstruction_inputs` declares the raw artefacts a
     provider's `build_execution_result` re-scans (provenance YAML, driver
     `*_cmec.json`) that the curated set would otherwise exclude.
   - `snapshot_native` sanitises the slot to placeholders before digesting,
     so native digests are machine-independent (re-mint uploads nothing) and
     replay can hydrate the blobs anywhere -- load-bearing now that the
     captured provenance/cmec files are path-bearing.

Rebuild stays on `build_execution_result`, so a replay re-derives the bundle
from the captured inputs (option 2) rather than from an incomplete output dir.

Tests: extra_globs behaviour, snapshot portability incl. `<SOFTWARE_ROOT_DIR>`
and machine-independent digests, and a reconstruction-input round-trip proving
replay rebuilds from a captured provenance file (positive) and fails without it
(negative).

Constraint: native blobs must stay machine-independent or re-mints churn the store
Constraint: capture_execution writes the production results dir, so native is only sanitised in the throwaway output slot, never in place there
Rejected: option 1 (rebuild from committed output.json) | neuters replay's ability to catch build_execution_result regressions
Directive: a provider declaring reconstruction_inputs must also ensure build_execution_result is deterministic under replay (stable session dir; idempotent in-place mutations like pmp clean_up_json)
Confidence: high
Scope-risk: moderate
Not-tested: real esmvaltool/pmp providers (no reconstruction_inputs declared yet; needs conda + baseline data)

Claude-Session: https://claude.ai/code/session_01XCLPbjwskcRnJJ3JoQ2Tng
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