climate_ref_core.testing
#
Test infrastructure for diagnostic testing.
This module provides: - TestCase and TestDataSpecification for defining test scenarios - YAML serialization for dataset catalogs (with paths stored separately) - Utilities for CMEC bundle and series regression validation
TestCase
#
A single test case for a diagnostic.
Test cases define scenarios for testing, with data resolved via:
- requests: ESGF requests to fetch data (use ref test-cases fetch)
- datasets_file: Path to a pre-built catalog YAML file
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
datasets_file = None
class-attribute
instance-attribute
#
Path to YAML file with dataset specification (relative to package).
description
instance-attribute
#
Human-readable description of what this test case covers.
name
instance-attribute
#
Name of the test case (e.g., 'default', 'short-timeseries').
requests = None
class-attribute
instance-attribute
#
Optional ESGF requests to fetch data for this test case.
TestCasePaths
#
Path resolver for test case data.
Provides access to all paths within a test case directory: - catalog.yaml: Dataset metadata (tracked in git) - catalog.paths.yaml: Local file paths (gitignored) - regression/: Regression outputs (tracked in git)
Can be constructed from: - A diagnostic + test case name (auto-resolves provider's test-data dir) - An explicit test_data_dir + diagnostic slug + test case name
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |
catalog
property
#
Path to catalog.yaml.
catalog_paths
property
#
Path to catalog.paths.yaml (gitignored, contains local file paths).
manifest
property
#
Path to manifest.json (the regression bundle manifest, tracked in git).
output
property
#
Path to the output/ directory (gitignored; holds materialised native slots).
regression
property
#
Path to regression/ directory.
root
instance-attribute
#
The test case directory (test_data_dir / diagnostic_slug / test_case_name).
test_data_dir
property
#
Path to the test-data directory (parent of diagnostic slug dir).
create()
#
exists()
#
from_diagnostic(diagnostic, test_case)
classmethod
#
Create from a diagnostic, auto-resolving the provider's test-data directory.
Returns None if the provider's test-data directory cannot be determined (e.g., not a development checkout).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
diagnostic
|
Diagnostic
|
The diagnostic to get paths for |
required |
test_case
|
str
|
Test case name (e.g., 'default') |
required |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
from_test_data_dir(test_data_dir, diagnostic_slug, test_case)
classmethod
#
Create from an explicit test data directory.
Use this when you have a test_data_dir fixture (in tests) or know the base path explicitly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
test_data_dir
|
Path
|
Base test data directory (e.g., from test fixture) |
required |
diagnostic_slug
|
str
|
The diagnostic slug |
required |
test_case
|
str
|
Test case name (e.g., 'default') |
required |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
output_slot(label='latest')
#
Path to a named output slot under output/ (gitignored).
A slot is a self-contained, inspectable snapshot of one execute/materialise:
the curated native set (flat, at manifest-relative paths) plus a regression/
subdirectory holding the rebuilt committed bundle and a .source.json stamp.
latest (the default) is overwritten on every run; named slots persist so two
runs can be diffed (e.g. --label before vs --label after).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
Slot name. Must be a single path segment (no separators or |
'latest'
|
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
TestDataSpecification
#
Test data specification for a diagnostic.
Contains multiple named test cases for testing different input datasets.
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
case_names
property
#
Get names of all test cases.
test_cases = field(factory=tuple)
class-attribute
instance-attribute
#
Collection of test cases for this diagnostic.
get_case(name)
#
Get a test case by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name of the test case to retrieve |
required |
Returns:
| Type | Description |
|---|---|
TestCase
|
The matching test case |
Raises:
| Type | Description |
|---|---|
StopIteration
|
If no test case with that name exists |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
catalog_changed_since_regression(paths)
#
Check if the catalog has changed since regression data was generated.
The baseline's input hash is read from manifest.json (catalog_hash),
the single coupling record; there is no separate sidecar.
Returns True if:
- No regression data exists (new test case)
- No manifest, or the manifest records no catalog_hash (legacy regression data)
- No catalog file exists
- The current catalog hash differs from the one recorded in the manifest
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
TestCasePaths
|
TestCasePaths for the test case |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if regression should be regenerated, False otherwise |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
collect_test_case_params(provider)
#
Collect all diagnostic/test_case pairs from a provider for parameterized testing.
Returns a list of pytest.param objects with (diagnostic, test_case_name) tuples, each with an id of "{diagnostic.slug}/{test_case.name}".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
DiagnosticProvider
|
The diagnostic provider to collect test cases from |
required |
Returns:
| Type | Description |
|---|---|
list[ParameterSet]
|
List of pytest.param objects for use with @pytest.mark.parametrize |
Example
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
excluded_test_case_diagnostics()
#
Diagnostic identifiers to skip when fetching or running test cases.
Reads the REF_TEST_CASES_SKIP environment variable
as a comma-separated list of diagnostic slugs or provider/diagnostic pairs.
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
get_catalog_hash(path)
#
Get the hash stored in an existing catalog file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
Path to the catalog YAML file |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The hash string if found, None if file doesn't exist or has no hash |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
is_test_case_excluded(provider_slug, diagnostic_slug)
#
Return True if the diagnostic is excluded via REF_TEST_CASES_SKIP.
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
load_datasets_from_yaml(path)
#
Load ExecutionDatasetCollection from a YAML file.
The YAML file structure:
cmip6:
slug_column: instance_id
selector:
source_id: ACCESS-ESM1-5
datasets:
- instance_id: CMIP6.CMIP...
variable_id: tas
filename: tas_Amon_ACCESS-ESM1-5_historical_r1i1p1f1_gn_185001-201412.nc
# ... other metadata
Paths are loaded from a separate .paths.yaml file if it exists,
allowing the main catalog to be version-controlled while paths
remain user-specific. Multi-file datasets have multiple rows with
paths keyed by {instance_id}::{filename}.
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
save_datasets_to_yaml(datasets, path, *, force=False)
#
Save ExecutionDatasetCollection to a YAML file.
Paths are saved to a separate .paths.yaml file to allow the main
catalog to be version-controlled while paths remain user-specific.
Multi-file datasets (e.g., time-chunked data) are stored as multiple rows,
one per file. Paths are keyed by {instance_id}::{filename} to support
multiple files per dataset.
By default, the catalog is only written if the content has changed
(detected via hash comparison). Use force=True to always write.
The gitignored .paths.yaml sidecar is (re)generated whenever it is missing, even
when the catalog content is unchanged: on a fresh checkout the version-controlled
catalog.yaml exists but the local paths file does not, and run/mint need it to
resolve inputs. In that case the catalog itself is left untouched (so a plain
ref test-cases fetch is enough — no --force required) and only the paths file is written.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
datasets
|
ExecutionDatasetCollection
|
The datasets to save |
required |
path
|
Path
|
Path to write the YAML file |
required |
force
|
bool
|
If True, always write the catalog even if unchanged |
False
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the catalog was (re)written, False if the catalog was left unchanged (the paths sidecar may still have been regenerated). |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
validate_cmec_bundles(diagnostic, result)
#
Validate CMEC bundles in an execution result.
Performs structural validation of the metric and output bundles.
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the result is not successful or bundles are invalid |
Source code in packages/climate-ref-core/src/climate_ref_core/testing.py
validate_series_regression(expected_path, actual_path, *, slug, replacements=None)
#
Assert that regenerated series match the committed regression series.
If expected_path does not exist (legacy regression data without a stored series),
the check is skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expected_path
|
Path
|
Path to the committed |
required |
actual_path
|
Path
|
Path to the freshly regenerated |
required |
slug
|
str
|
The diagnostic slug, used for error messages. |
required |
replacements
|
dict[str, str] | None
|
Optional |
None
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the regenerated series differ from the committed series. |