From 404213a69cdc11b9703967f5abccaedd8053742c Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Wed, 19 Feb 2025 09:11:04 -0600 Subject: [PATCH] Clarify intent of roundtrip tests --- tests/test_properties.py | 64 ++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/tests/test_properties.py b/tests/test_properties.py index d11086beea..f171aaf052 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -153,15 +153,56 @@ def test_vindex(data: st.DataObject) -> None: @given(store=stores, meta=array_metadata()) # type: ignore[misc] -async def test_roundtrip_array_metadata( +async def test_roundtrip_array_metadata_from_store( store: Store, meta: ArrayV2Metadata | ArrayV3Metadata ) -> None: + """ + Verify that the I/O for metadata in a store are lossless. + + This test serializes an ArrayV2Metadata or ArrayV3Metadata object to a dict + of buffers via `to_buffer_dict`, writes each buffer to a store under keys + prefixed with "0/", and then reads them back. The test asserts that each + retrieved buffer exactly matches the original buffer. + """ asdict = meta.to_buffer_dict(prototype=default_buffer_prototype()) for key, expected in asdict.items(): await store.set(f"0/{key}", expected) actual = await store.get(f"0/{key}", prototype=default_buffer_prototype()) assert actual == expected +@given(data=st.data(), zarr_format=zarr_formats) +def test_roundtrip_array_metadata_from_json(data: st.DataObject, zarr_format: int) -> None: + """ + Verify that JSON serialization and deserialization of metadata is lossless. + + For Zarr v2: + - The metadata is split into two JSON documents (one for array data and one + for attributes). The test merges the attributes back before deserialization. + For Zarr v3: + - All metadata is stored in a single JSON document. No manual merger is necessary. + + The test then converts both the original and round-tripped metadata objects + into dictionaries using `dataclasses.asdict` and uses a deep equality check + to verify that the roundtrip has preserved all fields (including special + cases like NaN, Infinity, complex numbers, and datetime values). + """ + metadata = data.draw(array_metadata(zarr_formats=st.just(zarr_format))) + buffer_dict = metadata.to_buffer_dict(prototype=default_buffer_prototype()) + + if zarr_format == 2: + zarray_dict = json.loads(buffer_dict[ZARRAY_JSON].to_bytes().decode()) + zattrs_dict = json.loads(buffer_dict[ZATTRS_JSON].to_bytes().decode()) + # zattrs and zarray are separate in v2, we have to add attributes back prior to `from_dict` + zarray_dict["attributes"] = zattrs_dict + metadata_roundtripped = ArrayV2Metadata.from_dict(zarray_dict) + else: + zarray_dict = json.loads(buffer_dict[ZARR_JSON].to_bytes().decode()) + metadata_roundtripped = ArrayV3Metadata.from_dict(zarray_dict) + + orig = dataclasses.asdict(metadata) + rt = dataclasses.asdict(metadata_roundtripped) + + assert deep_equal(orig, rt), f"Roundtrip mismatch:\nOriginal: {orig}\nRoundtripped: {rt}" # @st.composite # def advanced_indices(draw, *, shape): @@ -187,27 +228,6 @@ async def test_roundtrip_array_metadata( # assert_array_equal(nparray, zarray[:]) -@given(data=st.data(), zarr_format=zarr_formats) -def test_meta_roundtrip(data: st.DataObject, zarr_format: int) -> None: - metadata = data.draw(array_metadata(zarr_formats=st.just(zarr_format))) - buffer_dict = metadata.to_buffer_dict(prototype=default_buffer_prototype()) - - if zarr_format == 2: - zarray_dict = json.loads(buffer_dict[ZARRAY_JSON].to_bytes().decode()) - zattrs_dict = json.loads(buffer_dict[ZATTRS_JSON].to_bytes().decode()) - # zattrs and zarray are separate in v2, we have to add attributes back prior to `from_dict` - zarray_dict["attributes"] = zattrs_dict - metadata_roundtripped = ArrayV2Metadata.from_dict(zarray_dict) - else: - zarray_dict = json.loads(buffer_dict[ZARR_JSON].to_bytes().decode()) - metadata_roundtripped = ArrayV3Metadata.from_dict(zarray_dict) - - orig = dataclasses.asdict(metadata) - rt = dataclasses.asdict(metadata_roundtripped) - - assert deep_equal(orig, rt), f"Roundtrip mismatch:\nOriginal: {orig}\nRoundtripped: {rt}" - - @given(npst.from_dtype(dtype=np.dtype("float64"), allow_nan=True, allow_infinity=True)) def test_v2meta_nan_and_infinity(fill_value): metadata = ArrayV2Metadata(