Coverage for tests/test_ndf_common.py: 97%
77 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 15:24 -0700
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 15:24 -0700
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14import pytest
16try:
17 from lsst.images.ndf import (
18 HdsNameShrinker,
19 NdfPointerModel,
20 archive_path_to_hdf5_path,
21 )
22 from lsst.images.ndf._hds import DAT__SZNAM
24 HAVE_H5PY = True
25except ImportError:
26 HAVE_H5PY = False
28skip_no_h5py = pytest.mark.skipif(not HAVE_H5PY, reason="h5py is not installed")
31@skip_no_h5py
32def test_round_trips_through_json() -> None:
33 """Verify NdfPointerModel round-trips through JSON serialization."""
34 original = NdfPointerModel(path="/MORE/LSST/PSF")
35 json_bytes = original.model_dump_json().encode()
36 recovered = NdfPointerModel.model_validate_json(json_bytes)
37 assert recovered == original
40@skip_no_h5py
41def test_archive_path_to_hdf5_path() -> None:
42 """Verify archive_path_to_hdf5_path maps archive paths to HDF5 paths."""
43 shrinker = HdsNameShrinker()
44 assert archive_path_to_hdf5_path("", shrinker) == "/MORE/LSST/JSON"
45 assert archive_path_to_hdf5_path("/psf", shrinker) == "/MORE/LSST/PSF"
46 assert archive_path_to_hdf5_path("/psf/coefficients", shrinker) == "/MORE/LSST/PSF/COEFFICIENTS"
49@skip_no_h5py
50def test_archive_path_shrinks_long_components() -> None:
51 """Verify long path components are shrunk to the HDS name limit."""
52 shrinker = HdsNameShrinker()
53 result = archive_path_to_hdf5_path("/psf/this_component_is_too_long", shrinker)
54 assert result.startswith("/MORE/LSST/PSF/")
55 leaf = result.rsplit("/", 1)[-1]
56 assert len(leaf) <= DAT__SZNAM
57 # The short parent component is untouched; only the long leaf shrinks.
58 assert result.split("/")[3] == "PSF"
61@skip_no_h5py
62def test_archive_path_shrink_round_trips_to_same_value() -> None:
63 """Verify shrinking the same path twice returns the same HDF5 path."""
64 shrinker = HdsNameShrinker()
65 assert archive_path_to_hdf5_path("/noise_realizations/0", shrinker) == archive_path_to_hdf5_path(
66 "/noise_realizations/0", shrinker
67 )
70@skip_no_h5py
71def test_short_names_pass_through_uppercased() -> None:
72 """Verify short names are uppercased and passed through unchanged."""
73 shrinker = HdsNameShrinker()
74 assert shrinker.shrink("psf") == "PSF"
75 # A name exactly at the limit passes through unchanged (uppercased).
76 assert shrinker.shrink("a" * DAT__SZNAM) == "A" * DAT__SZNAM
77 # One character over the limit is shrunk to the limit.
78 assert len(shrinker.shrink("a" * (DAT__SZNAM + 1))) == DAT__SZNAM
81@skip_no_h5py
82def test_long_names_keep_prefix_and_get_counter_token() -> None:
83 """Verify long names are truncated to the HDS limit with a counter."""
84 shrinker = HdsNameShrinker()
85 shrunk = shrinker.shrink("noise_realizations")
86 assert len(shrunk) == DAT__SZNAM
87 assert shrunk == "NOISE_REALI_001"
90@skip_no_h5py
91def test_shrink_is_deterministic_per_instance() -> None:
92 """Verify shrinking the same name twice returns the same result."""
93 shrinker = HdsNameShrinker()
94 assert shrinker.shrink("noise_realizations") == shrinker.shrink("noise_realizations")
97@skip_no_h5py
98def test_distinct_long_names_get_distinct_tokens() -> None:
99 """Verify distinct long names with the same prefix get distinct tokens."""
100 shrinker = HdsNameShrinker()
101 # Identical truncated prefixes cannot collide because the counter
102 # increments for each newly assigned name.
103 assert shrinker.shrink("noise_realization_field") == "NOISE_REALI_001"
104 assert shrinker.shrink("noise_realization_other") == "NOISE_REALI_002"
107@skip_no_h5py
108def test_reserve_shortens_the_budget() -> None:
109 """Verify the reserve parameter reduces the available name length."""
110 shrinker = HdsNameShrinker()
111 shrunk = shrinker.shrink("noise_realizations", reserve=2)
112 assert len(shrunk) == DAT__SZNAM - 2
115@skip_no_h5py
116def test_version_one_matches_plain_shrink() -> None:
117 """Verify shrink_versioned(name, 1) equals plain shrink(name)."""
118 shrinker = HdsNameShrinker()
119 assert shrinker.shrink_versioned("noise_realizations", 1) == shrinker.shrink("noise_realizations")
122@skip_no_h5py
123def test_short_versioned_name_keeps_visible_suffix() -> None:
124 """Verify short names with a version suffix remain human-readable."""
125 shrinker = HdsNameShrinker()
126 assert shrinker.shrink_versioned("data", 2) == "DATA_2"
129@skip_no_h5py
130def test_long_versioned_name_preserves_suffix_within_limit() -> None:
131 """Verify long versioned names stay within the HDS limit."""
132 shrinker = HdsNameShrinker()
133 shrunk = shrinker.shrink_versioned("noise_realizations", 99)
134 assert len(shrunk) == DAT__SZNAM
135 assert shrunk.endswith("_99")
138@skip_no_h5py
139def test_same_base_different_versions_are_distinct() -> None:
140 """Verify the same base name with differing versions gives distinct
141 results.
142 """
143 shrinker = HdsNameShrinker()
144 assert shrinker.shrink_versioned("noise_realizations", 2) != shrinker.shrink_versioned(
145 "noise_realizations", 3
146 )