Skip to content

Commit f76e725

Browse files
committed
Add regression test
1 parent b66452a commit f76e725

File tree

2 files changed

+185
-2
lines changed

2 files changed

+185
-2
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ nosetests.xml
5050
dask-worker-space/
5151

5252
# asv environments
53-
.asv
53+
asv_bench/.asv
54+
asv_bench/pkgs
5455

5556
# Translations
5657
*.mo
@@ -68,7 +69,7 @@ dask-worker-space/
6869

6970
# xarray specific
7071
doc/_build
71-
generated/
72+
doc/generated/
7273
xarray/tests/data/*.grib.*.idx
7374

7475
# Sync tools

xarray/tests/test_backends.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import tempfile
1414
import uuid
1515
import warnings
16+
from collections import Counter
1617
from collections.abc import Generator, Iterator, Mapping
1718
from contextlib import ExitStack
1819
from io import BytesIO
@@ -2990,6 +2991,187 @@ def test_chunked_cftime_datetime(self) -> None:
29902991
assert original.chunks == actual.chunks
29912992

29922993

2994+
def attach_counter(f):
2995+
counter = Counter()
2996+
2997+
def wrapper(self, *args):
2998+
if f.__name__ in ["__contains__", "__getitem__"] and args[0] == "zarr.json":
2999+
# ignore this spam
3000+
pass
3001+
else:
3002+
print(f"Calling {f.__name__} with {args}")
3003+
counter.update(args[slice(1)]) if args else counter.update(("foo",))
3004+
return f(self, *args)
3005+
3006+
wrapper.counter = counter
3007+
return wrapper
3008+
3009+
3010+
class CountingStore(KVStoreV3):
3011+
def __init__(self):
3012+
super().__init__({})
3013+
self.instrumented_methods = [
3014+
"__iter__",
3015+
"__contains__",
3016+
"__setitem__",
3017+
"__getitem__",
3018+
"listdir",
3019+
"list_prefix",
3020+
]
3021+
3022+
@attach_counter
3023+
def __iter__(self):
3024+
return super().__iter__()
3025+
3026+
@attach_counter
3027+
def listdir(self, *args, **kwargs):
3028+
return super().listdir(*args, **kwargs)
3029+
3030+
@attach_counter
3031+
def list_prefix(self, *args, **kwargs):
3032+
return super().list_prefix(*args, **kwargs)
3033+
3034+
@attach_counter
3035+
def __contains__(self, key) -> bool:
3036+
return super().__contains__(key)
3037+
3038+
@attach_counter
3039+
def __getitem__(self, key):
3040+
return super().__getitem__(key)
3041+
3042+
@attach_counter
3043+
def __setitem__(self, *args, **kwargs):
3044+
return super().__setitem__(*args, **kwargs)
3045+
3046+
def summarize(self):
3047+
summary = {}
3048+
for method in self.instrumented_methods:
3049+
name = method.strip("__")
3050+
if counter := getattr(self, method).counter:
3051+
summary[name] = sum(counter.values())
3052+
else:
3053+
summary[name] = 0
3054+
return summary
3055+
3056+
def reset(self):
3057+
for method in self.instrumented_methods:
3058+
getattr(self, method).counter.clear()
3059+
3060+
3061+
@requires_zarr
3062+
@pytest.mark.skipif(not have_zarr_v3, reason="requires zarr version 3")
3063+
class TestInstrumentedZarrStore:
3064+
@contextlib.contextmanager
3065+
def create_zarr_target(self):
3066+
store = CountingStore()
3067+
# TODO: avoid the need for this.
3068+
store.reset()
3069+
yield store
3070+
3071+
def check_requests(self, expected, store):
3072+
summary = store.summarize()
3073+
for k in summary:
3074+
assert summary[k] <= expected[k], (k, summary)
3075+
store.reset()
3076+
3077+
def test_append(self) -> None:
3078+
original = Dataset({"foo": ("x", [1])}, coords={"x": [0]})
3079+
modified = Dataset({"foo": ("x", [2])}, coords={"x": [1]})
3080+
with self.create_zarr_target() as store:
3081+
original.to_zarr(store)
3082+
expected = {
3083+
"iter": 2,
3084+
"contains": 9,
3085+
"setitem": 9,
3086+
"getitem": 6,
3087+
"listdir": 2,
3088+
"list_prefix": 2,
3089+
}
3090+
self.check_requests(expected, store)
3091+
3092+
modified.to_zarr(store, mode="a", append_dim="x")
3093+
# v2024.03.0: {'iter': 6, 'contains': 2, 'setitem': 5, 'getitem': 10, 'listdir': 6, 'list_prefix': 0}
3094+
# 6057128b: {'iter': 5, 'contains': 2, 'setitem': 5, 'getitem': 10, "listdir": 5, "list_prefix": 0}
3095+
expected = {
3096+
"iter": 2,
3097+
"contains": 2,
3098+
"setitem": 5,
3099+
"getitem": 6,
3100+
"listdir": 2,
3101+
"list_prefix": 0,
3102+
}
3103+
self.check_requests(expected, store)
3104+
3105+
modified.to_zarr(store, mode="a-", append_dim="x")
3106+
expected = {
3107+
"iter": 2,
3108+
"contains": 2,
3109+
"setitem": 5,
3110+
"getitem": 6,
3111+
"listdir": 2,
3112+
"list_prefix": 0,
3113+
}
3114+
self.check_requests(expected, store)
3115+
3116+
with open_dataset(store, engine="zarr") as actual:
3117+
assert_identical(
3118+
actual, xr.concat([original, modified, modified], dim="x")
3119+
)
3120+
3121+
@requires_dask
3122+
def test_region_write(self) -> None:
3123+
ds = Dataset({"foo": ("x", [1, 2, 3])}, coords={"x": [0, 1, 2]}).chunk()
3124+
with self.create_zarr_target() as store:
3125+
ds.to_zarr(store, mode="w", compute=False)
3126+
expected = {
3127+
"iter": 2,
3128+
"contains": 7,
3129+
"setitem": 8,
3130+
"getitem": 6,
3131+
"listdir": 2,
3132+
"list_prefix": 4,
3133+
}
3134+
self.check_requests(expected, store)
3135+
3136+
ds.to_zarr(store, region={"x": slice(None)})
3137+
# v2024.03.0: {'iter': 5, 'contains': 2, 'setitem': 1, 'getitem': 6, 'listdir': 5, 'list_prefix': 0}
3138+
# 6057128b: {'iter': 4, 'contains': 2, 'setitem': 1, 'getitem': 5, 'listdir': 4, 'list_prefix': 0}
3139+
expected = {
3140+
"iter": 2,
3141+
"contains": 2,
3142+
"setitem": 1,
3143+
"getitem": 3,
3144+
"listdir": 2,
3145+
"list_prefix": 0,
3146+
}
3147+
self.check_requests(expected, store)
3148+
3149+
ds.to_zarr(store, region="auto")
3150+
# v2024.03.0: {'iter': 6, 'contains': 4, 'setitem': 1, 'getitem': 11, 'listdir': 6, 'list_prefix': 0}
3151+
# 6057128b: {'iter': 4, 'contains': 2, 'setitem': 1, 'getitem': 7, 'listdir': 4, 'list_prefix': 0}
3152+
expected = {
3153+
"iter": 2,
3154+
"contains": 2,
3155+
"setitem": 1,
3156+
"getitem": 5,
3157+
"listdir": 2,
3158+
"list_prefix": 0,
3159+
}
3160+
self.check_requests(expected, store)
3161+
3162+
expected = {
3163+
"iter": 1,
3164+
"contains": 2,
3165+
"setitem": 0,
3166+
"getitem": 5,
3167+
"listdir": 1,
3168+
"list_prefix": 0,
3169+
}
3170+
with open_dataset(store, engine="zarr") as actual:
3171+
assert_identical(actual, ds)
3172+
self.check_requests(expected, store)
3173+
3174+
29933175
@requires_zarr
29943176
class TestZarrDictStore(ZarrBase):
29953177
@contextlib.contextmanager

0 commit comments

Comments
 (0)