use hdf5_pure_rust::engine::writer::{CompoundFieldSpec, DtypeSpec};
use hdf5_pure_rust::format::messages::data_layout::ChunkIndexType;
use hdf5_pure_rust::hl::types::{FieldDescriptor, H5Type, TypeClass};
use hdf5_pure_rust::{Attribute, Dataset, H5Value, Location, Result, WritableFile};
fn file_has_member(file: &hdf5_pure_rust::File, expected: &str) -> hdf5_pure_rust::Result<bool> {
let mut found = false;
file.visit_member_names(|name| {
if name == expected {
found = true;
}
Ok(())
})?;
Ok(found)
}
fn group_member_summary_into(
group: &hdf5_pure_rust::Group,
expected: &[&str],
found: &mut [bool],
) -> hdf5_pure_rust::Result<usize> {
assert_eq!(expected.len(), found.len());
found.fill(false);
let mut count = 0;
group.visit_member_names(|name| {
count += 1;
for (idx, expected_name) in expected.iter().enumerate() {
if name == *expected_name {
found[idx] = true;
}
}
Ok(())
})?;
Ok(count)
}
fn location_attr_summary_into<L: Location>(
location: &L,
expected: &[&str],
found: &mut [bool],
) -> Result<usize> {
assert_eq!(expected.len(), found.len());
found.fill(false);
let mut count = 0;
location.visit_attr_names(|name| {
count += 1;
for (idx, expected_name) in expected.iter().enumerate() {
if name == *expected_name {
found[idx] = true;
}
}
Ok(())
})?;
Ok(count)
}
fn assert_fractal_heap_uses_filters(bytes: &[u8]) {
let mut found_filtered_heap = false;
for (pos, _) in bytes
.windows(4)
.enumerate()
.filter(|(_, window)| *window == b"FRHP")
{
let io_filter_pos = pos + 7;
if io_filter_pos + 2 <= bytes.len() {
let io_filter_len =
u16::from_le_bytes([bytes[io_filter_pos], bytes[io_filter_pos + 1]]);
found_filtered_heap |= io_filter_len > 0;
}
}
assert!(
found_filtered_heap,
"expected a filtered fractal heap header"
);
}
fn assert_dataset_values<T>(ds: &Dataset, expected: &[T]) -> Result<()>
where
T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
let mut values = vec![T::default(); ds.size()? as usize];
ds.read_into(&mut values)?;
assert_eq!(values, expected);
Ok(())
}
fn assert_dataset_raw(ds: &Dataset, expected: &[u8]) -> Result<()> {
let mut raw = vec![0; ds.size()? as usize * ds.element_size()?];
ds.read_raw_into(&mut raw)?;
assert_eq!(raw, expected);
Ok(())
}
fn assert_h5dump_dataset_read(path: &std::path::Path, dataset: &str, context: &str) {
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-d")
.arg(dataset)
.arg(path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -d failed or timed out on {context}: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
}
}
fn assert_h5py_script(path: &std::path::Path, script: &str, context: &str) {
let code = format!(
"import sys, h5py\n\
f = h5py.File(sys.argv[1], 'r')\n\
{script}\n\
f.close()\n\
print('OK')"
);
let out = std::process::Command::new("python3")
.arg("-c")
.arg(code)
.arg(path)
.output();
if let Ok(out) = out {
assert!(
out.status.success() && String::from_utf8_lossy(&out.stdout).contains("OK"),
"h5py verification failed on {context}: {}",
String::from_utf8_lossy(&out.stderr)
);
}
}
fn dataset_scalar<T>(ds: &Dataset) -> Result<T>
where
T: H5Type + Default,
{
let mut value = T::default();
ds.read_scalar_into(&mut value)?;
Ok(value)
}
fn assert_dataset_strings(ds: &Dataset, expected: &[&str]) -> Result<()> {
let mut strings = Vec::new();
ds.read_strings_into(&mut strings)?;
assert_eq!(strings.len(), expected.len());
for (actual, expected) in strings.iter().zip(expected) {
assert_eq!(actual, expected);
}
Ok(())
}
fn assert_dataset_field<T>(ds: &Dataset, field_name: &str, expected: &[T]) -> Result<()>
where
T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
let mut values = vec![T::default(); ds.size()? as usize];
ds.read_field_into(field_name, &mut values)?;
assert_eq!(values, expected);
Ok(())
}
fn assert_dataset_field_values(ds: &Dataset, field_name: &str, expected: &[H5Value]) -> Result<()> {
let mut values = Vec::new();
ds.read_field_values_into(field_name, &mut values)?;
assert_eq!(values, expected);
Ok(())
}
fn dataset_field_value_count(ds: &Dataset, field_name: &str) -> Result<usize> {
let mut count = 0;
ds.visit_field_values(field_name, |_| {
count += 1;
Ok(())
})?;
Ok(count)
}
fn dataset_has_filter_id(ds: &Dataset, id: u16) -> Result<bool> {
let mut found = false;
ds.visit_filters(|filter| {
if filter.id == id {
found = true;
}
Ok(())
})?;
Ok(found)
}
fn assert_attribute_values<T>(attr: &Attribute, expected: &[T]) -> Result<()>
where
T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
let len = attr.shape().iter().try_fold(1usize, |acc, &dim| {
acc.checked_mul(dim as usize)
.ok_or_else(|| hdf5_pure_rust::Error::InvalidFormat("attribute shape overflows".into()))
})?;
let mut values = vec![T::default(); len];
attr.read_into(&mut values)?;
assert_eq!(values, expected);
Ok(())
}
fn attribute_string(attr: &Attribute) -> Result<String> {
let mut value = String::new();
attr.read_string_into(&mut value)?;
Ok(value)
}
fn assert_attribute_strings(attr: &Attribute, expected: &[&str]) -> Result<()> {
let mut index = 0;
attr.visit_strings(|value| {
assert_eq!(Some(value), expected.get(index).copied());
index += 1;
Ok(())
})?;
assert_eq!(index, expected.len());
Ok(())
}
#[repr(C)]
#[derive(Clone, Copy)]
struct Point {
x: f64,
label: i32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct WidePair {
signed: i128,
unsigned: u128,
}
unsafe impl H5Type for Point {
fn type_size() -> usize {
std::mem::size_of::<Point>()
}
fn compound_fields_into(out: &mut Vec<FieldDescriptor>) -> Option<()> {
out.clear();
out.extend([
FieldDescriptor {
name: "x".to_string(),
offset: std::mem::offset_of!(Point, x),
size: std::mem::size_of::<f64>(),
type_class: TypeClass::Float,
},
FieldDescriptor {
name: "label".to_string(),
offset: std::mem::offset_of!(Point, label),
size: std::mem::size_of::<i32>(),
type_class: TypeClass::Integer { signed: true },
},
]);
Some(())
}
}
unsafe impl H5Type for WidePair {
fn type_size() -> usize {
std::mem::size_of::<WidePair>()
}
fn compound_fields_into(out: &mut Vec<FieldDescriptor>) -> Option<()> {
out.clear();
out.extend([
FieldDescriptor {
name: "signed".to_string(),
offset: std::mem::offset_of!(WidePair, signed),
size: std::mem::size_of::<i128>(),
type_class: TypeClass::Integer { signed: true },
},
FieldDescriptor {
name: "unsigned".to_string(),
offset: std::mem::offset_of!(WidePair, unsigned),
size: std::mem::size_of::<u128>(),
type_class: TypeClass::Integer { signed: false },
},
]);
Some(())
}
}
#[test]
fn test_writable_file_simple() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_simple.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("temperatures")
.shape(&[5])
.write::<f64>(&[20.0, 21.5, 22.0, 19.8, 23.1])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("temperatures").unwrap();
assert_dataset_values::<f64>(&ds, &[20.0, 21.5, 22.0, 19.8, 23.1]).unwrap();
}
}
#[test]
fn test_writable_file_i128_u128_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_i128_u128.h5");
let signed = [i128::MIN, -42, 0, i128::MAX];
let unsigned = [0u128, 42, 1u128 << 96, u128::MAX];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("signed")
.shape(&[signed.len() as u64])
.write::<i128>(&signed)
.unwrap();
wf.new_dataset_builder("unsigned")
.shape(&[unsigned.len() as u64])
.write::<u128>(&unsigned)
.unwrap();
wf.add_attr("wide_attr", i128::MIN + 7).unwrap();
wf.flush().unwrap();
}
let f = hdf5_pure_rust::File::open(&path).unwrap();
assert_dataset_values::<i128>(&f.dataset("signed").unwrap(), &signed).unwrap();
assert_dataset_values::<u128>(&f.dataset("unsigned").unwrap(), &unsigned).unwrap();
assert_eq!(
f.attr("wide_attr").unwrap().read_scalar::<i128>().unwrap(),
i128::MIN + 7
);
}
#[test]
fn test_writable_file_compound_i128_u128_fields_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compound_i128_u128.h5");
let data = [
WidePair {
signed: -123456789012345678901234567890i128,
unsigned: 123456789012345678901234567890u128,
},
WidePair {
signed: i128::MAX,
unsigned: u128::MAX,
},
];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("wide_pairs")
.shape(&[data.len() as u64])
.write::<WidePair>(&data)
.unwrap();
wf.flush().unwrap();
}
let f = hdf5_pure_rust::File::open(&path).unwrap();
let ds = f.dataset("wide_pairs").unwrap();
let expected_signed = data.iter().map(|value| value.signed).collect::<Vec<_>>();
let expected_unsigned = data.iter().map(|value| value.unsigned).collect::<Vec<_>>();
let expected_signed_values = data
.iter()
.map(|value| H5Value::Int(value.signed))
.collect::<Vec<_>>();
let expected_unsigned_values = data
.iter()
.map(|value| H5Value::UInt(value.unsigned))
.collect::<Vec<_>>();
assert_dataset_field::<i128>(&ds, "signed", &expected_signed).unwrap();
assert_dataset_field::<u128>(&ds, "unsigned", &expected_unsigned).unwrap();
assert_dataset_field_values(&ds, "signed", &expected_signed_values).unwrap();
assert_dataset_field_values(&ds, "unsigned", &expected_unsigned_values).unwrap();
}
#[test]
fn test_dataset_builder_rejects_shape_data_length_mismatch() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_shape_mismatch.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let err = wf
.new_dataset_builder("bad")
.shape(&[2, 3])
.write::<i32>(&[1, 2, 3, 4, 5])
.expect_err("shape/data element count mismatch should be rejected");
assert!(err
.to_string()
.contains("does not match shape element count"));
}
}
#[test]
fn test_dataset_builder_rejects_excessive_dataspace_rank() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_rank_too_large.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let err = wf
.new_dataset_builder("bad_rank")
.shape(&vec![1; 33])
.write::<u8>(&[0])
.expect_err("dataspace rank above supported maximum should be rejected");
assert!(err.to_string().contains("dataspace rank"));
}
}
#[test]
fn test_writable_rejects_duplicate_child_names_and_invalid_link_names() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_duplicate_child_names.h5");
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.write::<i32>(&[1, 2, 3])
.unwrap();
let err = wf
.new_dataset_builder("data")
.write::<i32>(&[4, 5, 6])
.expect_err("duplicate dataset name should be rejected");
assert!(err.to_string().contains("already exists"));
let err = match wf.create_group("bad/name") {
Ok(_) => panic!("slash-containing group name should be rejected"),
Err(err) => err,
};
assert!(err.to_string().contains("must not contain '/'"));
let err = wf
.link_soft("bad/link", "/data")
.expect_err("slash-containing soft-link name should be rejected");
assert!(err.to_string().contains("must not contain '/'"));
let err = wf
.link_soft("data", "/data")
.expect_err("soft link must not shadow an existing dataset");
assert!(err.to_string().contains("already exists"));
wf.link_soft("alias", "/data").unwrap();
let err = match wf.create_group("alias") {
Ok(_) => panic!("group must not shadow a pending soft link"),
Err(err) => err,
};
assert!(err.to_string().contains("already exists"));
}
#[test]
fn test_writable_file_with_groups() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_groups.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("sensors").unwrap();
g.new_dataset_builder("pressure")
.write::<f32>(&[1013.25, 1012.0, 1011.5])
.unwrap();
let f = wf.close().unwrap();
assert!(file_has_member(&f, "sensors").unwrap());
let g = f.group("sensors").unwrap();
let ds = g.open_dataset("pressure").unwrap();
assert_dataset_values::<f32>(&ds, &[1013.25, 1012.0, 1011.5]).unwrap();
}
}
#[test]
fn test_writable_group_attr() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_group_attr.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("sensors").unwrap();
g.add_attr("site_id", 17i64).unwrap();
g.new_dataset_builder("pressure")
.write::<f32>(&[1013.25, 1012.0])
.unwrap();
let f = wf.close().unwrap();
let g = f.group("sensors").unwrap();
let mut found = [false];
location_attr_summary_into(&g, &["site_id"], &mut found).unwrap();
assert!(found[0]);
let value: i64 = g.attr("site_id").unwrap().read_scalar::<i64>().unwrap();
assert_eq!(value, 17);
}
assert_h5py_script(
&path,
"g = f['sensors']\n\
assert g.attrs['site_id'] == 17\n\
d = g['pressure']\n\
assert d.shape == (2,)\n\
assert d[:].tolist() == [1013.25, 1012.0]",
"group attribute writer fixture",
);
}
#[test]
fn test_writable_rejects_duplicate_attribute_names() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_duplicate_attrs.h5");
let mut wf = WritableFile::create(&path).unwrap();
wf.add_attr("version", 1i32).unwrap();
let err = wf
.add_attr("version", 2i32)
.expect_err("duplicate root attribute should be rejected");
assert!(err.to_string().contains("already exists"));
let mut group = wf.create_group("metadata").unwrap();
group.add_attr("version", 1i32).unwrap();
let err = group
.add_attr_array("version", &[2i32, 3])
.expect_err("duplicate group attribute should be rejected");
assert!(err.to_string().contains("already exists"));
let err = match wf
.new_dataset_builder("values")
.attr("units", 1i32)
.unwrap()
.fixed_ascii_attr("units", "ms", 8)
{
Ok(_) => panic!("duplicate dataset builder attribute should be rejected"),
Err(err) => err,
};
assert!(err.to_string().contains("already exists"));
}
#[test]
fn test_writable_group_dense_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_group_dense_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("annotated").unwrap();
for idx in 0..16 {
g.add_attr(&format!("attr_{idx:02}"), idx as i64).unwrap();
}
let f = wf.close().unwrap();
let g = f.group("annotated").unwrap();
let mut found = [false, false];
let count = location_attr_summary_into(&g, &["attr_00", "attr_15"], &mut found).unwrap();
assert_eq!(count, 16);
assert!(found[0]);
assert!(found[1]);
assert_eq!(g.attr("attr_12").unwrap().read_scalar_i64(), Some(12));
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-A")
.arg("-g")
.arg("annotated")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -A failed or timed out on dense group attribute fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("attr_00"));
assert!(stdout.contains("attr_15"));
}
assert_h5py_script(
&path,
"attrs = f['annotated'].attrs\n\
assert len(attrs) == 16\n\
assert attrs['attr_00'] == 0\n\
assert attrs['attr_12'] == 12\n\
assert attrs['attr_15'] == 15",
"dense group attribute writer fixture",
);
}
#[test]
fn test_writable_dense_attrs_use_indirect_fractal_heap_blocks() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_attrs_indirect_heap.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("annotated").unwrap();
for idx in 0..40 {
g.add_fixed_ascii_attr(&format!("attr_{idx:02}"), "value", 256)
.unwrap();
}
let f = wf.close().unwrap();
let g = f.group("annotated").unwrap();
let mut found = [false, false];
let count = location_attr_summary_into(&g, &["attr_00", "attr_39"], &mut found).unwrap();
assert_eq!(count, 40);
assert!(found[0]);
assert!(found[1]);
assert_eq!(
attribute_string(&g.attr("attr_17").unwrap()).unwrap(),
"value"
);
}
let bytes = std::fs::read(&path).unwrap();
assert!(bytes.windows(4).any(|window| window == b"FHIB"));
assert_fractal_heap_uses_filters(&bytes);
}
#[test]
fn test_writable_root_and_group_array_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_array_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.add_attr_array("calibration", &[1.0f64, 2.5, 4.0])
.unwrap();
let mut g = wf.create_group("sensors").unwrap();
g.add_attr_array("ids", &[10i32, 20, 30, 40]).unwrap();
let f = wf.close().unwrap();
let root_attr = f.attr("calibration").unwrap();
assert_eq!(root_attr.shape(), &[3]);
assert_attribute_values::<f64>(&root_attr, &[1.0, 2.5, 4.0]).unwrap();
let group = f.group("sensors").unwrap();
let group_attr = group.attr("ids").unwrap();
assert_eq!(group_attr.shape(), &[4]);
assert_attribute_values::<i32>(&group_attr, &[10, 20, 30, 40]).unwrap();
}
assert_h5py_script(
&path,
"assert f.attrs['calibration'].tolist() == [1.0, 2.5, 4.0]\n\
assert f['sensors'].attrs['ids'].tolist() == [10, 20, 30, 40]",
"root and group array attribute writer fixture",
);
}
#[test]
fn test_writable_fixed_string_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_string_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.add_fixed_ascii_attr("project", "hdf", 8).unwrap();
let mut g = wf.create_group("metadata").unwrap();
g.add_fixed_utf8_attr("species", "猫", 8).unwrap();
let f = wf.close().unwrap();
assert_eq!(
attribute_string(&f.attr("project").unwrap()).unwrap(),
"hdf"
);
let group = f.group("metadata").unwrap();
assert_eq!(
attribute_string(&group.attr("species").unwrap()).unwrap(),
"猫"
);
}
assert_h5py_script(
&path,
"project = f.attrs['project']\n\
species = f['metadata'].attrs['species']\n\
project = project.decode('utf-8') if isinstance(project, bytes) else str(project)\n\
species = species.decode('utf-8') if isinstance(species, bytes) else str(species)\n\
assert project.rstrip('\\x00') == 'hdf'\n\
assert species.rstrip('\\x00') == '猫'",
"fixed string attribute writer fixture",
);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_writable_rejects_unrepresentable_fixed_string_lengths() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_string_len_overflow.h5");
let too_long = u32::MAX as usize + 1;
let mut wf = WritableFile::create(&path).unwrap();
let err = wf
.add_fixed_ascii_attr("root", "x", too_long)
.expect_err("root fixed string length should fit encoded u32");
assert!(err.to_string().contains("fixed string length"));
let err = wf
.create_group("metadata")
.unwrap()
.add_fixed_utf8_attr("group", "x", too_long)
.expect_err("group fixed string length should fit encoded u32");
assert!(err.to_string().contains("fixed string length"));
let err = match wf
.new_dataset_builder("values")
.fixed_ascii_attr("units", "x", too_long)
{
Ok(_) => panic!("dataset attribute fixed string length should fit encoded u32"),
Err(err) => err,
};
assert!(err.to_string().contains("fixed string length"));
let err = wf
.new_dataset_builder("names")
.write_fixed_ascii_strings(&["x"], too_long)
.expect_err("dataset fixed string length should fit encoded u32");
assert!(err.to_string().contains("fixed string length"));
}
#[test]
fn test_writable_routes_oversized_attrs_to_dense_storage() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_oversized_dense_attrs.h5");
let large_len = u16::MAX as usize + 1;
{
let mut wf = WritableFile::create(&path).unwrap();
let mut group = wf.create_group("metadata").unwrap();
group
.add_fixed_ascii_attr("large_attr", "x", large_len)
.unwrap();
wf.new_dataset_builder("data")
.fixed_ascii_attr("large_ds_attr", "y", large_len)
.unwrap()
.write::<i32>(&[1, 2, 3])
.unwrap();
let f = wf.close().unwrap();
let group = f.group("metadata").unwrap();
assert_eq!(
attribute_string(&group.attr("large_attr").unwrap()).unwrap(),
"x"
);
let ds = f.dataset("data").unwrap();
assert_dataset_values::<i32>(&ds, &[1, 2, 3]).unwrap();
assert_eq!(
attribute_string(&ds.attr("large_ds_attr").unwrap()).unwrap(),
"y"
);
}
}
#[test]
fn test_writable_dense_attrs_support_wider_heap_ids() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_attr_wide_heap_id.h5");
let large_len = (1usize << 24) + 1;
{
let mut wf = WritableFile::create(&path).unwrap();
let mut group = wf.create_group("metadata").unwrap();
group
.add_fixed_ascii_attr("very_large_attr", "z", large_len)
.unwrap();
for idx in 0..8 {
group.add_attr(&format!("attr_{idx}"), idx).unwrap();
}
let f = wf.close().unwrap();
let group = f.group("metadata").unwrap();
assert_eq!(
attribute_string(&group.attr("very_large_attr").unwrap()).unwrap(),
"z"
);
assert_attribute_values::<i32>(&group.attr("attr_7").unwrap(), &[7]).unwrap();
}
assert!(std::fs::read(&path)
.unwrap()
.windows(4)
.any(|window| window == b"BTHD"));
let bytes = std::fs::read(&path).unwrap();
let heap_pos = bytes
.windows(4)
.position(|window| window == b"FRHP")
.expect("dense attribute storage should emit a fractal heap header");
let heap_id_len = u16::from_le_bytes([bytes[heap_pos + 5], bytes[heap_pos + 6]]);
let max_heap_size = u16::from_le_bytes([bytes[heap_pos + 128], bytes[heap_pos + 129]]);
assert_eq!(heap_id_len, 9);
assert_eq!(max_heap_size, 32);
}
#[test]
fn test_writable_dense_attrs_cross_leaf_btree_boundary() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_attr_internal_btree.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut group = wf.create_group("metadata").unwrap();
for idx in 0..64 {
group.add_attr(&format!("attr_{idx:02}"), idx).unwrap();
}
let f = wf.close().unwrap();
let group = f.group("metadata").unwrap();
let mut found = [false, false];
let count =
location_attr_summary_into(&group, &["attr_00", "attr_63"], &mut found).unwrap();
assert_eq!(count, 64);
assert!(found[0]);
assert!(found[1]);
assert_attribute_values::<i32>(&group.attr("attr_42").unwrap(), &[42]).unwrap();
}
let bytes = std::fs::read(&path).unwrap();
assert!(bytes.windows(16).any(|window| {
window.starts_with(b"BTHD")
&& window.get(5) == Some(&8)
&& u16::from_le_bytes([window[12], window[13]]) > 0
}));
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-A")
.arg("-g")
.arg("metadata")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -A failed or timed out on dense attribute internal-B-tree fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("attr_00"));
assert!(stdout.contains("attr_63"));
}
assert_h5py_script(
&path,
"attrs = f['metadata'].attrs\n\
assert len(attrs) == 64\n\
assert attrs['attr_00'] == 0\n\
assert attrs['attr_42'] == 42\n\
assert attrs['attr_63'] == 63",
"dense attribute internal-B-tree writer fixture",
);
}
#[test]
fn test_writable_rejects_oversized_attribute_name_field() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_oversized_attr_name.h5");
let mut wf = WritableFile::create(&path).unwrap();
let name = "a".repeat(u16::MAX as usize);
wf.add_attr(&name, 1i32).unwrap();
let err = wf
.flush()
.expect_err("attribute name length should fit u16 field");
assert!(err.to_string().contains("attribute name"));
}
#[test]
fn test_writable_fixed_string_array_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_string_array_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.add_fixed_ascii_attr_array("stages", &["raw", "qc", "done"], 8)
.unwrap();
let mut g = wf.create_group("metadata").unwrap();
g.add_fixed_utf8_attr_array("labels", &["猫", "å"], 8)
.unwrap();
let f = wf.close().unwrap();
let stages = f.attr("stages").unwrap();
assert_eq!(stages.shape(), &[3]);
assert_attribute_strings(&stages, &["raw", "qc", "done"]).unwrap();
let labels = f.group("metadata").unwrap().attr("labels").unwrap();
assert_eq!(labels.shape(), &[2]);
assert_attribute_strings(&labels, &["猫", "å"]).unwrap();
}
assert_h5py_script(
&path,
"text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
stages = [text(value).rstrip('\\x00') for value in f.attrs['stages'].tolist()]\n\
labels = [text(value).rstrip('\\x00') for value in f['metadata'].attrs['labels'].tolist()]\n\
assert stages == ['raw', 'qc', 'done']\n\
assert labels == ['猫', 'å']",
"fixed string array attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_scalar_and_array_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dataset_builder_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("values")
.attr("version", 2i64)
.unwrap()
.attr_array("scale", &[1.0f64, 10.0, 100.0])
.unwrap()
.write::<i32>(&[4, 5, 6])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("values").unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(2));
let scale = ds.attr("scale").unwrap();
assert_eq!(scale.shape(), &[3]);
assert_attribute_values::<f64>(&scale, &[1.0, 10.0, 100.0]).unwrap();
}
assert_h5py_script(
&path,
"d = f['values']\n\
assert d[:].tolist() == [4, 5, 6]\n\
assert d.attrs['version'] == 2\n\
assert d.attrs['scale'].tolist() == [1.0, 10.0, 100.0]",
"dataset builder numeric attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_fixed_string_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dataset_string_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("values")
.fixed_ascii_attr("units", "ms", 8)
.unwrap()
.fixed_utf8_attr("label", "猫", 8)
.unwrap()
.write::<i32>(&[1, 2, 3])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("values").unwrap();
assert_eq!(attribute_string(&ds.attr("units").unwrap()).unwrap(), "ms");
assert_eq!(attribute_string(&ds.attr("label").unwrap()).unwrap(), "猫");
}
assert_h5py_script(
&path,
"d = f['values']\n\
text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
assert d[:].tolist() == [1, 2, 3]\n\
assert text(d.attrs['units']).rstrip('\\x00') == 'ms'\n\
assert text(d.attrs['label']).rstrip('\\x00') == '猫'",
"dataset builder fixed-string attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_fixed_string_array_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dataset_string_array_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("values")
.fixed_ascii_attr_array("units", &["ms", "s"], 8)
.unwrap()
.fixed_utf8_attr_array("labels", &["猫", "å"], 8)
.unwrap()
.write::<i32>(&[1, 2, 3])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("values").unwrap();
assert_attribute_strings(&ds.attr("units").unwrap(), &["ms", "s"]).unwrap();
assert_attribute_strings(&ds.attr("labels").unwrap(), &["猫", "å"]).unwrap();
}
assert_h5py_script(
&path,
"d = f['values']\n\
text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
assert d[:].tolist() == [1, 2, 3]\n\
assert [text(value).rstrip('\\x00') for value in d.attrs['units'].tolist()] == ['ms', 's']\n\
assert [text(value).rstrip('\\x00') for value in d.attrs['labels'].tolist()] == ['猫', 'å']",
"dataset builder fixed-string array attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_compact_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact_dataset_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("values")
.compact()
.attr("version", 3i64)
.unwrap()
.write::<i16>(&[7, 8, 9])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("values").unwrap();
assert_dataset_values::<i16>(&ds, &[7, 8, 9]).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(3));
}
assert_h5py_script(
&path,
"d = f['values']\n\
assert d[:].tolist() == [7, 8, 9]\n\
assert d.attrs['version'] == 3",
"compact dataset attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_attrs_with_explicit_fill_values() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_dataset_attrs_with_fill_values.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("contiguous")
.fill_value::<i32>(-7)
.attr("version", 4i64)
.unwrap()
.write::<i32>(&[1, 2, 3])
.unwrap();
wf.new_dataset_builder("compact")
.compact()
.fill_value::<i16>(-2)
.attr("version", 5i64)
.unwrap()
.write::<i16>(&[7, 8])
.unwrap();
wf.new_dataset_builder("scalar")
.fill_value::<u64>(99)
.attr("version", 6i64)
.unwrap()
.write_scalar::<u64>(42)
.unwrap();
let f = wf.close().unwrap();
let contiguous = f.dataset("contiguous").unwrap();
let plist = contiguous.create_plist().unwrap();
assert_eq!(plist.fill_value, Some((-7i32).to_le_bytes().to_vec()));
assert_eq!(
contiguous.attr("version").unwrap().read_scalar_i64(),
Some(4)
);
assert_dataset_values::<i32>(&contiguous, &[1, 2, 3]).unwrap();
let compact = f.dataset("compact").unwrap();
let plist = compact.create_plist().unwrap();
assert_eq!(plist.fill_value, Some((-2i16).to_le_bytes().to_vec()));
assert_eq!(compact.attr("version").unwrap().read_scalar_i64(), Some(5));
assert_dataset_values::<i16>(&compact, &[7, 8]).unwrap();
let scalar = f.dataset("scalar").unwrap();
let plist = scalar.create_plist().unwrap();
assert_eq!(plist.fill_value, Some(99u64.to_le_bytes().to_vec()));
assert_eq!(scalar.attr("version").unwrap().read_scalar_i64(), Some(6));
assert_eq!(dataset_scalar::<u64>(&scalar).unwrap(), 42);
}
assert_h5py_script(
&path,
"assert f['contiguous'][:].tolist() == [1, 2, 3]\n\
assert f['contiguous'].fillvalue == -7\n\
assert f['contiguous'].attrs['version'] == 4\n\
assert f['compact'][:].tolist() == [7, 8]\n\
assert f['compact'].fillvalue == -2\n\
assert f['compact'].attrs['version'] == 5\n\
assert int(f['scalar'][()]) == 42\n\
assert f['scalar'].fillvalue == 99\n\
assert f['scalar'].attrs['version'] == 6",
"dataset attribute and explicit fill-value writer fixture",
);
}
#[test]
fn test_writable_file_group_with_many_compact_links() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_many_links.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("many").unwrap();
for idx in 0..40 {
let name = format!("value_{idx:02}");
g.new_dataset_builder(&name).write::<i32>(&[idx]).unwrap();
}
let f = wf.close().unwrap();
let group = f.group("many").unwrap();
let mut found = [false, false];
let count =
group_member_summary_into(&group, &["value_00", "value_39"], &mut found).unwrap();
assert_eq!(count, 40);
assert!(found[0]);
assert!(found[1]);
let value_37 = group.open_dataset("value_37").unwrap();
assert_dataset_values::<i32>(&value_37, &[37]).unwrap();
}
let out = std::process::Command::new("h5dump")
.arg("-H")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump failed on many-link writer fixture: {}",
String::from_utf8_lossy(&out.stderr)
);
}
}
#[test]
fn test_writable_dense_links_support_wider_heap_ids() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_link_wide_heap_id.h5");
let long_name = format!("value_{}", "x".repeat(u16::MAX as usize + 64));
{
let mut wf = WritableFile::create(&path).unwrap();
let mut g = wf.create_group("many").unwrap();
for idx in 0..8 {
let name = format!("value_{idx:02}");
g.new_dataset_builder(&name).write::<i32>(&[idx]).unwrap();
}
g.new_dataset_builder(&long_name)
.write::<i32>(&[99])
.unwrap();
let f = wf.close().unwrap();
let group = f.group("many").unwrap();
let expected = ["value_00", long_name.as_str()];
let mut found = [false, false];
let count = group_member_summary_into(&group, &expected, &mut found).unwrap();
assert_eq!(count, 9);
assert!(found[0]);
assert!(found[1]);
}
assert!(std::fs::read(&path)
.unwrap()
.windows(4)
.any(|window| window == b"BTHD"));
}
#[test]
fn test_writable_dense_links_cross_leaf_btree_boundary() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_link_internal_btree.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut group = wf.create_group("many").unwrap();
for idx in 0..64 {
group
.new_dataset_builder(&format!("value_{idx:02}"))
.write::<i32>(&[idx])
.unwrap();
}
let f = wf.close().unwrap();
let group = f.group("many").unwrap();
let mut found = [false, false];
let count =
group_member_summary_into(&group, &["value_00", "value_63"], &mut found).unwrap();
assert_eq!(count, 64);
assert!(found[0]);
assert!(found[1]);
assert_dataset_values::<i32>(&group.open_dataset("value_42").unwrap(), &[42]).unwrap();
}
let bytes = std::fs::read(&path).unwrap();
assert!(bytes.windows(16).any(|window| {
window.starts_with(b"BTHD")
&& window.get(5) == Some(&5)
&& u16::from_le_bytes([window[12], window[13]]) > 0
}));
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-H")
.arg("-g")
.arg("many")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -H failed or timed out on dense link internal-B-tree fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("value_00"));
assert!(stdout.contains("value_63"));
}
assert_h5py_script(
&path,
"g = f['many']\n\
assert len(g) == 64\n\
assert sorted(g.keys())[0] == 'value_00'\n\
assert sorted(g.keys())[-1] == 'value_63'\n\
assert int(g['value_42'][0]) == 42",
"dense link internal-B-tree writer fixture",
);
}
#[test]
fn test_writable_dense_links_use_indirect_fractal_heap_blocks() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_dense_links_indirect_heap.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let mut group = wf.create_group("many").unwrap();
for idx in 0..40 {
let name = format!("value_{idx:02}_{}", "x".repeat(192));
group
.new_dataset_builder(&name)
.write::<i32>(&[idx])
.unwrap();
}
let f = wf.close().unwrap();
let group = f.group("many").unwrap();
let first = format!("value_{:02}_{}", 0, "x".repeat(192));
let last = format!("value_{:02}_{}", 39, "x".repeat(192));
let mut found = [false, false];
let count = group_member_summary_into(&group, &[&first, &last], &mut found).unwrap();
assert_eq!(count, 40);
assert!(found[0]);
assert!(found[1]);
assert_dataset_values::<i32>(&group.open_dataset(&last).unwrap(), &[39]).unwrap();
}
let bytes = std::fs::read(&path).unwrap();
assert!(bytes.windows(4).any(|window| window == b"FHIB"));
assert_fractal_heap_uses_filters(&bytes);
}
#[test]
fn test_writable_file_chunked_compressed() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..100).collect();
wf.new_dataset_builder("data")
.shape(&[100])
.chunk(&[25])
.deflate(4)
.shuffle()
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let mut vals = vec![0i32; ds.size().unwrap() as usize];
ds.read_into(&mut vals).unwrap();
assert_eq!(vals.len(), 100);
for (i, v) in vals.iter().enumerate() {
assert_eq!(*v, i as i32);
}
}
}
#[test]
fn test_writable_file_chunked_row_slab_streams_multidimensional_run() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_row_slab.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..24).collect();
wf.new_dataset_builder("fixed")
.shape(&[6, 4])
.chunk(&[2, 4])
.write::<i32>(&data)
.unwrap();
wf.new_dataset_builder("resizable")
.shape(&[6, 4])
.max_shape(&[12, 4])
.chunk(&[2, 4])
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let fixed = f.dataset("fixed").unwrap();
let fixed_info = fixed.info().unwrap();
assert_eq!(
fixed_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert_dataset_values::<i32>(&fixed, &data).unwrap();
let resizable = f.dataset("resizable").unwrap();
let resizable_info = resizable.info().unwrap();
assert_eq!(
resizable_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert_dataset_values::<i32>(&resizable, &data).unwrap();
}
}
#[test]
fn test_writable_file_chunked_filtered_row_slab_streams_multidimensional_run() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_filtered_row_slab.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..24).collect();
wf.new_dataset_builder("fixed")
.shape(&[6, 4])
.chunk(&[2, 4])
.shuffle()
.deflate(3)
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("fixed").unwrap();
let info = ds.info().unwrap();
assert_eq!(
info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert!(dataset_has_filter_id(&ds, 1).unwrap());
assert!(dataset_has_filter_id(&ds, 2).unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg("-d")
.arg("fixed")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on filtered row-slab fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("CHUNKED"));
assert!(stdout.contains("DEFLATE"));
assert!(stdout.contains("SHUFFLE"));
}
assert_h5py_script(
&path,
"d = f['fixed']\n\
assert d.shape == (6, 4)\n\
assert d.chunks == (2, 4)\n\
assert d.compression == 'gzip'\n\
assert d.shuffle\n\
assert d[:].reshape(-1).tolist() == list(range(24))",
"filtered fixed-array row-slab fixture",
);
}
#[test]
fn test_writable_file_chunked_row_slab_streams_partial_edge_run() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_chunked_partial_edge_row_slab.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..20).collect();
wf.new_dataset_builder("fixed")
.shape(&[5, 4])
.chunk(&[2, 4])
.write::<i32>(&data)
.unwrap();
wf.new_dataset_builder("resizable")
.shape(&[5, 4])
.max_shape(&[12, 4])
.chunk(&[2, 4])
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let fixed = f.dataset("fixed").unwrap();
let fixed_info = fixed.info().unwrap();
assert_eq!(
fixed_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert_dataset_values::<i32>(&fixed, &data).unwrap();
let resizable = f.dataset("resizable").unwrap();
let resizable_info = resizable.info().unwrap();
assert_eq!(
resizable_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert_dataset_values::<i32>(&resizable, &data).unwrap();
}
assert_h5py_script(
&path,
"expected = list(range(20))\n\
fixed = f['fixed']\n\
resizable = f['resizable']\n\
assert fixed.shape == (5, 4)\n\
assert fixed.chunks == (2, 4)\n\
assert fixed[:].reshape(-1).tolist() == expected\n\
assert resizable.shape == (5, 4)\n\
assert resizable.maxshape == (12, 4)\n\
assert resizable.chunks == (2, 4)\n\
assert resizable[:].reshape(-1).tolist() == expected",
"partial-edge fixed-array and B-tree v2 row-slab fixture",
);
}
#[test]
fn test_writable_file_chunked_filtered_row_slab_partial_edge_run() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_chunked_filtered_partial_edge_row_slab.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..20).collect();
wf.new_dataset_builder("fixed")
.shape(&[5, 4])
.chunk(&[2, 4])
.shuffle()
.deflate(3)
.write::<i32>(&data)
.unwrap();
wf.new_dataset_builder("resizable")
.shape(&[5, 4])
.max_shape(&[12, 4])
.chunk(&[2, 4])
.shuffle()
.deflate(3)
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let fixed = f.dataset("fixed").unwrap();
let fixed_info = fixed.info().unwrap();
assert_eq!(
fixed_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert!(dataset_has_filter_id(&fixed, 1).unwrap());
assert!(dataset_has_filter_id(&fixed, 2).unwrap());
assert_dataset_values::<i32>(&fixed, &data).unwrap();
let resizable = f.dataset("resizable").unwrap();
let resizable_info = resizable.info().unwrap();
assert_eq!(
resizable_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert!(dataset_has_filter_id(&resizable, 1).unwrap());
assert!(dataset_has_filter_id(&resizable, 2).unwrap());
assert_dataset_values::<i32>(&resizable, &data).unwrap();
}
assert_h5py_script(
&path,
"expected = list(range(20))\n\
fixed = f['fixed']\n\
resizable = f['resizable']\n\
assert fixed.shape == (5, 4)\n\
assert fixed.chunks == (2, 4)\n\
assert fixed.compression == 'gzip'\n\
assert fixed.shuffle\n\
assert fixed[:].reshape(-1).tolist() == expected\n\
assert resizable.shape == (5, 4)\n\
assert resizable.maxshape == (12, 4)\n\
assert resizable.chunks == (2, 4)\n\
assert resizable.compression == 'gzip'\n\
assert resizable.shuffle\n\
assert resizable[:].reshape(-1).tolist() == expected",
"filtered partial-edge fixed-array and B-tree v2 row-slab fixture",
);
}
#[test]
fn test_writable_file_fixed_array_deflate_parallel_threshold_tail_round_trip() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_fixed_array_deflate_parallel_tail.h5");
const CHUNK: usize = 2048;
const LEN: usize = CHUNK * 8 + 17;
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..LEN).map(|value| value as i32 * 3 - 11).collect();
wf.new_dataset_builder("data")
.shape(&[LEN as u64])
.chunk(&[CHUNK as u64])
.deflate(4)
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(
info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert!(dataset_has_filter_id(&ds, 1).unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
let mut boundary = vec![0i32; 7];
ds.read_slice_into(CHUNK - 3..CHUNK + 4, &mut boundary)
.unwrap();
assert_eq!(boundary, data[CHUNK - 3..CHUNK + 4]);
let mut tail = vec![0i32; 20];
ds.read_slice_into(LEN - 20..LEN, &mut tail).unwrap();
assert_eq!(tail, data[LEN - 20..LEN]);
}
}
#[test]
fn test_writable_file_extensible_array_deflate_parallel_threshold_tail_round_trip() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_extensible_array_deflate_parallel_tail.h5");
const CHUNK: usize = 2048;
const LEN: usize = CHUNK * 8 + 17;
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..LEN).map(|value| 7000 - value as i32 * 5).collect();
wf.new_dataset_builder("data")
.shape(&[LEN as u64])
.max_shape(&[(LEN * 2) as u64])
.chunk(&[CHUNK as u64])
.deflate(4)
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
assert!(dataset_has_filter_id(&ds, 1).unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
let mut boundary = vec![0i32; 7];
ds.read_slice_into(CHUNK - 3..CHUNK + 4, &mut boundary)
.unwrap();
assert_eq!(boundary, data[CHUNK - 3..CHUNK + 4]);
let mut tail = vec![0i32; 20];
ds.read_slice_into(LEN - 20..LEN, &mut tail).unwrap();
assert_eq!(tail, data[LEN - 20..LEN]);
}
}
#[test]
fn test_writable_file_chunked_filtered_non_slab_multidim_round_trip() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_chunked_filtered_non_slab_multidim.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..25).map(|value| value * 3 - 7).collect();
wf.new_dataset_builder("data")
.shape(&[5, 5])
.chunk(&[2, 3])
.shuffle()
.deflate(3)
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(
info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert!(dataset_has_filter_id(&ds, 1).unwrap());
assert!(dataset_has_filter_id(&ds, 2).unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg("-d")
.arg("data")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on filtered non-slab fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("CHUNKED"));
assert!(stdout.contains("DEFLATE"));
assert!(stdout.contains("SHUFFLE"));
}
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (5, 5)\n\
assert d.chunks == (2, 3)\n\
assert d.compression == 'gzip'\n\
assert d.shuffle\n\
assert d[:].reshape(-1).tolist() == [value * 3 - 7 for value in range(25)]",
"filtered fixed-array non-slab fixture",
);
}
#[test]
fn test_writable_file_chunked_fixed_array_beyond_one_page() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_fixed_array_deep.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..4097).collect();
wf.new_dataset_builder("data")
.shape(&[4097])
.chunk(&[1])
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
assert_eq!(
ds.info().unwrap().layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert_dataset_values::<i32>(&ds, &data).unwrap();
}
assert_h5dump_dataset_read(&path, "data", "fixed-array partial final page fixture");
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (4097,)\n\
assert d.chunks == (1,)\n\
assert d[:5].tolist() == [0, 1, 2, 3, 4]\n\
assert d[-3:].tolist() == [4094, 4095, 4096]",
"fixed-array partial final page fixture",
);
}
#[test]
fn test_writable_file_chunked_fletcher32() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_fletcher32.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..64).collect();
wf.new_dataset_builder("data")
.shape(&[64])
.chunk(&[16])
.fletcher32()
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
assert!(dataset_has_filter_id(&ds, 3).unwrap());
}
assert_h5dump_dataset_read(&path, "data", "Fletcher32 chunked writer fixture");
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (64,)\n\
assert d.chunks == (16,)\n\
assert d.fletcher32 is True\n\
assert d[:].tolist() == list(range(64))",
"Fletcher32 chunked writer fixture",
);
}
#[test]
fn test_dataset_builder_writes_max_shape_dataspace() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_max_shape.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("finite")
.shape(&[4])
.max_shape(&[10])
.chunk(&[2])
.write::<i32>(&[1, 2, 3, 4])
.unwrap();
wf.new_dataset_builder("unlimited_2d")
.shape(&[2, 3])
.resizable()
.chunk(&[1, 3])
.write::<i32>(&[1, 2, 3, 4, 5, 6])
.unwrap();
let f = wf.close().unwrap();
let finite = f.dataset("finite").unwrap();
let finite_space = finite.space().unwrap();
assert_eq!(finite_space.shape(), &[4]);
assert_eq!(finite_space.maxdims().unwrap(), &[10]);
let finite_info = finite.info().unwrap();
assert_eq!(finite_info.layout.version, 4);
assert_eq!(
finite_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert_dataset_values::<i32>(&finite, &[1, 2, 3, 4]).unwrap();
let unlimited = f.dataset("unlimited_2d").unwrap();
let unlimited_space = unlimited.space().unwrap();
assert_eq!(unlimited_space.shape(), &[2, 3]);
assert_eq!(unlimited_space.maxdims().unwrap(), &[u64::MAX, u64::MAX]);
let unlimited_info = unlimited.info().unwrap();
assert_eq!(unlimited_info.layout.version, 4);
assert_eq!(
unlimited_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert_dataset_values::<i32>(&unlimited, &[1, 2, 3, 4, 5, 6]).unwrap();
}
}
#[test]
fn test_dataset_builder_fixed_max_shape_uses_fixed_chunk_indexes() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_fixed_max_shape_chunk_indexes.h5");
let first = [30i16, 31, 32, 33];
let last = [40i16, 41, 42, 43];
{
let mut wf = WritableFile::create(&path).unwrap();
let grid_data: Vec<i32> = (0..16).collect();
wf.new_dataset_builder("fixed_grid")
.shape(&[16])
.max_shape(&[16])
.chunk(&[4])
.write::<i32>(&grid_data)
.unwrap();
wf.new_dataset_builder("fixed_single")
.shape(&[3])
.max_shape(&[3])
.chunk(&[8])
.write::<i32>(&[7, 8, 9])
.unwrap();
wf.new_dataset_builder("fixed_fill")
.shape(&[12])
.max_shape(&[12])
.chunk(&[4])
.fill_value::<i16>(-4)
.write_fill::<i16>()
.unwrap();
wf.new_dataset_builder("fixed_sparse")
.shape(&[16])
.max_shape(&[16])
.chunk(&[4])
.fill_value::<i16>(-2)
.write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let fixed_grid = f.dataset("fixed_grid").unwrap();
let fixed_grid_info = fixed_grid.info().unwrap();
assert_eq!(fixed_grid_info.layout.version, 4);
assert_eq!(
fixed_grid_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert_dataset_values::<i32>(&fixed_grid, &grid_data).unwrap();
let fixed_single = f.dataset("fixed_single").unwrap();
let fixed_single_info = fixed_single.info().unwrap();
assert_eq!(fixed_single_info.layout.version, 4);
assert_eq!(
fixed_single_info.layout.chunk_index_type,
Some(ChunkIndexType::SingleChunk)
);
assert_dataset_values::<i32>(&fixed_single, &[7, 8, 9]).unwrap();
let fixed_fill = f.dataset("fixed_fill").unwrap();
let fixed_fill_info = fixed_fill.info().unwrap();
assert_eq!(fixed_fill_info.layout.version, 4);
assert_eq!(
fixed_fill_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
let fill_values: Vec<i16> = fixed_fill.read().unwrap();
assert_eq!(fill_values, vec![-4; 12]);
let fixed_sparse = f.dataset("fixed_sparse").unwrap();
let fixed_sparse_info = fixed_sparse.info().unwrap();
assert_eq!(fixed_sparse_info.layout.version, 4);
assert_eq!(
fixed_sparse_info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
let sparse_values: Vec<i16> = fixed_sparse.read().unwrap();
assert_eq!(
sparse_values,
vec![30, 31, 32, 33, -2, -2, -2, -2, -2, -2, -2, -2, 40, 41, 42, 43]
);
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on fixed max-shape fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("fixed_grid"));
assert!(stdout.contains("fixed_single"));
assert!(stdout.contains("CHUNKED"));
}
assert_h5py_script(
&path,
"assert f['fixed_grid'].shape == (16,)\n\
assert f['fixed_grid'].chunks == (4,)\n\
assert f['fixed_grid'][:].tolist() == list(range(16))\n\
assert f['fixed_single'].shape == (3,)\n\
assert f['fixed_single'].chunks == (8,)\n\
assert f['fixed_single'][:].tolist() == [7, 8, 9]\n\
assert f['fixed_fill'][:].tolist() == [-4] * 12\n\
assert f['fixed_sparse'][:].tolist() == [30, 31, 32, 33, -2, -2, -2, -2, -2, -2, -2, -2, 40, 41, 42, 43]",
"fixed max-shape fixed-array and single-chunk fixture",
);
}
#[test]
fn test_dataset_builder_finite_max_shape_full_write_uses_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_finite_max_shape_full_btree_v2.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..300).collect();
wf.new_dataset_builder("data")
.shape(&[data.len() as u64])
.max_shape(&[600])
.chunk(&[1])
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(info.layout.version, 4);
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
assert_dataset_values::<i32>(&ds, &data).unwrap();
}
assert_h5dump_dataset_read(&path, "data", "finite max-shape full B-tree v2 fixture");
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (300,)\n\
assert d.maxshape == (600,)\n\
assert d.chunks == (1,)\n\
assert d.dtype.kind == 'i'\n\
assert d.dtype.itemsize == 4\n\
assert d[:].tolist() == list(range(300))",
"finite max-shape full B-tree v2 fixture",
);
}
#[test]
fn test_dataset_builder_large_max_shape_full_write_uses_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_large_max_shape_full_btree_v2.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<u8> = (0..1_000_001).map(|idx| (idx % 251) as u8).collect();
wf.new_dataset_builder("data")
.shape(&[data.len() as u64])
.max_shape(&[2_000_000])
.chunk(&[1])
.write::<u8>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(info.layout.version, 4);
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
assert_dataset_values::<u8>(&ds, &data).unwrap();
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on large max-shape full B-tree v2 fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("data"));
assert!(stdout.contains("CHUNKED"));
}
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (1000001,)\n\
assert d.maxshape == (2000000,)\n\
assert d.chunks == (1,)\n\
assert int(d[0]) == 0\n\
assert d[250:253].tolist() == [250, 0, 1]\n\
assert int(d[1000000]) == 16",
"large max-shape full B-tree v2 fixture",
);
}
#[test]
fn test_dataset_builder_chunked_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i32> = (0..16).collect();
wf.new_dataset_builder("data")
.shape(&[16])
.chunk(&[4])
.attr("version", 7i64)
.unwrap()
.attr_array("scale", &[1.0f64, 2.0])
.unwrap()
.write::<i32>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
assert_dataset_values::<i32>(&ds, &data).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(7));
assert_attribute_values::<f64>(&ds.attr("scale").unwrap(), &[1.0, 2.0]).unwrap();
}
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (16,)\n\
assert d.chunks == (4,)\n\
assert d[:].tolist() == list(range(16))\n\
assert d.attrs['version'] == 7\n\
assert d.attrs['scale'].tolist() == [1.0, 2.0]",
"chunked dataset attribute writer fixture",
);
}
#[test]
fn test_dataset_builder_rejects_invalid_chunk_dimensions() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_invalid_chunk_dims.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let zero_err = wf
.new_dataset_builder("zero_chunk")
.shape(&[4])
.chunk(&[0])
.write::<i32>(&[1, 2, 3, 4])
.expect_err("zero chunk dimension should be rejected");
assert!(zero_err.to_string().contains("chunk dimension 0 is zero"));
let rank_err = wf
.new_dataset_builder("rank_mismatch")
.shape(&[2, 2])
.chunk(&[2])
.write::<i32>(&[1, 2, 3, 4])
.expect_err("chunk rank mismatch should be rejected");
assert!(rank_err.to_string().contains("chunk dimension rank"));
let huge_chunk_err = wf
.new_dataset_builder("huge_chunk")
.shape(&[1])
.chunk(&[u32::MAX as u64 + 1])
.write::<u8>(&[0])
.expect_err("chunk dimensions must fit v3 layout's 32-bit fields");
assert!(huge_chunk_err.to_string().contains("32-bit layout field"));
}
}
#[test]
fn test_dataset_builder_rejects_invalid_deflate_level() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_invalid_deflate_level.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let err = wf
.new_dataset_builder("bad_deflate")
.shape(&[4])
.chunk(&[2])
.deflate(10)
.write::<i32>(&[1, 2, 3, 4])
.expect_err("deflate level above 9 should be rejected");
assert!(err.to_string().contains("deflate compression level 10"));
}
}
#[test]
fn test_dataset_builder_compressed_chunked_attrs_with_fill_value() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_compressed_chunked_attrs_with_fill.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data: Vec<i16> = (0..30).collect();
wf.new_dataset_builder("data")
.shape(&[30])
.chunk(&[8])
.shuffle()
.deflate(3)
.fill_value::<i16>(-9)
.attr("version", 8i64)
.unwrap()
.write::<i16>(&data)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
assert_dataset_values::<i16>(&ds, &data).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(8));
let plist = ds.create_plist().unwrap();
assert_eq!(plist.fill_value, Some((-9i16).to_le_bytes().to_vec()));
}
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (30,)\n\
assert d.chunks == (8,)\n\
assert d.compression == 'gzip'\n\
assert d.compression_opts == 3\n\
assert d.shuffle\n\
assert d.fillvalue == -9\n\
assert d.attrs['version'] == 8\n\
assert d[:].tolist() == list(range(30))",
"compressed chunked attribute and fill-value writer fixture",
);
}
#[test]
fn test_dataset_builder_sparse_chunked_fill_only_dataset() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_sparse_chunked_fill_only.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[1_000])
.chunk(&[128])
.fill_value::<i32>(-11)
.attr("version", 14i64)
.unwrap()
.write_fill::<i32>()
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(14));
let values: Vec<i32> = ds.read().unwrap();
assert_eq!(values.len(), 1_000);
assert!(values.iter().all(|&value| value == -11));
let plist = ds.create_plist().unwrap();
assert_eq!(plist.fill_value, Some((-11i32).to_le_bytes().to_vec()));
}
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (1000,)\n\
assert d.chunks == (128,)\n\
assert d.fillvalue == -11\n\
assert d.attrs['version'] == 14\n\
assert d[:10].tolist() == [-11] * 10\n\
assert d[990:].tolist() == [-11] * 10",
"sparse chunked fill-only writer fixture",
);
}
#[test]
fn test_dataset_builder_sparse_max_shape_fill_only_uses_modern_indexes() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_max_shape_fill_only_modern.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("single_grow")
.shape(&[12])
.max_shape(&[24])
.chunk(&[4])
.fill_value::<i16>(-3)
.write_fill::<i16>()
.unwrap();
wf.new_dataset_builder("multi_grow")
.shape(&[4, 4])
.max_shape(&[8, 8])
.chunk(&[2, 2])
.fill_value::<i32>(-8)
.write_fill::<i32>()
.unwrap();
let f = wf.close().unwrap();
let single = f.dataset("single_grow").unwrap();
let single_info = single.info().unwrap();
assert_eq!(single_info.layout.version, 4);
assert_eq!(
single_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
let single_values: Vec<i16> = single.read().unwrap();
assert_eq!(single_values, vec![-3; 12]);
let multi = f.dataset("multi_grow").unwrap();
let multi_info = multi.info().unwrap();
assert_eq!(multi_info.layout.version, 4);
assert_eq!(
multi_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
let multi_values: Vec<i32> = multi.read().unwrap();
assert_eq!(multi_values, vec![-8; 16]);
}
assert_h5dump_dataset_read(
&path,
"single_grow",
"single grow fill-only B-tree v2 fixture",
);
assert_h5dump_dataset_read(
&path,
"multi_grow",
"multi grow fill-only B-tree v2 fixture",
);
assert_h5py_script(
&path,
"single = f['single_grow']\n\
assert single.shape == (12,)\n\
assert single.maxshape == (24,)\n\
assert single.chunks == (4,)\n\
assert single.fillvalue == -3\n\
assert single[:].tolist() == [-3] * 12\n\
multi = f['multi_grow']\n\
assert multi.shape == (4, 4)\n\
assert multi.maxshape == (8, 8)\n\
assert multi.chunks == (2, 2)\n\
assert multi.fillvalue == -8\n\
assert multi[:].tolist() == [[-8] * 4 for _ in range(4)]",
"fill-only growable B-tree v2 writer fixtures",
);
}
#[test]
fn test_dataset_builder_sparse_chunked_explicit_chunks() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_chunked_explicit_chunks.h5");
let first = [1i32; 128];
let middle = [5i32; 128];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[1_000])
.chunk(&[128])
.fill_value::<i32>(-7)
.deflate(1)
.write_chunks::<i32, _, _>([([0u64], &first[..]), ([512u64], &middle[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let values: Vec<i32> = ds.read().unwrap();
assert_eq!(values.len(), 1_000);
assert_eq!(&values[..128], &first);
assert_eq!(&values[512..640], &middle);
assert!(values[128..512].iter().all(|&value| value == -7));
assert!(values[640..].iter().all(|&value| value == -7));
assert!(dataset_has_filter_id(&ds, 1).unwrap());
}
}
#[test]
fn test_dataset_builder_sparse_unfiltered_explicit_chunks_use_fixed_array() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_unfiltered_explicit_chunks_fa.h5");
let first = [2u16; 8];
let last = [7u16; 8];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[32])
.chunk(&[8])
.fill_value::<u16>(99)
.write_chunks::<u16, _, _>([([0u64], &first[..]), ([24u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let info = ds.info().unwrap();
assert_eq!(
info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
let values: Vec<u16> = ds.read().unwrap();
assert_eq!(values.len(), 32);
assert_eq!(&values[..8], &first);
assert!(values[8..24].iter().all(|&value| value == 99));
assert_eq!(&values[24..], &last);
}
assert_h5dump_dataset_read(
&path,
"data",
"sparse unfiltered fixed-array explicit-chunk fixture",
);
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (32,)\n\
assert d.chunks == (8,)\n\
assert d.dtype.kind == 'u'\n\
assert d.dtype.itemsize == 2\n\
assert d.fillvalue == 99\n\
assert d[:].tolist() == [2] * 8 + [99] * 16 + [7] * 8",
"sparse unfiltered fixed-array explicit-chunk fixture",
);
}
#[test]
fn test_dataset_builder_sparse_filtered_explicit_chunks_use_fixed_array() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_filtered_explicit_chunks_fa.h5");
let first = [4u16; 8];
let last = [11u16; 8];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[32])
.chunk(&[8])
.fill_value::<u16>(99)
.deflate(1)
.write_chunks::<u16, _, _>([([0u64], &first[..]), ([24u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let info = ds.info().unwrap();
assert_eq!(
info.layout.chunk_index_type,
Some(ChunkIndexType::FixedArray)
);
assert!(dataset_has_filter_id(&ds, 1).unwrap());
let values: Vec<u16> = ds.read().unwrap();
assert_eq!(values.len(), 32);
assert_eq!(&values[..8], &first);
assert!(values[8..24].iter().all(|&value| value == 99));
assert_eq!(&values[24..], &last);
}
assert_h5dump_dataset_read(
&path,
"data",
"sparse filtered fixed-array explicit-chunk fixture",
);
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (32,)\n\
assert d.chunks == (8,)\n\
assert d.compression == 'gzip'\n\
assert d.compression_opts == 1\n\
assert d.dtype.kind == 'u'\n\
assert d.dtype.itemsize == 2\n\
assert d.fillvalue == 99\n\
assert d[:].tolist() == [4] * 8 + [99] * 16 + [11] * 8",
"sparse filtered fixed-array explicit-chunk fixture",
);
}
#[test]
fn test_dataset_builder_sparse_chunked_explicit_chunks_with_max_shape_uses_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_chunked_explicit_chunks_btree_v2.h5");
let first = [3i16; 4];
let last = [9i16; 4];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[16])
.max_shape(&[32])
.chunk(&[4])
.fill_value::<i16>(-2)
.write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let info = ds.info().unwrap();
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
let values: Vec<i16> = ds.read().unwrap();
assert_eq!(values.len(), 16);
assert_eq!(&values[..4], &first);
assert!(values[4..12].iter().all(|&value| value == -2));
assert_eq!(&values[12..], &last);
}
assert_h5dump_dataset_read(
&path,
"data",
"sparse unfiltered B-tree v2 explicit-chunk fixture",
);
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (16,)\n\
assert d.maxshape == (32,)\n\
assert d.chunks == (4,)\n\
assert d.dtype.kind == 'i'\n\
assert d.dtype.itemsize == 2\n\
assert d.fillvalue == -2\n\
assert d[:].tolist() == [3] * 4 + [-2] * 8 + [9] * 4",
"sparse unfiltered B-tree v2 explicit-chunk fixture",
);
}
#[test]
fn test_dataset_builder_sparse_filtered_explicit_chunks_with_max_shape_uses_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_filtered_explicit_chunks_btree_v2.h5");
let first = [6i16; 4];
let last = [12i16; 4];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[16])
.max_shape(&[32])
.chunk(&[4])
.fill_value::<i16>(-2)
.deflate(1)
.write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
assert!(ds.is_chunked().unwrap());
let info = ds.info().unwrap();
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
assert!(dataset_has_filter_id(&ds, 1).unwrap());
let values: Vec<i16> = ds.read().unwrap();
assert_eq!(values.len(), 16);
assert_eq!(&values[..4], &first);
assert!(values[4..12].iter().all(|&value| value == -2));
assert_eq!(&values[12..], &last);
}
assert_h5dump_dataset_read(
&path,
"data",
"sparse filtered B-tree v2 explicit-chunk fixture",
);
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (16,)\n\
assert d.maxshape == (32,)\n\
assert d.chunks == (4,)\n\
assert d.compression == 'gzip'\n\
assert d.compression_opts == 1\n\
assert d.dtype.kind == 'i'\n\
assert d.dtype.itemsize == 2\n\
assert d.fillvalue == -2\n\
assert d[:].tolist() == [6] * 4 + [-2] * 8 + [12] * 4",
"sparse filtered B-tree v2 explicit-chunk fixture",
);
}
#[test]
fn test_dataset_builder_sparse_multidim_growable_chunks_use_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_multidim_growable_chunks_btree_v2.h5");
let first = [10i32, 11, 12, 13];
let last = [30i32, 31, 32, 33];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[4, 4])
.max_shape(&[8, 8])
.chunk(&[2, 2])
.fill_value::<i32>(-5)
.write_chunks::<i32, _, _>([([0u64, 0u64], &first[..]), ([2u64, 2u64], &last[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(info.layout.version, 4);
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
let values: Vec<i32> = ds.read().unwrap();
assert_eq!(
values,
vec![10, 11, -5, -5, 12, 13, -5, -5, -5, -5, 30, 31, -5, -5, 32, 33,]
);
}
assert_h5dump_dataset_read(
&path,
"data",
"sparse multidimensional growable B-tree v2 fixture",
);
assert_h5py_script(
&path,
"d = f['data']\n\
assert d.shape == (4, 4)\n\
assert d.maxshape == (8, 8)\n\
assert d.chunks == (2, 2)\n\
assert d.dtype.kind == 'i'\n\
assert d.dtype.itemsize == 4\n\
assert int(d[1, 2]) == -5\n\
assert d[:].tolist() == [[10, 11, -5, -5], [12, 13, -5, -5], [-5, -5, 30, 31], [-5, -5, 32, 33]]",
"sparse multidimensional growable B-tree v2 fixture",
);
}
#[test]
fn test_dataset_builder_sparse_explicit_chunks_large_max_shape_uses_btree_v2() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_sparse_explicit_chunks_btree_v2.h5");
let first = [5u8];
let far = [23u8];
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("data")
.shape(&[1_000_001])
.max_shape(&[2_000_000])
.chunk(&[1])
.fill_value::<u8>(9)
.write_chunks::<u8, _, _>([([0u64], &first[..]), ([1_000_000u64], &far[..])])
.unwrap();
wf.new_dataset_builder("filtered")
.shape(&[1_000_001])
.max_shape(&[2_000_000])
.chunk(&[1])
.fill_value::<u8>(7)
.deflate(1)
.write_chunks::<u8, _, _>([([0u64], &first[..]), ([1_000_000u64], &far[..])])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("data").unwrap();
let info = ds.info().unwrap();
assert_eq!(info.layout.version, 4);
assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
let values: Vec<u8> = ds.read().unwrap();
assert_eq!(values.len(), 1_000_001);
assert_eq!(values[0], 5);
assert_eq!(values[1], 9);
assert_eq!(values[999_999], 9);
assert_eq!(values[1_000_000], 23);
let filtered = f.dataset("filtered").unwrap();
let filtered_info = filtered.info().unwrap();
assert_eq!(filtered_info.layout.version, 4);
assert_eq!(
filtered_info.layout.chunk_index_type,
Some(ChunkIndexType::BTreeV2)
);
assert!(dataset_has_filter_id(&filtered, 1).unwrap());
let filtered_values: Vec<u8> = filtered.read().unwrap();
assert_eq!(filtered_values.len(), 1_000_001);
assert_eq!(filtered_values[0], 5);
assert_eq!(filtered_values[1], 7);
assert_eq!(filtered_values[999_999], 7);
assert_eq!(filtered_values[1_000_000], 23);
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on large sparse B-tree v2 explicit-chunk fixtures: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("data"));
assert!(stdout.contains("filtered"));
assert!(stdout.contains("CHUNKED"));
assert!(stdout.contains("DEFLATE"));
}
assert_h5py_script(
&path,
"data = f['data']\n\
assert data.shape == (1000001,)\n\
assert data.maxshape == (2000000,)\n\
assert data.chunks == (1,)\n\
assert data[0:3].tolist() == [5, 9, 9]\n\
assert data[999998:].tolist() == [9, 9, 23]\n\
filtered = f['filtered']\n\
assert filtered.shape == (1000001,)\n\
assert filtered.maxshape == (2000000,)\n\
assert filtered.chunks == (1,)\n\
assert filtered.compression == 'gzip'\n\
assert filtered[0:3].tolist() == [5, 7, 7]\n\
assert filtered[999998:].tolist() == [7, 7, 23]",
"large sparse B-tree v2 explicit-chunk fixtures",
);
}
#[test]
fn test_writable_file_scalar() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_scalar.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("pi")
.write_scalar::<f64>(std::f64::consts::PI)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("pi").unwrap();
let val = dataset_scalar::<f64>(&ds).unwrap();
assert!((val - std::f64::consts::PI).abs() < 1e-15);
}
assert_h5py_script(
&path,
"d = f['pi']\n\
assert d.shape == ()\n\
assert abs(float(d[()]) - 3.141592653589793) < 1e-15",
"scalar dataset fixture",
);
}
#[test]
fn test_dataset_builder_compact_scalar_and_rejected_scalar_options() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_scalar_options.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("compact_scalar")
.compact()
.attr("version", 13i64)
.unwrap()
.write_scalar::<i32>(99)
.unwrap();
let chunked_err = wf
.new_dataset_builder("bad_chunked_scalar")
.chunk(&[1])
.write_scalar::<i32>(1)
.expect_err("scalar chunked storage should be rejected");
assert!(chunked_err.to_string().contains("scalar dataset writer"));
let max_shape_err = wf
.new_dataset_builder("bad_max_scalar")
.max_shape(&[1])
.write_scalar::<i32>(1)
.expect_err("scalar max dimensions should be rejected");
assert!(max_shape_err.to_string().contains("max dimensions"));
let f = wf.close().unwrap();
let ds = f.dataset("compact_scalar").unwrap();
assert_eq!(dataset_scalar::<i32>(&ds).unwrap(), 99);
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(13));
}
}
#[test]
fn test_writable_file_compact() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("tiny")
.compact()
.write::<u8>(&[1, 2, 3, 4, 5])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("tiny").unwrap();
assert_dataset_values::<u8>(&ds, &[1, 2, 3, 4, 5]).unwrap();
}
}
#[test]
fn test_dataset_builder_rejects_invalid_compact_options() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact_option_errors.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let chunked_err = wf
.new_dataset_builder("bad_chunked_compact")
.compact()
.chunk(&[1])
.write::<u8>(&[1, 2, 3])
.expect_err("compact plus chunked storage should be rejected");
assert!(chunked_err.to_string().contains("compact dataset writer"));
let max_shape_err = wf
.new_dataset_builder("bad_max_compact")
.compact()
.max_shape(&[8])
.write::<u8>(&[1, 2, 3])
.expect_err("compact plus max dimensions should be rejected");
assert!(max_shape_err.to_string().contains("max dimensions"));
}
}
#[test]
fn test_dataset_builder_rejects_oversized_compact_payload() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact_too_large.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let data = vec![0u8; u16::MAX as usize + 1];
let err = wf
.new_dataset_builder("too_large")
.compact()
.write::<u8>(&data)
.expect_err("compact payload larger than u16 should be rejected");
assert!(err.to_string().contains("compact dataset payload"));
}
}
#[test]
fn test_writable_file_explicit_fill_value_properties() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_fill_value.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("with_fill")
.fill_properties(1, 2)
.fill_value::<i32>(-7)
.write::<i32>(&[1, 2, 3])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("with_fill").unwrap();
let plist = ds.create_plist().unwrap();
assert_eq!(plist.fill_alloc_time, Some(1));
assert_eq!(plist.fill_time, Some(2));
assert!(plist.fill_value_defined);
assert_eq!(plist.fill_value, Some((-7i32).to_le_bytes().to_vec()));
assert_dataset_values::<i32>(&ds, &[1, 2, 3]).unwrap();
}
}
#[test]
fn test_writable_file_compact_fixed_strings() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact_strings.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.compact()
.write_fixed_ascii_strings(&["red", "green", "blue"], 8)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert_dataset_strings(&ds, &["red", "green", "blue"]).unwrap();
}
}
#[test]
fn test_dataset_builder_fixed_string_datasets_with_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_fixed_string_dataset_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("contiguous")
.attr("version", 10i64)
.unwrap()
.write_fixed_ascii_strings(&["red", "green"], 8)
.unwrap();
wf.new_dataset_builder("compact")
.compact()
.attr("version", 11i64)
.unwrap()
.write_fixed_utf8_strings(&["猫", "å"], 8)
.unwrap();
let f = wf.close().unwrap();
let contiguous = f.dataset("contiguous").unwrap();
assert_dataset_strings(&contiguous, &["red", "green"]).unwrap();
assert_eq!(
contiguous.attr("version").unwrap().read_scalar_i64(),
Some(10)
);
let compact = f.dataset("compact").unwrap();
assert_dataset_strings(&compact, &["猫", "å"]).unwrap();
assert_eq!(compact.attr("version").unwrap().read_scalar_i64(), Some(11));
}
}
#[test]
fn test_dataset_builder_chunked_fixed_string_dataset() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_fixed_strings.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.shape(&[4])
.chunk(&[2])
.shuffle()
.deflate(3)
.attr("version", 12i64)
.unwrap()
.write_fixed_ascii_strings(&["red", "green", "blue", "gold"], 8)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert!(ds.is_chunked().unwrap());
assert_dataset_strings(&ds, &["red", "green", "blue", "gold"]).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(12));
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg("-d")
.arg("names")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on chunked fixed string fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("STRSIZE 8"));
assert!(stdout.contains("CHUNKED"));
assert!(stdout.contains("DEFLATE"));
assert!(stdout.contains("SHUFFLE"));
}
assert_h5py_script(
&path,
"d = f['names']\n\
assert d.shape == (4,)\n\
assert d.chunks == (2,)\n\
assert d.compression == 'gzip'\n\
assert d.compression_opts == 3\n\
assert d.shuffle\n\
assert [value.decode('ascii').rstrip('\\x00') for value in d[:].tolist()] == ['red', 'green', 'blue', 'gold']\n\
assert d.attrs['version'] == 12",
"chunked fixed ASCII string fixture",
);
}
#[test]
fn test_writable_file_vlen_utf8_strings() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_vlen_strings.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.write_vlen_utf8_strings(&["", "猫", "alpha"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert_dataset_strings(&ds, &["", "猫", "alpha"]).unwrap();
}
let out = std::process::Command::new("h5dump")
.arg("-H")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump failed on vlen string writer fixture: {}",
String::from_utf8_lossy(&out.stderr)
);
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-d")
.arg("names")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -d failed or timed out on vlen string writer fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("alpha"));
assert!(stdout.contains("STRSIZE H5T_VARIABLE"));
}
assert_h5py_script(
&path,
"d = f['names']\n\
import h5py\n\
assert d.shape == (3,)\n\
assert h5py.check_string_dtype(d.dtype).encoding == 'utf-8'\n\
assert [value.decode('utf-8') for value in d[:].tolist()] == ['', '猫', 'alpha']",
"contiguous vlen UTF-8 string fixture",
);
}
#[test]
fn test_writable_file_chunked_filtered_vlen_utf8_strings() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_chunked_vlen_strings.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.shape(&[5])
.chunk(&[2])
.shuffle()
.deflate(3)
.attr("version", 13i64)
.unwrap()
.write_vlen_utf8_strings(&["", "猫", "alpha", "beta", "delta"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert!(ds.is_chunked().unwrap());
assert!(dataset_has_filter_id(&ds, 1).unwrap());
assert!(dataset_has_filter_id(&ds, 2).unwrap());
assert_dataset_strings(&ds, &["", "猫", "alpha", "beta", "delta"]).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(13));
}
let out = std::process::Command::new("timeout")
.arg("10")
.arg("h5dump")
.arg("-pH")
.arg("-d")
.arg("names")
.arg(&path)
.output();
if let Ok(out) = out {
assert!(
out.status.success(),
"h5dump -pH failed or timed out on chunked vlen string fixture: status={:?}, stderr={}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("STRSIZE H5T_VARIABLE"));
assert!(stdout.contains("CHUNKED"));
assert!(stdout.contains("DEFLATE"));
assert!(stdout.contains("SHUFFLE"));
}
assert_h5py_script(
&path,
"d = f['names']\n\
assert d.shape == (5,)\n\
assert d.chunks == (2,)\n\
assert d.compression == 'gzip'\n\
assert d.compression_opts == 3\n\
assert d.shuffle\n\
assert [value.decode('utf-8') for value in d[:].tolist()] == ['', '猫', 'alpha', 'beta', 'delta']\n\
assert d.attrs['version'] == 13",
"chunked filtered vlen UTF-8 string fixture",
);
}
#[test]
fn test_writable_file_chunked_vlen_utf8_strings_with_fletcher32() {
let dir = tempfile::tempdir().unwrap();
let path = dir
.path()
.join("api_write_chunked_vlen_strings_fletcher32.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.shape(&[3])
.chunk(&[1])
.fletcher32()
.write_vlen_utf8_strings(&["alpha", "", "猫"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert!(ds.is_chunked().unwrap());
assert!(dataset_has_filter_id(&ds, 3).unwrap());
assert_dataset_strings(&ds, &["alpha", "", "猫"]).unwrap();
}
assert_h5dump_dataset_read(
&path,
"names",
"chunked VLEN UTF-8 Fletcher32 string fixture",
);
assert_h5py_script(
&path,
"d = f['names']\n\
assert d.shape == (3,)\n\
assert d.chunks == (1,)\n\
assert d.fletcher32 is True\n\
assert [value.decode('utf-8') for value in d[:].tolist()] == ['alpha', '', '猫']",
"chunked VLEN UTF-8 Fletcher32 string fixture",
);
}
#[test]
fn test_writable_file_vlen_utf8_strings_accept_fill_values() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_vlen_string_fill.h5");
let empty_vlen_descriptor = 0u128.to_le_bytes().to_vec();
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("contiguous")
.shape(&[2])
.fill_value::<u128>(0)
.write_vlen_utf8_strings(&["alpha", "beta"])
.unwrap();
wf.new_dataset_builder("chunked")
.shape(&[3])
.chunk(&[1])
.fill_value::<u128>(0)
.write_vlen_utf8_strings(&["", "猫", "gamma"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("contiguous").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert_dataset_strings(&ds, &["alpha", "beta"]).unwrap();
let plist = ds.create_plist().unwrap();
assert!(plist.fill_value_defined);
assert_eq!(plist.fill_value, Some(empty_vlen_descriptor.clone()));
let ds = f.dataset("chunked").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert!(ds.is_chunked().unwrap());
assert_dataset_strings(&ds, &["", "猫", "gamma"]).unwrap();
let plist = ds.create_plist().unwrap();
assert!(plist.fill_value_defined);
assert_eq!(plist.fill_value, Some(empty_vlen_descriptor));
}
assert_h5py_script(
&path,
"contiguous = f['contiguous']\n\
assert h5py.check_string_dtype(contiguous.dtype).encoding == 'utf-8'\n\
assert [value.decode('utf-8') for value in contiguous[:].tolist()] == ['alpha', 'beta']\n\
assert contiguous.fillvalue == b''\n\
chunked = f['chunked']\n\
assert h5py.check_string_dtype(chunked.dtype).encoding == 'utf-8'\n\
assert chunked.chunks == (1,)\n\
assert [value.decode('utf-8') for value in chunked[:].tolist()] == ['', '猫', 'gamma']\n\
assert chunked.fillvalue == b''",
"VLEN UTF-8 zero-descriptor fill-value writer fixture",
);
}
#[test]
fn test_writable_file_vlen_utf8_strings_encode_string_fill_value() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_vlen_string_text_fill.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("contiguous")
.shape(&[2])
.vlen_utf8_fill_value("fallback")
.write_vlen_utf8_strings(&["alpha", "beta"])
.unwrap();
wf.new_dataset_builder("chunked")
.shape(&[3])
.chunk(&[1])
.vlen_utf8_fill_value("missing")
.write_vlen_utf8_strings(&["", "猫", "gamma"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("contiguous").unwrap();
assert_dataset_strings(&ds, &["alpha", "beta"]).unwrap();
let plist = ds.create_plist().unwrap();
let fill = plist
.fill_value
.expect("vlen string fill should be encoded");
assert_eq!(fill.len(), 16);
assert_eq!(u32::from_le_bytes(fill[..4].try_into().unwrap()), 8);
assert_ne!(&fill[4..12], &[0; 8]);
assert_eq!(u32::from_le_bytes(fill[12..16].try_into().unwrap()), 1);
let ds = f.dataset("chunked").unwrap();
assert!(ds.is_chunked().unwrap());
assert_dataset_strings(&ds, &["", "猫", "gamma"]).unwrap();
let plist = ds.create_plist().unwrap();
let fill = plist
.fill_value
.expect("chunked vlen string fill should be encoded");
assert_eq!(fill.len(), 16);
assert_eq!(u32::from_le_bytes(fill[..4].try_into().unwrap()), 7);
assert_ne!(&fill[4..12], &[0; 8]);
assert_eq!(u32::from_le_bytes(fill[12..16].try_into().unwrap()), 1);
}
assert_h5py_script(
&path,
"contiguous = f['contiguous']\n\
assert h5py.check_string_dtype(contiguous.dtype).encoding == 'utf-8'\n\
assert [value.decode('utf-8') for value in contiguous[:].tolist()] == ['alpha', 'beta']\n\
assert contiguous.fillvalue == b'fallback'\n\
chunked = f['chunked']\n\
assert h5py.check_string_dtype(chunked.dtype).encoding == 'utf-8'\n\
assert chunked.chunks == (1,)\n\
assert [value.decode('utf-8') for value in chunked[:].tolist()] == ['', '猫', 'gamma']\n\
assert chunked.fillvalue == b'missing'",
"VLEN UTF-8 string fill-value writer fixture",
);
}
#[test]
fn test_writable_file_vlen_utf8_strings_split_global_heap_collections() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_many_vlen_strings.h5");
let values: Vec<String> = (0..=u16::MAX).map(|i| format!("kmer-{i}")).collect();
let refs: Vec<&str> = values.iter().map(String::as_str).collect();
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("kmers")
.write_vlen_utf8_strings(&refs)
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("kmers").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
let strings = ds.read_strings().unwrap();
assert_eq!(strings.len(), refs.len());
assert_eq!(strings.first().map(String::as_str), Some("kmer-0"));
assert_eq!(
strings.get(u16::MAX as usize).map(String::as_str),
Some("kmer-65535")
);
}
}
#[test]
fn test_dataset_builder_vlen_utf8_strings_with_attrs() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_vlen_string_attrs.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("names")
.attr("version", 9i64)
.unwrap()
.fixed_utf8_attr("label", "猫", 8)
.unwrap()
.write_vlen_utf8_strings(&["", "alpha", "猫"])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("names").unwrap();
assert!(ds.dtype().unwrap().is_vlen());
assert_dataset_strings(&ds, &["", "alpha", "猫"]).unwrap();
assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(9));
assert_eq!(attribute_string(&ds.attr("label").unwrap()).unwrap(), "猫");
}
}
#[test]
fn test_writable_file_compact_compound() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_compact_compound.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("points")
.compact()
.write::<Point>(&[Point { x: 1.5, label: 10 }, Point { x: 2.5, label: 20 }])
.unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("points").unwrap();
assert_dataset_field::<f64>(&ds, "x", &[1.5, 2.5]).unwrap();
assert_dataset_field::<i32>(&ds, "label", &[10, 20]).unwrap();
}
}
#[test]
fn test_dataset_builder_write_raw_with_explicit_complex_dtype() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_raw_complex_dtype.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let enum_data: Vec<u8> = [0u16, 1, 2]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect();
wf.new_dataset_builder("status")
.write_raw_with_dtype(
DtypeSpec::Enum {
base: Box::new(DtypeSpec::U16),
members: vec![
("zero".to_string(), 0),
("one".to_string(), 1),
("two".to_string(), 2),
],
},
&enum_data,
)
.unwrap();
wf.new_dataset_builder("opaque")
.shape(&[2])
.write_raw_with_dtype(
DtypeSpec::Opaque {
size: 4,
tag: "payload".to_string(),
},
b"abcdwxyz",
)
.unwrap();
let array_data: Vec<u8> = (0i16..12).flat_map(i16::to_le_bytes).collect();
wf.new_dataset_builder("cells")
.shape(&[2])
.write_raw_with_dtype(
DtypeSpec::Array {
dims: vec![2, 3],
base: Box::new(DtypeSpec::I16),
},
&array_data,
)
.unwrap();
let nested = DtypeSpec::Compound {
size: 8,
fields: vec![
CompoundFieldSpec {
name: "a".to_string(),
offset: 0,
dtype: DtypeSpec::I32,
},
CompoundFieldSpec {
name: "b".to_string(),
offset: 4,
dtype: DtypeSpec::F32,
},
],
};
let nested_compound = DtypeSpec::Compound {
size: 16,
fields: vec![
CompoundFieldSpec {
name: "id".to_string(),
offset: 0,
dtype: DtypeSpec::I32,
},
CompoundFieldSpec {
name: "nested".to_string(),
offset: 8,
dtype: nested,
},
],
};
let mut compound_data = Vec::new();
for (id, a, b) in [(7i32, 10i32, 1.25f32), (8, 20, 2.5)] {
compound_data.extend_from_slice(&id.to_le_bytes());
compound_data.extend_from_slice(&[0; 4]);
compound_data.extend_from_slice(&a.to_le_bytes());
compound_data.extend_from_slice(&b.to_le_bytes());
}
wf.new_dataset_builder("nested")
.shape(&[2])
.write_raw_with_dtype(nested_compound, &compound_data)
.unwrap();
let f = wf.close().unwrap();
let status = f.dataset("status").unwrap();
assert!(status.dtype().unwrap().is_enum());
assert_dataset_values::<u16>(&status, &[0, 1, 2]).unwrap();
let opaque = f.dataset("opaque").unwrap();
assert_eq!(opaque.dtype().unwrap().opaque_tag_str(), Some("payload"));
assert_dataset_raw(&opaque, b"abcdwxyz").unwrap();
let cells = f.dataset("cells").unwrap();
let cells_dtype = cells.dtype().unwrap();
let dims = cells_dtype
.array_dims_iter()
.unwrap()
.collect::<Result<Vec<_>>>()
.unwrap();
let base = cells_dtype.array_base().unwrap();
assert_eq!(dims, vec![2, 3]);
assert_eq!(base.size(), 2);
assert_dataset_raw(&cells, &array_data).unwrap();
let nested = f.dataset("nested").unwrap();
assert_eq!(dataset_field_value_count(&nested, "nested").unwrap(), 2);
}
}
#[test]
fn test_dataset_builder_rejects_unsupported_enum_base_types() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_bad_enum_base.h5");
let mut wf = WritableFile::create(&path).unwrap();
let err = wf
.new_dataset_builder("wide_enum")
.write_raw_with_dtype(
DtypeSpec::Enum {
base: Box::new(DtypeSpec::U128),
members: vec![("wide".to_string(), 1)],
},
&1u128.to_le_bytes(),
)
.expect_err("16-byte enum base should be rejected until member values are widened");
assert!(err
.to_string()
.contains("enum writer supports only integer base datatypes up to 8 bytes"));
let err = wf
.new_dataset_builder("float_enum")
.write_raw_with_dtype(
DtypeSpec::Enum {
base: Box::new(DtypeSpec::F32),
members: vec![("bad".to_string(), 1)],
},
&1f32.to_le_bytes(),
)
.expect_err("non-integer enum base should be rejected");
assert!(err
.to_string()
.contains("enum writer supports only integer base datatypes up to 8 bytes"));
}
#[test]
fn test_dataset_builder_rejects_unencodable_explicit_dtype() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_bad_explicit_dtype.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
let array_err = wf
.new_dataset_builder("bad_array")
.write_raw_with_dtype(
DtypeSpec::Array {
dims: vec![1; u8::MAX as usize + 1],
base: Box::new(DtypeSpec::U8),
},
&[0],
)
.expect_err("array datatype rank should fit in the encoded rank byte");
assert!(array_err.to_string().contains("array datatype rank"));
let opaque_err = wf
.new_dataset_builder("bad_opaque")
.write_raw_with_dtype(
DtypeSpec::Opaque {
size: 1,
tag: "x".repeat(u8::MAX as usize),
},
&[0],
)
.expect_err("opaque tag length should fit in the encoded tag-length byte");
assert!(opaque_err.to_string().contains("opaque tag"));
}
}
#[test]
fn test_writable_file_h5dump_interop() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_h5dump.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.new_dataset_builder("values")
.write::<f64>(&[1.0, 2.0, 3.0])
.unwrap();
wf.flush().unwrap();
}
let out = std::process::Command::new("h5dump").arg(&path).output();
if let Ok(out) = out {
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"h5dump failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(stdout.contains("1, 2, 3"));
}
}
#[test]
fn test_writable_file_root_attr() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_attr.h5");
{
let mut wf = WritableFile::create(&path).unwrap();
wf.add_attr("version", 42i64).unwrap();
wf.new_dataset_builder("data")
.write::<f32>(&[1.0, 2.0])
.unwrap();
let f = wf.close().unwrap();
let attr = f.attr("version").unwrap();
let val: i64 = attr.read_scalar::<i64>().unwrap();
assert_eq!(val, 42);
}
}
#[test]
fn test_writable_file_oversized_object_header_uses_continuation_chunk() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("api_write_header_continuation.h5");
let payload = vec![7u8; 60_000];
let attr_len = 60_000;
{
let mut wf = WritableFile::create(&path).unwrap();
let mut builder = wf.new_dataset_builder("compact");
for idx in 0..8 {
builder = builder
.fixed_ascii_attr(&format!("attr_{idx}"), "x", attr_len)
.unwrap();
}
builder.compact().write::<u8>(&payload).unwrap();
let f = wf.close().unwrap();
let ds = f.dataset("compact").unwrap();
assert_dataset_values::<u8>(&ds, &payload).unwrap();
let expected = [
"attr_0", "attr_1", "attr_2", "attr_3", "attr_4", "attr_5", "attr_6", "attr_7",
];
let mut found = [false; 8];
let count = location_attr_summary_into(&ds, &expected, &mut found).unwrap();
assert_eq!(count, expected.len());
assert!(found.iter().all(|present| *present));
assert_eq!(attribute_string(&ds.attr("attr_7").unwrap()).unwrap(), "x");
}
let image = std::fs::read(&path).unwrap();
let continuation_chunks = image.windows(4).filter(|window| *window == b"OCHK").count();
assert!(
continuation_chunks >= 2,
"oversized compact root header should be split into chained v2 continuation chunks, found {continuation_chunks}"
);
}