From a59d83d47d06ef838c0e70227ac70730837abaa0 Mon Sep 17 00:00:00 2001 From: osobh Date: Sat, 26 Sep 2026 00:35:25 -0500 Subject: [PATCH] test: compare header and datatype damage with what h5py refuses h5py writes a dataset (libver earliest, so version-1 object headers) and the script damages one field of a copy: a layout message flagged shareable, a message size that is not a multiple of 8, a compound field that repeats an earlier name or overlaps it, an empty enum member name, a float exponent overlapping the mantissa. h5py refuses every damaged copy, and clawhdf5 must refuse exactly those and read the valid files. All of them but the unaligned one (then an UnexpectedEof) read before this branch. The helper now reads any datatype (File::read_multi) rather than only integers. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../tests/header_validation_interop.rs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/crates/clawhdf5/tests/header_validation_interop.rs b/crates/clawhdf5/tests/header_validation_interop.rs index 23509e1..14dc9d7 100644 --- a/crates/clawhdf5/tests/header_validation_interop.rs +++ b/crates/clawhdf5/tests/header_validation_interop.rs @@ -85,7 +85,9 @@ fn clawhdf5_reads(path: &Path) -> Result<(), String> { let ds = file.dataset("d").map_err(|e| format!("dataset: {e}"))?; ds.dtype().map_err(|e| format!("dtype: {e}"))?; ds.shape().map_err(|e| format!("shape: {e}"))?; - ds.read_i32().map(|_| ()).map_err(|e| format!("read: {e}")) + file.read_multi(&["d"]) + .map(|_| ()) + .map_err(|e| format!("read: {e}")) } /// h5py's verdict for each file must be `expected`, and clawhdf5 must read @@ -198,3 +200,72 @@ for libver in ("earliest", "latest"): assert!(err.to_string().contains("truncated file"), "{name}: {err}"); } } + +#[test] +fn header_and_datatype_damage_libhdf5_refuses_is_refused() { + skip_if_no_python!(); + let dir = tempfile::tempdir().unwrap(); + // Each case damages one field of a valid file h5py wrote (libver + // earliest, so version-1 object headers); all of these read before. + let verdicts = h5py_verdicts( + dir.path(), + r#" +def write(name, make): + path = os.path.join(d, name + ".h5") + with h5py.File(path, "w", libver="earliest") as f: + make(f) + return bytearray(open(path, "rb").read()) + +def save(name, data): + open(os.path.join(d, name + ".h5"), "wb").write(data) + +# A layout message (type 8, 24 bytes, v1 header) flagged shareable. +data = write("layout_good", lambda f: f.create_dataset("d", data=np.arange(4, dtype=" 0 +bad = bytearray(data); bad[at + 4] |= 0x40 +save("layout_shareable", bad) +# A message size that is not a multiple of 8 in a v1 header. +bad = bytearray(data); bad[at + 2] = 23 +save("layout_unaligned", bad) + +# A compound whose second field repeats the first's name, or overlaps it. +dt = np.dtype([("aa", "