Files
clawhdf5/crates/clawhdf5/tests/shared_message_v1.rs
T
osobhandClaude Opus 5.5 883980f2bd test: compare h5py's v1 compound field names with what clawhdf5 reads
The test compared h5py against its own expected table, so it passed with
the fix reverted. Found by the adversarial review.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 22:44:05 -05:00

126 lines
4.1 KiB
Rust

//! Version-1 shared messages (HDF5 1.6 era). A dataset that uses a committed
//! datatype stores a *shared* datatype message pointing at the type's object
//! header. In version 1 that pointer is a 1.6 "symbol table entry": after six
//! reserved bytes comes a length-sized heap offset, *then* the address.
//!
//! Fixture: `tcompound.h5` from libhdf5's own tool tests
//! (`tools/test/testfiles/tcompound.h5`, HDF5 source tree, BSD-style
//! licence), 8 KiB. Its datasets use committed compound types through v1
//! shared messages. The expected types are what h5dump 1.14.6 and h5py 3.16
//! (HDF5 2.0) report; the h5py cross-check runs when python3 with h5py is
//! available (required with `CLAWHDF5_REQUIRE_INTEROP=1`).
use std::process::Command;
use clawhdf5::{DType, File};
const FIXTURE: &[u8] = include_bytes!("../../clawhdf5-format/tests/fixtures/tcompound.h5");
fn python() -> String {
std::env::var("CLAWHDF5_PYTHON").unwrap_or_else(|_| "python3".to_string())
}
fn interop_required() -> bool {
std::env::var("CLAWHDF5_REQUIRE_INTEROP").is_ok_and(|v| v == "1")
}
fn python_available() -> bool {
Command::new(python())
.args(["-c", "import h5py, numpy"])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn compound(fields: &[(&str, DType)]) -> DType {
DType::Compound(
fields
.iter()
.map(|(n, t)| (n.to_string(), t.clone()))
.collect(),
)
}
fn expected() -> Vec<(&'static str, DType)> {
let int_float = |i: &str, f: &str| compound(&[(i, DType::I32), (f, DType::F32)]);
vec![
("group1/dset2", int_float("int_name", "float_name")),
(
"group1/dset3",
compound(&[
("int_array", DType::Array(Box::new(DType::I32), vec![4])),
(
"float_array",
DType::Array(Box::new(DType::F32), vec![5, 6]),
),
]),
),
("group1/dset4", int_float("int", "float")),
("group2/dset5", int_float("int", "float")),
]
}
#[test]
fn v1_shared_datatype_resolves_to_the_committed_type() {
// Reading the heap offset as the address used to land on the superblock
// and fail with InvalidObjectHeaderVersion.
let file = File::from_bytes(FIXTURE.to_vec()).unwrap();
for (path, dtype) in expected() {
assert_eq!(
file.dataset(path).unwrap().dtype().unwrap(),
dtype,
"{path}"
);
}
}
#[test]
fn v1_shared_datatype_field_names_match_h5py() {
if !python_available() {
assert!(
!interop_required(),
"CLAWHDF5_REQUIRE_INTEROP=1 but python3 with h5py is not available"
);
eprintln!("SKIP: python3 with h5py not available");
return;
}
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tcompound.h5");
std::fs::write(&path, FIXTURE).unwrap();
let script = format!(
r#"
import h5py
with h5py.File("{path}", "r") as f:
for p in ("group1/dset2", "group1/dset3", "group1/dset4", "group2/dset5"):
print(p, *f[p].dtype.names)
"#,
path = path.display()
);
let out = Command::new(python())
.args(["-c", &script])
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
let theirs: Vec<&str> = stdout.lines().collect();
// Read the types through clawhdf5, not from `expected()`, so this checks
// our reader against libhdf5 rather than the table against h5py.
let file = File::from_bytes(FIXTURE.to_vec()).unwrap();
let ours: Vec<String> = expected()
.into_iter()
.map(|(p, _)| (p, file.dataset(p).unwrap().dtype().unwrap()))
.map(|(p, t)| match t {
DType::Compound(fields) => {
let names: Vec<String> = fields.into_iter().map(|(n, _)| n).collect();
format!("{p} {}", names.join(" "))
}
other => panic!("{other:?}"),
})
.collect();
assert_eq!(theirs, ours);
}