Skip to content

Commit 4d467a7

Browse files
committed
Test more edge cases
1 parent e9d06c2 commit 4d467a7

File tree

1 file changed

+122
-14
lines changed

1 file changed

+122
-14
lines changed

tests/test_unified_chunk_grid.py

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def test_dimension_index_to_chunk_last_valid(
107107
lambda: RectilinearChunkGridMetadata.from_dict(
108108
{
109109
"name": "rectilinear",
110-
"configuration": {"kind": "inline", "chunk_shapes": [[10, 20, 30], [50, 50]]}, # type: ignore[typeddict-item]
110+
"configuration": {"kind": "inline", "chunk_shapes": [[10, 20, 30], [50, 50]]},
111111
}
112112
),
113113
lambda: zarr.create_array(MemoryStore(), shape=(30,), chunks=[[10, 20]], dtype="int32"),
@@ -546,13 +546,13 @@ def test_rle_expand_rejects_invalid(rle_input: list[Any], match: str) -> None:
546546

547547
def test_expand_rle_bare_integer_floats_accepted() -> None:
548548
"""JSON parsers may emit 10.0 for the integer 10; expand_rle should handle it."""
549-
result = expand_rle([10.0, 20.0]) # type: ignore[list-item]
549+
result = expand_rle([10.0, 20.0])
550550
assert result == [10, 20]
551551

552552

553553
def test_expand_rle_pair_with_float_count() -> None:
554554
"""expand_rle accepts float repeat counts that are integer-valued"""
555-
result = expand_rle([[10, 3.0]]) # type: ignore[list-item]
555+
result = expand_rle([[10, 3.0]])
556556
assert result == [10, 10, 10]
557557

558558

@@ -595,6 +595,16 @@ def test_is_rectilinear_chunks(value: Any, expected: bool) -> None:
595595
assert _is_rectilinear_chunks(value) is expected
596596

597597

598+
def test_is_rectilinear_chunks_handles_broken_iterable() -> None:
599+
"""_is_rectilinear_chunks returns False for objects that raise on iteration."""
600+
601+
class BrokenIter:
602+
def __iter__(self) -> Any:
603+
raise TypeError("cannot iterate")
604+
605+
assert _is_rectilinear_chunks(BrokenIter()) is False
606+
607+
598608
# ---------------------------------------------------------------------------
599609
# Serialization tests
600610
# ---------------------------------------------------------------------------
@@ -619,6 +629,24 @@ def test_serialization_unknown_name_parse() -> None:
619629
parse_chunk_grid({"name": "hexagonal", "configuration": {}})
620630

621631

632+
def test_from_metadata_unknown_chunk_grid_type() -> None:
633+
"""ChunkGrid.from_metadata raises TypeError for unrecognised chunk grid metadata."""
634+
from unittest.mock import MagicMock
635+
636+
from zarr.core.metadata.v3 import ArrayV3Metadata
637+
638+
mock_meta = MagicMock(spec=ArrayV3Metadata)
639+
mock_meta.chunk_grid = MagicMock() # not Regular or Rectilinear
640+
with pytest.raises(TypeError, match="Unknown chunk grid metadata type"):
641+
ChunkGrid.from_metadata(mock_meta)
642+
643+
644+
def test_from_sizes_rejects_empty_edge_list() -> None:
645+
"""ChunkGrid.from_sizes raises ValueError when a dimension has an empty edge list."""
646+
with pytest.raises(ValueError, match="at least one chunk"):
647+
ChunkGrid.from_sizes((10,), ([],))
648+
649+
622650
# ---------------------------------------------------------------------------
623651
# Spec compliance tests
624652
# ---------------------------------------------------------------------------
@@ -651,7 +679,7 @@ def test_spec_integer_shorthand_per_dimension() -> None:
651679
"configuration": {"kind": "inline", "chunk_shapes": [4, [1, 2, 3]]},
652680
}
653681
meta = parse_chunk_grid(data)
654-
g = ChunkGrid.from_sizes((6, 6), meta.chunk_shapes) # type: ignore[union-attr]
682+
g = ChunkGrid.from_sizes((6, 6), meta.chunk_shapes)
655683
assert _edges(g, 0) == (4, 4)
656684
assert _edges(g, 1) == (1, 2, 3)
657685

@@ -663,7 +691,7 @@ def test_spec_mixed_rle_and_bare_integers() -> None:
663691
"configuration": {"kind": "inline", "chunk_shapes": [[[1, 3], 3]]},
664692
}
665693
meta = parse_chunk_grid(data)
666-
g = ChunkGrid.from_sizes((6,), meta.chunk_shapes) # type: ignore[union-attr]
694+
g = ChunkGrid.from_sizes((6,), meta.chunk_shapes)
667695
assert _edges(g, 0) == (1, 1, 1, 3)
668696

669697

@@ -674,7 +702,7 @@ def test_spec_overflow_chunks_allowed() -> None:
674702
"configuration": {"kind": "inline", "chunk_shapes": [[4, 4, 4]]},
675703
}
676704
meta = parse_chunk_grid(data)
677-
g = ChunkGrid.from_sizes((6,), meta.chunk_shapes) # type: ignore[union-attr]
705+
g = ChunkGrid.from_sizes((6,), meta.chunk_shapes)
678706
assert _edges(g, 0) == (4, 4, 4)
679707

680708

@@ -694,7 +722,7 @@ def test_spec_example() -> None:
694722
},
695723
}
696724
meta = parse_chunk_grid(data)
697-
g = ChunkGrid.from_sizes((6, 6, 6, 6, 6), meta.chunk_shapes) # type: ignore[union-attr]
725+
g = ChunkGrid.from_sizes((6, 6, 6, 6, 6), meta.chunk_shapes)
698726
assert _edges(g, 0) == (4, 4)
699727
assert _edges(g, 1) == (1, 2, 3)
700728
assert _edges(g, 2) == (4, 4)
@@ -747,7 +775,7 @@ def test_parse_chunk_grid_rectilinear_extent_mismatch_raises(
747775
}
748776
meta = parse_chunk_grid(data)
749777
with pytest.raises(ValueError, match=match):
750-
ChunkGrid.from_sizes(array_shape, meta.chunk_shapes) # type: ignore[union-attr]
778+
ChunkGrid.from_sizes(array_shape, meta.chunk_shapes)
751779

752780

753781
def test_parse_chunk_grid_rectilinear_extent_match_passes() -> None:
@@ -757,7 +785,7 @@ def test_parse_chunk_grid_rectilinear_extent_match_passes() -> None:
757785
"configuration": {"kind": "inline", "chunk_shapes": [[10, 20, 30], [25, 25]]},
758786
}
759787
meta = parse_chunk_grid(data)
760-
g = ChunkGrid.from_sizes((60, 50), meta.chunk_shapes) # type: ignore[union-attr]
788+
g = ChunkGrid.from_sizes((60, 50), meta.chunk_shapes)
761789
assert g.grid_shape == (3, 2)
762790

763791

@@ -769,7 +797,7 @@ def test_parse_chunk_grid_rectilinear_ndim_mismatch_raises() -> None:
769797
}
770798
meta = parse_chunk_grid(data)
771799
with pytest.raises(ValueError, match="3 dimensions but chunk_sizes has 2"):
772-
ChunkGrid.from_sizes((30, 50, 100), meta.chunk_shapes) # type: ignore[union-attr]
800+
ChunkGrid.from_sizes((30, 50, 100), meta.chunk_shapes)
773801

774802

775803
def test_parse_chunk_grid_rectilinear_rle_extent_validated() -> None:
@@ -779,10 +807,10 @@ def test_parse_chunk_grid_rectilinear_rle_extent_validated() -> None:
779807
"configuration": {"kind": "inline", "chunk_shapes": [[[10, 5]], [[25, 2]]]},
780808
}
781809
meta = parse_chunk_grid(data)
782-
g = ChunkGrid.from_sizes((50, 50), meta.chunk_shapes) # type: ignore[union-attr]
810+
g = ChunkGrid.from_sizes((50, 50), meta.chunk_shapes)
783811
assert g.grid_shape == (5, 2)
784812
with pytest.raises(ValueError, match="extent 100 exceeds sum of edges 50"):
785-
ChunkGrid.from_sizes((100, 50), meta.chunk_shapes) # type: ignore[union-attr]
813+
ChunkGrid.from_sizes((100, 50), meta.chunk_shapes)
786814

787815

788816
def test_parse_chunk_grid_varying_dimension_extent_mismatch_on_chunkgrid_input() -> None:
@@ -1044,6 +1072,31 @@ def test_sharding_accepts_divisible_rectilinear() -> None:
10441072
)
10451073

10461074

1075+
def test_sharding_rejects_non_divisible_among_repeated_edges() -> None:
1076+
"""Shard validation catches a non-divisible edge even among many repeated valid ones."""
1077+
from zarr.codecs.sharding import ShardingCodec
1078+
from zarr.core.dtype import Float32
1079+
from zarr.core.metadata.v3 import RectilinearChunkGridMetadata
1080+
1081+
# edges (10, 10, 7) — 7 is not divisible by 5
1082+
codec = ShardingCodec(chunk_shape=(5,))
1083+
grid_meta = RectilinearChunkGridMetadata(chunk_shapes=((10, 10, 7),))
1084+
with pytest.raises(ValueError, match="divisible"):
1085+
codec.validate(shape=(27,), dtype=Float32(), chunk_grid=grid_meta)
1086+
1087+
1088+
def test_sharding_accepts_all_repeated_divisible_edges() -> None:
1089+
"""Shard validation passes when all distinct edges are divisible by inner chunk size."""
1090+
from zarr.codecs.sharding import ShardingCodec
1091+
from zarr.core.dtype import Float32
1092+
from zarr.core.metadata.v3 import RectilinearChunkGridMetadata
1093+
1094+
# edges (10, 10, 20, 10) — unique values {10, 20}, both divisible by 5
1095+
codec = ShardingCodec(chunk_shape=(5,))
1096+
grid_meta = RectilinearChunkGridMetadata(chunk_shapes=((10, 10, 20, 10),))
1097+
codec.validate(shape=(50,), dtype=Float32(), chunk_grid=grid_meta)
1098+
1099+
10471100
# ---------------------------------------------------------------------------
10481101
# Edge cases
10491102
# ---------------------------------------------------------------------------
@@ -1093,6 +1146,35 @@ def test_edge_case_zero_size_or_extent(size: int, extent: int) -> None:
10931146
assert g[0] is None
10941147

10951148

1149+
def test_edge_case_zero_size_data_and_indices() -> None:
1150+
"""FixedDimension(size=0) handles data_size, index_to_chunk, and indices_to_chunks safely."""
1151+
d = FixedDimension(size=0, extent=0)
1152+
# Zero-sized chunks have zero data
1153+
assert d.data_size(0) == 0
1154+
# Vectorized lookup maps every index to chunk 0 (avoids division by zero)
1155+
indices = np.array([0, 0, 0], dtype=np.intp)
1156+
np.testing.assert_array_equal(d.indices_to_chunks(indices), np.zeros(3, dtype=np.intp))
1157+
1158+
1159+
def test_edge_case_zero_size_nonzero_extent_index() -> None:
1160+
"""FixedDimension(size=0, extent>0) maps valid indices to chunk 0 without dividing by zero."""
1161+
d = FixedDimension(size=0, extent=5)
1162+
assert d.nchunks == 0
1163+
# index_to_chunk avoids division by zero and returns 0
1164+
assert d.index_to_chunk(0) == 0
1165+
assert d.index_to_chunk(4) == 0
1166+
1167+
1168+
def test_edge_case_zero_size_data_and_index() -> None:
1169+
"""FixedDimension(size=0) returns zero for data_size and maps indices to chunk 0."""
1170+
d = FixedDimension(size=0, extent=0)
1171+
# data_size returns 0 for a zero-sized chunk
1172+
assert d.data_size(0) == 0
1173+
# vectorized indices_to_chunks returns zeros
1174+
indices = np.array([0, 0, 0], dtype=np.intp)
1175+
np.testing.assert_array_equal(d.indices_to_chunks(indices), np.zeros(3, dtype=np.intp))
1176+
1177+
10961178
# -- 0-d grid --
10971179

10981180

@@ -1753,6 +1835,17 @@ def test_varying_dimension_negative_extent_rejected() -> None:
17531835
VaryingDimension([10, 20], extent=-1)
17541836

17551837

1838+
def test_varying_dimension_zero_extent() -> None:
1839+
"""VaryingDimension with extent=0 has zero active chunks but retains all grid cells."""
1840+
d = VaryingDimension([10, 20], extent=0)
1841+
assert d.nchunks == 0
1842+
assert d.ngridcells == 2
1843+
# No chunks overlap [0, 0), so the grid is structurally non-empty but logically empty
1844+
g = ChunkGrid(dimensions=(d,))
1845+
assert g.grid_shape == (0,)
1846+
assert list(g) == []
1847+
1848+
17561849
def test_varying_dimension_boundary_chunk_spec() -> None:
17571850
"""ChunkGrid with a boundary VaryingDimension produces correct ChunkSpec."""
17581851
g = ChunkGrid(dimensions=(VaryingDimension([10, 20, 30], extent=50),))
@@ -2508,6 +2601,21 @@ def test_array_sharded_chunk_sizes() -> None:
25082601
# ---------------------------------------------------------------------------
25092602

25102603

2604+
def test_chunk_grid_repr_regular() -> None:
2605+
"""ChunkGrid repr shows uniform chunk sizes and array shape for regular grids."""
2606+
grid = ChunkGrid.from_sizes((100, 200), (10, 20))
2607+
r = repr(grid)
2608+
assert r == "ChunkGrid(chunk_sizes=(10, 20), array_shape=(100, 200))"
2609+
2610+
2611+
def test_chunk_grid_repr_rectilinear() -> None:
2612+
"""ChunkGrid repr shows per-chunk edge tuples for rectilinear dimensions."""
2613+
grid = ChunkGrid.from_sizes((30,), ([10, 20],))
2614+
r = repr(grid)
2615+
assert "(10, 20)" in r
2616+
assert "(30,)" in r
2617+
2618+
25112619
def test_info_display_rectilinear() -> None:
25122620
"""Array.info should not crash for rectilinear grids."""
25132621
store = zarr.storage.MemoryStore()
@@ -2612,7 +2720,7 @@ def test_rectilinear_from_dict(
26122720
json_input: dict[str, Any], expected_chunk_shapes: tuple[int | tuple[int, ...], ...]
26132721
) -> None:
26142722
"""RectilinearChunkGridMetadata.from_dict correctly parses all spec forms."""
2615-
grid = RectilinearChunkGridMetadata.from_dict(json_input) # type: ignore[arg-type]
2723+
grid = RectilinearChunkGridMetadata.from_dict(json_input)
26162724
assert grid.chunk_shapes == expected_chunk_shapes
26172725

26182726

@@ -2654,7 +2762,7 @@ def test_rectilinear_to_dict(
26542762
)
26552763
def test_rectilinear_roundtrip(json_input: dict[str, Any]) -> None:
26562764
"""from_dict -> to_dict -> from_dict produces the same grid."""
2657-
grid1 = RectilinearChunkGridMetadata.from_dict(json_input) # type: ignore[arg-type]
2765+
grid1 = RectilinearChunkGridMetadata.from_dict(json_input)
26582766
grid2 = RectilinearChunkGridMetadata.from_dict(grid1.to_dict())
26592767
assert grid1.chunk_shapes == grid2.chunk_shapes
26602768

0 commit comments

Comments
 (0)