1
+ import math
1
2
import sys
2
3
from typing import Any , Literal
3
4
4
5
import hypothesis .extra .numpy as npst
5
6
import hypothesis .strategies as st
6
7
import numpy as np
7
- from hypothesis import given , settings # noqa: F401
8
+ from hypothesis import event , given , settings # noqa: F401
8
9
from hypothesis .strategies import SearchStrategy
9
10
10
11
import zarr
28
29
)
29
30
30
31
32
+ @st .composite # type: ignore[misc]
33
+ def keys (draw : st .DrawFn , * , max_num_nodes : int | None = None ) -> Any :
34
+ return draw (st .lists (node_names , min_size = 1 , max_size = max_num_nodes ).map ("/" .join ))
35
+
36
+
37
+ @st .composite # type: ignore[misc]
38
+ def paths (draw : st .DrawFn , * , max_num_nodes : int | None = None ) -> Any :
39
+ return draw (st .just ("/" ) | keys (max_num_nodes = max_num_nodes ))
40
+
41
+
31
42
def v3_dtypes () -> st .SearchStrategy [np .dtype ]:
32
43
return (
33
44
npst .boolean_dtypes ()
@@ -87,17 +98,19 @@ def clear_store(x: Store) -> Store:
87
98
node_names = st .text (zarr_key_chars , min_size = 1 ).filter (
88
99
lambda t : t not in ("." , ".." ) and not t .startswith ("__" )
89
100
)
101
+ short_node_names = st .text (zarr_key_chars , max_size = 3 , min_size = 1 ).filter (
102
+ lambda t : t not in ("." , ".." ) and not t .startswith ("__" )
103
+ )
90
104
array_names = node_names
91
105
attrs = st .none () | st .dictionaries (_attr_keys , _attr_values )
92
- keys = st .lists (node_names , min_size = 1 ).map ("/" .join )
93
- paths = st .just ("/" ) | keys
94
106
# st.builds will only call a new store constructor for different keyword arguments
95
107
# i.e. stores.examples() will always return the same object per Store class.
96
108
# So we map a clear to reset the store.
97
109
stores = st .builds (MemoryStore , st .just ({})).map (clear_store )
98
110
compressors = st .sampled_from ([None , "default" ])
99
111
zarr_formats : st .SearchStrategy [ZarrFormat ] = st .sampled_from ([3 , 2 ])
100
- array_shapes = npst .array_shapes (max_dims = 4 , min_side = 0 )
112
+ # We de-prioritize arrays having dim sizes 0, 1, 2
113
+ array_shapes = npst .array_shapes (max_dims = 4 , min_side = 3 ) | npst .array_shapes (max_dims = 4 , min_side = 0 )
101
114
102
115
103
116
@st .composite # type: ignore[misc]
@@ -152,13 +165,15 @@ def numpy_arrays(
152
165
draw : st .DrawFn ,
153
166
* ,
154
167
shapes : st .SearchStrategy [tuple [int , ...]] = array_shapes ,
155
- zarr_formats : st .SearchStrategy [ZarrFormat ] = zarr_formats ,
168
+ dtype : np .dtype [Any ] | None = None ,
169
+ zarr_formats : st .SearchStrategy [ZarrFormat ] | None = zarr_formats ,
156
170
) -> Any :
157
171
"""
158
172
Generate numpy arrays that can be saved in the provided Zarr format.
159
173
"""
160
174
zarr_format = draw (zarr_formats )
161
- dtype = draw (v3_dtypes () if zarr_format == 3 else v2_dtypes ())
175
+ if dtype is None :
176
+ dtype = draw (v3_dtypes () if zarr_format == 3 else v2_dtypes ())
162
177
if np .issubdtype (dtype , np .str_ ):
163
178
safe_unicode_strings = safe_unicode_for_dtype (dtype )
164
179
return draw (npst .arrays (dtype = dtype , shape = shapes , elements = safe_unicode_strings ))
@@ -174,11 +189,19 @@ def chunk_shapes(draw: st.DrawFn, *, shape: tuple[int, ...]) -> tuple[int, ...]:
174
189
st .tuples (* [st .integers (min_value = 0 if size == 0 else 1 , max_value = size ) for size in shape ])
175
190
)
176
191
# 2. and now generate the chunks tuple
177
- return tuple (
192
+ chunks = tuple (
178
193
size // nchunks if nchunks > 0 else 0
179
194
for size , nchunks in zip (shape , numchunks , strict = True )
180
195
)
181
196
197
+ for c in chunks :
198
+ event ("chunk size" , c )
199
+
200
+ if any ((c != 0 and s % c != 0 ) for s , c in zip (shape , chunks , strict = True )):
201
+ event ("smaller last chunk" )
202
+
203
+ return chunks
204
+
182
205
183
206
@st .composite # type: ignore[misc]
184
207
def shard_shapes (
@@ -211,7 +234,7 @@ def arrays(
211
234
shapes : st .SearchStrategy [tuple [int , ...]] = array_shapes ,
212
235
compressors : st .SearchStrategy = compressors ,
213
236
stores : st .SearchStrategy [StoreLike ] = stores ,
214
- paths : st .SearchStrategy [str | None ] = paths ,
237
+ paths : st .SearchStrategy [str | None ] = paths (), # noqa: B008
215
238
array_names : st .SearchStrategy = array_names ,
216
239
arrays : st .SearchStrategy | None = None ,
217
240
attrs : st .SearchStrategy = attrs ,
@@ -267,23 +290,56 @@ def arrays(
267
290
return a
268
291
269
292
293
+ @st .composite # type: ignore[misc]
294
+ def simple_arrays (
295
+ draw : st .DrawFn ,
296
+ * ,
297
+ shapes : st .SearchStrategy [tuple [int , ...]] = array_shapes ,
298
+ ) -> Any :
299
+ return draw (
300
+ arrays (
301
+ shapes = shapes ,
302
+ paths = paths (max_num_nodes = 2 ),
303
+ array_names = short_node_names ,
304
+ attrs = st .none (),
305
+ compressors = st .sampled_from ([None , "default" ]),
306
+ )
307
+ )
308
+
309
+
270
310
def is_negative_slice (idx : Any ) -> bool :
271
311
return isinstance (idx , slice ) and idx .step is not None and idx .step < 0
272
312
273
313
314
+ @st .composite # type: ignore[misc]
315
+ def end_slices (draw : st .DrawFn , * , shape : tuple [int ]) -> Any :
316
+ """
317
+ A strategy that slices ranges that include the last chunk.
318
+ This is intended to stress-test handling of a possibly smaller last chunk.
319
+ """
320
+ slicers = []
321
+ for size in shape :
322
+ start = draw (st .integers (min_value = size // 2 , max_value = size - 1 ))
323
+ length = draw (st .integers (min_value = 0 , max_value = size - start ))
324
+ slicers .append (slice (start , start + length ))
325
+ event ("drawing end slice" )
326
+ return tuple (slicers )
327
+
328
+
274
329
@st .composite # type: ignore[misc]
275
330
def basic_indices (draw : st .DrawFn , * , shape : tuple [int ], ** kwargs : Any ) -> Any :
276
331
"""Basic indices without unsupported negative slices."""
277
- return draw (
278
- npst .basic_indices (shape = shape , ** kwargs ).filter (
279
- lambda idxr : (
280
- not (
281
- is_negative_slice (idxr )
282
- or (isinstance (idxr , tuple ) and any (is_negative_slice (idx ) for idx in idxr ))
283
- )
332
+ strategy = npst .basic_indices (shape = shape , ** kwargs ).filter (
333
+ lambda idxr : (
334
+ not (
335
+ is_negative_slice (idxr )
336
+ or (isinstance (idxr , tuple ) and any (is_negative_slice (idx ) for idx in idxr ))
284
337
)
285
338
)
286
339
)
340
+ if math .prod (shape ) >= 3 :
341
+ strategy = end_slices (shape = shape ) | strategy
342
+ return draw (strategy )
287
343
288
344
289
345
@st .composite # type: ignore[misc]
0 commit comments