Skip to main content

hdf5_metno/
error.rs

1use std::convert::Infallible;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5use std::ops::Deref;
6use std::panic;
7use std::ptr::{self, addr_of_mut};
8
9use ndarray::ShapeError;
10
11use crate::error_codes::{MajorErrorCode, MinorErrorCode};
12
13#[cfg(not(feature = "1.10.0"))]
14use hdf5_sys::h5::hssize_t;
15use hdf5_sys::h5e::{
16    H5E_DEFAULT, H5E_WALK_DOWNWARD, H5E_auto2_t, H5E_error2_t, H5Eget_current_stack, H5Eget_msg,
17    H5Eprint2, H5Eset_auto2, H5Ewalk2,
18};
19
20use crate::internal_prelude::*;
21
22/// Silence errors emitted by `hdf5`
23///
24/// Safety: This version is not thread-safe and must be synchronised
25/// with other calls to `hdf5`
26pub(crate) unsafe fn silence_errors_no_sync(silence: bool) {
27    // Cast function with different argument types. This is safe because H5Eprint2 is
28    // documented to support this interface
29    let h5eprint: Option<unsafe extern "C" fn(hid_t, *mut libc::FILE) -> herr_t> =
30        Some(H5Eprint2 as _);
31    let h5eprint: H5E_auto2_t = std::mem::transmute(h5eprint);
32    H5Eset_auto2(H5E_DEFAULT, if silence { None } else { h5eprint }, ptr::null_mut());
33}
34
35/// Silence errors emitted by `hdf5`
36pub fn silence_errors(silence: bool) {
37    h5lock!(silence_errors_no_sync(silence));
38}
39
40/// A stack of error records from an HDF5 library call.
41#[repr(transparent)]
42#[derive(Clone)]
43pub struct ErrorStack(Handle);
44
45impl ObjectClass for ErrorStack {
46    const NAME: &'static str = "errorstack";
47    const VALID_TYPES: &'static [H5I_type_t] = &[H5I_ERROR_STACK];
48
49    fn from_handle(handle: Handle) -> Self {
50        Self(handle)
51    }
52
53    fn handle(&self) -> &Handle {
54        &self.0
55    }
56
57    // TODO: short_repr()
58}
59
60impl ErrorStack {
61    pub(crate) fn from_current() -> Result<Self> {
62        let stack_id = h5lock!(H5Eget_current_stack());
63        Handle::try_new(stack_id).map(Self)
64    }
65
66    /// Expands the error stack to a format which is easier to handle
67    // known HDF5 bug: H5Eget_msg() used in this function may corrupt
68    // the current stack, so we use self over &self
69    pub fn expand(self) -> Result<ExpandedErrorStack> {
70        struct CallbackData {
71            stack: ExpandedErrorStack,
72            err: Option<Error>,
73        }
74        unsafe extern "C" fn callback(
75            _: c_uint, err_desc: *const H5E_error2_t, data: *mut c_void,
76        ) -> herr_t {
77            panic::catch_unwind(|| unsafe {
78                let data = &mut *(data.cast::<CallbackData>());
79                if data.err.is_some() {
80                    return 0;
81                }
82                let closure = |e: H5E_error2_t| -> Result<ErrorFrame> {
83                    let (desc, func) = (string_from_cstr(e.desc), string_from_cstr(e.func_name));
84                    let major = get_h5_str(|m, s| H5Eget_msg(e.maj_num, ptr::null_mut(), m, s))?;
85                    let minor = get_h5_str(|m, s| H5Eget_msg(e.min_num, ptr::null_mut(), m, s))?;
86                    let major_code = MajorErrorCode::from_id(e.maj_num);
87                    let minor_code = MinorErrorCode::from_id(e.min_num);
88                    let line = e.line.try_into().unwrap();
89                    let file_name = string_from_cstr(e.file_name);
90                    Ok(ErrorFrame::new(
91                        &desc, &func, &major, &minor, major_code, minor_code, line, file_name,
92                    ))
93                };
94                match closure(*err_desc) {
95                    Ok(frame) => {
96                        data.stack.push(frame);
97                    }
98                    Err(err) => {
99                        data.err = Some(err);
100                    }
101                }
102                0
103            })
104            .unwrap_or(-1)
105        }
106
107        let mut data = CallbackData { stack: ExpandedErrorStack::new(), err: None };
108        let data_ptr: *mut c_void = addr_of_mut!(data).cast::<c_void>();
109
110        let stack_id = self.handle().id();
111        h5lock!({
112            H5Ewalk2(stack_id, H5E_WALK_DOWNWARD, Some(callback), data_ptr);
113        });
114
115        data.err.map_or(Ok(data.stack), Err)
116    }
117}
118
119/// An error record for an HDF5 library call.
120#[derive(Clone, Debug)]
121pub struct ErrorFrame {
122    desc: String,
123    func: String,
124    major: String,
125    minor: String,
126    description: String,
127    major_code: MajorErrorCode,
128    minor_code: MinorErrorCode,
129    line: usize,
130    file_name: String,
131}
132
133impl ErrorFrame {
134    pub(crate) fn new(
135        desc: &str, func: &str, major: &str, minor: &str, major_code: MajorErrorCode,
136        minor_code: MinorErrorCode, line: usize, file_name: String,
137    ) -> Self {
138        Self {
139            desc: desc.into(),
140            func: func.into(),
141            major: major.into(),
142            minor: minor.into(),
143            description: format!("{func}(): {desc}"),
144            major_code,
145            minor_code,
146            line,
147            file_name: file_name.into(),
148        }
149    }
150
151    /// Returns the error description.
152    pub fn desc(&self) -> &str {
153        self.desc.as_ref()
154    }
155
156    /// Returns the function name.
157    pub fn func(&self) -> &str {
158        self.func.as_ref()
159    }
160
161    /// Returns the major code: which part of the library failed.
162    pub fn major_code(&self) -> MajorErrorCode {
163        self.major_code
164    }
165
166    /// Returns the minor code: how the operation failed.
167    pub fn minor_code(&self) -> MinorErrorCode {
168        self.minor_code
169    }
170
171    /// Returns a message with the error description and the relevant function name.
172    pub fn description(&self) -> &str {
173        self.description.as_ref()
174    }
175
176    /// Returns the line in the source which triggered the error.
177    pub fn line(&self) -> usize {
178        self.line
179    }
180
181    /// Returns the file name in the source which triggered the error.
182    pub fn file_name(&self) -> &str {
183        self.file_name.as_ref()
184    }
185
186    /// Returns a message with the error description and the relevant function name.
187    pub fn detail(&self) -> Option<String> {
188        Some(format!("Error in {}(): {} [{}: {}]", self.func, self.desc, self.major, self.minor))
189    }
190}
191
192/// A converted [`ErrorStack`] with methods to access [`ErrorFrame`] data.
193#[derive(Clone, Debug)]
194pub struct ExpandedErrorStack {
195    frames: Vec<ErrorFrame>,
196    description: Option<String>,
197}
198
199impl Deref for ExpandedErrorStack {
200    type Target = [ErrorFrame];
201
202    fn deref(&self) -> &Self::Target {
203        &self.frames
204    }
205}
206
207impl Default for ExpandedErrorStack {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213impl ExpandedErrorStack {
214    pub(crate) fn new() -> Self {
215        Self { frames: Vec::new(), description: None }
216    }
217
218    pub(crate) fn push(&mut self, frame: ErrorFrame) {
219        self.frames.push(frame);
220
221        self.description = match self.frames[..] {
222            [] => unreachable!(),
223            [ref first] => Some(first.description().to_owned()),
224            [ref first, .., ref last] => Some(format!("{}: {}", first.description(), last.desc())),
225        }
226    }
227
228    /// Returns the top [`ErrorFrame`] of the stack, or `None` if it is empty.
229    pub fn top(&self) -> Option<&ErrorFrame> {
230        self.first()
231    }
232
233    /// Returns the major code of every frame, outermost first.
234    pub fn major_codes(&self) -> impl DoubleEndedIterator<Item = MajorErrorCode> + '_ {
235        self.frames.iter().map(ErrorFrame::major_code)
236    }
237
238    /// Returns the minor code of every frame, outermost first.
239    pub fn minor_codes(&self) -> impl DoubleEndedIterator<Item = MinorErrorCode> + '_ {
240        self.frames.iter().map(ErrorFrame::minor_code)
241    }
242
243    /// Returns whether any frame in the stack carries the given major code.
244    pub fn contains_major(&self, code: MajorErrorCode) -> bool {
245        self.major_codes().any(|c| c == code)
246    }
247
248    /// Returns whether any frame in the stack carries the given minor code.
249    ///
250    /// The cause is usually on an inner frame, so prefer this over [`top`](Self::top): opening
251    /// a corrupt file reports `CantOpenFile` on the outermost frame and `NotHdf5` further down.
252    pub fn contains_minor(&self, code: MinorErrorCode) -> bool {
253        self.minor_codes().any(|c| c == code)
254    }
255
256    /// Returns the description of the error on top of the stack.
257    pub fn description(&self) -> &str {
258        match self.description {
259            None => "unknown library error",
260            Some(ref desc) => desc.as_ref(),
261        }
262    }
263
264    /// Returns a detailed message for the error on top of the stack, or `None` if it is empty.
265    pub fn detail(&self) -> Option<String> {
266        self.top().and_then(ErrorFrame::detail)
267    }
268}
269
270/// The error type for HDF5-related functions.
271#[derive(Clone)]
272pub enum Error {
273    /// An error occurred in the C API of the HDF5 library. Full error stack is captured.
274    HDF5(ErrorStack),
275    /// A user error occurred in the high-level Rust API (e.g., invalid user input).
276    Internal(String),
277}
278
279/// A type for results generated by HDF5-related functions where the `Err` type is
280/// set to `hdf5::Error`.
281pub type Result<T, E = Error> = ::std::result::Result<T, E>;
282
283impl Error {
284    /// Obtain the current error stack. The stack might be empty, which
285    /// will result in a valid error stack
286    pub fn query() -> Result<Self> {
287        if let Ok(stack) = ErrorStack::from_current() {
288            Ok(Self::HDF5(stack))
289        } else {
290            Err(Self::Internal("Could not get errorstack".to_owned()))
291        }
292    }
293
294    /// Returns the expanded error stack, or `None` if there is none to expand.
295    pub fn stack(&self) -> Option<ExpandedErrorStack> {
296        match *self {
297            Self::Internal(_) => None,
298            Self::HDF5(ref stack) => stack.clone().expand().ok(),
299        }
300    }
301
302    /// Returns whether any frame carries the given major code, `false` for
303    /// [`Internal`](Self::Internal) errors.
304    pub fn contains_major(&self, code: MajorErrorCode) -> bool {
305        self.stack().is_some_and(|s| s.contains_major(code))
306    }
307
308    /// Returns whether any frame carries the given minor code, `false` for
309    /// [`Internal`](Self::Internal) errors.
310    ///
311    /// ```no_run
312    /// use hdf5_metno as hdf5;
313    /// use hdf5::MinorErrorCode;
314    ///
315    /// # let dir = tempfile::tempdir().unwrap();
316    /// # let path = dir.path().join("corrupt.h5");
317    /// # std::fs::write(&path, b"not an HDF5 file").unwrap();
318    /// let err = hdf5::File::open(&path).unwrap_err();
319    /// assert!(err.contains_minor(MinorErrorCode::NotHdf5));
320    /// ```
321    pub fn contains_minor(&self, code: MinorErrorCode) -> bool {
322        self.stack().is_some_and(|s| s.contains_minor(code))
323    }
324}
325
326impl From<&str> for Error {
327    fn from(desc: &str) -> Self {
328        Self::Internal(desc.into())
329    }
330}
331
332impl From<String> for Error {
333    fn from(desc: String) -> Self {
334        Self::Internal(desc)
335    }
336}
337
338impl From<Infallible> for Error {
339    fn from(_: Infallible) -> Self {
340        unreachable!("Infallible error can never be constructed")
341    }
342}
343
344impl fmt::Debug for Error {
345    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346        match *self {
347            Self::Internal(ref desc) => f.write_str(desc),
348            Self::HDF5(ref stack) => match stack.clone().expand() {
349                Ok(stack) => f.write_str(stack.description()),
350                Err(_) => f.write_str("Could not get error stack"),
351            },
352        }
353    }
354}
355
356impl fmt::Display for Error {
357    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
358        match *self {
359            Self::Internal(ref desc) => f.write_str(desc),
360            Self::HDF5(ref stack) => match stack.clone().expand() {
361                Ok(stack) => f.write_str(stack.description()),
362                Err(_) => f.write_str("Could not get error stack"),
363            },
364        }
365    }
366}
367
368impl StdError for Error {}
369
370impl From<ShapeError> for Error {
371    fn from(err: ShapeError) -> Self {
372        format!("shape error: {err}").into()
373    }
374}
375
376impl From<Error> for io::Error {
377    fn from(err: Error) -> Self {
378        Self::new(io::ErrorKind::Other, err)
379    }
380}
381
382pub fn h5check<T: H5ErrorCode>(value: T) -> Result<T> {
383    H5ErrorCode::h5check(value)
384}
385
386#[allow(unused)]
387pub fn is_err_code<T: H5ErrorCode>(value: T) -> bool {
388    H5ErrorCode::is_err_code(value)
389}
390
391pub trait H5ErrorCode: Copy {
392    fn is_err_code(value: Self) -> bool;
393
394    fn h5check(value: Self) -> Result<Self> {
395        if Self::is_err_code(value) { Err(Error::query().unwrap_or_else(|e| e)) } else { Ok(value) }
396    }
397}
398
399impl H5ErrorCode for hsize_t {
400    fn is_err_code(value: Self) -> bool {
401        value == 0
402    }
403}
404
405impl H5ErrorCode for herr_t {
406    fn is_err_code(value: Self) -> bool {
407        value < 0
408    }
409}
410
411#[cfg(feature = "1.10.0")]
412impl H5ErrorCode for hid_t {
413    fn is_err_code(value: Self) -> bool {
414        value < 0
415    }
416}
417
418#[cfg(not(feature = "1.10.0"))]
419impl H5ErrorCode for hssize_t {
420    fn is_err_code(value: Self) -> bool {
421        value < 0
422    }
423}
424
425impl H5ErrorCode for libc::ssize_t {
426    fn is_err_code(value: Self) -> bool {
427        value < 0
428    }
429}
430
431#[cfg(test)]
432pub mod tests {
433    use hdf5_sys::h5p::{H5Pclose, H5Pcreate};
434
435    use crate::error_codes::{MAJOR_CODES, MINOR_CODES};
436    use crate::globals::{H5E_CANTOPENFILE, H5E_FILE, H5P_ROOT};
437    use crate::internal_prelude::*;
438    use crate::test::with_tmp_path;
439    use crate::{MajorErrorCode, MinorErrorCode};
440    use hdf5_sys::h5e::H5Eget_msg;
441    use std::fs;
442    use std::ptr;
443
444    use super::ExpandedErrorStack;
445
446    /// Checks every code this crate declares against the linked HDF5 itself: the description
447    /// must be byte-identical to what `H5Eget_msg` returns for that id. This is what validates
448    /// the transcribed table, and it fails if a variant is wired to the wrong global, if a
449    /// description literal is wrong, or if a renamed code's `meta` gates overlap so that the
450    /// wrong arm is selected.
451    #[test]
452    pub fn test_descriptions_match_the_linked_library() {
453        assert!(!MAJOR_CODES.is_empty() && !MINOR_CODES.is_empty());
454        for (&id, &code) in MAJOR_CODES.iter() {
455            assert_eq!(scrub(code.description().unwrap()), scrub(&msg_of(id)), "{code:?}");
456        }
457        for (&id, &code) in MINOR_CODES.iter() {
458            // H5E_LOGFAIL is upstream's binary-compatibility alias for H5E_LOGGING and carries
459            // its own message. Both ids resolve to Logging, so only the canonical one can
460            // match, therefor we pin the alias's message (instead of skipping it).
461            #[cfg(all(feature = "1.10.5", not(feature = "1.12.0")))]
462            if id == *crate::globals::H5E_LOGFAIL {
463                assert_eq!(code, MinorErrorCode::Logging);
464                assert_eq!(msg_of(id), "old H5E_LOGGING_g (maintained for binary compatibility)");
465                continue;
466            }
467            assert_eq!(scrub(code.description().unwrap()), scrub(&msg_of(id)), "{code:?}");
468        }
469    }
470
471    fn msg_of(id: hid_t) -> String {
472        h5lock!(get_h5_str(|m, s| H5Eget_msg(id, ptr::null_mut(), m, s))).unwrap()
473    }
474
475    /// Replace typos and the "atom" -> "ID" rename so that the string comparisons are stable across versions
476    fn scrub(s: &str) -> String {
477        s.replace("atom", "ID")
478            .replace("accessability", "accessibility")
479            .replace("accessibilty", "accessibility")
480    }
481
482    /// Checks the codes captured for a real frame vs. that frame's own text.
483    /// The test above checks the id -> description mapping, this one validates that `expand`
484    /// converts the right id to the right enum.
485    #[test]
486    pub fn test_frame_codes_agree_with_frame_text() {
487        with_tmp_path(|path| {
488            fs::write(&path, b"garbage data").unwrap();
489            let err = File::open(&path).unwrap_err();
490            let stack = err.stack().unwrap();
491            assert!(stack.len() >= 3, "expected a multi-frame stack, got {}", stack.len());
492
493            for frame in stack.iter() {
494                // detail() renders "Error in f(): desc [<major msg>: <minor msg>]" from the strings H5Eget_msg returned.
495                let major_code_desc = frame.major_code().description().unwrap();
496                let minor_code_desc = frame.minor_code().description().unwrap();
497                let maj_min_desc = format!("[{major_code_desc}: {minor_code_desc}]");
498                let detail = frame.detail().unwrap();
499                assert!(
500                    scrub(&detail).ends_with(&scrub(&maj_min_desc)),
501                    "{detail:?} does not end with {maj_min_desc:?}"
502                );
503            }
504            assert_eq!(stack.major_codes().count(), stack.len());
505            assert_eq!(stack.minor_codes().count(), stack.len());
506        });
507    }
508
509    /// Every id the linked HDF5 registers must map to exactly one variant, and no two variants
510    /// may share an id. The reverse is not asserted: a variant whose symbol this HDF5 lacks is
511    /// deliberately unreachable.
512    #[test]
513    pub fn test_symbol_mapping_is_unambiguous() {
514        for (&id, &code) in MAJOR_CODES.iter() {
515            assert!(MajorErrorCode::all().contains(&code), "{code:?} is not a declared variant");
516            assert_ne!(id, H5I_INVALID_HID);
517        }
518        for (&id, &code) in MINOR_CODES.iter() {
519            assert!(MinorErrorCode::all().contains(&code), "{code:?} is not a declared variant");
520            assert_ne!(id, H5I_INVALID_HID);
521        }
522        // the map is populated: these two exist in every supported HDF5
523        assert_eq!(MajorErrorCode::from_id(*H5E_FILE), MajorErrorCode::File);
524        assert_eq!(MinorErrorCode::from_id(*H5E_CANTOPENFILE), MinorErrorCode::CantOpenFile);
525    }
526
527    #[test]
528    pub fn test_unknown_error_code_is_preserved() {
529        // H5I_INVALID_HID is never a registered message id
530        let minor = MinorErrorCode::from_id(H5I_INVALID_HID);
531        assert_eq!(minor, MinorErrorCode::Other(H5I_INVALID_HID));
532        assert_eq!(minor.name(), None);
533        assert_eq!(minor.description(), None);
534        assert_eq!(minor.to_string(), format!("unknown error code ({H5I_INVALID_HID})"));
535
536        let major = MajorErrorCode::from_id(H5I_INVALID_HID);
537        assert_eq!(major, MajorErrorCode::Other(H5I_INVALID_HID));
538        assert_eq!(major.name(), None);
539        assert_eq!(major.description(), None);
540        assert_eq!(major.to_string(), format!("unknown error code ({H5I_INVALID_HID})"));
541
542        // a major id must not resolve as a minor code, or vice versa
543        assert_eq!(MinorErrorCode::from_id(*H5E_FILE), MinorErrorCode::Other(*H5E_FILE));
544        assert_eq!(
545            MajorErrorCode::from_id(*H5E_CANTOPENFILE),
546            MajorErrorCode::Other(*H5E_CANTOPENFILE)
547        );
548    }
549
550    #[test]
551    pub fn test_error_codes_on_stack() {
552        let err = h5lock!({
553            let plist_id = H5Pcreate(*H5P_ROOT);
554            H5Pclose(plist_id);
555            H5Pclose(plist_id);
556            Error::query()
557        })
558        .unwrap();
559        let stack = err.stack().unwrap();
560        let top = stack.top().unwrap();
561        assert_eq!(top.major_code(), MajorErrorCode::PropertyList);
562        assert_eq!(top.major_code().name(), Some("H5E_PLIST"));
563        assert_eq!(top.minor_code(), MinorErrorCode::CantFree);
564        assert!(err.contains_major(MajorErrorCode::PropertyList));
565        assert!(!err.contains_major(MajorErrorCode::File));
566        // codes are resolved for every frame, not just the top one
567        assert!(stack.major_codes().all(|c| !matches!(c, MajorErrorCode::Other(_))));
568        assert!(stack.minor_codes().all(|c| !matches!(c, MinorErrorCode::Other(_))));
569    }
570
571    /// The three file-open failure modes must be distinguishable by code, not by message text.
572    #[test]
573    pub fn test_file_error_codes_are_distinguishable() {
574        // a corrupt file is reported as NotHdf5 by an inner frame
575        with_tmp_path(|path| {
576            fs::write(&path, b"garbage data").unwrap();
577            let err = File::open_rw(&path).unwrap_err();
578            assert!(err.contains_minor(MinorErrorCode::NotHdf5), "{err:?}");
579            assert!(err.contains_major(MajorErrorCode::File));
580            // the VOL frames in a file-open stack must resolve to a named code (not Other)
581            #[cfg(feature = "1.12.0")]
582            assert!(err.contains_major(MajorErrorCode::VirtualObjectLayer), "{err:?}");
583            let stack = err.stack().unwrap();
584            assert!(stack.major_codes().all(|c| !matches!(c, MajorErrorCode::Other(_))), "{err:?}");
585            assert!(stack.minor_codes().all(|c| !matches!(c, MinorErrorCode::Other(_))), "{err:?}");
586        });
587
588        // a missing file reports CantOpenFile
589        with_tmp_path(|path| {
590            let err = File::open_rw(&path).unwrap_err();
591            assert!(err.contains_minor(MinorErrorCode::CantOpenFile), "{err:?}");
592            assert!(!err.contains_minor(MinorErrorCode::NotHdf5), "{err:?}");
593        });
594
595        // The specific minor code for creating over an existing file varies by HDF5 version and
596        // build (CantCreate, FileExists and CantOpenFile have all been seen), and the EEXIST is
597        // only visible as errno text in the innermost frame. So assert on what's stable: it is a
598        // File error and not a corrupt-superblock read
599        with_tmp_path(|path| {
600            File::create(&path).unwrap();
601            let err = File::create_excl(&path).unwrap_err();
602            let minors: Vec<_> = err.stack().unwrap().minor_codes().collect();
603            assert!(err.contains_major(MajorErrorCode::File), "minors={minors:?}: {err}");
604            assert!(!err.contains_minor(MinorErrorCode::NotHdf5), "minors={minors:?}: {err}");
605        });
606    }
607
608    /// Internal errors carry no HDF5 stack.
609    #[test]
610    pub fn test_internal_error_has_no_codes() {
611        let err = Error::Internal("nope".into());
612        assert!(err.stack().is_none());
613        assert!(!err.contains_minor(MinorErrorCode::NotHdf5));
614        assert!(!err.contains_major(MajorErrorCode::File));
615    }
616
617    #[test]
618    pub fn test_error_code_display() {
619        assert_eq!(MinorErrorCode::NotHdf5.to_string(), "Not an HDF5 file");
620        assert_eq!(MajorErrorCode::File.to_string(), "File accessibility");
621    }
622
623    #[test]
624    pub fn test_error_stack() {
625        let stack = h5lock!({
626            let plist_id = H5Pcreate(*H5P_ROOT);
627            H5Pclose(plist_id);
628            Error::query()
629        })
630        .unwrap();
631        let stack = match stack {
632            Error::HDF5(stack) => stack,
633            Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
634        }
635        .expand()
636        .unwrap();
637        assert!(stack.is_empty());
638
639        let stack = h5lock!({
640            let plist_id = H5Pcreate(*H5P_ROOT);
641            H5Pclose(plist_id);
642            H5Pclose(plist_id);
643            Error::query()
644        })
645        .unwrap();
646        let stack = match stack {
647            Error::HDF5(stack) => stack,
648            Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
649        }
650        .expand()
651        .unwrap();
652        assert_eq!(stack.description(), "H5Pclose(): can't close: can't locate ID");
653        assert_eq!(
654            &stack.detail().unwrap(),
655            "Error in H5Pclose(): can't close [Property lists: Unable to free object]"
656        );
657
658        assert!(stack.len() >= 2 && stack.len() <= 4); // depending on HDF5 version
659        assert!(!stack.is_empty());
660
661        assert_eq!(stack[0].description(), "H5Pclose(): can't close");
662        assert_eq!(
663            &stack[0].detail().unwrap(),
664            "Error in H5Pclose(): can't close \
665             [Property lists: Unable to free object]"
666        );
667
668        #[cfg(not(feature = "1.14.0"))]
669        {
670            assert_eq!(stack.last().unwrap().description(), "H5I_dec_ref(): can't locate ID");
671            assert_eq!(
672                &stack.last().unwrap().detail().unwrap(),
673                "Error in H5I_dec_ref(): can't locate ID \
674             [Object atom: Unable to find atom information (already closed?)]"
675            );
676        }
677        #[cfg(feature = "1.14.0")]
678        {
679            assert_eq!(stack.last().unwrap().description(), "H5I__dec_ref(): can't locate ID");
680            assert_eq!(
681                &stack.last().unwrap().detail().unwrap(),
682                "Error in H5I__dec_ref(): can't locate ID \
683             [Object ID: Unable to find ID information (already closed?)]"
684            );
685        }
686
687        let empty_stack = ExpandedErrorStack::new();
688        assert!(empty_stack.is_empty());
689        assert_eq!(empty_stack.len(), 0);
690    }
691
692    #[test]
693    pub fn test_h5call() {
694        let result_no_error = h5call!({
695            let plist_id = H5Pcreate(*H5P_ROOT);
696            H5Pclose(plist_id)
697        });
698        assert!(result_no_error.is_ok());
699
700        let result_error = h5call!({
701            let plist_id = H5Pcreate(*H5P_ROOT);
702            H5Pclose(plist_id);
703            H5Pclose(plist_id)
704        });
705        assert!(result_error.is_err());
706    }
707
708    #[test]
709    pub fn test_h5try() {
710        fn f1() -> Result<herr_t> {
711            h5try!(H5Pcreate(*H5P_ROOT));
712            Ok(100)
713        }
714
715        assert_eq!(f1().unwrap(), 100);
716
717        fn f2() -> Result<herr_t> {
718            h5try!(H5Pcreate(123456));
719            Ok(100)
720        }
721
722        assert!(f2().is_err());
723    }
724}