Skip to content

Commit 3ab711d

Browse files
committed
test(storage): add failing tests for FsspecStore.get_ranges
1 parent 17d9f75 commit 3ab711d

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# tests/test_store/test_fsspec_get_ranges.py
2+
"""Lightweight integration tests for FsspecStore.get_ranges using MemoryFileSystem.
3+
4+
These don't need moto/s3 — they exercise the new method against an in-process
5+
fsspec MemoryFileSystem wrapped in the async wrapper.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import pytest
11+
12+
from zarr.abc.store import RangeByteRequest
13+
from zarr.core._coalesce import DEFAULT_COALESCE_OPTIONS, CoalesceOptions
14+
from zarr.core.buffer import Buffer, default_buffer_prototype
15+
from zarr.storage import FsspecStore
16+
from zarr.storage._fsspec import _make_async
17+
18+
fsspec = pytest.importorskip("fsspec")
19+
20+
21+
@pytest.fixture
22+
def memory_store() -> FsspecStore:
23+
"""An FsspecStore backed by fsspec MemoryFileSystem (wrapped async)."""
24+
from fsspec.implementations.memory import MemoryFileSystem
25+
26+
# Each test gets a clean filesystem; MemoryFileSystem is a singleton per target_options,
27+
# so clear state explicitly.
28+
fs: MemoryFileSystem = MemoryFileSystem()
29+
fs.store.clear()
30+
fs.pseudo_dirs.clear()
31+
async_fs = _make_async(fs)
32+
return FsspecStore(fs=async_fs, path="/root")
33+
34+
35+
async def _write(store: FsspecStore, key: str, data: bytes) -> None:
36+
buf = default_buffer_prototype().buffer.from_bytes(data)
37+
await store.set(key, buf)
38+
39+
40+
async def test_get_ranges_happy_path(memory_store: FsspecStore) -> None:
41+
blob = bytes(i % 256 for i in range(1024))
42+
await _write(memory_store, "blob", blob)
43+
proto = default_buffer_prototype()
44+
45+
ranges = [
46+
RangeByteRequest(0, 10),
47+
RangeByteRequest(100, 110),
48+
RangeByteRequest(500, 520),
49+
]
50+
groups: list[list[tuple[int, Buffer | None]]] = [
51+
list(group) async for group in memory_store.get_ranges("blob", ranges, prototype=proto)
52+
]
53+
54+
flat: dict[int, bytes] = {}
55+
for group in groups:
56+
for idx, buf in group:
57+
assert buf is not None
58+
flat[idx] = buf.to_bytes()
59+
60+
assert flat[0] == blob[0:10]
61+
assert flat[1] == blob[100:110]
62+
assert flat[2] == blob[500:520]
63+
64+
65+
async def test_get_ranges_missing_key_yields_nothing(memory_store: FsspecStore) -> None:
66+
proto = default_buffer_prototype()
67+
groups: list[list[tuple[int, Buffer | None]]] = [
68+
list(group)
69+
async for group in memory_store.get_ranges(
70+
"does-not-exist", [RangeByteRequest(0, 10)], prototype=proto
71+
)
72+
]
73+
assert groups == []
74+
75+
76+
async def test_default_coalesce_options_on_store_without_arg() -> None:
77+
from fsspec.implementations.memory import MemoryFileSystem
78+
79+
fs = MemoryFileSystem()
80+
fs.store.clear()
81+
store = FsspecStore(fs=_make_async(fs), path="/x")
82+
assert store.coalesce_options == DEFAULT_COALESCE_OPTIONS
83+
84+
85+
async def test_coalesce_options_wired_through() -> None:
86+
from fsspec.implementations.memory import MemoryFileSystem
87+
88+
fs = MemoryFileSystem()
89+
fs.store.clear()
90+
custom: CoalesceOptions = {
91+
"max_gap_bytes": 0,
92+
"max_coalesced_bytes": 1 << 20,
93+
"max_concurrency": 2,
94+
}
95+
store = FsspecStore(fs=_make_async(fs), path="/x", coalesce_options=custom)
96+
assert store.coalesce_options == custom
97+
98+
99+
async def test_get_ranges_mixed_range_types(memory_store: FsspecStore) -> None:
100+
"""Covers RangeByteRequest, OffsetByteRequest, SuffixByteRequest, and None in one call."""
101+
from zarr.abc.store import OffsetByteRequest, SuffixByteRequest
102+
103+
blob = bytes(i % 256 for i in range(512))
104+
await _write(memory_store, "mixed", blob)
105+
proto = default_buffer_prototype()
106+
107+
ranges = [
108+
RangeByteRequest(0, 10),
109+
OffsetByteRequest(500),
110+
SuffixByteRequest(12),
111+
None,
112+
]
113+
flat: dict[int, bytes] = {}
114+
async for group in memory_store.get_ranges("mixed", ranges, prototype=proto):
115+
for idx, buf in group:
116+
assert buf is not None
117+
flat[idx] = buf.to_bytes()
118+
119+
assert flat[0] == blob[0:10]
120+
assert flat[1] == blob[500:]
121+
assert flat[2] == blob[-12:]
122+
assert flat[3] == blob

0 commit comments

Comments
 (0)