Skip to content
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

Improve test coverage for storage classes #2693

Open
wants to merge 28 commits into
base: main
Choose a base branch
from

Conversation

maxrjones
Copy link
Member

@maxrjones maxrjones commented Jan 13, 2025

This PR improves the test coverage for the various storage classes. While testing the storage classes, I fixed a few issues:

  • Implement open() for LoggingStore
  • Add _is_open property and setter for WrapperStore
  • Use stdout rather than stderr as the default stream for LoggingStore
  • Ensure that ZipStore is open before getting or setting any values
  • Update equality for LoggingStore and WrapperStore such that the types much be equal. This is an opinionated change. For example, previously a LocalStore and LoggingStore instance could be evaluated as equal, whereas now they are distinct.

Here's the change in coverage:

src/zarr/abc/store.py                    84% -> 93%
src/zarr/storage/__init__.py             94% -> 94%
src/zarr/storage/_utils.py               94% -> 97%
src/zarr/storage/common.py               80% -> 91%
src/zarr/storage/fsspec.py               25% -> 90%
src/zarr/storage/local.py                86% -> 92%
src/zarr/storage/logging.py              62% -> 96%
src/zarr/storage/memory.py               82% -> 85%
src/zarr/storage/wrapper.py              56% -> 94%
src/zarr/storage/zip.py                  96% -> 97%
src/zarr/testing/store.py                92% -> 99%

src/zarr/storage/memory.py coverage is low because it includes the GPUStore and I don't have a test environment with cuda. I'm opening this PR now even though it's not at 100% coverage because I don't expect to have much time to work on it during the week and would rather the PR not get stale if the team has time for a review.

The set partial values methods are addressed separately because they require discussion (xref #2688).

src/zarr/storage/_fsspec.py Outdated Show resolved Hide resolved
@@ -18,6 +19,8 @@

counter: defaultdict[str, int]

T_Store = TypeVar("T_Store", bound=Store)


class LoggingStore(WrapperStore[Store]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should WrapperStore be generic w.r.t T_Store here?

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we check that the error message in the ValueError has the expected content? We don't want this test to succeed because of a ValueError unrelated to the store being already open.

await store._open()
assert not store._is_open

async def test_read_only_store_raises(self, open_kwargs: dict[str, Any]) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contrary to the name, this test doesn't seem to check that an exception is raised

@@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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 open mean? cc @jhamman

Comment on lines +223 to +226
async def test_getsize_raises(self, store: S) -> None:
"""
Test the result of store.getsize().
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the method name and the docstring don't quite match the behavior of the test

Comment on lines +242 to +245
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.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/zarr/testing/store.py Outdated Show resolved Hide resolved
@pytest.mark.parametrize("zarr_format", [2, 3])
async def test_contains_group(local_store, write_group: bool, zarr_format: ZarrFormat) -> None:
"""
Test contains group method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we parametrize this over path, ensuring that we check a level of nesting? e.g. @pytest.mark.parametrize('path', ['foo', 'foo/bar'])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and similarly for the contains_array tests

Comment on lines +47 to +48
with pytest.raises(ValueError):
assert await contains_array(store_path, zarr_format="3.0")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
with pytest.raises(ValueError):
assert await contains_array(store_path, zarr_format="3.0")

deduplicate

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a distinct check for contains_array rather than contains_group. I can parameterize these functions to make it more concise and clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah oops, I missed that these were testing different methods

Comment on lines +106 to +107
with pytest.raises(ValueError):
await StorePath.open(LocalStore(str(tmpdir), read_only=False), path=None, mode="x")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets parametrize the test over mode instead of repeating nearly identical checks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we would need to parametrize over (read_only, mode) tuples

@@ -53,3 +54,17 @@ def test_creates_new_directory(self, tmp_path: pathlib.Path):

store = self.store_cls(root=target)
zarr.group(store=store)

def test_invalid_root_raises(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a docstring explaining what this test is checking

@d-v-b
Copy link
Contributor

d-v-b commented Jan 13, 2025

this looks great, I had some minor suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants