430 lines
11 KiB
Rust
430 lines
11 KiB
Rust
//! Tests for the `#[derive(H5Type)]` proc macro.
|
|
|
|
use clawhdf5_derive::H5Type;
|
|
use clawhdf5_format::data_read::{read_as_f64, read_as_i32, read_compound_fields};
|
|
use clawhdf5_format::datatype::Datatype;
|
|
|
|
// ---- Test structs ----
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct SimpleF64 {
|
|
x: f64,
|
|
y: f64,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct MixedNumerics {
|
|
a: f64,
|
|
b: f32,
|
|
c: i32,
|
|
d: u16,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct AllIntegers {
|
|
a: i8,
|
|
b: i16,
|
|
c: i32,
|
|
d: i64,
|
|
e: u8,
|
|
f: u16,
|
|
g: u32,
|
|
h: u64,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct WithBool {
|
|
flag: bool,
|
|
value: f64,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct WithArray {
|
|
coords: [f64; 3],
|
|
id: i32,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct SingleField {
|
|
value: f64,
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct IntArrayStruct {
|
|
data: [i32; 4],
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct U8ArrayStruct {
|
|
bytes: [u8; 8],
|
|
}
|
|
|
|
#[derive(H5Type, Debug, PartialEq)]
|
|
struct ManyFields {
|
|
a: f64,
|
|
b: f32,
|
|
c: i8,
|
|
d: i16,
|
|
e: i32,
|
|
f: i64,
|
|
g: u8,
|
|
h: u16,
|
|
i: u32,
|
|
j: u64,
|
|
k: bool,
|
|
}
|
|
|
|
// ---- Test 1: Simple struct datatype generation ----
|
|
|
|
#[test]
|
|
fn simple_f64_datatype() {
|
|
let dt = SimpleF64::hdf5_datatype();
|
|
match &dt {
|
|
Datatype::Compound { size, members } => {
|
|
assert_eq!(*size, 16); // 2 x f64
|
|
assert_eq!(members.len(), 2);
|
|
assert_eq!(members[0].name, "x");
|
|
assert_eq!(members[0].byte_offset, 0);
|
|
assert_eq!(members[1].name, "y");
|
|
assert_eq!(members[1].byte_offset, 8);
|
|
}
|
|
_ => panic!("expected Compound datatype"),
|
|
}
|
|
}
|
|
|
|
// ---- Test 2: Nested/mixed numerics datatype ----
|
|
|
|
#[test]
|
|
fn mixed_numerics_datatype() {
|
|
let dt = MixedNumerics::hdf5_datatype();
|
|
match &dt {
|
|
Datatype::Compound { size, members } => {
|
|
assert_eq!(*size, 18); // 8 + 4 + 4 + 2
|
|
assert_eq!(members.len(), 4);
|
|
assert_eq!(members[0].name, "a");
|
|
assert_eq!(members[0].byte_offset, 0);
|
|
assert_eq!(members[1].name, "b");
|
|
assert_eq!(members[1].byte_offset, 8);
|
|
assert_eq!(members[2].name, "c");
|
|
assert_eq!(members[2].byte_offset, 12);
|
|
assert_eq!(members[3].name, "d");
|
|
assert_eq!(members[3].byte_offset, 16);
|
|
}
|
|
_ => panic!("expected Compound datatype"),
|
|
}
|
|
}
|
|
|
|
// ---- Test 3: Round-trip serialization of simple struct ----
|
|
|
|
#[test]
|
|
fn simple_f64_roundtrip() {
|
|
let s = SimpleF64 { x: 3.125, y: 2.75 };
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 16);
|
|
let s2 = SimpleF64::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 4: Round-trip of mixed numerics ----
|
|
|
|
#[test]
|
|
fn mixed_numerics_roundtrip() {
|
|
let s = MixedNumerics {
|
|
a: 1.0,
|
|
b: 2.5,
|
|
c: -42,
|
|
d: 1000,
|
|
};
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 18);
|
|
let s2 = MixedNumerics::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 5: All integer types ----
|
|
|
|
#[test]
|
|
fn all_integers_roundtrip() {
|
|
let s = AllIntegers {
|
|
a: -1,
|
|
b: -256,
|
|
c: -100_000,
|
|
d: -1_000_000_000,
|
|
e: 255,
|
|
f: 65535,
|
|
g: 4_000_000_000,
|
|
h: 10_000_000_000,
|
|
};
|
|
let bytes = s.to_bytes();
|
|
let expected_size = 1 + 2 + 4 + 8 + 1 + 2 + 4 + 8;
|
|
assert_eq!(bytes.len(), expected_size);
|
|
let s2 = AllIntegers::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 6: Bool field ----
|
|
|
|
#[test]
|
|
fn with_bool_roundtrip() {
|
|
let s = WithBool {
|
|
flag: true,
|
|
value: 99.9,
|
|
};
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 9); // 1 + 8
|
|
assert_eq!(bytes[0], 1); // true = 1
|
|
let s2 = WithBool::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
|
|
let s_false = WithBool {
|
|
flag: false,
|
|
value: 0.0,
|
|
};
|
|
let bytes2 = s_false.to_bytes();
|
|
assert_eq!(bytes2[0], 0);
|
|
let s3 = WithBool::from_bytes(&bytes2);
|
|
assert_eq!(s_false, s3);
|
|
}
|
|
|
|
// ---- Test 7: Fixed-size array field ----
|
|
|
|
#[test]
|
|
fn with_array_roundtrip() {
|
|
let s = WithArray {
|
|
coords: [1.0, 2.0, 3.0],
|
|
id: 42,
|
|
};
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 28); // 3*8 + 4
|
|
let s2 = WithArray::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 8: Array datatype generation ----
|
|
|
|
#[test]
|
|
fn array_datatype_structure() {
|
|
let dt = WithArray::hdf5_datatype();
|
|
match &dt {
|
|
Datatype::Compound { size, members } => {
|
|
assert_eq!(*size, 28);
|
|
assert_eq!(members.len(), 2);
|
|
assert_eq!(members[0].name, "coords");
|
|
match &members[0].datatype {
|
|
Datatype::Array {
|
|
base_type,
|
|
dimensions,
|
|
} => {
|
|
assert_eq!(dimensions, &vec![3u32]);
|
|
assert!(matches!(
|
|
**base_type,
|
|
Datatype::FloatingPoint { size: 8, .. }
|
|
));
|
|
}
|
|
_ => panic!("expected Array datatype for coords"),
|
|
}
|
|
}
|
|
_ => panic!("expected Compound datatype"),
|
|
}
|
|
}
|
|
|
|
// ---- Test 8b: Single field struct ----
|
|
|
|
#[test]
|
|
fn single_field_roundtrip() {
|
|
let s = SingleField { value: 42.0 };
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 8);
|
|
let s2 = SingleField::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 9: Compound bytes compatible with data_read ----
|
|
|
|
#[test]
|
|
fn compound_read_compatibility() {
|
|
let items = vec![
|
|
SimpleF64 { x: 1.0, y: 10.0 },
|
|
SimpleF64 { x: 2.0, y: 20.0 },
|
|
SimpleF64 { x: 3.0, y: 30.0 },
|
|
];
|
|
let mut raw = Vec::new();
|
|
for item in &items {
|
|
raw.extend_from_slice(&item.to_bytes());
|
|
}
|
|
|
|
let dt = SimpleF64::hdf5_datatype();
|
|
let fields = read_compound_fields(&raw, &dt).unwrap();
|
|
assert_eq!(fields.len(), 2);
|
|
|
|
let x_vals = read_as_f64(&fields[0].raw_data, &fields[0].datatype).unwrap();
|
|
assert_eq!(x_vals, vec![1.0, 2.0, 3.0]);
|
|
|
|
let y_vals = read_as_f64(&fields[1].raw_data, &fields[1].datatype).unwrap();
|
|
assert_eq!(y_vals, vec![10.0, 20.0, 30.0]);
|
|
}
|
|
|
|
// ---- Test 10: Round-trip through FileWriter ----
|
|
|
|
#[test]
|
|
fn roundtrip_through_file_writer() {
|
|
use clawhdf5_format::data_read::read_raw_data;
|
|
use clawhdf5_format::file_writer::FileWriter;
|
|
use clawhdf5_format::object_header::ObjectHeader;
|
|
use clawhdf5_format::signature::find_signature;
|
|
use clawhdf5_format::superblock::Superblock;
|
|
|
|
let items = vec![
|
|
MixedNumerics {
|
|
a: 1.5,
|
|
b: 2.5,
|
|
c: -10,
|
|
d: 100,
|
|
},
|
|
MixedNumerics {
|
|
a: 3.0,
|
|
b: 4.0,
|
|
c: 20,
|
|
d: 200,
|
|
},
|
|
];
|
|
|
|
let dt = MixedNumerics::hdf5_datatype();
|
|
let mut raw_data = Vec::new();
|
|
for item in &items {
|
|
raw_data.extend_from_slice(&item.to_bytes());
|
|
}
|
|
|
|
let mut fw = FileWriter::new();
|
|
fw.create_dataset("compound_ds")
|
|
.with_compound_data(dt.clone(), raw_data.clone(), 2);
|
|
let file_bytes = fw.finish().unwrap();
|
|
|
|
// Parse the file back
|
|
let sig_offset = find_signature(&file_bytes).unwrap();
|
|
let sb = Superblock::parse(&file_bytes, sig_offset).unwrap();
|
|
let oh = ObjectHeader::parse(
|
|
&file_bytes,
|
|
sb.root_group_address as usize,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap();
|
|
|
|
// Find the compound dataset link
|
|
let mut ds_addr = None;
|
|
for msg in &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 == "compound_ds"
|
|
&& let clawhdf5_format::link_message::LinkTarget::Hard {
|
|
object_header_address,
|
|
} = link.link_target
|
|
{
|
|
ds_addr = Some(object_header_address);
|
|
}
|
|
}
|
|
}
|
|
let ds_addr = ds_addr.expect("compound_ds link not found");
|
|
|
|
let ds_oh = ObjectHeader::parse(
|
|
&file_bytes,
|
|
ds_addr as usize,
|
|
sb.offset_size,
|
|
sb.length_size,
|
|
)
|
|
.unwrap();
|
|
|
|
// Extract datatype, dataspace, and layout
|
|
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 (parsed_dt, _) = clawhdf5_format::datatype::Datatype::parse(&msg.data).unwrap();
|
|
found_dt = Some(parsed_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 parsed_dt = found_dt.expect("no datatype");
|
|
let parsed_ds = found_ds.expect("no dataspace");
|
|
let layout = found_layout.expect("no layout");
|
|
|
|
let raw_back = read_raw_data(&file_bytes, &layout, &parsed_ds, &parsed_dt).unwrap();
|
|
let fields = read_compound_fields(&raw_back, &parsed_dt).unwrap();
|
|
|
|
let a_vals = read_as_f64(&fields[0].raw_data, &fields[0].datatype).unwrap();
|
|
assert_eq!(a_vals, vec![1.5, 3.0]);
|
|
|
|
let c_vals = read_as_i32(&fields[2].raw_data, &fields[2].datatype).unwrap();
|
|
assert_eq!(c_vals, vec![-10, 20]);
|
|
}
|
|
|
|
// ---- Test 11: Many fields struct ----
|
|
|
|
#[test]
|
|
fn many_fields_roundtrip() {
|
|
let s = ManyFields {
|
|
a: 1.0,
|
|
b: 2.0,
|
|
c: -3,
|
|
d: 400,
|
|
e: -500,
|
|
f: 6_000_000,
|
|
g: 7,
|
|
h: 800,
|
|
i: 9_000,
|
|
j: 10_000_000_000,
|
|
k: true,
|
|
};
|
|
let bytes = s.to_bytes();
|
|
let s2 = ManyFields::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 12: Integer array struct ----
|
|
|
|
#[test]
|
|
fn int_array_roundtrip() {
|
|
let s = IntArrayStruct {
|
|
data: [10, 20, -30, 40],
|
|
};
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 16); // 4 x i32
|
|
let s2 = IntArrayStruct::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|
|
|
|
// ---- Test 13: U8 array struct ----
|
|
|
|
#[test]
|
|
fn u8_array_roundtrip() {
|
|
let s = U8ArrayStruct {
|
|
bytes: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
};
|
|
let bytes = s.to_bytes();
|
|
assert_eq!(bytes.len(), 8);
|
|
let s2 = U8ArrayStruct::from_bytes(&bytes);
|
|
assert_eq!(s, s2);
|
|
}
|