|
21 | 21 | from zarr.core.buffer import Buffer, BufferPrototype, cpu, default_buffer_prototype
|
22 | 22 | from zarr.core.sync import SyncMixin
|
23 | 23 | from zarr.storage import LocalStore, MemoryStore
|
24 |
| -from zarr.testing.strategies import key_ranges, node_names, np_array_and_chunks, numpy_arrays |
| 24 | +from zarr.testing.strategies import ( |
| 25 | + basic_indices, |
| 26 | + chunk_paths, |
| 27 | + key_ranges, |
| 28 | + node_names, |
| 29 | + np_array_and_chunks, |
| 30 | + numpy_arrays, |
| 31 | +) |
25 | 32 | from zarr.testing.strategies import keys as zarr_keys
|
26 | 33 |
|
27 | 34 | MAX_BINARY_SIZE = 100
|
@@ -120,6 +127,120 @@ def add_array(
|
120 | 127 | )
|
121 | 128 | self.all_arrays.add(path)
|
122 | 129 |
|
| 130 | + @rule() |
| 131 | + def clear(self) -> None: |
| 132 | + note("clearing") |
| 133 | + import zarr |
| 134 | + |
| 135 | + self._sync(self.store.clear()) |
| 136 | + self._sync(self.model.clear()) |
| 137 | + |
| 138 | + assert self._sync(self.store.is_empty("/")) |
| 139 | + assert self._sync(self.model.is_empty("/")) |
| 140 | + |
| 141 | + self.all_groups.clear() |
| 142 | + self.all_arrays.clear() |
| 143 | + |
| 144 | + zarr.group(store=self.store) |
| 145 | + zarr.group(store=self.model) |
| 146 | + |
| 147 | + # TODO: MemoryStore is broken? |
| 148 | + # assert not self._sync(self.store.is_empty("/")) |
| 149 | + # assert not self._sync(self.model.is_empty("/")) |
| 150 | + |
| 151 | + def draw_directory(self, data: DataObject) -> str: |
| 152 | + group_st = st.sampled_from(sorted(self.all_groups)) if self.all_groups else st.nothing() |
| 153 | + array_st = st.sampled_from(sorted(self.all_arrays)) if self.all_arrays else st.nothing() |
| 154 | + array_or_group = data.draw(st.one_of(group_st, array_st)) |
| 155 | + if data.draw(st.booleans()) and array_or_group in self.all_arrays: |
| 156 | + arr = zarr.open_array(path=array_or_group, store=self.model) |
| 157 | + path = data.draw( |
| 158 | + st.one_of( |
| 159 | + st.sampled_from([array_or_group]), |
| 160 | + chunk_paths(ndim=arr.ndim, numblocks=arr.cdata_shape).map( |
| 161 | + lambda x: f"{array_or_group}/c/" |
| 162 | + ), |
| 163 | + ) |
| 164 | + ) |
| 165 | + else: |
| 166 | + path = array_or_group |
| 167 | + return path |
| 168 | + |
| 169 | + @precondition(lambda self: bool(self.all_groups)) |
| 170 | + @rule(data=st.data()) |
| 171 | + def check_list_dir(self, data: DataObject) -> None: |
| 172 | + path = self.draw_directory(data) |
| 173 | + note(f"list_dir for {path=!r}") |
| 174 | + # Consider .list_dir("path/to/array") for an array with a single chunk. |
| 175 | + # The MemoryStore model will return `"c", "zarr.json"` only if the chunk exists |
| 176 | + # If that chunk was deleted, then `"c"` is not returned. |
| 177 | + # LocalStore will not have this behaviour :/ |
| 178 | + # There are similar consistency issues with delete_dir("/path/to/array/c/0/0") |
| 179 | + assume(not isinstance(self.store, LocalStore)) |
| 180 | + model_ls = sorted(self._sync_iter(self.model.list_dir(path))) |
| 181 | + store_ls = sorted(self._sync_iter(self.store.list_dir(path))) |
| 182 | + assert model_ls == store_ls, (model_ls, store_ls) |
| 183 | + |
| 184 | + @precondition(lambda self: bool(self.all_arrays)) |
| 185 | + @rule(data=st.data()) |
| 186 | + def delete_chunk(self, data: DataObject) -> None: |
| 187 | + array = data.draw(st.sampled_from(sorted(self.all_arrays))) |
| 188 | + arr = zarr.open_array(path=array, store=self.model) |
| 189 | + chunk_path = data.draw(chunk_paths(ndim=arr.ndim, numblocks=arr.cdata_shape, subset=False)) |
| 190 | + path = f"{array}/c/{chunk_path}" |
| 191 | + note(f"deleting chunk {path=!r}") |
| 192 | + self._sync(self.model.delete(path)) |
| 193 | + self._sync(self.store.delete(path)) |
| 194 | + |
| 195 | + @precondition(lambda self: bool(self.all_arrays)) |
| 196 | + @rule(data=st.data()) |
| 197 | + def overwrite_array_basic_indexing(self, data: DataObject) -> None: |
| 198 | + array = data.draw(st.sampled_from(sorted(self.all_arrays))) |
| 199 | + model_array = zarr.open_array(path=array, store=self.model) |
| 200 | + store_array = zarr.open_array(path=array, store=self.store) |
| 201 | + slicer = data.draw(basic_indices(shape=model_array.shape)) |
| 202 | + note(f"overwriting array with basic indexer: {slicer=}") |
| 203 | + new_data = data.draw( |
| 204 | + npst.arrays(shape=np.shape(model_array[slicer]), dtype=model_array.dtype) |
| 205 | + ) |
| 206 | + model_array[slicer] = new_data |
| 207 | + store_array[slicer] = new_data |
| 208 | + |
| 209 | + @precondition(lambda self: bool(self.all_arrays)) |
| 210 | + @rule(data=st.data()) |
| 211 | + def resize_array(self, data: DataObject) -> None: |
| 212 | + array = data.draw(st.sampled_from(sorted(self.all_arrays))) |
| 213 | + model_array = zarr.open_array(path=array, store=self.model) |
| 214 | + store_array = zarr.open_array(path=array, store=self.store) |
| 215 | + ndim = model_array.ndim |
| 216 | + new_shape = tuple( |
| 217 | + 0 if oldsize == 0 else newsize |
| 218 | + for newsize, oldsize in zip( |
| 219 | + data.draw(npst.array_shapes(max_dims=ndim, min_dims=ndim, min_side=0)), |
| 220 | + model_array.shape, |
| 221 | + strict=True, |
| 222 | + ) |
| 223 | + ) |
| 224 | + |
| 225 | + note(f"resizing array from {model_array.shape} to {new_shape}") |
| 226 | + model_array.resize(new_shape) |
| 227 | + store_array.resize(new_shape) |
| 228 | + |
| 229 | + @precondition(lambda self: bool(self.all_arrays) or bool(self.all_groups)) |
| 230 | + @rule(data=st.data()) |
| 231 | + def delete_dir(self, data: DataObject) -> None: |
| 232 | + path = self.draw_directory(data) |
| 233 | + note(f"delete_dir with {path=!r}") |
| 234 | + self._sync(self.model.delete_dir(path)) |
| 235 | + self._sync(self.store.delete_dir(path)) |
| 236 | + |
| 237 | + matches = set() |
| 238 | + for node in self.all_groups | self.all_arrays: |
| 239 | + if node.startswith(path): |
| 240 | + matches.add(node) |
| 241 | + self.all_groups = self.all_groups - matches |
| 242 | + self.all_arrays = self.all_arrays - matches |
| 243 | + |
123 | 244 | # @precondition(lambda self: bool(self.all_groups))
|
124 | 245 | # @precondition(lambda self: bool(self.all_arrays))
|
125 | 246 | # @rule(data=st.data())
|
@@ -230,13 +351,19 @@ def delete_group_using_del(self, data: DataObject) -> None:
|
230 | 351 | # self.check_group_arrays(group)
|
231 | 352 | # t1 = time.time()
|
232 | 353 | # note(f"Checks took {t1 - t0} sec.")
|
233 |
| - |
234 | 354 | @invariant()
|
235 | 355 | def check_list_prefix_from_root(self) -> None:
|
236 | 356 | model_list = self._sync_iter(self.model.list_prefix(""))
|
237 | 357 | store_list = self._sync_iter(self.store.list_prefix(""))
|
238 |
| - note(f"Checking {len(model_list)} keys") |
239 |
| - assert sorted(model_list) == sorted(store_list) |
| 358 | + note(f"Checking {len(model_list)} expected keys vs {len(store_list)} actual keys") |
| 359 | + assert sorted(model_list) == sorted(store_list), ( |
| 360 | + sorted(model_list), |
| 361 | + sorted(store_list), |
| 362 | + ) |
| 363 | + |
| 364 | + # check that our internal state matches that of the store and model |
| 365 | + assert all(f"{path}/zarr.json" in model_list for path in self.all_groups | self.all_arrays) |
| 366 | + assert all(f"{path}/zarr.json" in store_list for path in self.all_groups | self.all_arrays) |
240 | 367 |
|
241 | 368 |
|
242 | 369 | class SyncStoreWrapper(zarr.core.sync.SyncMixin):
|
|
0 commit comments