1+ from typing import Generic , TypeVar
2+
13import pytest
24
35from zarr .abc .store import Store
46from zarr .buffer import Buffer
7+ from zarr .store .core import _normalize_interval_index
58from zarr .testing .utils import assert_bytes_equal
69
10+ S = TypeVar ("S" , bound = Store )
11+
12+
13+ class StoreTests (Generic [S ]):
14+ store_cls : type [S ]
715
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
1032
1133 @pytest .fixture (scope = "function" )
1234 def store (self ) -> Store :
1335 return self .store_cls ()
1436
15- def test_store_type (self , store : Store ) -> None :
37+ def test_store_type (self , store : S ) -> None :
1638 assert isinstance (store , Store )
1739 assert isinstance (store , self .store_cls )
1840
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
2146
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
2652
2753 @pytest .mark .parametrize ("key" , ["c/0" , "foo/c/0.0" , "foo/0/0" ])
2854 @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" ])
3470 @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 :
3692 # 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+
3896 # 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 )
41111
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+ )
46115
47- async def test_exists (self , store : Store ) -> None :
116+ async def test_exists (self , store : S ) -> None :
48117 assert not await store .exists ("foo" )
49118 await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
50119 assert await store .exists ("foo/zarr.json" )
51120
52- async def test_delete (self , store : Store ) -> None :
121+ async def test_delete (self , store : S ) -> None :
53122 await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
54123 assert await store .exists ("foo/zarr.json" )
55124 await store .delete ("foo/zarr.json" )
56125 assert not await store .exists ("foo/zarr.json" )
57126
58- async def test_list (self , store : Store ) -> None :
127+ async def test_list (self , store : S ) -> None :
59128 assert [k async for k in store .list ()] == []
60129 await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
61130 keys = [k async for k in store .list ()]
@@ -69,11 +138,11 @@ async def test_list(self, store: Store) -> None:
69138 f"foo/c/{ i } " , Buffer .from_bytes (i .to_bytes (length = 3 , byteorder = "little" ))
70139 )
71140
72- async def test_list_prefix (self , store : Store ) -> None :
141+ async def test_list_prefix (self , store : S ) -> None :
73142 # TODO: we currently don't use list_prefix anywhere
74- pass
143+ raise NotImplementedError
75144
76- async def test_list_dir (self , store : Store ) -> None :
145+ async def test_list_dir (self , store : S ) -> None :
77146 assert [k async for k in store .list_dir ("" )] == []
78147 assert [k async for k in store .list_dir ("foo" )] == []
79148 await store .set ("foo/zarr.json" , Buffer .from_bytes (b"bar" ))
0 commit comments