use std::fmt::{self, Debug};
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ptr;
use hdf5_sys::h5o::H5Ocopy;
#[allow(deprecated)]
use hdf5_sys::h5o::H5Oset_comment;
#[cfg(feature = "1.10.3")]
use hdf5_sys::h5o::{H5O_INFO_BASIC, H5O_INFO_NUM_ATTRS, H5O_INFO_TIME};
#[cfg(feature = "1.12.0")]
use hdf5_sys::h5o::{
H5O_info2_t, H5O_token_t, H5Oget_info_by_name3, H5Oget_info3, H5Oopen_by_token,
};
#[cfg(not(feature = "1.10.3"))]
use hdf5_sys::h5o::{H5Oget_info_by_name1, H5Oget_info1};
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
use hdf5_sys::h5o::{H5Oget_info_by_name2, H5Oget_info2};
#[cfg(not(feature = "1.12.0"))]
use hdf5_sys::{h5::haddr_t, h5o::H5O_info1_t, h5o::H5Oopen_by_addr};
use hdf5_sys::{
h5a::{H5A_info_t, H5Adelete, H5Aget_info_by_name, H5Aiterate2, H5Aopen, H5Aopen_by_idx},
h5f::H5Fget_name,
h5i::{H5Iget_file_id, H5Iget_name},
h5o::{H5O_type_t, H5Oget_comment},
};
use crate::internal_prelude::*;
use super::attribute::{AttrInfo, AttributeBuilderEmpty};
use super::iteration::visit;
#[repr(transparent)]
#[derive(Clone)]
pub struct Location(Handle);
impl ObjectClass for Location {
const NAME: &'static str = "location";
const VALID_TYPES: &'static [H5I_type_t] =
&[H5I_FILE, H5I_GROUP, H5I_DATATYPE, H5I_DATASET, H5I_ATTR];
fn from_handle(handle: Handle) -> Self {
Self(handle)
}
fn handle(&self) -> &Handle {
&self.0
}
fn short_repr(&self) -> Option<String> {
Some(format!("\"{}\"", self.name()))
}
}
impl Debug for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.debug_fmt(f)
}
}
impl Deref for Location {
type Target = Object;
fn deref(&self) -> &Object {
unsafe { self.transmute() }
}
}
impl Location {
pub fn name(&self) -> String {
h5lock!(get_h5_str(|m, s| H5Iget_name(self.id(), m, s)).unwrap_or_else(|_| String::new()))
}
pub fn filename(&self) -> String {
h5lock!(get_h5_str(|m, s| H5Fget_name(self.id(), m, s)).unwrap_or_else(|_| String::new()))
}
pub fn file(&self) -> Result<File> {
File::from_id(h5try!(H5Iget_file_id(self.id())))
}
pub fn comment(&self) -> Option<String> {
let comment = h5lock!(get_h5_str(|m, s| H5Oget_comment(self.id(), m, s)).ok());
comment.and_then(|c| if c.is_empty() { None } else { Some(c) })
}
#[deprecated(note = "attributes are preferred to comments")]
pub fn set_comment(&self, comment: &str) -> Result<()> {
let comment = to_cstring(comment)?;
#[allow(deprecated)]
h5call!(H5Oset_comment(self.id(), comment.as_ptr())).and(Ok(()))
}
#[deprecated(note = "attributes are preferred to comments")]
pub fn clear_comment(&self) -> Result<()> {
#[allow(deprecated)]
h5call!(H5Oset_comment(self.id(), ptr::null_mut())).and(Ok(()))
}
pub fn new_attr<T: H5Type>(&self) -> AttributeBuilderEmpty {
AttributeBuilder::new(self).empty::<T>()
}
pub fn new_attr_builder(&self) -> AttributeBuilder {
AttributeBuilder::new(self)
}
pub fn attr(&self, name: &str) -> Result<Attribute> {
let name = to_cstring(name)?;
Attribute::from_id(h5try!(H5Aopen(self.id(), name.as_ptr(), H5P_DEFAULT)))
}
pub fn attr_by_index(
&self, index_type: IndexType, iteration_order: IterationOrder, index: u64,
) -> Result<Attribute> {
Attribute::from_id(h5try!(H5Aopen_by_idx(
self.id(),
b".\0".as_ptr().cast::<c_char>(),
index_type.into(),
iteration_order.into(),
index,
H5P_DEFAULT,
H5P_DEFAULT
)))
}
pub fn attr_info(&self, name: &str) -> Result<AttrInfo> {
let name = to_cstring(name)?;
let mut info = MaybeUninit::<H5A_info_t>::uninit();
h5call!(H5Aget_info_by_name(
self.id(),
b".\0".as_ptr().cast::<c_char>(),
name.as_ptr(),
info.as_mut_ptr(),
H5P_DEFAULT
))?;
let info = unsafe { info.assume_init() };
Ok(AttrInfo::from(&info))
}
pub fn attr_names(&self) -> Result<Vec<String>> {
self.attr_names_by(IndexType::Name, IterationOrder::Increasing)
}
pub fn attr_names_by(
&self, index_type: IndexType, iteration_order: IterationOrder,
) -> Result<Vec<String>> {
let mut names = vec![];
self.iter_attrs(index_type, iteration_order, |name, _| {
names.push(name.to_owned());
Ok(())
})?;
Ok(names)
}
pub fn attrs(
&self, index_type: IndexType, iteration_order: IterationOrder,
) -> Result<Vec<(String, AttrInfo)>> {
let mut attrs = vec![];
self.iter_attrs(index_type, iteration_order, |name, info| {
attrs.push((name.to_owned(), info));
Ok(())
})?;
Ok(attrs)
}
pub fn iter_attrs<F>(
&self, index_type: IndexType, iteration_order: IterationOrder, mut op: F,
) -> Result<()>
where
F: FnMut(&str, AttrInfo) -> Result<()>,
{
self.iter_attrs_from(IterationCursor::start(index_type, iteration_order), |name, info| {
op(name, info)?;
Ok(None::<()>)
})?;
Ok(())
}
pub fn find_attr<B, F>(
&self, index_type: IndexType, iteration_order: IterationOrder, op: F,
) -> Result<Option<B>>
where
F: FnMut(&str, AttrInfo) -> Result<Option<B>>,
{
match self.iter_attrs_from(IterationCursor::start(index_type, iteration_order), op)? {
Some((value, _)) => Ok(Some(value)),
None => Ok(None),
}
}
pub fn iter_attrs_from<B, F>(
&self, cursor: IterationCursor, op: F,
) -> Result<Option<(B, IterationCursor)>>
where
F: FnMut(&str, AttrInfo) -> Result<Option<B>>,
{
visit(
cursor,
|| Ok(self.loc_info()?.num_attrs as u64),
op,
|index_type, iteration_order, position, callback, op_data| unsafe {
H5Aiterate2(
self.id(),
index_type,
iteration_order,
position,
Some(callback),
op_data,
)
},
)
}
pub fn delete_attr(&self, name: &str) -> Result<()> {
let name = to_cstring(name)?;
h5call!(H5Adelete(self.id(), name.as_ptr()))?;
Ok(())
}
pub fn loc_info(&self) -> Result<LocationInfo> {
H5O_get_info(self.id(), true)
}
pub fn loc_type(&self) -> Result<LocationType> {
Ok(H5O_get_info(self.id(), false)?.loc_type)
}
pub fn loc_info_by_name(&self, name: &str) -> Result<LocationInfo> {
let name = to_cstring(name)?;
H5O_get_info_by_name(self.id(), name.as_ptr(), true)
}
pub fn loc_type_by_name(&self, name: &str) -> Result<LocationType> {
let name = to_cstring(name)?;
Ok(H5O_get_info_by_name(self.id(), name.as_ptr(), false)?.loc_type)
}
pub fn open_by_token(&self, token: LocationToken) -> Result<Self> {
H5O_open_by_token(self.id(), token)
}
pub fn reference<R: ObjectReference>(&self, name: &str) -> Result<R> {
R::create(self, name)
}
pub fn dereference<R: ObjectReference>(&self, reference: &R) -> Result<ReferencedObject> {
reference.dereference(self)
}
pub fn copy_to(&self, dst_loc: &Location, dst_name: &str) -> Result<()> {
self.copy_to_with_props(dst_loc, dst_name, None, None)
}
pub fn copy_to_with_props(
&self, dst_loc: &Location, dst_name: &str, ocpypl: Option<&PropertyList>,
lcpl: Option<&PropertyList>,
) -> Result<()> {
if let Some(pl) = ocpypl {
if !pl.is_class(PropertyListClass::ObjectCopy) {
fail!("Property list must be of class ObjectCopy");
}
}
if let Some(pl) = lcpl {
if !pl.is_class(PropertyListClass::LinkCreate) {
fail!("Property list must be of class LinkCreate");
}
}
let dst_name = to_cstring(dst_name)?;
let ocpypl_id = ocpypl.map_or(H5P_DEFAULT, |p| p.id());
let lcpl_id = lcpl.map_or(H5P_DEFAULT, |p| p.id());
h5call!(H5Ocopy(
self.id(), b".\0".as_ptr() as *const c_char, dst_loc.id(), dst_name.as_ptr(), ocpypl_id, lcpl_id ))?;
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocationToken(
#[cfg(not(feature = "1.12.0"))] haddr_t,
#[cfg(feature = "1.12.0")] H5O_token_t,
);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LocationType {
Group,
Dataset,
NamedDatatype,
#[cfg(feature = "1.12.0")]
#[cfg_attr(docsrs, doc(cfg(feature = "1.12.0")))]
TypeMap,
}
impl From<H5O_type_t> for LocationType {
fn from(loc_type: H5O_type_t) -> Self {
match loc_type {
H5O_type_t::H5O_TYPE_DATASET => Self::Dataset,
H5O_type_t::H5O_TYPE_NAMED_DATATYPE => Self::NamedDatatype,
#[cfg(feature = "1.12.0")]
H5O_type_t::H5O_TYPE_MAP => Self::TypeMap,
_ => Self::Group, }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocationInfo {
pub fileno: u64,
pub token: LocationToken,
pub loc_type: LocationType,
pub num_links: usize,
pub atime: i64,
pub mtime: i64,
pub ctime: i64,
pub btime: i64,
pub num_attrs: usize,
}
#[cfg(not(feature = "1.12.0"))]
impl From<H5O_info1_t> for LocationInfo {
fn from(info: H5O_info1_t) -> Self {
Self {
fileno: info.fileno as _,
token: LocationToken(info.addr),
loc_type: info.type_.into(),
num_links: info.rc as _,
atime: info.atime as _,
mtime: info.mtime as _,
ctime: info.ctime as _,
btime: info.btime as _,
num_attrs: info.num_attrs as _,
}
}
}
#[cfg(feature = "1.12.0")]
impl From<H5O_info2_t> for LocationInfo {
fn from(info: H5O_info2_t) -> Self {
Self {
fileno: info.fileno as _,
token: LocationToken(info.token),
loc_type: info.type_.into(),
num_links: info.rc as _,
atime: info.atime as _,
mtime: info.mtime as _,
ctime: info.ctime as _,
btime: info.btime as _,
num_attrs: info.num_attrs as _,
}
}
}
#[cfg(feature = "1.10.3")]
fn info_fields(full: bool) -> c_uint {
if full { H5O_INFO_BASIC | H5O_INFO_NUM_ATTRS | H5O_INFO_TIME } else { H5O_INFO_BASIC }
}
#[allow(non_snake_case, unused_variables)]
pub(crate) fn H5O_get_info(loc_id: hid_t, full: bool) -> Result<LocationInfo> {
let mut info_buf = MaybeUninit::uninit();
let info_ptr = info_buf.as_mut_ptr();
#[cfg(feature = "1.12.0")]
h5call!(H5Oget_info3(loc_id, info_ptr, info_fields(full)))?;
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
h5call!(H5Oget_info2(loc_id, info_ptr, info_fields(full)))?;
#[cfg(not(feature = "1.10.3"))]
h5call!(H5Oget_info1(loc_id, info_ptr))?;
let info = unsafe { info_buf.assume_init() };
Ok(info.into())
}
#[allow(non_snake_case, unused_variables)]
fn H5O_get_info_by_name(loc_id: hid_t, name: *const c_char, full: bool) -> Result<LocationInfo> {
let mut info_buf = MaybeUninit::uninit();
let info_ptr = info_buf.as_mut_ptr();
#[cfg(feature = "1.12.0")]
h5call!(H5Oget_info_by_name3(loc_id, name, info_ptr, info_fields(full), H5P_DEFAULT))?;
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
h5call!(H5Oget_info_by_name2(loc_id, name, info_ptr, info_fields(full), H5P_DEFAULT))?;
#[cfg(not(feature = "1.10.3"))]
h5call!(H5Oget_info_by_name1(loc_id, name, info_ptr, H5P_DEFAULT))?;
let info = unsafe { info_buf.assume_init() };
Ok(info.into())
}
#[allow(non_snake_case)]
fn H5O_open_by_token(loc_id: hid_t, token: LocationToken) -> Result<Location> {
#[cfg(not(feature = "1.12.0"))]
{
Location::from_id(h5call!(H5Oopen_by_addr(loc_id, token.0))?)
}
#[cfg(feature = "1.12.0")]
{
Location::from_id(h5call!(H5Oopen_by_token(loc_id, token.0))?)
}
}
#[cfg(test)]
pub mod tests {
use crate::hl::plist::common::AttrCreationOrder;
#[cfg(feature = "1.10.2")]
use crate::hl::plist::file_access::LibraryVersion;
use crate::hl::plist::link_create::CharEncoding;
use crate::{hl::plist::object_copy::ObjectCopy, internal_prelude::*, plist::LinkCreate};
#[test]
pub fn test_filename() {
with_tmp_path(|path| {
assert_eq!(File::create(&path).unwrap().filename(), path.to_str().unwrap());
})
}
#[test]
pub fn test_name() {
with_tmp_file(|file| {
assert_eq!(file.name(), "/");
})
}
#[test]
pub fn test_file() {
with_tmp_file(|file| {
assert_eq!(file.file().unwrap().id(), file.id());
})
}
#[test]
pub fn test_comment() {
#[allow(deprecated)]
with_tmp_file(|file| {
assert!(file.comment().is_none());
assert!(file.set_comment("foo").is_ok());
assert_eq!(file.comment().unwrap(), "foo");
assert!(file.clear_comment().is_ok());
assert!(file.comment().is_none());
})
}
#[test]
pub fn test_location_info() {
let new_file = |path| {
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
File::with_options().with_fapl(|p| p.libver_v110()).create(path)
} else {
File::create(path)
}
}
};
with_tmp_path(|path| {
let file = new_file(path).unwrap();
let token = {
let group = file.create_group("group").unwrap();
assert_eq!(file.loc_type_by_name("group").unwrap(), LocationType::Group);
let info = group.loc_info().unwrap();
assert_eq!(info.num_links, 1);
assert_eq!(info.loc_type, LocationType::Group);
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
assert!(info.btime > 0);
} else {
assert_eq!(info.btime, 0);
}
}
assert_eq!(info.btime == 0, info.mtime == 0);
assert_eq!(info.btime == 0, info.ctime == 0);
assert_eq!(info.btime == 0, info.atime == 0);
assert_eq!(info.num_attrs, 0);
info.token
};
let group = file.open_by_token(token).unwrap().as_group().unwrap();
assert_eq!(group.name(), "/group");
let token = {
let var = group
.new_dataset_builder()
.obj_track_times(true)
.empty::<i8>()
.create("var")
.unwrap();
var.new_attr::<i16>().create("attr1").unwrap();
var.new_attr::<i32>().create("attr2").unwrap();
group.link_hard("var", "hard1").unwrap();
group.link_hard("var", "hard2").unwrap();
group.link_hard("var", "hard3").unwrap();
group.link_hard("var", "hard4").unwrap();
group.link_hard("var", "hard5").unwrap();
group.link_soft("var", "soft1").unwrap();
group.link_soft("var", "soft2").unwrap();
group.link_soft("var", "soft3").unwrap();
assert_eq!(file.loc_type_by_name("/group/var").unwrap(), LocationType::Dataset);
let info = var.loc_info().unwrap();
assert_eq!(info.num_links, 6); assert_eq!(info.loc_type, LocationType::Dataset);
assert!(info.ctime > 0);
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
assert!(info.btime > 0);
} else {
assert_eq!(info.btime, 0);
}
}
assert_eq!(info.btime == 0, info.mtime == 0);
assert_eq!(info.btime == 0, info.atime == 0);
assert_eq!(info.num_attrs, 2);
info.token
};
let var = file.open_by_token(token).unwrap();
assert!(var.name().starts_with("/group/hard"));
let info = file.loc_info_by_name("group").unwrap();
let group = file.open_by_token(info.token).unwrap();
assert_eq!(group.name(), "/group");
let info = file.loc_info_by_name("/group/var").unwrap();
let var = file.open_by_token(info.token).unwrap();
assert!(var.name().starts_with("/group/hard"));
assert!(file.loc_info_by_name("gibberish").is_err());
})
}
#[test]
pub fn test_copy_dataset_between_files() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let src_group = src_file.create_group("src_group").unwrap();
let src_dataset =
src_group.new_dataset::<i32>().shape([5]).create("src_group").unwrap();
src_dataset.write(&[1, 2, 3, 4, 5]).unwrap();
let src_attr = src_dataset.new_attr::<f64>().create("src_attr").unwrap();
src_attr.write_scalar(&42.0).unwrap();
let dst_file = File::create(&dst_path).unwrap();
let dst_group = dst_file.create_group("dst_group").unwrap();
src_dataset.copy_to(&dst_group, "copied_dataset").unwrap();
let copied_dataset = dst_group.dataset("copied_dataset").unwrap();
let read_data: Vec<i32> = copied_dataset.read_1d().unwrap().to_vec();
assert_eq!(read_data, &[1, 2, 3, 4, 5]);
let copied_attr = copied_dataset.attr("src_attr").unwrap();
let attr_value: f64 = copied_attr.read_scalar().unwrap();
assert_eq!(attr_value, 42.0);
})
})
}
#[test]
pub fn test_copy_group_with_nested_content() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let group1 = src_file.create_group("group1").unwrap();
let subgroup = group1.create_group("subgroup").unwrap();
let ds1 = group1.new_dataset::<i32>().shape([3]).create("dataset1").unwrap();
ds1.write(&[10, 20, 30]).unwrap();
let ds2 = subgroup.new_dataset::<f64>().shape([2]).create("dataset2").unwrap();
ds2.write(&[1.5, 2.5]).unwrap();
let dst_file = File::create(&dst_path).unwrap();
group1.copy_to(&dst_file, "copied_group1").unwrap();
let copied_group = dst_file.group("copied_group1").unwrap();
let copied_ds1 = copied_group.dataset("dataset1").unwrap();
let data1: Vec<i32> = copied_ds1.read_1d().unwrap().to_vec();
assert_eq!(data1, vec![10, 20, 30]);
let copied_subgroup = copied_group.group("subgroup").unwrap();
let copied_ds2 = copied_subgroup.dataset("dataset2").unwrap();
let data2: Vec<f64> = copied_ds2.read_1d().unwrap().to_vec();
assert_eq!(data2, vec![1.5, 2.5]);
})
})
}
#[test]
pub fn test_copy_without_attributes() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let dataset = src_file.new_dataset::<i32>().shape([3]).create("data").unwrap();
dataset.write(&[10, 20, 30]).unwrap();
let src_attr = dataset.new_attr::<i32>().create("foo_attr").unwrap();
src_attr.write_scalar(&100).unwrap();
let dst_file = File::create(&dst_path).unwrap();
let ocpypl = ObjectCopy::build().copy_without_attr(true).finish().unwrap();
dataset
.copy_to_with_props(&dst_file, "copied_no_attrs", Some(&ocpypl), None)
.unwrap();
src_file.close().unwrap();
let copied = dst_file.dataset("copied_no_attrs").unwrap();
let copied_data: Vec<i32> = copied.read_1d().unwrap().to_vec();
assert_eq!(copied_data, vec![10, 20, 30]);
let attr_names = copied.attr_names().unwrap();
assert!(attr_names.is_empty(), "Expected no attributes, but found: {attr_names:?}",);
})
})
}
#[test]
pub fn test_copy_with_link_create_intermediate_groups() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let dataset = src_file.new_dataset::<i32>().shape([3]).create("data").unwrap();
dataset.write(&[100, 200, 300]).unwrap();
let dst_file = File::create(&dst_path).unwrap();
assert!(dataset.copy_to(&dst_file, "level1/level2/level3/copied_data").is_err());
dataset
.copy_to_with_props(
&dst_file,
"level1/level2/level3/copied_data",
None,
Some(
&LinkCreate::build().create_intermediate_group(true).finish().unwrap(),
),
)
.unwrap();
assert!(dst_file.group("level1").is_ok());
assert!(dst_file.group("level1/level2").is_ok());
assert!(dst_file.group("level1/level2/level3").is_ok());
let copied = dst_file.dataset("level1/level2/level3/copied_data").unwrap();
let data: Vec<i32> = copied.read_1d().unwrap().to_vec();
assert_eq!(data, vec![100, 200, 300]);
})
})
}
#[test]
pub fn test_iter_attrs_order() {
with_tmp_file(|file| {
let obj = file.create_group("o").unwrap();
for name in ["foo", "123", "bar"] {
obj.new_attr::<u32>().create(name).unwrap();
}
let names = |order| obj.attr_names_by(IndexType::Name, order).unwrap();
assert_eq!(names(IterationOrder::Increasing), ["123", "bar", "foo"]);
assert_eq!(names(IterationOrder::Decreasing), ["foo", "bar", "123"]);
assert_eq!(obj.attr_names().unwrap(), ["123", "bar", "foo"]);
let empty = file.create_group("empty").unwrap();
assert!(
empty.attr_names_by(IndexType::Name, IterationOrder::Native).unwrap().is_empty()
);
})
}
#[test]
pub fn test_iter_attrs_creation_order() {
with_tmp_file(|file| {
let obj = file
.create_group_builder()
.with_gcpl(|gcpl| gcpl.attr_creation_order(AttrCreationOrder::Tracked))
.create("o")
.unwrap();
obj.new_attr::<u32>().create("foo").unwrap();
obj.new_attr::<u64>().char_encoding(CharEncoding::Ascii).create("123").unwrap();
obj.new_attr::<u32>().create("bar").unwrap();
let attr = |name: &str, order, char_encoding, data_size| {
(
name.to_owned(),
AttrInfo { creation_order: Some(order), char_encoding, data_size },
)
};
let foo = attr("foo", 0, CharEncoding::Utf8, 4);
let num = attr("123", 1, CharEncoding::Ascii, 8);
let bar = attr("bar", 2, CharEncoding::Utf8, 4);
let attrs = |order| obj.attrs(IndexType::CreationOrder, order).unwrap();
assert_eq!(attrs(IterationOrder::Increasing), [foo.clone(), num.clone(), bar.clone()]);
assert_eq!(attrs(IterationOrder::Decreasing), [bar, num, foo]);
})
}
#[test]
pub fn test_find_attr() {
with_tmp_file(|file| {
for name in ["a", "b", "c"] {
file.new_attr::<u32>().create(name).unwrap();
}
let find = |wanted: &str| {
let mut visited = vec![];
let found = file
.find_attr(IndexType::Name, IterationOrder::Increasing, |name, info| {
visited.push(name.to_owned());
if name == wanted { Ok(Some(info.data_size)) } else { Ok(None) }
})
.unwrap();
(found, visited)
};
let (found, visited) = find("b");
assert_eq!(found, Some(4));
assert_eq!(visited, ["a", "b"]);
let (found, visited) = find("z");
assert_eq!(found, None);
assert_eq!(visited, ["a", "b", "c"]);
})
}
#[test]
pub fn test_iter_attrs_from() {
with_tmp_file(|file| {
for name in ["a", "b", "c"] {
file.new_attr::<u32>().create(name).unwrap();
}
let start = IterationCursor::start(IndexType::Name, IterationOrder::Increasing);
let stop_at = |cursor, wanted: &str| {
let mut visited = vec![];
let stopped = file
.iter_attrs_from(cursor, |name, _| {
visited.push(name.to_owned());
if name == wanted { Ok(Some(())) } else { Ok(None) }
})
.unwrap();
(stopped, visited)
};
let (stopped, visited) = stop_at(start, "b");
assert_eq!(visited, ["a", "b"]);
assert_eq!(stopped, Some(((), start.skip(2))));
let (stopped, visited) = stop_at(start.skip(2), "z");
assert_eq!(visited, ["c"]);
assert_eq!(stopped, None);
let (stopped, visited) = stop_at(start.skip(3), "a");
assert!(visited.is_empty());
assert_eq!(stopped, None);
})
}
#[test]
pub fn test_attr_by_index() {
with_tmp_file(|file| {
let obj = file
.create_group_builder()
.with_gcpl(|gcpl| gcpl.attr_creation_order(AttrCreationOrder::Tracked))
.create("o")
.unwrap();
for name in ["c", "a", "b"] {
obj.new_attr::<u32>().create(name).unwrap();
}
let name = |index_type, iteration_order, index| {
obj.attr_by_index(index_type, iteration_order, index).unwrap().name()
};
assert_eq!(name(IndexType::CreationOrder, IterationOrder::Increasing, 1), "a");
assert_eq!(name(IndexType::CreationOrder, IterationOrder::Decreasing, 0), "b");
assert_eq!(name(IndexType::Name, IterationOrder::Increasing, 2), "c");
let err =
obj.attr_by_index(IndexType::Name, IterationOrder::Increasing, 3).unwrap_err();
assert!(err.contains_major(MajorErrorCode::Args), "{err:?}");
assert!(err.contains_minor(MinorErrorCode::BadValue), "{err:?}");
})
}
#[test]
pub fn test_attr_info() {
with_tmp_file(|file| {
file.new_attr::<u32>().create("a").unwrap();
file.new_attr::<u64>().char_encoding(CharEncoding::Ascii).create("b").unwrap();
let expected = AttrInfo {
creation_order: Some(1),
char_encoding: CharEncoding::Ascii,
data_size: 8,
};
assert_eq!(file.attr_info("b").unwrap(), expected);
let reported = file
.find_attr(IndexType::Name, IterationOrder::Increasing, |name, info| {
if name == "b" { Ok(Some(info)) } else { Ok(None) }
})
.unwrap();
assert_eq!(reported, Some(expected));
let err = file.attr_info("missing").unwrap_err();
assert!(err.contains_major(MajorErrorCode::Attr), "{err:?}");
assert!(err.contains_minor(MinorErrorCode::NotFound), "{err:?}");
})
}
fn attrs_by_creation_order(
file: &File, name: &str, creation_order: AttrCreationOrder, dense: bool,
) -> Vec<(String, Option<u32>)> {
let obj = file
.create_group_builder()
.with_gcpl(|gcpl| {
gcpl.attr_creation_order(creation_order);
if dense {
gcpl.attr_phase_change(0, 0);
}
gcpl
})
.create(name)
.unwrap();
for name in ["c", "a", "b"] {
obj.new_attr::<u32>().create(name).unwrap();
}
let attrs = obj.attrs(IndexType::CreationOrder, IterationOrder::Increasing).unwrap();
attrs.into_iter().map(|(name, info)| (name, info.creation_order)).collect()
}
fn assert_storage_sequence(attrs: &[(String, Option<u32>)]) {
let expected =
[("c".to_owned(), Some(0)), ("a".to_owned(), Some(1)), ("b".to_owned(), Some(2))];
assert_eq!(attrs, expected);
}
fn assert_name_index_fallback(attrs: &[(String, Option<u32>)]) {
let mut names: Vec<&str> = attrs.iter().map(|(name, _)| name.as_str()).collect();
names.sort_unstable();
assert_eq!(names, ["a", "b", "c"]);
assert!(attrs.iter().all(|(_, order)| order.is_none()), "{attrs:?}");
}
#[test]
pub fn test_iter_attrs_untracked_default_format() {
with_tmp_file(|file| {
let compact =
attrs_by_creation_order(&file, "compact", AttrCreationOrder::Untracked, false);
assert_storage_sequence(&compact);
let dense = attrs_by_creation_order(&file, "dense", AttrCreationOrder::Untracked, true);
if cfg!(feature = "2.0.0") {
assert_name_index_fallback(&dense);
} else {
assert_storage_sequence(&dense);
}
})
}
#[cfg(feature = "1.10.2")]
#[test]
pub fn test_iter_attrs_v2_object_header() {
for low in [LibraryVersion::V18, LibraryVersion::latest()] {
with_tmp_path(|path| {
let file = File::with_options()
.with_fapl(|fapl| fapl.libver_bounds(low, LibraryVersion::latest()))
.create(&path)
.unwrap();
let attrs = |name, creation_order, dense| {
attrs_by_creation_order(&file, name, creation_order, dense)
};
assert_storage_sequence(&attrs(
"tracked_compact",
AttrCreationOrder::Tracked,
false,
));
assert_storage_sequence(&attrs("tracked_dense", AttrCreationOrder::Tracked, true));
assert_storage_sequence(&attrs(
"untracked_compact",
AttrCreationOrder::Untracked,
false,
));
assert_name_index_fallback(&attrs(
"untracked_dense",
AttrCreationOrder::Untracked,
true,
));
})
}
}
}