use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use rust_hdf5::{H5File, LibverBound};
fn unique_tmp(label: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!(
"rust_hdf5_superblock_{}_{}_{}",
label,
std::process::id(),
n
));
std::fs::create_dir_all(&dir).unwrap();
dir.join(format!("{label}.h5"))
}
fn cleanup(path: &Path) {
let _ = std::fs::remove_file(path);
if let Some(dir) = path.parent() {
let _ = std::fs::remove_dir_all(dir);
}
}
fn superblock_version(path: &Path) -> u8 {
let bytes = std::fs::read(path).unwrap();
assert_eq!(&bytes[..8], b"\x89HDF\r\n\x1a\n", "{}", path.display());
bytes[8]
}
fn write_contiguous(file: &H5File, name: &str) {
file.new_dataset::<i32>()
.shape([8usize])
.create(name)
.unwrap()
.write_raw(&(0..8i32).collect::<Vec<_>>())
.unwrap();
}
fn write_chunked(file: &H5File, name: &str) {
file.new_dataset::<i32>()
.shape([8usize])
.chunk(&[4])
.create(name)
.unwrap()
.write_raw(&(0..8i32).collect::<Vec<_>>())
.unwrap();
}
#[test]
fn a_file_of_contiguous_datasets_is_written_at_version_2() {
let path = unique_tmp("contiguous");
let file = H5File::create(&path).unwrap();
write_contiguous(&file, "data");
file.root_group().create_group("g").unwrap();
file.set_attr_string("note", "root attribute").unwrap();
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
cleanup(&path);
}
#[test]
fn a_file_with_a_chunked_dataset_is_written_at_version_3() {
let path = unique_tmp("chunked");
let file = H5File::create(&path).unwrap();
write_chunked(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), 3);
cleanup(&path);
}
#[test]
fn each_libver_bound_selects_its_superblock_version() {
for (bound, expected) in [
(LibverBound::Earliest, 0),
(LibverBound::V18, 2),
(LibverBound::V110, 3),
(LibverBound::V112, 3),
(LibverBound::V114, 3),
(LibverBound::V200, 3),
] {
let path = unique_tmp(&format!("bound_{bound:?}"));
let file = H5File::options().libver(bound).create(&path).unwrap();
write_contiguous(&file, "data");
file.root_group().create_group("g").unwrap();
file.close().unwrap();
assert_eq!(superblock_version(&path), expected, "bound {bound:?}");
cleanup(&path);
}
}
#[test]
fn a_chunked_dataset_leaves_a_v18_file_at_version_2() {
let path = unique_tmp("v18_chunked");
let file = H5File::options()
.libver(LibverBound::V18)
.create(&path)
.unwrap();
write_chunked(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
cleanup(&path);
}
#[test]
fn libver_latest_raises_a_contiguous_file_to_version_3() {
let path = unique_tmp("latest");
let file = H5File::create(&path).unwrap();
file.set_libver_latest(true).unwrap();
write_contiguous(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), 3);
cleanup(&path);
}
#[test]
fn an_swmr_file_is_written_at_version_3() {
use rust_hdf5::swmr::SwmrFileWriter;
let path = unique_tmp("swmr");
let mut writer = SwmrFileWriter::create(&path).unwrap();
let idx = writer
.create_streaming_dataset::<f32>("stream", &[4u64])
.unwrap();
writer.start_swmr().unwrap();
assert_eq!(superblock_version(&path), 3, "at start_swmr");
let frame: Vec<u8> = (0..4u32).flat_map(|j| (j as f32).to_le_bytes()).collect();
writer.append_frame(idx, &frame).unwrap();
writer.close().unwrap();
assert_eq!(superblock_version(&path), 3);
cleanup(&path);
}
#[test]
fn an_swmr_session_on_a_reopened_pre_v3_file_is_refused() {
use rust_hdf5::swmr::SwmrFileWriter;
for (label, libver, expect_sb) in [
("classic", Some(LibverBound::Earliest), 0u8),
("v18", Some(LibverBound::V18), 2),
("default", None, 2),
] {
let path = unique_tmp(&format!("swmr_reopen_{label}"));
let mut options = H5File::options();
if let Some(libver) = libver {
options = options.libver(libver);
}
let file = options.create(&path).unwrap();
write_contiguous(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), expect_sb, "{label}");
let mut writer = SwmrFileWriter::open_append(&path).unwrap();
let err = writer.start_swmr().unwrap_err().to_string();
assert!(
err.contains(&format!("superblock is version {expect_sb}")),
"{label}: the refusal must name the version it found: {err}"
);
assert!(err.contains("version-3"), "{label}: {err}");
drop(writer);
assert_eq!(superblock_version(&path), expect_sb, "{label}");
cleanup(&path);
}
}
#[test]
fn an_swmr_session_on_a_reopened_v3_file_is_allowed() {
use rust_hdf5::swmr::SwmrFileWriter;
let path = unique_tmp("swmr_reopen_v3");
let mut writer = SwmrFileWriter::create(&path).unwrap();
let idx = writer
.create_streaming_dataset::<f32>("stream", &[4u64])
.unwrap();
writer.start_swmr().unwrap();
let frame: Vec<u8> = (0..4u32).flat_map(|j| (j as f32).to_le_bytes()).collect();
writer.append_frame(idx, &frame).unwrap();
writer.close().unwrap();
assert_eq!(superblock_version(&path), 3);
let mut writer = SwmrFileWriter::open_append(&path).unwrap();
writer.start_swmr().unwrap();
writer.close().unwrap();
assert_eq!(superblock_version(&path), 3);
cleanup(&path);
}
#[test]
fn appending_contiguous_data_leaves_a_version_2_file_at_version_2() {
let path = unique_tmp("append_contig");
let file = H5File::create(&path).unwrap();
write_contiguous(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
let file = H5File::open_rw(&path).unwrap();
write_contiguous(&file, "added");
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
let file = H5File::open(&path).unwrap();
let mut names = file.dataset_names();
names.sort();
assert_eq!(names, vec!["added", "data"]);
drop(file);
cleanup(&path);
}
#[test]
fn appending_a_chunked_dataset_leaves_a_version_2_file_at_version_2() {
let path = unique_tmp("append_chunked");
let file = H5File::create(&path).unwrap();
write_contiguous(&file, "data");
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
let file = H5File::open_rw(&path).unwrap();
write_chunked(&file, "added");
file.close().unwrap();
assert_eq!(superblock_version(&path), 2);
let file = H5File::open(&path).unwrap();
assert_eq!(
file.dataset("added").unwrap().read_raw::<i32>().unwrap(),
(0..8i32).collect::<Vec<_>>()
);
drop(file);
cleanup(&path);
}
#[test]
fn a_version_3_file_stays_at_version_3_when_its_chunked_dataset_goes_away() {
let path = unique_tmp("no_downgrade");
let file = H5File::create(&path).unwrap();
write_contiguous(&file, "data");
write_chunked(&file, "chunky");
file.close().unwrap();
assert_eq!(superblock_version(&path), 3);
let file = H5File::open_rw(&path).unwrap();
file.delete_dataset("chunky").unwrap();
write_contiguous(&file, "added");
file.close().unwrap();
assert_eq!(superblock_version(&path), 3);
let file = H5File::open(&path).unwrap();
let mut names = file.dataset_names();
names.sort();
assert_eq!(names, vec!["added", "data"]);
drop(file);
cleanup(&path);
}