1
1
# Stateful tests for arbitrary Zarr stores.
2
-
3
-
4
2
import hypothesis .strategies as st
3
+ import pytest
5
4
from hypothesis import assume , note
6
5
from hypothesis .stateful import (
7
6
RuleBasedStateMachine ,
7
+ Settings ,
8
+ initialize ,
8
9
invariant ,
9
10
precondition ,
10
11
rule ,
12
+ run_state_machine_as_test ,
11
13
)
12
14
from hypothesis .strategies import DataObject
13
15
14
16
import zarr
15
17
from zarr .abc .store import AccessMode , Store
16
18
from zarr .core .buffer import BufferPrototype , cpu , default_buffer_prototype
17
- from zarr .store import MemoryStore
18
- from zarr .testing .strategies import key_ranges , paths
19
+ from zarr .store import LocalStore , ZipStore
20
+ from zarr .testing .strategies import key_ranges
21
+ from zarr .testing .strategies import keys as zarr_keys
22
+
23
+ MAX_BINARY_SIZE = 100
19
24
20
25
21
26
class SyncStoreWrapper (zarr .core .sync .SyncMixin ):
@@ -99,13 +104,17 @@ class ZarrStoreStateMachine(RuleBasedStateMachine):
99
104
https://hypothesis.readthedocs.io/en/latest/stateful.html
100
105
"""
101
106
102
- def __init__ (self ) -> None :
107
+ def __init__ (self , store : Store ) -> None :
103
108
super ().__init__ ()
104
109
self .model : dict [str , bytes ] = {}
105
- self .store = SyncStoreWrapper (MemoryStore ( mode = "w" ) )
110
+ self .store = SyncStoreWrapper (store )
106
111
self .prototype = default_buffer_prototype ()
107
112
108
- @rule (key = paths , data = st .binary (min_size = 0 , max_size = 100 ))
113
+ @initialize ()
114
+ def init_store (self ):
115
+ self .store .clear ()
116
+
117
+ @rule (key = zarr_keys , data = st .binary (min_size = 0 , max_size = MAX_BINARY_SIZE ))
109
118
def set (self , key : str , data : DataObject ) -> None :
110
119
note (f"(set) Setting { key !r} with { data } " )
111
120
assert not self .store .mode .readonly
@@ -114,7 +123,7 @@ def set(self, key: str, data: DataObject) -> None:
114
123
self .model [key ] = data_buf
115
124
116
125
@precondition (lambda self : len (self .model .keys ()) > 0 )
117
- @rule (key = paths , data = st .data ())
126
+ @rule (key = zarr_keys , data = st .data ())
118
127
def get (self , key : str , data : DataObject ) -> None :
119
128
key = data .draw (
120
129
st .sampled_from (sorted (self .model .keys ()))
@@ -124,16 +133,18 @@ def get(self, key: str, data: DataObject) -> None:
124
133
# to bytes here necessary because data_buf set to model in set()
125
134
assert self .model [key ].to_bytes () == (store_value .to_bytes ())
126
135
127
- @rule (key = paths , data = st .data ())
128
- def get_invalid_keys (self , key : str , data : DataObject ) -> None :
136
+ @rule (key = zarr_keys , data = st .data ())
137
+ def get_invalid_zarr_keys (self , key : str , data : DataObject ) -> None :
129
138
note ("(get_invalid)" )
130
139
assume (key not in self .model )
131
140
assert self .store .get (key , self .prototype ) is None
132
141
133
142
@precondition (lambda self : len (self .model .keys ()) > 0 )
134
143
@rule (data = st .data ())
135
144
def get_partial_values (self , data : DataObject ) -> None :
136
- key_range = data .draw (key_ranges (keys = st .sampled_from (sorted (self .model .keys ()))))
145
+ key_range = data .draw (
146
+ key_ranges (keys = st .sampled_from (sorted (self .model .keys ())), max_size = MAX_BINARY_SIZE )
147
+ )
137
148
note (f"(get partial) { key_range = } " )
138
149
obs_maybe = self .store .get_partial_values (key_range , self .prototype )
139
150
observed = []
@@ -173,16 +184,20 @@ def clear(self) -> None:
173
184
self .store .clear ()
174
185
self .model .clear ()
175
186
187
+ assert self .store .empty ()
188
+
176
189
assert len (self .model .keys ()) == len (list (self .store .list ())) == 0
177
190
178
191
@rule ()
192
+ # Local store can be non-empty when there are subdirectories but no files
193
+ @precondition (lambda self : not isinstance (self .store .store , LocalStore ))
179
194
def empty (self ) -> None :
180
195
note ("(empty)" )
181
196
182
197
# make sure they either both are or both aren't empty (same state)
183
198
assert self .store .empty () == (not self .model )
184
199
185
- @rule (key = paths )
200
+ @rule (key = zarr_keys )
186
201
def exists (self , key : str ) -> None :
187
202
note ("(exists)" )
188
203
@@ -191,9 +206,9 @@ def exists(self, key: str) -> None:
191
206
@invariant ()
192
207
def check_paths_equal (self ) -> None :
193
208
note ("Checking that paths are equal" )
194
- paths = list (self .store .list ())
209
+ paths = sorted (self .store .list ())
195
210
196
- assert list (self .model .keys ()) == paths
211
+ assert sorted (self .model .keys ()) == paths
197
212
198
213
@invariant ()
199
214
def check_vals_equal (self ) -> None :
@@ -203,24 +218,32 @@ def check_vals_equal(self) -> None:
203
218
assert val .to_bytes () == store_item
204
219
205
220
@invariant ()
206
- def check_num_keys_equal (self ) -> None :
207
- note ("check num keys equal" )
221
+ def check_num_zarr_keys_equal (self ) -> None :
222
+ note ("check num zarr_keys equal" )
208
223
209
224
assert len (self .model ) == len (list (self .store .list ()))
210
225
211
226
@invariant ()
212
- def check_keys (self ) -> None :
227
+ def check_zarr_keys (self ) -> None :
213
228
keys = list (self .store .list ())
214
229
215
- if len ( keys ) == 0 :
230
+ if not keys :
216
231
assert self .store .empty () is True
217
232
218
- elif len ( keys ) != 0 :
233
+ else :
219
234
assert self .store .empty () is False
220
235
221
236
for key in keys :
222
237
assert self .store .exists (key ) is True
223
238
note ("checking keys / exists / empty" )
224
239
225
240
226
- StatefulStoreTest = ZarrStoreStateMachine .TestCase
241
+ def test_zarr_hierarchy (sync_store : Store ) -> None :
242
+ def mk_test_instance_sync ():
243
+ return ZarrStoreStateMachine (sync_store )
244
+
245
+ if isinstance (sync_store , ZipStore ):
246
+ pytest .skip (reason = "ZipStore does not support delete" )
247
+ if isinstance (sync_store , LocalStore ):
248
+ pytest .skip (reason = "This test has errors" )
249
+ run_state_machine_as_test (mk_test_instance_sync , settings = Settings (report_multiple_bugs = True ))
0 commit comments