Skip to content

Commit 63aaa65

Browse files
committed
test: patch holes in test coverage
1 parent 8dcc4e2 commit 63aaa65

2 files changed

Lines changed: 71 additions & 9 deletions

File tree

tests/test_codecs/test_cast_value.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ def test_serialization_roundtrip(codec: CastValue) -> None:
106106
assert codec == restored
107107

108108

109+
# ---------------------------------------------------------------------------
110+
# Construction
111+
# ---------------------------------------------------------------------------
112+
113+
114+
def test_construction_accepts_zdtype_object() -> None:
115+
"""data_type can be a ZDType instance, not just a JSON string."""
116+
from zarr.core.dtype import UInt8
117+
118+
codec = CastValue(data_type=UInt8())
119+
assert codec.dtype.to_native_dtype() == np.dtype("uint8")
120+
121+
122+
def test_construction_rejects_invalid_target_dtype() -> None:
123+
"""Construction rejects target dtypes not in PERMITTED_DATA_TYPE_NAMES."""
124+
with pytest.raises(ValueError, match="Invalid target data type"):
125+
CastValue(data_type="complex64")
126+
127+
109128
# ---------------------------------------------------------------------------
110129
# Validation
111130
# ---------------------------------------------------------------------------

tests/test_codecs/test_scale_offset.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
import zarr
99
from tests.test_codecs.conftest import Expect, ExpectErr
10-
from zarr.codecs.scale_offset import ScaleOffset, _decode, _encode
10+
from zarr.codecs.scale_offset import (
11+
ScaleOffset,
12+
_decode,
13+
_decode_fits_natively,
14+
_encode,
15+
)
1116
from zarr.core.buffer.core import default_buffer_prototype
1217
from zarr.storage._memory import MemoryStore
1318

@@ -124,7 +129,6 @@ def test_construction_accepts_numeric(
124129
)
125130
def test_encode_decode_roundtrip(dtype: str, offset: float, scale: float) -> None:
126131
"""Data survives encode → decode."""
127-
import zarr
128132

129133
arr = zarr.create_array(
130134
store={},
@@ -142,8 +146,6 @@ def test_encode_decode_roundtrip(dtype: str, offset: float, scale: float) -> Non
142146

143147
def test_fill_value_transformed() -> None:
144148
"""Fill value is transformed through the encode formula and read back correctly."""
145-
import zarr
146-
147149
arr = zarr.create_array(
148150
store={},
149151
shape=(10,),
@@ -178,7 +180,6 @@ def test_identity_is_noop() -> None:
178180

179181
def test_rejects_complex_dtype() -> None:
180182
"""Complex dtypes are rejected at array creation time."""
181-
import zarr
182183

183184
with pytest.raises(ValueError, match="only supports integer and floating-point"):
184185
zarr.create_array(
@@ -194,7 +195,6 @@ def test_rejects_complex_dtype() -> None:
194195

195196
def test_uint64_large_value_roundtrip() -> None:
196197
"""uint64 values above 2**63 must survive encode+decode (spec requires uint64 support)."""
197-
import zarr
198198

199199
arr = zarr.create_array(
200200
store={},
@@ -213,7 +213,6 @@ def test_uint64_large_value_roundtrip() -> None:
213213

214214
def test_float_nan_inf_preserved() -> None:
215215
"""NaN and Inf are representable in float dtypes per IEEE 754 and must pass through."""
216-
from zarr.codecs.scale_offset import _decode, _encode
217216

218217
arr = np.array([1.0, np.nan, np.inf, -np.inf], dtype="float64")
219218
encoded = _encode(arr, np.float64(0.0), np.float64(2.0))
@@ -228,7 +227,6 @@ def test_float_nan_inf_preserved() -> None:
228227

229228
def test_uint64_encode_rejects_underflow() -> None:
230229
"""uint64 underflow during encode raises rather than silently wrapping."""
231-
import zarr
232230

233231
arr = zarr.create_array(
234232
store={},
@@ -245,7 +243,6 @@ def test_uint64_encode_rejects_underflow() -> None:
245243

246244
def test_rejects_zero_scale() -> None:
247245
"""scale=0 is rejected (destroys data and breaks decode division)."""
248-
import zarr
249246

250247
with pytest.raises(ValueError, match="scale must be non-zero"):
251248
zarr.create_array(
@@ -412,3 +409,49 @@ async def test_decode_rejects_integer_overflow_on_offset_add() -> None:
412409
await arr.store_path.store.set("c/0", buf)
413410
with pytest.raises(ValueError, match="outside the range of dtype int8"):
414411
arr[:]
412+
413+
414+
def test_decode_fits_natively_negative_scale() -> None:
415+
"""_decode_fits_natively handles negative scale by swapping bounds."""
416+
# For a negative scale, x // scale flips the relationship between min/max.
417+
# The function should use info.max // scale as the lower bound and info.min // scale
418+
# as the upper bound.
419+
dtype = np.dtype("int16")
420+
# scale=-2 inverts; offset=0 means range is just q_lo..q_hi
421+
assert _decode_fits_natively(dtype, offset=0, scale=-2) is True
422+
# An offset that pushes the range out of bounds returns False
423+
assert _decode_fits_natively(dtype, offset=100000, scale=-2) is False
424+
425+
426+
async def test_decode_int_widened_path() -> None:
427+
"""When _decode_fits_natively returns False, decode falls through to the widened path."""
428+
# For uint32 with offset near max, q_hi + offset can exceed uint32 if computed in target dtype.
429+
# The widened path uses int64 arithmetic and range-checks the result.
430+
# We bypass encode by writing raw bytes directly to the store.
431+
store = MemoryStore()
432+
arr = zarr.create_array(
433+
store=store,
434+
shape=(3,),
435+
dtype="uint32",
436+
chunks=(3,),
437+
# offset large enough that _decode_fits_natively returns False
438+
filters=[ScaleOffset(offset=2**31, scale=1)],
439+
compressors=None,
440+
# fill_value must be >= offset to avoid uint32 underflow during encode
441+
fill_value=2**31,
442+
)
443+
# Encoded values that, when added to offset, stay within uint32
444+
buf = default_buffer_prototype().buffer.from_bytes(
445+
np.array([0, 100, 1000], dtype="uint32").tobytes()
446+
)
447+
await arr.store_path.store.set("c/0", buf)
448+
expected = np.array([2**31, 2**31 + 100, 2**31 + 1000], dtype="uint32")
449+
np.testing.assert_array_equal(arr[:], expected)
450+
451+
452+
def test_compute_encoded_size() -> None:
453+
"""compute_encoded_size returns the input byte length unchanged (codec is fixed-size)."""
454+
codec = ScaleOffset(offset=0, scale=1)
455+
# The chunk_spec argument is unused; pass any sentinel
456+
assert codec.compute_encoded_size(input_byte_length=100, _chunk_spec=None) == 100 # type: ignore[arg-type]
457+
assert codec.compute_encoded_size(input_byte_length=0, _chunk_spec=None) == 0 # type: ignore[arg-type]

0 commit comments

Comments
 (0)