-
-
Notifications
You must be signed in to change notification settings - Fork 346
Improve test coverage for storage classes #2693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 27 commits
2ea442c
7f76575
98b7392
18be47f
69ce1d7
5877355
b4310fd
d08458e
f663694
cf62f67
31f9931
964aeaa
332f564
4d4d728
30d1323
9764204
fefd666
6f240c2
d2bbd9d
85f44db
51c0c15
ddd6bc9
62a528c
5f00efd
6923337
0792fa8
dd0de05
4dba40c
48abe94
bf4589d
5b37417
74647de
c8ebcd0
1e96600
cc14e07
5148dd6
bf58808
e1caef0
1922d2d
45ea40d
b281bc9
9a75da2
ca83bd6
0d6eccf
9059135
1e080c0
1587fd1
5e16b3d
128ef99
1faa66c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,7 @@ src/zarr/_version.py | |
data/* | ||
src/fixture/ | ||
fixture/ | ||
junit.xml | ||
|
||
.DS_Store | ||
tests/.hypothesis | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
import asyncio | ||
import pickle | ||
from abc import abstractmethod | ||
from typing import TYPE_CHECKING, Generic, TypeVar | ||
|
||
from zarr.storage import WrapperStore | ||
|
@@ -37,30 +38,53 @@ class StoreTests(Generic[S, B]): | |
store_cls: type[S] | ||
buffer_cls: type[B] | ||
|
||
@abstractmethod | ||
async def set(self, store: S, key: str, value: Buffer) -> None: | ||
""" | ||
Insert a value into a storage backend, with a specific key. | ||
This should not not use any store methods. Bypassing the store methods allows them to be | ||
tested. | ||
""" | ||
raise NotImplementedError | ||
... | ||
|
||
@abstractmethod | ||
async def get(self, store: S, key: str) -> Buffer: | ||
""" | ||
Retrieve a value from a storage backend, by key. | ||
This should not not use any store methods. Bypassing the store methods allows them to be | ||
tested. | ||
""" | ||
... | ||
|
||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
@pytest.fixture | ||
def store_kwargs(self) -> dict[str, Any]: | ||
return {"read_only": False} | ||
"""Kwargs for instantiating a store""" | ||
... | ||
|
||
@abstractmethod | ||
dstansby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_store_repr(self, store: S) -> None: ... | ||
|
||
@abstractmethod | ||
def test_store_supports_writes(self, store: S) -> None: ... | ||
|
||
@abstractmethod | ||
def test_store_supports_partial_writes(self, store: S) -> None: ... | ||
|
||
@abstractmethod | ||
def test_store_supports_listing(self, store: S) -> None: ... | ||
|
||
@pytest.fixture | ||
async def store(self, store_kwargs: dict[str, Any]) -> Store: | ||
return await self.store_cls.open(**store_kwargs) | ||
def open_kwargs(self, store_kwargs: dict[str, Any]) -> dict[str, Any]: | ||
return store_kwargs | ||
|
||
@pytest.fixture | ||
async def store(self, open_kwargs: dict[str, Any]) -> Store: | ||
return await self.store_cls.open(**open_kwargs) | ||
|
||
@pytest.fixture | ||
async def store_not_open(self, store_kwargs: dict[str, Any]) -> Store: | ||
return self.store_cls(**store_kwargs) | ||
|
||
def test_store_type(self, store: S) -> None: | ||
assert isinstance(store, Store) | ||
|
@@ -86,16 +110,23 @@ def test_store_read_only(self, store: S) -> None: | |
store.read_only = False # type: ignore[misc] | ||
|
||
@pytest.mark.parametrize("read_only", [True, False]) | ||
async def test_store_open_read_only( | ||
self, store_kwargs: dict[str, Any], read_only: bool | ||
) -> None: | ||
store_kwargs["read_only"] = read_only | ||
store = await self.store_cls.open(**store_kwargs) | ||
async def test_store_open_read_only(self, open_kwargs: dict[str, Any], read_only: bool) -> None: | ||
open_kwargs["read_only"] = read_only | ||
store = await self.store_cls.open(**open_kwargs) | ||
assert store._is_open | ||
assert store.read_only == read_only | ||
|
||
async def test_read_only_store_raises(self, store_kwargs: dict[str, Any]) -> None: | ||
kwargs = {**store_kwargs, "read_only": True} | ||
async def test_store_context_manager(self, open_kwargs: dict[str, Any]) -> None: | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Test that the context manager closes the store | ||
with await self.store_cls.open(**open_kwargs) as store: | ||
assert store._is_open | ||
# Test trying to open an already open store | ||
with pytest.raises(ValueError): | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await store._open() | ||
assert not store._is_open | ||
|
||
async def test_read_only_store_raises(self, open_kwargs: dict[str, Any]) -> None: | ||
d-v-b marked this conversation as resolved.
Show resolved
Hide resolved
|
||
kwargs = {**open_kwargs, "read_only": True} | ||
store = await self.store_cls.open(**kwargs) | ||
assert store.read_only | ||
|
||
|
@@ -107,18 +138,6 @@ async def test_read_only_store_raises(self, store_kwargs: dict[str, Any]) -> Non | |
with pytest.raises(ValueError): | ||
await store.delete("foo") | ||
|
||
def test_store_repr(self, store: S) -> None: | ||
raise NotImplementedError | ||
|
||
def test_store_supports_writes(self, store: S) -> None: | ||
raise NotImplementedError | ||
|
||
def test_store_supports_partial_writes(self, store: S) -> None: | ||
raise NotImplementedError | ||
|
||
def test_store_supports_listing(self, store: S) -> None: | ||
raise NotImplementedError | ||
|
||
@pytest.mark.parametrize("key", ["c/0", "foo/c/0.0", "foo/0/0"]) | ||
@pytest.mark.parametrize("data", [b"\x01\x02\x03\x04", b""]) | ||
@pytest.mark.parametrize( | ||
|
@@ -135,6 +154,26 @@ async def test_get(self, store: S, key: str, data: bytes, byte_range: ByteReques | |
expected = data_buf[start:stop] | ||
assert_bytes_equal(observed, expected) | ||
|
||
async def test_get_not_open(self, store_not_open: S) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is rather surprising -- I would expect that a non-open store would not support IO of any kind. what exactly does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is also surprising to me! |
||
""" | ||
Ensure that data can be read from the store that isn't yet open using the store.get method. | ||
""" | ||
assert not store_not_open._is_open | ||
data_buf = self.buffer_cls.from_bytes(b"\x01\x02\x03\x04") | ||
key = "c/0" | ||
await self.set(store_not_open, key, data_buf) | ||
observed = await store_not_open.get(key, prototype=default_buffer_prototype()) | ||
assert_bytes_equal(observed, data_buf) | ||
|
||
async def test_get_raises(self, store: S) -> None: | ||
""" | ||
Ensure that a ValueError is raise for invalid byte range syntax | ||
""" | ||
data_buf = self.buffer_cls.from_bytes(b"\x01\x02\x03\x04") | ||
await self.set(store, "c/0", data_buf) | ||
with pytest.raises((ValueError, TypeError)): | ||
await store.get("c/0", prototype=default_buffer_prototype(), byte_range=(0, 2)) # type: ignore[arg-type] | ||
|
||
async def test_get_many(self, store: S) -> None: | ||
""" | ||
Ensure that multiple keys can be retrieved at once with the _get_many method. | ||
|
@@ -157,6 +196,37 @@ async def test_get_many(self, store: S) -> None: | |
expected_kvs = sorted(((k, b) for k, b in zip(keys, values, strict=False))) | ||
assert observed_kvs == expected_kvs | ||
|
||
@pytest.mark.parametrize("key", ["c/0", "foo/c/0.0", "foo/0/0"]) | ||
@pytest.mark.parametrize("data", [b"\x01\x02\x03\x04", b""]) | ||
async def test_getsize(self, store: S, key: str, data: bytes) -> None: | ||
""" | ||
Test the result of store.getsize(). | ||
""" | ||
data_buf = self.buffer_cls.from_bytes(data) | ||
expected = len(data_buf) | ||
await self.set(store, key, data_buf) | ||
observed = await store.getsize(key) | ||
assert observed == expected | ||
|
||
async def test_getsize_prefix(self, store: S) -> None: | ||
""" | ||
Test the result of store.getsize_prefix(). | ||
""" | ||
data_buf = self.buffer_cls.from_bytes(b"\x01\x02\x03\x04") | ||
keys = ["c/0/0", "c/0/1", "c/1/0", "c/1/1"] | ||
keys_values = [(k, data_buf) for k in keys] | ||
await store._set_many(keys_values) | ||
expected = len(data_buf) * len(keys) | ||
observed = await store.getsize_prefix("c") | ||
assert observed == expected | ||
|
||
async def test_getsize_raises(self, store: S) -> None: | ||
""" | ||
Test the result of store.getsize(). | ||
""" | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
with pytest.raises(FileNotFoundError): | ||
await store.getsize("c/1000") | ||
|
||
@pytest.mark.parametrize("key", ["zarr.json", "c/0", "foo/c/0.0", "foo/0/0"]) | ||
@pytest.mark.parametrize("data", [b"\x01\x02\x03\x04", b""]) | ||
async def test_set(self, store: S, key: str, data: bytes) -> None: | ||
|
@@ -169,6 +239,17 @@ async def test_set(self, store: S, key: str, data: bytes) -> None: | |
observed = await self.get(store, key) | ||
assert_bytes_equal(observed, data_buf) | ||
|
||
async def test_set_not_open(self, store_not_open: S) -> None: | ||
""" | ||
Ensure that data can be written to the store that's not yet open using the store.set method. | ||
""" | ||
Comment on lines
+247
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assert not store_not_open._is_open | ||
data_buf = self.buffer_cls.from_bytes(b"\x01\x02\x03\x04") | ||
key = "c/0" | ||
await store_not_open.set(key, data_buf) | ||
observed = await self.get(store_not_open, key) | ||
assert_bytes_equal(observed, data_buf) | ||
|
||
async def test_set_many(self, store: S) -> None: | ||
""" | ||
Test that a dict of key : value pairs can be inserted into the store via the | ||
|
@@ -181,6 +262,13 @@ async def test_set_many(self, store: S) -> None: | |
for k, v in store_dict.items(): | ||
assert (await self.get(store, k)).to_bytes() == v.to_bytes() | ||
|
||
async def test_set_raises(self, store: S) -> None: | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Ensure that set raises a Type or Value Error for invalid buffer arguments. | ||
""" | ||
with pytest.raises((ValueError, TypeError)): | ||
await store.set("c/0", 0) # type: ignore[arg-type] | ||
|
||
@pytest.mark.parametrize( | ||
"key_ranges", | ||
[ | ||
|
Uh oh!
There was an error while loading. Please reload this page.