Skip to content

Commit 5d0d204

Browse files
Kurouto Agentclaude
authored andcommitted
test: add golden parity tests for List<Struct> nullable item field (PLT-1048)
Adds TestListStructNullableInnerField with 3 tests covering the edge case where a LargeList<Struct>'s item field has nullable=True vs nullable=False. Golden values (0000012c... and 000001713f...) are verified byte-exact against the Rust starfix crate via cargo test list_struct_nullable_inner_field_parity. This completes the cross-implementation parity verification for PLT-1048: all 129 Python tests and 67 Rust tests pass, and all golden values are confirmed consistent between the two implementations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b2fd8ca commit 5d0d204

1 file changed

Lines changed: 75 additions & 1 deletion

File tree

tests/test_golden_parity_r2.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
of truth; these tests are regression guards that will fail immediately if the
66
Python implementation diverges.
77
8-
Covers 37+ distinct Arrow tables / arrays across categories:
8+
Covers 40+ distinct Arrow tables / arrays across categories:
99
- Empty arrays (0 elements)
1010
- All-null arrays
1111
- Large boolean arrays (multi-byte bitvec)
@@ -14,6 +14,7 @@
1414
- Three-level nested structs
1515
- Struct with list child
1616
- List<Struct>, nullable List<Struct>
17+
- List<Struct> with nullable vs non-nullable item field (PLT-1048 edge case)
1718
- Non-nullable lists
1819
- Mixed-type structs
1920
- Mixed struct+list record batches
@@ -899,6 +900,79 @@ def test_multi_batch_list(self):
899900
# ---------------------------------------------------------------------------
900901

901902

903+
# ---------------------------------------------------------------------------
904+
# LargeList<Struct> with nullable vs non-nullable inner (item) field
905+
#
906+
# This exercises the structural-only BTreeMap entry created for List<Struct>.
907+
# When inner_field.nullable=True the entry's BitVec is allocated but never
908+
# populated (bit_count=0), so 8 zero bytes are prepended to the structural
909+
# digest at finalisation. Both Rust and Python handle this identically.
910+
# Golden values verified via `cargo test list_struct_nullable_inner_field_parity`
911+
# against the Rust starfix crate (PLT-1048).
912+
# ---------------------------------------------------------------------------
913+
914+
915+
class TestListStructNullableInnerField:
916+
"""LargeList<Struct> — nullable vs non-nullable item field parity."""
917+
918+
_ids = [1, 2, 3, 4]
919+
_labels = ["a", "b", "c", "d"]
920+
921+
def _make_list_array(self, inner_nullable: bool) -> "pa.LargeListArray":
922+
ids = pa.array(self._ids, type=pa.int32())
923+
labels = pa.array(self._labels, type=pa.large_utf8())
924+
struct_fields = [
925+
pa.field("id", pa.int32(), nullable=False),
926+
pa.field("label", pa.large_utf8(), nullable=False),
927+
]
928+
struct_arr = pa.StructArray.from_arrays(
929+
[ids, labels],
930+
fields=struct_fields,
931+
)
932+
inner_field = pa.field("item", pa.struct(struct_fields), nullable=inner_nullable)
933+
offsets = pa.array([0, 2, 4], type=pa.int64())
934+
return pa.LargeListArray.from_arrays(
935+
offsets,
936+
struct_arr,
937+
type=pa.large_list(inner_field),
938+
)
939+
940+
def test_nullable_inner_field(self):
941+
"""LargeList<Struct> with nullable=True item field.
942+
943+
ids=[1,2,3,4], labels=['a','b','c','d'], offsets=[0,2,4]
944+
inner_field.nullable=True → structural-only entry has empty BitVec.
945+
Golden value confirmed against Rust (PLT-1048).
946+
"""
947+
result = ArrowDigester.hash_array(self._make_list_array(inner_nullable=True)).hex()
948+
assert result == "0000012c2b1c1d5b4c3dc46ed5335834dbf0d7386c38e607a398a2897fdaf1df387e1c"
949+
950+
def test_non_nullable_inner_field(self):
951+
"""LargeList<Struct> with nullable=False item field.
952+
953+
ids=[1,2,3,4], labels=['a','b','c','d'], offsets=[0,2,4]
954+
inner_field.nullable=False → no BitVec in structural-only entry.
955+
Golden value confirmed against Rust (PLT-1048).
956+
"""
957+
result = ArrowDigester.hash_array(self._make_list_array(inner_nullable=False)).hex()
958+
assert result == "000001713fa0e500c9aebea61039b30371fd84c0dff8cd3b96b4266978658bf73e4d8c"
959+
960+
def test_nullable_differs_from_non_nullable(self):
961+
"""Nullable vs non-nullable inner field must produce different hashes."""
962+
hash_nullable = ArrowDigester.hash_array(self._make_list_array(inner_nullable=True)).hex()
963+
hash_not_nullable = ArrowDigester.hash_array(
964+
self._make_list_array(inner_nullable=False)
965+
).hex()
966+
assert hash_nullable != hash_not_nullable, (
967+
"nullable and non-nullable item fields must produce distinct hashes"
968+
)
969+
970+
971+
# ---------------------------------------------------------------------------
972+
# Float special values
973+
# ---------------------------------------------------------------------------
974+
975+
902976
class TestFloatSpecialValues:
903977
def test_float64_special_values(self):
904978
"""Float64 array with NaN, Inf, -Inf, 0.0, -0.0."""

0 commit comments

Comments
 (0)