|
13 | 13 | from zarr.core.buffer import default_buffer_prototype |
14 | 14 | from zarr.core.chunk_grids import is_regular_1d, is_regular_nd |
15 | 15 | from zarr.core.config import config |
16 | | -from zarr.core.dtype import UInt8 |
| 16 | +from zarr.core.dtype import Float64, UInt8 |
17 | 17 | from zarr.core.group import GroupMetadata, parse_node_type |
| 18 | +from zarr.core.metadata.v2 import ArrayV2Metadata |
18 | 19 | from zarr.core.metadata.v3 import ( |
19 | 20 | ARRAY_METADATA_KEYS, |
20 | 21 | ArrayMetadataJSON_V3, |
@@ -335,6 +336,74 @@ def test_init_extra_fields_collision() -> None: |
335 | 336 | ) |
336 | 337 |
|
337 | 338 |
|
| 339 | +# --------------------------------------------------------------------------- |
| 340 | +# Equality |
| 341 | +# --------------------------------------------------------------------------- |
| 342 | + |
| 343 | + |
| 344 | +def test_eq_nan_fill_value() -> None: |
| 345 | + """Two metadata objects with an identical NaN fill_value compare equal. |
| 346 | +
|
| 347 | + NaN is not equal to itself under IEEE 754, so the default dataclass __eq__ |
| 348 | + reports two otherwise-identical metadata objects as unequal. Metadata |
| 349 | + equality must treat matching NaN fill values as equal (see issue #2929). |
| 350 | + """ |
| 351 | + a = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value="NaN")) # type: ignore[arg-type] |
| 352 | + b = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value="NaN")) # type: ignore[arg-type] |
| 353 | + assert a == b |
| 354 | + |
| 355 | + |
| 356 | +def test_eq_distinct_fill_value() -> None: |
| 357 | + """Metadata objects that differ only in fill_value do not compare equal.""" |
| 358 | + a = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value=0.0)) # type: ignore[arg-type] |
| 359 | + b = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value=1.0)) # type: ignore[arg-type] |
| 360 | + assert a != b |
| 361 | + |
| 362 | + |
| 363 | +@pytest.mark.parametrize("fill_value", ["Infinity", "-Infinity"]) |
| 364 | +def test_eq_inf_fill_value(fill_value: str) -> None: |
| 365 | + """Two metadata objects with an identical infinite fill_value compare equal.""" |
| 366 | + a = ArrayV3Metadata.from_dict( |
| 367 | + minimal_metadata_dict_v3(data_type="float64", fill_value=fill_value) # type: ignore[arg-type] |
| 368 | + ) |
| 369 | + b = ArrayV3Metadata.from_dict( |
| 370 | + minimal_metadata_dict_v3(data_type="float64", fill_value=fill_value) # type: ignore[arg-type] |
| 371 | + ) |
| 372 | + assert a == b |
| 373 | + |
| 374 | + |
| 375 | +def test_hash_consistent_with_eq_nan_fill_value() -> None: |
| 376 | + """Equal metadata objects with a NaN fill_value hash equal. |
| 377 | +
|
| 378 | + NaN hashes by identity, so a field-based hash would break the |
| 379 | + ``a == b implies hash(a) == hash(b)`` invariant for objects that compare |
| 380 | + equal under the to_dict-based __eq__. |
| 381 | + """ |
| 382 | + a = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value="NaN")) # type: ignore[arg-type] |
| 383 | + b = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value="NaN")) # type: ignore[arg-type] |
| 384 | + assert a == b |
| 385 | + assert hash(a) == hash(b) |
| 386 | + |
| 387 | + |
| 388 | +def test_eq_non_metadata() -> None: |
| 389 | + """Comparison against a non-metadata object returns False rather than erroring.""" |
| 390 | + a = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value=0.0)) # type: ignore[arg-type] |
| 391 | + assert a != object() |
| 392 | + |
| 393 | + |
| 394 | +def test_eq_across_zarr_formats() -> None: |
| 395 | + """A v2 and v3 metadata describing the same array do not compare equal. |
| 396 | +
|
| 397 | + Each __eq__ guards on its own concrete type and returns NotImplemented |
| 398 | + otherwise, so the two versions are never equal even when they describe the |
| 399 | + same array. |
| 400 | + """ |
| 401 | + v3 = ArrayV3Metadata.from_dict(minimal_metadata_dict_v3(data_type="float64", fill_value=0.0)) # type: ignore[arg-type] |
| 402 | + v2 = ArrayV2Metadata(shape=(4, 4), dtype=Float64(), chunks=(4, 4), fill_value=0.0, order="C") |
| 403 | + assert v2 != v3 |
| 404 | + assert v3 != v2 |
| 405 | + |
| 406 | + |
338 | 407 | # --------------------------------------------------------------------------- |
339 | 408 | # JSON indent |
340 | 409 | # --------------------------------------------------------------------------- |
|
0 commit comments