//! Comprehensive tests for clawhdf5-netcdf4. //! //! We create NetCDF-4 style HDF5 files using clawhdf5's FileBuilder with the //! appropriate NetCDF-4 conventions (CLASS=DIMENSION_SCALE, _Netcdf4Dimid, //! _NCProperties, CF attributes, etc.). use clawhdf5::{AttrValue, FileBuilder}; use clawhdf5_netcdf4::{Dimension, FillValue, NcType, NetCDF4File}; // --------------------------------------------------------------------------- // Helpers: build NetCDF-4 style HDF5 files in memory // --------------------------------------------------------------------------- /// Create a simple NetCDF-4 file with lat/lon dimensions and a temperature variable. fn make_simple_netcdf4() -> Vec { let mut b = FileBuilder::new(); // Root attributes (NetCDF-4 provenance) b.set_attr( "_NCProperties", AttrValue::String("version=2,clawhdf5=0.1.0".into()), ); b.set_attr("Conventions", AttrValue::String("CF-1.8".into())); b.set_attr( "history", AttrValue::String("created by clawhdf5-netcdf4 test".into()), ); // Dimension: lat (3 values) b.create_dataset("lat") .with_f64_data(&[-30.0, 0.0, 30.0]) .with_shape(&[3]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)) .set_attr("units", AttrValue::String("degrees_north".into())) .set_attr("long_name", AttrValue::String("latitude".into())) .set_attr("standard_name", AttrValue::String("latitude".into())) .set_attr("axis", AttrValue::String("Y".into())); // Dimension: lon (4 values) b.create_dataset("lon") .with_f64_data(&[0.0, 90.0, 180.0, 270.0]) .with_shape(&[4]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(1)) .set_attr("units", AttrValue::String("degrees_east".into())) .set_attr("long_name", AttrValue::String("longitude".into())) .set_attr("standard_name", AttrValue::String("longitude".into())) .set_attr("axis", AttrValue::String("X".into())); // Variable: temperature (3x4) b.create_dataset("temperature") .with_f64_data(&[ 20.0, 22.0, 25.0, 23.0, 28.0, 30.0, 29.0, 27.0, 15.0, 18.0, 20.0, 17.0, ]) .with_shape(&[3, 4]) .set_attr("units", AttrValue::String("K".into())) .set_attr("long_name", AttrValue::String("Surface Temperature".into())) .set_attr( "standard_name", AttrValue::String("surface_temperature".into()), ) .set_attr("_FillValue", AttrValue::F64(-9999.0)); b.finish().unwrap() } /// Create a NetCDF-4 file with scale_factor and add_offset for packed data. fn make_packed_netcdf4() -> Vec { let mut b = FileBuilder::new(); b.set_attr( "_NCProperties", AttrValue::String("version=2,clawhdf5=0.1.0".into()), ); // Dimension: time (5 values) b.create_dataset("time") .with_f64_data(&[0.0, 1.0, 2.0, 3.0, 4.0]) .with_shape(&[5]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)) .set_attr("units", AttrValue::String("days since 2000-01-01".into())) .set_attr("calendar", AttrValue::String("standard".into())) .set_attr("axis", AttrValue::String("T".into())); // Variable: packed_temp with scale_factor and add_offset // Unpacked = packed * 0.01 + 273.15 b.create_dataset("packed_temp") .with_f64_data(&[100.0, 200.0, -9999.0, 300.0, 150.0]) .with_shape(&[5]) .set_attr("scale_factor", AttrValue::F64(0.01)) .set_attr("add_offset", AttrValue::F64(273.15)) .set_attr("_FillValue", AttrValue::F64(-9999.0)) .set_attr("missing_value", AttrValue::F64(-9999.0)) .set_attr("units", AttrValue::String("K".into())) .set_attr("long_name", AttrValue::String("Packed Temperature".into())); b.finish().unwrap() } /// Create a NetCDF-4 file with groups. fn make_grouped_netcdf4() -> Vec { let mut b = FileBuilder::new(); b.set_attr( "_NCProperties", AttrValue::String("version=2,clawhdf5=0.1.0".into()), ); b.set_attr("title", AttrValue::String("Grouped NetCDF-4".into())); // Root-level dimension b.create_dataset("time") .with_f64_data(&[0.0, 1.0, 2.0]) .with_shape(&[3]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)) .set_attr("units", AttrValue::String("hours since 2020-01-01".into())); // Group: surface let mut g_surface = b.create_group("surface"); g_surface .create_dataset("pressure") .with_f64_data(&[1013.25, 1012.0, 1011.5]) .set_attr("units", AttrValue::String("hPa".into())) .set_attr("long_name", AttrValue::String("Surface Pressure".into())); g_surface.set_attr("description", AttrValue::String("Surface variables".into())); let finished_surface = g_surface.finish(); b.add_group(finished_surface); // Group: upper_air let mut g_upper = b.create_group("upper_air"); g_upper .create_dataset("wind_speed") .with_f64_data(&[5.0, 7.5, 6.2]) .set_attr("units", AttrValue::String("m/s".into())) .set_attr( "long_name", AttrValue::String("Wind Speed at 500hPa".into()), ); let finished_upper = g_upper.finish(); b.add_group(finished_upper); b.finish().unwrap() } /// Create a file with various data types. fn make_multitype_netcdf4() -> Vec { let mut b = FileBuilder::new(); // Dimension b.create_dataset("x") .with_f64_data(&[1.0, 2.0, 3.0]) .with_shape(&[3]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)); // Float64 variable b.create_dataset("var_f64") .with_f64_data(&[1.1, 2.2, 3.3]) .with_shape(&[3]); // Float32 variable b.create_dataset("var_f32") .with_f32_data(&[1.5f32, 2.5, 3.5]) .with_shape(&[3]); // Int32 variable b.create_dataset("var_i32") .with_i32_data(&[10, 20, 30]) .with_shape(&[3]); // Int64 variable b.create_dataset("var_i64") .with_i64_data(&[100, 200, 300]) .with_shape(&[3]); // UInt8 variable b.create_dataset("var_u8") .with_u8_data(&[1, 2, 3]) .with_shape(&[3]); b.finish().unwrap() } /// Create a file with valid_range and additional CF attributes. fn make_cf_rich_netcdf4() -> Vec { let mut b = FileBuilder::new(); b.create_dataset("level") .with_f64_data(&[1000.0, 850.0, 500.0, 200.0]) .with_shape(&[4]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)) .set_attr("units", AttrValue::String("hPa".into())) .set_attr("long_name", AttrValue::String("Pressure Level".into())) .set_attr("axis", AttrValue::String("Z".into())) .set_attr("valid_range", AttrValue::F64Array(vec![0.0, 1100.0])); b.finish().unwrap() } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[test] fn test_open_netcdf4_from_bytes() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); assert!(format!("{file:?}").contains("NetCDF4File")); } #[test] fn test_nc_properties() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let props = file.nc_properties().unwrap(); assert!(props.is_some()); assert!(props.unwrap().contains("clawhdf5")); } #[test] fn test_global_attrs() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let attrs = file.global_attrs().unwrap(); assert!(matches!( attrs.get("Conventions"), Some(AttrValue::String(s)) if s == "CF-1.8" )); assert!(matches!( attrs.get("history"), Some(AttrValue::String(s)) if s.contains("clawhdf5-netcdf4") )); } #[test] fn test_read_dimensions() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let dims = file.dimensions().unwrap(); assert_eq!(dims.len(), 2); let lat_dim = dims.iter().find(|d| d.name == "lat").unwrap(); assert_eq!(lat_dim.size, 3); assert!(!lat_dim.is_unlimited); let lon_dim = dims.iter().find(|d| d.name == "lon").unwrap(); assert_eq!(lon_dim.size, 4); assert!(!lon_dim.is_unlimited); } #[test] fn test_dimension_order_by_dimid() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let dims = file.dimensions().unwrap(); // Dimensions should be sorted by _Netcdf4Dimid: lat=0, lon=1 assert_eq!(dims[0].name, "lat"); assert_eq!(dims[1].name, "lon"); } #[test] fn test_read_variables() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let vars = file.variables().unwrap(); // Should have lat, lon, temperature assert_eq!(vars.len(), 3); let var_names: Vec<&str> = vars.iter().map(|v| v.name()).collect(); assert!(var_names.contains(&"lat")); assert!(var_names.contains(&"lon")); assert!(var_names.contains(&"temperature")); } #[test] fn test_variable_dimensions() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut temp = file.variable("temperature").unwrap(); let dims = temp.dimensions(); assert_eq!(dims.len(), 2); assert_eq!(dims[0].size, 3); // lat assert_eq!(dims[1].size, 4); // lon let shape = temp.shape().unwrap(); assert_eq!(shape, vec![3, 4]); let _ = temp.attrs().unwrap(); } #[test] fn test_variable_cf_attributes() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut temp = file.variable("temperature").unwrap(); let cf = temp.cf_attributes().unwrap(); assert_eq!(cf.units.as_deref(), Some("K")); assert_eq!(cf.long_name.as_deref(), Some("Surface Temperature")); assert_eq!(cf.standard_name.as_deref(), Some("surface_temperature")); assert_eq!(cf.fill_value, Some(FillValue::Float(-9999.0))); assert!(cf.scale_factor.is_none()); assert!(cf.add_offset.is_none()); } #[test] fn test_coordinate_variable() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let lat = file.variable("lat").unwrap(); // lat is a coordinate variable: 1-D with name matching its dimension assert!(lat.is_coordinate()); let temp = file.variable("temperature").unwrap(); assert!(!temp.is_coordinate()); } #[test] fn test_coordinate_cf_attrs() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut lat = file.variable("lat").unwrap(); let cf = lat.cf_attributes().unwrap(); assert_eq!(cf.units.as_deref(), Some("degrees_north")); assert_eq!(cf.axis.as_deref(), Some("Y")); assert_eq!(cf.standard_name.as_deref(), Some("latitude")); } #[test] fn test_read_raw_f64() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let lat = file.variable("lat").unwrap(); let data = lat.read_raw_f64().unwrap(); assert_eq!(data, vec![-30.0, 0.0, 30.0]); } #[test] fn test_read_temperature_data() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut temp = file.variable("temperature").unwrap(); let data = temp.read_f64().unwrap(); // No scale_factor/add_offset, so data should be unchanged assert_eq!(data.len(), 12); assert_eq!(data[0], 20.0); assert_eq!(data[4], 28.0); } #[test] fn test_scale_factor_add_offset() { let bytes = make_packed_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut var = file.variable("packed_temp").unwrap(); let cf = var.cf_attributes().unwrap(); assert_eq!(cf.scale_factor, Some(0.01)); assert_eq!(cf.add_offset, Some(273.15)); let data = var.read_f64().unwrap(); assert_eq!(data.len(), 5); // packed=100 -> 100*0.01 + 273.15 = 274.15 assert!((data[0] - 274.15).abs() < 1e-10); // packed=200 -> 200*0.01 + 273.15 = 275.15 assert!((data[1] - 275.15).abs() < 1e-10); // packed=-9999 (fill) -> NaN assert!(data[2].is_nan()); // packed=300 -> 300*0.01 + 273.15 = 276.15 assert!((data[3] - 276.15).abs() < 1e-10); // packed=150 -> 150*0.01 + 273.15 = 274.65 assert!((data[4] - 274.65).abs() < 1e-10); } #[test] fn test_raw_read_bypasses_scaling() { let bytes = make_packed_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("packed_temp").unwrap(); let raw = var.read_raw_f64().unwrap(); assert_eq!(raw, vec![100.0, 200.0, -9999.0, 300.0, 150.0]); } #[test] fn test_groups() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut group_names = file.group_names().unwrap(); group_names.sort(); assert_eq!(group_names, vec!["surface", "upper_air"]); } #[test] fn test_group_attrs() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let surface = file.group("surface").unwrap(); let attrs = surface.attrs().unwrap(); assert!(matches!( attrs.get("description"), Some(AttrValue::String(s)) if s == "Surface variables" )); } #[test] fn test_group_variables() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let surface = file.group("surface").unwrap(); let var_names = surface.variable_names().unwrap(); assert!(var_names.contains(&"pressure".to_string())); let mut pressure = surface.variable("pressure").unwrap(); let data = pressure.read_f64().unwrap(); assert_eq!(data, vec![1013.25, 1012.0, 1011.5]); let cf = pressure.cf_attributes().unwrap(); assert_eq!(cf.units.as_deref(), Some("hPa")); assert_eq!(cf.long_name.as_deref(), Some("Surface Pressure")); } #[test] fn test_group_nested_access() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let upper = file.group("upper_air").unwrap(); let mut wind = upper.variable("wind_speed").unwrap(); let data = wind.read_f64().unwrap(); assert_eq!(data, vec![5.0, 7.5, 6.2]); let cf = wind.cf_attributes().unwrap(); assert_eq!(cf.units.as_deref(), Some("m/s")); } #[test] fn test_nc_type_f64() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_f64").unwrap(); assert_eq!(var.nc_type().unwrap(), NcType::Double); } #[test] fn test_nc_type_f32() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_f32").unwrap(); assert_eq!(var.nc_type().unwrap(), NcType::Float); } #[test] fn test_nc_type_i32() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_i32").unwrap(); assert_eq!(var.nc_type().unwrap(), NcType::Int); } #[test] fn test_nc_type_i64() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_i64").unwrap(); assert_eq!(var.nc_type().unwrap(), NcType::Int64); } #[test] fn test_nc_type_u8() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_u8").unwrap(); assert_eq!(var.nc_type().unwrap(), NcType::UByte); } #[test] fn test_read_i32_data() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_i32").unwrap(); let data = var.read_raw_i32().unwrap(); assert_eq!(data, vec![10, 20, 30]); } #[test] fn test_read_f32_data() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_f32").unwrap(); let data = var.read_raw_f32().unwrap(); assert_eq!(data, vec![1.5f32, 2.5, 3.5]); } #[test] fn test_read_i64_data() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_i64").unwrap(); let data = var.read_raw_i64().unwrap(); assert_eq!(data, vec![100, 200, 300]); } #[test] fn test_valid_range() { let bytes = make_cf_rich_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut var = file.variable("level").unwrap(); let cf = var.cf_attributes().unwrap(); assert_eq!(cf.valid_range, Some((0.0, 1100.0))); assert_eq!(cf.axis.as_deref(), Some("Z")); } #[test] fn test_nc_type_display() { assert_eq!(NcType::Double.to_string(), "NC_DOUBLE"); assert_eq!(NcType::Float.to_string(), "NC_FLOAT"); assert_eq!(NcType::Int.to_string(), "NC_INT"); assert_eq!(NcType::Byte.to_string(), "NC_BYTE"); assert_eq!(NcType::String.to_string(), "NC_STRING"); } #[test] fn test_variable_not_found_error() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let err = file.variable("nonexistent").unwrap_err(); assert!(err.to_string().contains("variable not found")); } #[test] fn test_group_not_found_error() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let err = file.group("nonexistent").unwrap_err(); assert!(err.to_string().contains("group not found")); } #[test] fn test_error_display() { use clawhdf5_netcdf4::Error; let err = Error::DimensionNotFound("time".into()); assert_eq!(err.to_string(), "dimension not found: time"); let err = Error::TypeError("cannot convert".into()); assert_eq!(err.to_string(), "type error: cannot convert"); let err = Error::NotNetCDF4("missing signature".into()); assert!(err.to_string().contains("not a NetCDF-4 file")); } #[test] fn test_fill_value_as_f64() { assert_eq!(FillValue::Float(1.0).as_f64(), Some(1.0)); assert_eq!(FillValue::Int(-999).as_f64(), Some(-999.0)); assert_eq!(FillValue::UInt(42).as_f64(), Some(42.0)); assert_eq!(FillValue::String("na".into()).as_f64(), None); } #[test] fn test_time_dimension_with_calendar() { let bytes = make_packed_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut time_var = file.variable("time").unwrap(); let cf = time_var.cf_attributes().unwrap(); assert_eq!(cf.units.as_deref(), Some("days since 2000-01-01")); assert_eq!(cf.calendar.as_deref(), Some("standard")); assert_eq!(cf.axis.as_deref(), Some("T")); } #[test] fn test_open_from_disk() { let bytes = make_simple_netcdf4(); let path = std::env::temp_dir().join("clawhdf5_netcdf4_test.nc"); std::fs::write(&path, &bytes).unwrap(); let file = NetCDF4File::open(&path).unwrap(); let dims = file.dimensions().unwrap(); assert_eq!(dims.len(), 2); std::fs::remove_file(&path).ok(); } #[test] fn test_global_attrs_title() { let bytes = make_grouped_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let attrs = file.global_attrs().unwrap(); assert!(matches!( attrs.get("title"), Some(AttrValue::String(s)) if s == "Grouped NetCDF-4" )); } #[test] fn test_real_world_temperature_grid() { // Simulate a real-world temperature dataset with lat/lon/time let mut b = FileBuilder::new(); b.set_attr("Conventions", AttrValue::String("CF-1.8".into())); b.set_attr( "_NCProperties", AttrValue::String("version=2,clawhdf5=0.1.0".into()), ); b.set_attr( "title", AttrValue::String("ERA5 Temperature Reanalysis".into()), ); b.set_attr("institution", AttrValue::String("ECMWF".into())); // Time dimension (2 steps) b.create_dataset("time") .with_f64_data(&[0.0, 6.0]) .with_shape(&[2]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(0)) .set_attr("units", AttrValue::String("hours since 2020-01-01".into())) .set_attr("calendar", AttrValue::String("proleptic_gregorian".into())) .set_attr("axis", AttrValue::String("T".into())); // Latitude dimension (3 values) b.create_dataset("latitude") .with_f64_data(&[90.0, 0.0, -90.0]) .with_shape(&[3]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(1)) .set_attr("units", AttrValue::String("degrees_north".into())) .set_attr("axis", AttrValue::String("Y".into())); // Longitude dimension (4 values) b.create_dataset("longitude") .with_f64_data(&[0.0, 90.0, 180.0, 270.0]) .with_shape(&[4]) .set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into())) .set_attr("_Netcdf4Dimid", AttrValue::I64(2)) .set_attr("units", AttrValue::String("degrees_east".into())) .set_attr("axis", AttrValue::String("X".into())); // Temperature variable (2x3x4) with scale/offset let temp_data: Vec = (0..24).map(|i| i as f64 * 100.0).collect(); b.create_dataset("t2m") .with_f64_data(&temp_data) .with_shape(&[2, 3, 4]) .set_attr("units", AttrValue::String("K".into())) .set_attr("long_name", AttrValue::String("2 metre temperature".into())) .set_attr("standard_name", AttrValue::String("air_temperature".into())) .set_attr("scale_factor", AttrValue::F64(0.001)) .set_attr("add_offset", AttrValue::F64(273.15)) .set_attr("_FillValue", AttrValue::F64(-32767.0)); let bytes = b.finish().unwrap(); let file = NetCDF4File::from_bytes(bytes).unwrap(); // Check dimensions let dims = file.dimensions().unwrap(); assert_eq!(dims.len(), 3); assert_eq!(dims[0].name, "time"); assert_eq!(dims[0].size, 2); assert_eq!(dims[1].name, "latitude"); assert_eq!(dims[1].size, 3); assert_eq!(dims[2].name, "longitude"); assert_eq!(dims[2].size, 4); // Check variables let vars = file.variables().unwrap(); assert_eq!(vars.len(), 4); // time, latitude, longitude, t2m // Check temperature with scaling let mut t2m = file.variable("t2m").unwrap(); let cf = t2m.cf_attributes().unwrap(); assert_eq!(cf.scale_factor, Some(0.001)); assert_eq!(cf.add_offset, Some(273.15)); assert_eq!(cf.standard_name.as_deref(), Some("air_temperature")); let data = t2m.read_f64().unwrap(); assert_eq!(data.len(), 24); // First value: 0.0 * 0.001 + 273.15 = 273.15 assert!((data[0] - 273.15).abs() < 1e-10); // Second value: 100.0 * 0.001 + 273.15 = 273.25 assert!((data[1] - 273.25).abs() < 1e-10); // Check global attrs let attrs = file.global_attrs().unwrap(); assert!(matches!( attrs.get("institution"), Some(AttrValue::String(s)) if s == "ECMWF" )); } #[test] fn test_variable_read_raw_bytes() { let bytes = make_multitype_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let var = file.variable("var_i32").unwrap(); let raw = var.read_raw().unwrap(); // 3 i32 values = 12 bytes assert_eq!(raw.len(), 12); // First i32 = 10 let val = i32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]); assert_eq!(val, 10); } #[test] fn test_hdf5_file_access() { let bytes = make_simple_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let hdf5 = file.hdf5_file(); // Can use the underlying HDF5 file for advanced operations assert!(hdf5.superblock().version >= 2); } #[test] fn test_missing_value_attribute() { let bytes = make_packed_netcdf4(); let file = NetCDF4File::from_bytes(bytes).unwrap(); let mut var = file.variable("packed_temp").unwrap(); let cf = var.cf_attributes().unwrap(); assert_eq!(cf.missing_value, Some(FillValue::Float(-9999.0))); } #[test] fn test_dimension_struct_equality() { let d1 = Dimension { name: "time".into(), size: 10, is_unlimited: false, }; let d2 = Dimension { name: "time".into(), size: 10, is_unlimited: false, }; assert_eq!(d1, d2); let d3 = Dimension { name: "time".into(), size: 10, is_unlimited: true, }; assert_ne!(d1, d3); }