1
+ from typing import Generic , TypeVar
2
+
1
3
import pytest
2
4
3
5
from zarr .abc .store import Store
4
6
from zarr .buffer import Buffer
7
+ from zarr .store .core import _normalize_interval_index
5
8
from zarr .testing .utils import assert_bytes_equal
6
9
10
+ S = TypeVar ("S" , bound = Store )
11
+
12
+
13
+ class StoreTests (Generic [S ]):
14
+ store_cls : type [S ]
7
15
8
- class StoreTests :
9
- store_cls : type [Store ]
16
+ def set (self , store : S , key : str , value : Buffer ) -> None :
17
+ """
18
+ Insert a value into a storage backend, with a specific key.
19
+ This should not not use any store methods. Bypassing the store methods allows them to be
20
+ tested.
21
+ """
22
+ raise NotImplementedError
23
+
24
+ def get (self , store : S , key : str ) -> Buffer :
25
+ """
26
+ Retrieve a value from a storage backend, by key.
27
+ This should not not use any store methods. Bypassing the store methods allows them to be
28
+ tested.
29
+ """
30
+
31
+ raise NotImplementedError
10
32
11
33
@pytest .fixture (scope = "function" )
12
34
def store (self ) -> Store :
13
35
return self .store_cls ()
14
36
15
- def test_store_type (self , store : Store ) -> None :
37
+ def test_store_type (self , store : S ) -> None :
16
38
assert isinstance (store , Store )
17
39
assert isinstance (store , self .store_cls )
18
40
19
- def test_store_repr (self , store : Store ) -> None :
20
- assert repr (store )
41
+ def test_store_repr (self , store : S ) -> None :
42
+ raise NotImplementedError
43
+
44
+ def test_store_supports_writes (self , store : S ) -> None :
45
+ raise NotImplementedError
21
46
22
- def test_store_capabilities (self , store : Store ) -> None :
23
- assert store .supports_writes
24
- assert store .supports_partial_writes
25
- assert store .supports_listing
47
+ def test_store_supports_partial_writes (self , store : S ) -> None :
48
+ raise NotImplementedError
49
+
50
+ def test_store_supports_listing (self , store : S ) -> None :
51
+ raise NotImplementedError
26
52
27
53
@pytest .mark .parametrize ("key" , ["c/0" , "foo/c/0.0" , "foo/0/0" ])
28
54
@pytest .mark .parametrize ("data" , [b"\x01 \x02 \x03 \x04 " , b"" ])
29
- async def test_set_get_bytes_roundtrip (self , store : Store , key : str , data : bytes ) -> None :
30
- await store .set (key , Buffer .from_bytes (data ))
31
- assert_bytes_equal (await store .get (key ), data )
32
-
33
- @pytest .mark .parametrize ("key" , ["foo/c/0" ])
55
+ @pytest .mark .parametrize ("byte_range" , (None , (0 , None ), (1 , None ), (1 , 2 ), (None , 1 )))
56
+ async def test_get (
57
+ self , store : S , key : str , data : bytes , byte_range : None | tuple [int | None , int | None ]
58
+ ) -> None :
59
+ """
60
+ Ensure that data can be read from the store using the store.get method.
61
+ """
62
+ data_buf = Buffer .from_bytes (data )
63
+ self .set (store , key , data_buf )
64
+ observed = await store .get (key , byte_range = byte_range )
65
+ start , length = _normalize_interval_index (data_buf , interval = byte_range )
66
+ expected = data_buf [start : start + length ]
67
+ assert_bytes_equal (observed , expected )
68
+
69
+ @pytest .mark .parametrize ("key" , ["zarr.json" , "c/0" , "foo/c/0.0" , "foo/0/0" ])
34
70
@pytest .mark .parametrize ("data" , [b"\x01 \x02 \x03 \x04 " , b"" ])
35
- async def test_get_partial_values (self , store : Store , key : str , data : bytes ) -> None :
71
+ async def test_set (self , store : S , key : str , data : bytes ) -> None :
72
+ """
73
+ Ensure that data can be written to the store using the store.set method.
74
+ """
75
+ data_buf = Buffer .from_bytes (data )
76
+ await store .set (key , data_buf )
77
+ observed = self .get (store , key )
78
+ assert_bytes_equal (observed , data_buf )
79
+
80
+ @pytest .mark .parametrize (
81
+ "key_ranges" ,
82
+ (
83
+ [],
84
+ [("zarr.json" , (0 , 1 ))],
85
+ [("c/0" , (0 , 1 )), ("zarr.json" , (0 , None ))],
86
+ [("c/0/0" , (0 , 1 )), ("c/0/1" , (None , 2 )), ("c/0/2" , (0 , 3 ))],
87
+ ),
88
+ )
89
+ async def test_get_partial_values (
90
+ self , store : S , key_ranges : list [tuple [str , tuple [int | None , int | None ]]]
91
+ ) -> None :
36
92
# put all of the data
37
- await store .set (key , Buffer .from_bytes (data ))
93
+ for key , _ in key_ranges :
94
+ self .set (store , key , Buffer .from_bytes (bytes (key , encoding = "utf-8" )))
95
+
38
96
# read back just part of it
39
- vals = await store .get_partial_values ([(key , (0 , 2 ))])
40
- assert_bytes_equal (vals [0 ], data [0 :2 ])
97
+ observed_maybe = await store .get_partial_values (key_ranges = key_ranges )
98
+
99
+ observed : list [Buffer ] = []
100
+ expected : list [Buffer ] = []
101
+
102
+ for obs in observed_maybe :
103
+ assert obs is not None
104
+ observed .append (obs )
105
+
106
+ for idx in range (len (observed )):
107
+ key , byte_range = key_ranges [idx ]
108
+ result = await store .get (key , byte_range = byte_range )
109
+ assert result is not None
110
+ expected .append (result )
41
111
42
- # read back multiple parts of it at once
43
- vals = await store .get_partial_values ([(key , (0 , 2 )), (key , (2 , 4 ))])
44
- assert_bytes_equal (vals [0 ], data [0 :2 ])
45
- assert_bytes_equal (vals [1 ], data [2 :4 ])
112
+ assert all (
113
+ obs .to_bytes () == exp .to_bytes () for obs , exp in zip (observed , expected , strict = True )
114
+ )
46
115
47
- async def test_exists (self , store : Store ) -> None :
116
+ async def test_exists (self , store : S ) -> None :
48
117
assert not await store .exists ("foo" )
49
118
await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
50
119
assert await store .exists ("foo/zarr.json" )
51
120
52
- async def test_delete (self , store : Store ) -> None :
121
+ async def test_delete (self , store : S ) -> None :
53
122
await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
54
123
assert await store .exists ("foo/zarr.json" )
55
124
await store .delete ("foo/zarr.json" )
56
125
assert not await store .exists ("foo/zarr.json" )
57
126
58
- async def test_list (self , store : Store ) -> None :
127
+ async def test_list (self , store : S ) -> None :
59
128
assert [k async for k in store .list ()] == []
60
129
await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
61
130
keys = [k async for k in store .list ()]
@@ -69,11 +138,11 @@ async def test_list(self, store: Store) -> None:
69
138
f"foo/c/{ i } " , Buffer .from_bytes (i .to_bytes (length = 3 , byteorder = "little" ))
70
139
)
71
140
72
- async def test_list_prefix (self , store : Store ) -> None :
141
+ async def test_list_prefix (self , store : S ) -> None :
73
142
# TODO: we currently don't use list_prefix anywhere
74
- pass
143
+ raise NotImplementedError
75
144
76
- async def test_list_dir (self , store : Store ) -> None :
145
+ async def test_list_dir (self , store : S ) -> None :
77
146
assert [k async for k in store .list_dir ("" )] == []
78
147
assert [k async for k in store .list_dir ("foo" )] == []
79
148
await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
0 commit comments