Skip to content

climate_ref_core.regression.capture #

Capture of regression baselines from a diagnostic execution.

Capture operates on the curated subset of files persisted for an execution, not the raw output in the "scratch" directory. This avoids the need to maintain a separate ignore list for regression captures.

It produces two things:

  • the small committed bundle (series.json / diagnostic.json / output.json) written into the test case regression/ directory, sanitised text-only for portability and tracked in git
  • a native snapshot: a {relpath: NativeEntry} map recording the sha256 digest and size of every persisted native file, for the manifest and the object store.

build_native_snapshot(base_dir, relpaths) #

Record a sha256 + size snapshot of each persisted native file.

Parameters:

Name Type Description Default
base_dir Path

The per-execution results directory the relpaths are resolved against.

required
relpaths list[Path]

The persisted files (relative to base_dir), e.g. the return value of :func:~climate_ref_core.output_files.copy_execution_outputs.

required

Returns:

Type Description
dict[str, NativeEntry]

Mapping of POSIX relpath -> :class:NativeEntry for every persisted file.

Source code in packages/climate-ref-core/src/climate_ref_core/regression/capture.py
def build_native_snapshot(base_dir: Path, relpaths: list[Path]) -> dict[str, NativeEntry]:
    """
    Record a sha256 + size snapshot of each persisted native file.

    Parameters
    ----------
    base_dir
        The per-execution results directory the relpaths are resolved against.
    relpaths
        The persisted files (relative to ``base_dir``), e.g. the return value of
        :func:`~climate_ref_core.output_files.copy_execution_outputs`.

    Returns
    -------
    :
        Mapping of POSIX relpath -> :class:`NativeEntry` for every persisted file.
    """
    entries: dict[str, NativeEntry] = {}
    for relpath in relpaths:
        path = base_dir / relpath
        entries[relpath.as_posix()] = NativeEntry(sha256=sha256_file(path), size=path.stat().st_size)
    return entries

materialise_native(native, store, dest) #

Materialise a native snapshot from a store into a destination directory.

For each (relpath, entry) the blob is fetched from store (keyed by its sha256 digest) to dest / relpath, creating parent directories as needed.

Parameters:

Name Type Description Default
native dict[str, NativeEntry]

Mapping of relpath -> :class:NativeEntry (from a manifest).

required
store NativeStore

A content-addressed :class:~climate_ref_core.regression.store.NativeStore.

required
dest Path

The destination directory the snapshot is materialised into.

required
Source code in packages/climate-ref-core/src/climate_ref_core/regression/capture.py
def materialise_native(native: dict[str, NativeEntry], store: NativeStore, dest: Path) -> None:
    """
    Materialise a native snapshot from a store into a destination directory.

    For each ``(relpath, entry)`` the blob is fetched from ``store`` (keyed by its
    sha256 digest) to ``dest / relpath``, creating parent directories as needed.

    Parameters
    ----------
    native
        Mapping of relpath -> :class:`NativeEntry` (from a manifest).
    store
        A content-addressed :class:`~climate_ref_core.regression.store.NativeStore`.
    dest
        The destination directory the snapshot is materialised into.
    """
    for relpath, entry in native.items():
        # Defend against path traversal: a hand-edited or hostile manifest could
        # carry an absolute path or one with '..' components that escapes dest.
        target = safe_path(relpath, dest, label="native path")
        target.parent.mkdir(parents=True, exist_ok=True)
        store.fetch(entry.sha256, target)

write_committed_bundle(source_dir, regression_dir, *, placeholders) #

Write the sanitised committed CMEC bundle into regression_dir.

Copies each committed artefact present in source_dir into regression_dir, rewrites absolute paths to portable placeholders in place (:meth:~climate_ref_core.output_files.PlaceholderMap.sanitise), then canonicalises every committed JSON file -- rounding floats and redacting host/user CMEC provenance into a deterministic on-disk form (:func:_canonicalise_committed_bundle). When a committed artefact is absent from source_dir, any stale copy left in regression_dir from a previous capture is removed so it is not re-digested.

Parameters:

Name Type Description Default
source_dir Path

Directory holding the freshly persisted CMEC artefacts (the per-execution results directory).

required
regression_dir Path

The destination regression/ directory (created if needed).

required
placeholders PlaceholderMap

The placeholder map for this execution, already bound to the output directory via :meth:~climate_ref_core.output_files.PlaceholderMap.with_output. Its absolute paths are rewritten to portable <TOKEN> placeholders in the copied bundle.

required

Returns:

Type Description
dict[str, str]

The committed digests {filename: sha256} of the bytes just written, suitable for :attr:Manifest.committed.

Raises:

Type Description
ValueError

If placeholders is not bound to an output directory. An unbound map would leave execution-specific output paths in the committed bundle and digest those machine-specific bytes.

Source code in packages/climate-ref-core/src/climate_ref_core/regression/capture.py
def write_committed_bundle(
    source_dir: Path,
    regression_dir: Path,
    *,
    placeholders: PlaceholderMap,
) -> dict[str, str]:
    """
    Write the sanitised committed CMEC bundle into ``regression_dir``.

    Copies each committed artefact present in ``source_dir`` into ``regression_dir``,
    rewrites absolute paths to portable placeholders in place
    (:meth:`~climate_ref_core.output_files.PlaceholderMap.sanitise`),
    then canonicalises every committed JSON file -- rounding floats and redacting host/user CMEC
    provenance into a deterministic on-disk form (:func:`_canonicalise_committed_bundle`).
    When a committed artefact is absent from ``source_dir``,
    any stale copy left in ``regression_dir`` from a previous capture is removed so it is not re-digested.

    Parameters
    ----------
    source_dir
        Directory holding the freshly persisted CMEC artefacts (the per-execution
        results directory).
    regression_dir
        The destination ``regression/`` directory (created if needed).
    placeholders
        The placeholder map for this execution, already bound to the output directory via
        :meth:`~climate_ref_core.output_files.PlaceholderMap.with_output`. Its absolute paths are
        rewritten to portable ``<TOKEN>`` placeholders in the copied bundle.

    Returns
    -------
    :
        The committed digests ``{filename: sha256}`` of the bytes just written,
        suitable for :attr:`Manifest.committed`.

    Raises
    ------
    ValueError
        If ``placeholders`` is not bound to an output directory.
        An unbound map would leave execution-specific output paths in the committed bundle
        and digest those machine-specific bytes.
    """
    if not placeholders.is_output_bound:
        raise ValueError(
            "placeholders must be bound to an output directory via with_output() "
            "before writing a committed bundle"
        )

    regression_dir.mkdir(parents=True, exist_ok=True)

    for filename in COMMITTED_BUNDLE_FILES:
        source = source_dir / filename
        dest = regression_dir / filename
        if source.exists():
            shutil.copy(source, dest)
        else:
            # Drop a stale copy from a previous capture so it is not re-digested.
            dest.unlink(missing_ok=True)

    placeholders.sanitise(regression_dir)
    # Canonicalise every committed file (round floats, redact provenance, re-dump deterministically)
    # before digesting, so the recorded digests are over the stable, portable bytes. Placeholder
    # substitution only rewrites path strings, so its order relative to canonicalisation is immaterial.
    _canonicalise_committed_bundle(regression_dir)
    return compute_committed_digests(regression_dir)