|
4 | 4 | import numpy as np |
5 | 5 | import pytest |
6 | 6 |
|
7 | | -from tests.test_codecs.conftest import ExpectErr |
| 7 | +from tests.test_codecs.conftest import Expect, ExpectErr |
8 | 8 | from zarr.core.chunk_grids import ( |
9 | 9 | _guess_regular_chunks, |
| 10 | + normalize_chunks_1d, |
10 | 11 | normalize_chunks_nd, |
11 | 12 | resolve_outer_and_inner_chunks, |
12 | 13 | ) |
@@ -128,103 +129,93 @@ def test_chunk_layout_nested() -> None: |
128 | 129 | assert top.inner.inner.inner is None |
129 | 130 |
|
130 | 131 |
|
131 | | -def test_normalize_chunks_1d_errors() -> None: |
132 | | - from zarr.core.chunk_grids import normalize_chunks_1d |
133 | | - |
134 | | - with pytest.raises(ValueError, match="Chunk size must be positive"): |
135 | | - normalize_chunks_1d(0, 100) |
136 | | - with pytest.raises(ValueError, match="Chunk size must be positive"): |
137 | | - normalize_chunks_1d(-2, 100) |
138 | | - with pytest.raises(ValueError, match="must not be empty"): |
139 | | - normalize_chunks_1d([], 100) |
140 | | - with pytest.raises(ValueError, match="must be positive"): |
141 | | - normalize_chunks_1d([10, -1, 10], 100) |
142 | | - with pytest.raises(ValueError, match="do not sum to span"): |
143 | | - normalize_chunks_1d([10, 20], 100) |
144 | | - |
145 | | - |
146 | 132 | @pytest.mark.parametrize( |
147 | 133 | "case", |
148 | 134 | [ |
149 | | - # The motivating case: nested/RLE form for a single dim. |
| 135 | + ExpectErr(input=(0, 100), msg="Chunk size must be positive", exception_cls=ValueError), |
| 136 | + ExpectErr(input=(-2, 100), msg="Chunk size must be positive", exception_cls=ValueError), |
| 137 | + ExpectErr(input=([], 100), msg="must not be empty", exception_cls=ValueError), |
| 138 | + ExpectErr(input=([10, -1, 10], 100), msg="must be positive", exception_cls=ValueError), |
| 139 | + ExpectErr(input=([10, 20], 100), msg="do not sum to span", exception_cls=ValueError), |
| 140 | + # Nested/RLE form for a single dim is rejected with offending indices. |
150 | 141 | ExpectErr( |
151 | 142 | input=([[3, 3], 1], 7), |
152 | 143 | msg="non-integer element(s) ([3, 3],) at indices (0,)", |
153 | 144 | exception_cls=TypeError, |
154 | 145 | ), |
155 | | - # Multiple non-int elements report all offending indices. |
| 146 | + # Multiple non-int elements: all offending indices reported. |
156 | 147 | ExpectErr( |
157 | 148 | input=([1, [2, 2], 1, [3]], 9), |
158 | 149 | msg="non-integer element(s) ([2, 2], [3]) at indices (1, 3)", |
159 | 150 | exception_cls=TypeError, |
160 | 151 | ), |
161 | | - # Strings are also non-integers and should be reported the same way. |
| 152 | + # Strings are non-integers and should be reported the same way. |
162 | 153 | ExpectErr( |
163 | 154 | input=([2, "3", 5], 10), |
164 | 155 | msg="non-integer element(s) ('3',) at indices (1,)", |
165 | 156 | exception_cls=TypeError, |
166 | 157 | ), |
167 | 158 | ], |
168 | | - ids=["rle-single-dim", "multiple-non-ints", "string-element"], |
| 159 | + ids=[ |
| 160 | + "zero-uniform", |
| 161 | + "negative-uniform", |
| 162 | + "empty-list", |
| 163 | + "negative-element", |
| 164 | + "wrong-sum", |
| 165 | + "rle-single-dim", |
| 166 | + "multiple-non-ints", |
| 167 | + "string-element", |
| 168 | + ], |
169 | 169 | ) |
170 | | -def test_normalize_chunks_1d_rejects_non_int_elements( |
171 | | - case: ExpectErr[tuple[list[Any], int]], |
172 | | -) -> None: |
173 | | - """Reject nested/RLE-style chunk specs with a precise error pointing at offending indices.""" |
174 | | - from zarr.core.chunk_grids import normalize_chunks_1d |
175 | | - |
| 170 | +def test_normalize_chunks_1d_errors(case: ExpectErr[tuple[Any, int]]) -> None: |
| 171 | + """Invalid 1D chunk specifications are rejected with informative error messages.""" |
176 | 172 | chunks, span = case.input |
177 | 173 | with pytest.raises(case.exception_cls, match=re.escape(case.msg)): |
178 | 174 | normalize_chunks_1d(chunks, span=span) |
179 | 175 |
|
180 | 176 |
|
181 | | -def test_normalize_chunks_nd_rejects_rle_inner_dim() -> None: |
182 | | - """End-to-end: a per-dim RLE form like [[3, 3], 1] surfaces the precise error.""" |
183 | | - with pytest.raises( |
184 | | - TypeError, match=re.escape("non-integer element(s) ([3, 3],) at indices (0,)") |
185 | | - ): |
186 | | - normalize_chunks_nd([[6, 4], [[3, 3], 1]], (10, 10)) |
187 | | - |
188 | | - |
189 | | -def test_normalize_chunks_errors() -> None: |
190 | | - with pytest.raises(ValueError, match="does not accept None"): |
191 | | - normalize_chunks_nd(None, (100,)) |
192 | | - with pytest.raises(ValueError): |
193 | | - normalize_chunks_nd("foo", (100,)) |
194 | | - with pytest.raises(ValueError, match="dimensions"): |
195 | | - normalize_chunks_nd((100, 10), (100,)) |
196 | | - with pytest.raises(ValueError, match="dimensions"): |
197 | | - normalize_chunks_nd((10,), (100, 100)) |
198 | | - |
| 177 | +@pytest.mark.parametrize( |
| 178 | + "case", |
| 179 | + [ |
| 180 | + ExpectErr(input=(None, (100,)), msg="does not accept None", exception_cls=ValueError), |
| 181 | + ExpectErr(input=("foo", (100,)), msg="dimensions", exception_cls=ValueError), |
| 182 | + ExpectErr(input=((100, 10), (100,)), msg="dimensions", exception_cls=ValueError), |
| 183 | + ExpectErr(input=((10,), (100, 100)), msg="dimensions", exception_cls=ValueError), |
| 184 | + # End-to-end: per-dim RLE surfaces through normalize_chunks_nd. |
| 185 | + ExpectErr( |
| 186 | + input=([[6, 4], [[3, 3], 1]], (10, 10)), |
| 187 | + msg="non-integer element(s) ([3, 3],) at indices (0,)", |
| 188 | + exception_cls=TypeError, |
| 189 | + ), |
| 190 | + ], |
| 191 | + ids=["none", "string", "too-many-dims", "too-few-dims", "rle-inner-dim"], |
| 192 | +) |
| 193 | +def test_normalize_chunks_nd_errors(case: ExpectErr[tuple[Any, tuple[int, ...]]]) -> None: |
| 194 | + """Invalid N-D chunk specifications are rejected with informative error messages.""" |
| 195 | + chunks, shape = case.input |
| 196 | + with pytest.raises(case.exception_cls, match=re.escape(case.msg)): |
| 197 | + normalize_chunks_nd(chunks, shape) |
199 | 198 |
|
200 | | -def test_normalize_chunks_1d_uniform_returns_int64_array() -> None: |
201 | | - """The uniform-chunks branch must return a 1D int64 array — this is the |
202 | | - representation that enables O(1) construction via np.full.""" |
203 | | - from zarr.core.chunk_grids import normalize_chunks_1d |
204 | 199 |
|
205 | | - result = normalize_chunks_1d(1000, 100_000) |
| 200 | +@pytest.mark.parametrize( |
| 201 | + "case", |
| 202 | + [ |
| 203 | + # uniform-chunks branch: one int → broadcast across span via np.full. |
| 204 | + Expect(input=(1000, 100_000), expected=[1000] * 100), |
| 205 | + # explicit-per-chunk branch. |
| 206 | + Expect(input=([10, 20, 30, 40], 100), expected=[10, 20, 30, 40]), |
| 207 | + # -1 sentinel branch: one chunk covering the full span. |
| 208 | + Expect(input=(-1, 100), expected=[100]), |
| 209 | + ], |
| 210 | + ids=["uniform", "explicit-list", "full-span-sentinel"], |
| 211 | +) |
| 212 | +def test_normalize_chunks_1d_returns_int64_array( |
| 213 | + case: Expect[tuple[Any, int], list[int]], |
| 214 | +) -> None: |
| 215 | + """Every branch of normalize_chunks_1d must produce a 1D int64 array.""" |
| 216 | + chunks, span = case.input |
| 217 | + result = normalize_chunks_1d(chunks, span) |
206 | 218 | assert isinstance(result, np.ndarray) |
207 | 219 | assert result.dtype == np.int64 |
208 | 220 | assert result.ndim == 1 |
209 | | - assert result.shape == (100,) |
210 | | - assert (result == 1000).all() |
211 | | - |
212 | | - |
213 | | -def test_normalize_chunks_1d_explicit_list_returns_int64_array() -> None: |
214 | | - """The explicit-per-chunk branch must also produce an int64 array.""" |
215 | | - from zarr.core.chunk_grids import normalize_chunks_1d |
216 | | - |
217 | | - result = normalize_chunks_1d([10, 20, 30, 40], 100) |
218 | | - assert isinstance(result, np.ndarray) |
219 | | - assert result.dtype == np.int64 |
220 | | - assert result.tolist() == [10, 20, 30, 40] |
221 | | - |
222 | | - |
223 | | -def test_normalize_chunks_1d_full_span_returns_int64_array() -> None: |
224 | | - """The -1 sentinel branch must also produce an int64 array.""" |
225 | | - from zarr.core.chunk_grids import normalize_chunks_1d |
226 | | - |
227 | | - result = normalize_chunks_1d(-1, 100) |
228 | | - assert isinstance(result, np.ndarray) |
229 | | - assert result.dtype == np.int64 |
230 | | - assert result.tolist() == [100] |
| 221 | + assert result.tolist() == case.expected |
0 commit comments