CI / test (push) Failing after 15s
- Add .gitea/workflows/ci.yml running scripts/ci-test.sh (fmt, clippy,
test, no_std check) on push/PR to main.
- Fix stale rustyhdf5-py/rustyhdf5-format package names in
ci-test.sh/check-nostd.sh, which had been silently no-op'ing those
checks (cargo warns but doesn't fail on an unknown --exclude/-p
target).
- With those checks actually running, fix the real issues they surface:
- clippy: useless_conversion in chunked_write.rs, byte_char_slices in
global_heap.rs/object_header.rs.
- cargo fmt: apply formatting across the workspace (whitespace only).
- no_std (thumbv7em-none-eabihf) build errors in clawhdf5-format:
core::sync::atomic::AtomicU64 doesn't exist on that target (no
native 64-bit atomics) — switch profiling.rs's counters to
portable-atomic, which falls back to a CAS-based emulation there
and is a no-op wrapper elsewhere. Add missing alloc imports for
Box (filters.rs), Vec (filters_szip.rs), and format! (dict_encoding.rs)
on no_std paths. Replace f64::powi (std/libm-only) with a small
local exponentiation-by-squaring helper in the scale-offset filter.
314 lines
9.6 KiB
Rust
314 lines
9.6 KiB
Rust
//! Integration tests for HDF5 reference type reading.
|
|
|
|
use clawhdf5_format::data_read::{read_object_references, read_region_references};
|
|
use clawhdf5_format::datatype::{Datatype, ReferenceType};
|
|
|
|
#[test]
|
|
fn object_ref_single_valid() {
|
|
let dt = Datatype::Reference {
|
|
size: 8,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let raw = 4096u64.to_le_bytes().to_vec();
|
|
let refs = read_object_references(&raw, &dt, 8).unwrap();
|
|
assert_eq!(refs.len(), 1);
|
|
assert_eq!(refs[0].address, 4096);
|
|
assert!(!refs[0].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_null_detection() {
|
|
let dt = Datatype::Reference {
|
|
size: 8,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let raw = u64::MAX.to_le_bytes().to_vec();
|
|
let refs = read_object_references(&raw, &dt, 8).unwrap();
|
|
assert_eq!(refs.len(), 1);
|
|
assert!(refs[0].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_multiple() {
|
|
let dt = Datatype::Reference {
|
|
size: 8,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let mut raw = Vec::new();
|
|
raw.extend_from_slice(&100u64.to_le_bytes());
|
|
raw.extend_from_slice(&u64::MAX.to_le_bytes());
|
|
raw.extend_from_slice(&200u64.to_le_bytes());
|
|
raw.extend_from_slice(&300u64.to_le_bytes());
|
|
raw.extend_from_slice(&u64::MAX.to_le_bytes());
|
|
|
|
let refs = read_object_references(&raw, &dt, 8).unwrap();
|
|
assert_eq!(refs.len(), 5);
|
|
assert_eq!(refs[0].address, 100);
|
|
assert!(refs[1].is_null());
|
|
assert_eq!(refs[2].address, 200);
|
|
assert_eq!(refs[3].address, 300);
|
|
assert!(refs[4].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_4byte_offset_size() {
|
|
let dt = Datatype::Reference {
|
|
size: 4,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let mut raw = Vec::new();
|
|
raw.extend_from_slice(&256u32.to_le_bytes());
|
|
raw.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
|
|
let refs = read_object_references(&raw, &dt, 4).unwrap();
|
|
assert_eq!(refs.len(), 2);
|
|
assert_eq!(refs[0].address, 256);
|
|
assert!(refs[1].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_wrong_type_errors() {
|
|
let dt = Datatype::Reference {
|
|
size: 12,
|
|
ref_type: ReferenceType::DatasetRegion,
|
|
};
|
|
let raw = vec![0u8; 12];
|
|
let err = read_object_references(&raw, &dt, 8).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
clawhdf5_format::error::FormatError::TypeMismatch { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_non_reference_type_errors() {
|
|
let dt = Datatype::FixedPoint {
|
|
size: 8,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
signed: false,
|
|
bit_offset: 0,
|
|
bit_precision: 64,
|
|
};
|
|
let raw = vec![0u8; 8];
|
|
let err = read_object_references(&raw, &dt, 8).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
clawhdf5_format::error::FormatError::TypeMismatch { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn object_ref_size_mismatch() {
|
|
let dt = Datatype::Reference {
|
|
size: 8,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let raw = vec![0u8; 7]; // not a multiple of 8
|
|
let err = read_object_references(&raw, &dt, 8).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
clawhdf5_format::error::FormatError::DataSizeMismatch { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn region_ref_basic() {
|
|
let dt = Datatype::Reference {
|
|
size: 12,
|
|
ref_type: ReferenceType::DatasetRegion,
|
|
};
|
|
let raw: Vec<u8> = (0..24).collect();
|
|
let refs = read_region_references(&raw, &dt).unwrap();
|
|
assert_eq!(refs.len(), 2);
|
|
assert_eq!(refs[0].raw, (0u8..12).collect::<Vec<u8>>());
|
|
assert_eq!(refs[1].raw, (12u8..24).collect::<Vec<u8>>());
|
|
}
|
|
|
|
#[test]
|
|
fn region_ref_wrong_type_errors() {
|
|
let dt = Datatype::Reference {
|
|
size: 8,
|
|
ref_type: ReferenceType::Object,
|
|
};
|
|
let raw = vec![0u8; 8];
|
|
let err = read_region_references(&raw, &dt).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
clawhdf5_format::error::FormatError::TypeMismatch { .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn region_ref_size_mismatch() {
|
|
let dt = Datatype::Reference {
|
|
size: 12,
|
|
ref_type: ReferenceType::DatasetRegion,
|
|
};
|
|
let raw = vec![0u8; 11]; // not a multiple of 12
|
|
let err = read_region_references(&raw, &dt).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
clawhdf5_format::error::FormatError::DataSizeMismatch { .. }
|
|
));
|
|
}
|
|
|
|
// ---- h5py integration: read object references from an h5py-created file ----
|
|
|
|
#[test]
|
|
fn h5py_object_reference_roundtrip() {
|
|
// Generate an HDF5 file with h5py containing object references
|
|
let path = std::env::temp_dir().join("clawhdf5_test_objrefs.h5");
|
|
let script = format!(
|
|
r#"
|
|
import h5py
|
|
import numpy as np
|
|
|
|
f = h5py.File('{}', 'w')
|
|
f.create_dataset('target_a', data=[1.0, 2.0, 3.0])
|
|
f.create_dataset('target_b', data=[10, 20, 30])
|
|
refs = [f['target_a'].ref, f['target_b'].ref]
|
|
f.create_dataset('refs', data=refs)
|
|
f.close()
|
|
print('ok')
|
|
"#,
|
|
path.display()
|
|
);
|
|
let output = std::process::Command::new("python3")
|
|
.args(["-c", &script])
|
|
.output();
|
|
|
|
let output = match output {
|
|
Ok(o) if o.status.success() => o,
|
|
_ => {
|
|
eprintln!("skipping h5py_object_reference_roundtrip: python3+h5py not available");
|
|
return;
|
|
}
|
|
};
|
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
|
if !stdout.trim().contains("ok") {
|
|
eprintln!("skipping h5py_object_reference_roundtrip: h5py script failed");
|
|
return;
|
|
}
|
|
|
|
// Read the file and parse object references
|
|
let file_data = std::fs::read(&path).unwrap();
|
|
let sig_offset = clawhdf5_format::signature::find_signature(&file_data).unwrap();
|
|
let sb = clawhdf5_format::superblock::Superblock::parse(&file_data, sig_offset).unwrap();
|
|
let root_oh = clawhdf5_format::object_header::ObjectHeader::parse(
|
|
&file_data,
|
|
sb.root_group_address as usize,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap();
|
|
|
|
// Find the 'refs' dataset -- handle both v1 (SymbolTable/B-tree) and v2 (Link) groups
|
|
let mut refs_addr = None;
|
|
|
|
// Try v2 Link messages first
|
|
for msg in &root_oh.messages {
|
|
if msg.msg_type == clawhdf5_format::message_type::MessageType::Link {
|
|
let link = clawhdf5_format::link_message::LinkMessage::parse(&msg.data, sb.offset_size)
|
|
.unwrap();
|
|
if link.name == "refs"
|
|
&& let clawhdf5_format::link_message::LinkTarget::Hard {
|
|
object_header_address,
|
|
} = link.link_target
|
|
{
|
|
refs_addr = Some(object_header_address);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fall back to v1 SymbolTable (B-tree + local heap) used by default h5py files
|
|
if refs_addr.is_none() {
|
|
for msg in &root_oh.messages {
|
|
if msg.msg_type == clawhdf5_format::message_type::MessageType::SymbolTable {
|
|
let sym = clawhdf5_format::symbol_table::SymbolTableMessage::parse(
|
|
&msg.data,
|
|
sb.offset_size,
|
|
)
|
|
.unwrap();
|
|
let entries = clawhdf5_format::group_v1::resolve_v1_group_entries(
|
|
&file_data,
|
|
&sym,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap();
|
|
for entry in entries {
|
|
if entry.name == "refs" {
|
|
refs_addr = Some(entry.object_header_address);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let refs_addr = refs_addr.expect("'refs' dataset link not found");
|
|
|
|
let ds_oh = clawhdf5_format::object_header::ObjectHeader::parse(
|
|
&file_data,
|
|
refs_addr as usize,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut found_dt = None;
|
|
let mut found_ds = None;
|
|
let mut found_layout = None;
|
|
for msg in &ds_oh.messages {
|
|
match msg.msg_type {
|
|
clawhdf5_format::message_type::MessageType::Datatype => {
|
|
let (dt, _) = clawhdf5_format::datatype::Datatype::parse(&msg.data).unwrap();
|
|
found_dt = Some(dt);
|
|
}
|
|
clawhdf5_format::message_type::MessageType::Dataspace => {
|
|
found_ds = Some(
|
|
clawhdf5_format::dataspace::Dataspace::parse(&msg.data, sb.length_size)
|
|
.unwrap(),
|
|
);
|
|
}
|
|
clawhdf5_format::message_type::MessageType::DataLayout => {
|
|
found_layout = Some(
|
|
clawhdf5_format::data_layout::DataLayout::parse(
|
|
&msg.data,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap(),
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let ref_dt = found_dt.expect("no datatype in refs dataset");
|
|
let ref_ds = found_ds.expect("no dataspace in refs dataset");
|
|
let ref_layout = found_layout.expect("no layout in refs dataset");
|
|
|
|
// Verify the datatype is an object reference
|
|
match &ref_dt {
|
|
Datatype::Reference { ref_type, .. } => {
|
|
assert_eq!(*ref_type, ReferenceType::Object);
|
|
}
|
|
_ => panic!("expected Reference datatype, got {:?}", ref_dt),
|
|
}
|
|
|
|
let raw = clawhdf5_format::data_read::read_raw_data(&file_data, &ref_layout, &ref_ds, &ref_dt)
|
|
.unwrap();
|
|
|
|
let obj_refs = read_object_references(&raw, &ref_dt, sb.offset_size).unwrap();
|
|
assert_eq!(obj_refs.len(), 2);
|
|
|
|
// Both references should be non-null and point to valid addresses
|
|
assert!(!obj_refs[0].is_null());
|
|
assert!(!obj_refs[1].is_null());
|
|
assert_ne!(obj_refs[0].address, obj_refs[1].address);
|
|
|
|
// Clean up
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|