Skip to content

Commit 579ff16

Browse files
d-v-bclaude
andcommitted
test: add tests for SupportsSetRange on MemoryStore and LocalStore
Tests cover isinstance check, async set_range, sync set_range_sync, and edge case (writing at end of value). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e57bd5a commit 579ff16

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

src/zarr/abc/store.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,15 @@ async def set_if_not_exists(self, default: Buffer) -> None: ...
712712

713713
@runtime_checkable
714714
class SupportsSetRange(Protocol):
715-
"""Protocol for stores that support writing to a byte range within an existing value."""
715+
"""Protocol for stores that support writing to a byte range within an existing value.
716+
717+
Overwrites ``len(value)`` bytes starting at byte offset ``start`` within the
718+
existing stored value for ``key``. The key must already exist and the write
719+
must fit within the existing value (i.e., ``start + len(value) <= len(existing)``).
720+
721+
Behavior when the write extends past the end of the existing value is
722+
implementation-specific and should not be relied upon.
723+
"""
716724

717725
async def set_range(self, key: str, value: Buffer, start: int) -> None: ...
718726

tests/test_store/test_local.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import zarr
1212
from zarr import create_array
13+
from zarr.abc.store import SupportsSetRange
1314
from zarr.core.buffer import Buffer, cpu
1415
from zarr.core.sync import sync
1516
from zarr.storage import LocalStore
@@ -162,6 +163,54 @@ def test_get_json_sync_with_prototype_none(
162163
result = store._get_json_sync(key, prototype=buffer_cls)
163164
assert result == data
164165

166+
def test_supports_set_range(self, store: LocalStore) -> None:
167+
"""LocalStore should implement SupportsSetRange."""
168+
assert isinstance(store, SupportsSetRange)
169+
170+
@pytest.mark.parametrize(
171+
("start", "patch", "expected"),
172+
[
173+
(0, b"XX", b"XXAAAAAAAA"),
174+
(3, b"XX", b"AAAXXAAAAA"),
175+
(8, b"XX", b"AAAAAAAAXX"),
176+
(0, b"ZZZZZZZZZZ", b"ZZZZZZZZZZ"),
177+
(5, b"B", b"AAAAABAAAA"),
178+
(0, b"BCDE", b"BCDEAAAAAA"),
179+
],
180+
ids=["start", "middle", "end", "full-overwrite", "single-byte", "multi-byte-start"],
181+
)
182+
async def test_set_range(
183+
self, store: LocalStore, start: int, patch: bytes, expected: bytes
184+
) -> None:
185+
"""set_range should overwrite bytes at the given offset."""
186+
await store.set("test/key", cpu.Buffer.from_bytes(b"AAAAAAAAAA"))
187+
await store.set_range("test/key", cpu.Buffer.from_bytes(patch), start=start)
188+
result = await store.get("test/key", prototype=cpu.buffer_prototype)
189+
assert result is not None
190+
assert result.to_bytes() == expected
191+
192+
@pytest.mark.parametrize(
193+
("start", "patch", "expected"),
194+
[
195+
(0, b"XX", b"XXAAAAAAAA"),
196+
(3, b"XX", b"AAAXXAAAAA"),
197+
(8, b"XX", b"AAAAAAAAXX"),
198+
(0, b"ZZZZZZZZZZ", b"ZZZZZZZZZZ"),
199+
(5, b"B", b"AAAAABAAAA"),
200+
(0, b"BCDE", b"BCDEAAAAAA"),
201+
],
202+
ids=["start", "middle", "end", "full-overwrite", "single-byte", "multi-byte-start"],
203+
)
204+
def test_set_range_sync(
205+
self, store: LocalStore, start: int, patch: bytes, expected: bytes
206+
) -> None:
207+
"""set_range_sync should overwrite bytes at the given offset."""
208+
sync(store.set("test/key", cpu.Buffer.from_bytes(b"AAAAAAAAAA")))
209+
store.set_range_sync("test/key", cpu.Buffer.from_bytes(patch), start=start)
210+
result = store.get_sync(key="test/key", prototype=cpu.buffer_prototype)
211+
assert result is not None
212+
assert result.to_bytes() == expected
213+
165214

166215
@pytest.mark.parametrize("exclusive", [True, False])
167216
def test_atomic_write_successful(tmp_path: pathlib.Path, exclusive: bool) -> None:

tests/test_store/test_memory.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
import zarr
12+
from zarr.abc.store import SupportsSetRange
1213
from zarr.core.buffer import Buffer, cpu, gpu
1314
from zarr.core.sync import sync
1415
from zarr.errors import ZarrUserWarning
@@ -127,6 +128,55 @@ def test_get_json_sync_with_prototype_none(
127128
result = store._get_json_sync(key, prototype=buffer_cls)
128129
assert result == data
129130

131+
def test_supports_set_range(self, store: MemoryStore) -> None:
132+
"""MemoryStore should implement SupportsSetRange."""
133+
assert isinstance(store, SupportsSetRange)
134+
135+
@pytest.mark.parametrize(
136+
("start", "patch", "expected"),
137+
[
138+
(0, b"XX", b"XXAAAAAAAA"),
139+
(3, b"XX", b"AAAXXAAAAA"),
140+
(8, b"XX", b"AAAAAAAAXX"),
141+
(0, b"ZZZZZZZZZZ", b"ZZZZZZZZZZ"),
142+
(5, b"B", b"AAAAABAAAA"),
143+
(0, b"BCDE", b"BCDEAAAAAA"),
144+
],
145+
ids=["start", "middle", "end", "full-overwrite", "single-byte", "multi-byte-start"],
146+
)
147+
async def test_set_range(
148+
self, store: MemoryStore, start: int, patch: bytes, expected: bytes
149+
) -> None:
150+
"""set_range should overwrite bytes at the given offset."""
151+
await store.set("test/key", cpu.Buffer.from_bytes(b"AAAAAAAAAA"))
152+
await store.set_range("test/key", cpu.Buffer.from_bytes(patch), start=start)
153+
result = await store.get("test/key", prototype=cpu.buffer_prototype)
154+
assert result is not None
155+
assert result.to_bytes() == expected
156+
157+
@pytest.mark.parametrize(
158+
("start", "patch", "expected"),
159+
[
160+
(0, b"XX", b"XXAAAAAAAA"),
161+
(3, b"XX", b"AAAXXAAAAA"),
162+
(8, b"XX", b"AAAAAAAAXX"),
163+
(0, b"ZZZZZZZZZZ", b"ZZZZZZZZZZ"),
164+
(5, b"B", b"AAAAABAAAA"),
165+
(0, b"BCDE", b"BCDEAAAAAA"),
166+
],
167+
ids=["start", "middle", "end", "full-overwrite", "single-byte", "multi-byte-start"],
168+
)
169+
def test_set_range_sync(
170+
self, store: MemoryStore, start: int, patch: bytes, expected: bytes
171+
) -> None:
172+
"""set_range_sync should overwrite bytes at the given offset."""
173+
store._is_open = True
174+
store._store_dict["test/key"] = cpu.Buffer.from_bytes(b"AAAAAAAAAA")
175+
store.set_range_sync("test/key", cpu.Buffer.from_bytes(patch), start=start)
176+
result = store.get_sync(key="test/key", prototype=cpu.buffer_prototype)
177+
assert result is not None
178+
assert result.to_bytes() == expected
179+
130180

131181
# TODO: fix this warning
132182
@pytest.mark.filterwarnings("ignore:Unclosed client session:ResourceWarning")

0 commit comments

Comments
 (0)