//! 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); }