hwlocality/
errors.rs

1//! Generic error handling primitives
2//!
3//! While we do not shy away from using context-specific error types that
4//! provide higher-quality error messages, for some common patterns we do emit
5//! generic error types, which are implemented in this module.
6//
7// --- Implementation details ---
8//
9// At the implementation level, this is also the place where all the low-level
10// handling of hwloc errors is implemented.
11
12use crate::object::{TopologyObject, TopologyObjectID};
13#[cfg(doc)]
14use crate::topology::Topology;
15use derive_more::From;
16use errno::Errno;
17#[allow(unused)]
18#[cfg(test)]
19use similar_asserts::assert_eq;
20use std::{
21    error::Error,
22    ffi::{c_int, c_uint},
23    fmt::Debug,
24    ptr::NonNull,
25};
26use thiserror::Error;
27
28/// Set errno to an initial value, eventually bring it back to normal
29struct ErrnoGuard(Errno);
30//
31impl ErrnoGuard {
32    /// Set errno to a new value, schedule a reset when this is dropped
33    fn new(errno: Errno) -> Self {
34        let old_errno = errno::errno();
35        errno::set_errno(errno);
36        Self(old_errno)
37    }
38}
39//
40impl Drop for ErrnoGuard {
41    fn drop(&mut self) {
42        errno::set_errno(self.0);
43    }
44}
45
46/// Do something with errno checking
47///
48/// Call a user-provided callback, which tells if symptoms of a C-side error
49/// were observed. If so, check for appearance of nonzero errno values and
50/// report them.
51///
52/// When this function returns, errno is back to the state where it was before
53/// the user callback was invoked.
54fn check_errno<R: Copy + Ord>(
55    callback: impl FnOnce() -> R,
56    lowest_good_value: R,
57) -> (R, Option<Errno>) {
58    let _guard = ErrnoGuard::new(Errno(0));
59    let result = callback();
60    /// Outlined to reduce code bloat
61    fn interpret_errno(should_check_errno: bool) -> Option<Errno> {
62        should_check_errno
63            .then(|| {
64                let errno = errno::errno();
65                (errno != Errno(0)).then_some(errno)
66            })
67            .flatten()
68    }
69    (result, interpret_errno(result < lowest_good_value))
70}
71
72/// Raw error emitted by hwloc functions that follow the usual convention
73///
74/// Hwloc APIs almost always error out by returning -1 if they return an
75/// integer, or a null pointer if they return a pointer.
76///
77/// They may additionally change the value of errno to report additional detail
78/// about what happened.
79///
80/// If no additional detail is provided by the hwloc documentation, we will
81/// assume this error handling convention and report errors using the present
82/// struct. Where possible errno values are clarified in the hwloc docs, we will
83/// assume they are the only errors that can occur, translate them into a
84/// higher-level Rust errors and panic if another errno value is observed.
85#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
86#[error("{api} failed with errno {errno:?}")]
87pub struct RawHwlocError {
88    /// Hwloc entry point that failed
89    pub api: &'static str,
90
91    /// Observed errno value, if errno was set
92    #[cfg_attr(windows, doc = "")]
93    #[cfg_attr(windows, doc = "Note that even for entry points where hwloc is")]
94    #[cfg_attr(windows, doc = "documented to set errno, this member may be")]
95    #[cfg_attr(windows, doc = "`None` on Windows. This happens because ")]
96    #[cfg_attr(windows, doc = "Windows has multiple implementations of its")]
97    #[cfg_attr(windows, doc = "standard C library, called C Run-Times (CRTs),")]
98    #[cfg_attr(windows, doc = "and getting your application to be linked")]
99    #[cfg_attr(windows, doc = "against the same CRT as your hwloc DLL is")]
100    #[cfg_attr(windows, doc = "basically a matter of fragile guesswork...")]
101    pub errno: Option<Errno>,
102}
103
104/// Call an hwloc entry point that returns a `*mut T` that should not be null
105///
106/// # Errors
107///
108/// See the documentation of `call` to know when the entry point can error out
109pub(crate) fn call_hwloc_ptr_mut<T>(
110    api: &'static str,
111    call: impl FnOnce() -> *mut T,
112) -> Result<NonNull<T>, RawHwlocError> {
113    let (result, errno) = check_errno(call, 1 as *mut T);
114    NonNull::new(result).ok_or(RawHwlocError { api, errno })
115}
116
117/// Call an hwloc entry point that returns a `*const T` that should not be null
118///
119/// # Errors
120///
121/// See the documentation of `call` to know when the entry point can error out
122pub(crate) fn call_hwloc_ptr<T>(
123    api: &'static str,
124    call: impl FnOnce() -> *const T,
125) -> Result<NonNull<T>, RawHwlocError> {
126    call_hwloc_ptr_mut(api, || call().cast_mut())
127}
128
129/// Call an hwloc entry point that returns 0 on success and -1 on failure
130///
131/// Other positive and negative values are not expected and will result into
132/// an application panic.
133///
134/// This behavior is followed by most hwloc APIs, but there are exceptions.
135///
136/// # Errors
137///
138/// See the documentation of `call` to know when the entry point can error out
139pub(crate) fn call_hwloc_zero_or_minus1(
140    api: &'static str,
141    call: impl FnOnce() -> c_int,
142) -> Result<(), RawHwlocError> {
143    /// Outlined to reduce code bloat
144    fn check_raw_result(raw_result: Result<c_uint, RawHwlocError>) -> Result<(), RawHwlocError> {
145        match raw_result {
146            Ok(0) => Ok(()),
147            Ok(positive) => unreachable!(
148                "Unexpected integer > 0 from hwloc function that should return 0 or -1: {positive}"
149            ),
150            Err(e) => Err(e),
151        }
152    }
153    check_raw_result(call_hwloc_positive_or_minus1(api, call))
154}
155
156/// Call an hwloc entry point that returns an `int` with standard boolean values
157/// (1 if true, 0 if false)
158pub(crate) fn call_hwloc_bool(
159    api: &'static str,
160    call: impl FnOnce() -> c_int,
161) -> Result<bool, RawHwlocError> {
162    /// Outlined to reduce code bloat
163    fn check_raw_result(
164        api: &'static str,
165        raw_result: Result<c_uint, RawHwlocError>,
166    ) -> Result<bool, RawHwlocError> {
167        match raw_result {
168            Ok(1) => Ok(true),
169            Ok(0) => Ok(false),
170            Ok(other) => unreachable!("Got unexpected boolean value {other} from {api}"),
171            Err(e) => Err(e),
172        }
173    }
174    check_raw_result(api, call_hwloc_positive_or_minus1(api, call))
175}
176
177/// Call an hwloc entry point that returns an `int` where -1 signals failure and
178/// other negative values are not expected
179///
180/// If you only expect 0 as a positive result, consider using
181/// [`call_hwloc_zero_or_minus1()`] instead.
182///
183/// # Errors
184///
185/// See the documentation of `call` to know when the entry point can error out
186pub(crate) fn call_hwloc_positive_or_minus1(
187    api: &'static str,
188    call: impl FnOnce() -> c_int,
189) -> Result<c_uint, RawHwlocError> {
190    /// Outlined to reduce code bloat
191    fn check_raw_result(
192        raw_result: Result<c_int, RawNegIntError>,
193    ) -> Result<c_uint, RawHwlocError> {
194        match raw_result {
195            Ok(positive) => {
196                Ok(c_uint::try_from(positive).expect("Cannot happen due to 0 threshold above"))
197            }
198            Err(RawNegIntError {
199                api,
200                result: -1,
201                errno,
202            }) => Err(RawHwlocError { api, errno }),
203            Err(other_err) => {
204                unreachable!("Unexpected negative integer != -1 from hwloc function: {other_err}")
205            }
206        }
207    }
208    check_raw_result(call_hwloc_int_raw(api, call, 0))
209}
210
211/// Raw error emitted by hwloc functions that returns a negative int on failure
212///
213/// A few hwloc functions (most prominently bitmap queries and topology diffing)
214/// return negative integer values other than -1 when erroring out. This error
215/// type is an extension of [`RawHwlocError`] that allows you to catch and
216/// process those negative return values.
217#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
218#[error("{api} failed with result {result} and errno {errno:?}")]
219pub(crate) struct RawNegIntError {
220    /// Hwloc entry point that failed
221    pub(crate) api: &'static str,
222
223    /// Return value (may not be positive)
224    pub(crate) result: c_int,
225
226    /// Observed errno value, if errno was set
227    pub(crate) errno: Option<Errno>,
228}
229//
230/// Call an hwloc entry point that returns int and post-process its result
231///
232/// Result values lower than `lowest_good_value` are treated as errors
233pub(crate) fn call_hwloc_int_raw(
234    api: &'static str,
235    call: impl FnOnce() -> c_int,
236    lowest_good_value: c_int,
237) -> Result<c_int, RawNegIntError> {
238    /// Outlined to reduce code bloat
239    fn check_raw_result(
240        api: &'static str,
241        (result, errno): (c_int, Option<Errno>),
242        lowest_good_value: c_int,
243    ) -> Result<c_int, RawNegIntError> {
244        (result >= lowest_good_value)
245            .then_some(result)
246            .ok_or(RawNegIntError { api, result, errno })
247    }
248    check_raw_result(api, check_errno(call, lowest_good_value), lowest_good_value)
249}
250
251/// A function errored out either on the Rust or hwloc side
252///
253/// This is typically used for functions which have known failure modes on the
254/// Rust side (e.g. takes a string input that must not contain NUL chars), but
255/// whose hwloc-side error reporting policy is undocumented or only partially
256/// documented.
257///
258/// If the hwloc documentation contains an exhaustive list of failure modes, we
259/// trust it and return a pure Rust error type, panicking if another hwloc
260/// error is observed.
261#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq)]
262pub enum HybridError<RustError: Error> {
263    /// An error was caught on the Rust side
264    #[error(transparent)]
265    Rust(#[from] RustError),
266
267    /// An error was caught on the hwloc side
268    #[error(transparent)]
269    // Unfortunately, this type cannot implement both #[from] RustError and
270    // #[from] RawHwlocError as rustc rightly complains that nothing prevents
271    // RustError to be RawHwlocError at the type system level.
272    //
273    // I choose to implement From for RustError because that type has unbounded
274    // complexity and thus benefits the most from it.
275    Hwloc(RawHwlocError),
276}
277//
278impl<RustError: Error> HybridError<RustError> {
279    /// Assume that all expected Rust-side error sources have been handled and
280    /// panic otherwise, but still allow unexpected hwloc errors
281    pub fn expect_only_hwloc(self, msg: &str) -> RawHwlocError {
282        match self {
283            Self::Hwloc(e) => e,
284            Self::Rust(e) => panic!("{msg} (got unexpected Rust error {e}"),
285        }
286    }
287}
288
289/// A string meant for hwloc consumption contained the NUL char
290///
291/// hwloc, like most C APIs, cannot handle strings with inner NULs, so you
292/// should not pass a string containing such characters as a parameter to an
293/// hwloc API.
294///
295/// This generic error type is only used when the only error that can occur is
296/// that the input string is invalid. Otherwise, a more complex error type that
297/// accounts for the possibility of NUL errors among others will be emitted.
298#[derive(Copy, Clone, Debug, Default, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
299#[error("can't pass a string with NUL chars to hwloc")]
300pub struct NulError;
301
302/// A method was passed an invalid parameter
303///
304/// This generic error type is only used when there is only a single way a
305/// function parameter can be invalid, and the fact that it is invalid does
306/// not depend on the value of another parameter. Otherwise, a more descriptive
307/// dedicated error type will be used.
308#[derive(Copy, Clone, Debug, Default, Eq, Error, From, Hash, Ord, PartialEq, PartialOrd)]
309#[error("parameter {0:?} isn't valid for this operation")]
310pub struct ParameterError<Parameter: Debug>(pub Parameter);
311
312/// An invalid set of flags was passed to a function
313///
314/// Many hwloc APIs only accept particular combinations of flags. You may want
315/// to cross-check the documentation of the flags type and that of the function
316/// you were trying to call for more information.
317///
318/// This generic error type is only used when the validity of flags does not
319/// depend on the value of other function parameters. Otherwise, a more
320/// descriptive dedicated error type will be used.
321pub type FlagsError<Flags> = ParameterError<Flags>;
322
323/// A [`Topology`] method was passed in a [`TopologyObject`] that does not
324/// belong to said topology
325///
326/// Given that this is an obscure usage error that has tiny odds of happening in
327/// the real world, it is not systematically reported as an error. Methods
328/// whose semantics boil down to "return entity that matches this query if it
329/// exists and `None` otherwise" may instead return `None` in this scenario.
330//
331// --- Implementation notes ---
332//
333// Not implementing Copy or exposing the inner data at this point because I want
334// to keep options open for switching to another way to describe objects (Debug
335// string, etc) in the future.
336#[allow(missing_copy_implementations)]
337#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
338#[error("object #{0} doesn't belong to this topology")]
339pub struct ForeignObjectError(TopologyObjectID);
340//
341impl<'topology> From<&'topology TopologyObject> for ForeignObjectError {
342    fn from(object: &'topology TopologyObject) -> Self {
343        Self(object.global_persistent_index())
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use crate::tests::assert_panics;
351    use proptest::prelude::*;
352    #[allow(unused)]
353    use similar_asserts::assert_eq;
354    use static_assertions::{assert_impl_all, assert_not_impl_any, assert_type_eq_all};
355    use std::{
356        fmt::{self, Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
357        hash::Hash,
358        io::{self, Read},
359        num::{NonZeroU32, NonZeroUsize},
360        ops::Deref,
361        panic::UnwindSafe,
362        ptr,
363    };
364
365    // Check that public types in this module keep implementing all expected
366    // traits, in the interest of detecting future semver-breaking changes
367    assert_type_eq_all!(FlagsError<()>, ParameterError<()>);
368    assert_impl_all!(ForeignObjectError:
369        Clone, Error, Hash, Sized, Sync, Unpin, UnwindSafe
370    );
371    assert_not_impl_any!(ForeignObjectError:
372        Binary, Copy, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex,
373        Octal, PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write,
374        io::Write
375    );
376    assert_impl_all!(HybridError<NulError>:
377        Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
378    );
379    assert_not_impl_any!(HybridError<NulError>:
380        Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
381        PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
382    );
383    assert_impl_all!(NulError:
384        Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
385    );
386    assert_not_impl_any!(NulError:
387        Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
388        Read, UpperExp, UpperHex, fmt::Write, io::Write
389    );
390    assert_impl_all!(ParameterError<()>:
391        Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
392    );
393    assert_not_impl_any!(ParameterError<()>:
394        Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
395        Read, UpperExp, UpperHex, fmt::Write, io::Write
396    );
397    assert_impl_all!(RawHwlocError:
398        Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
399    );
400    assert_not_impl_any!(RawHwlocError:
401        Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
402        PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
403    );
404
405    proptest! {
406        #[test]
407        fn check_errno_normal(
408            output: i128,
409            lowest_good: i128,
410            start_errno: i32,
411            new_errno: NonZeroU32,
412        ) {
413            // Test boilerplate
414            let start_errno = Errno(start_errno.wrapping_abs());
415            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
416            let _errno_guard = ErrnoGuard::new(start_errno);
417
418            // Errno is only checked on failure
419            let expected_errno = (output < lowest_good).then_some(new_errno);
420            prop_assert_eq!(
421                super::check_errno(
422                    || {
423                        errno::set_errno(new_errno);
424                        output
425                    },
426                    lowest_good
427                ),
428                (output, expected_errno)
429            );
430            prop_assert_eq!(errno::errno(), start_errno);
431        }
432
433        #[test]
434        fn check_errno_in_vain(output: i128, lowest_good: i128, start_errno: i32) {
435            // Not setting errno on failure is handled properly
436            let start_errno = Errno(start_errno.wrapping_abs());
437            let _errno_guard = ErrnoGuard::new(start_errno);
438            prop_assert_eq!(super::check_errno(|| output, lowest_good), (output, None));
439            prop_assert_eq!(errno::errno(), start_errno);
440        }
441
442        #[test]
443        fn ptr_success(nonnull: NonZeroUsize, start_errno: i32, new_errno: NonZeroU32) {
444            // Test boilerplate
445            let start_errno = Errno(start_errno.wrapping_abs());
446            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
447            let _errno_guard = ErrnoGuard::new(start_errno);
448            let api = "foo";
449            let nonnull_ptr = NonNull::new(nonnull.get() as *mut u8).unwrap();
450
451            // Non-null output means success
452            prop_assert_eq!(
453                super::call_hwloc_ptr(api, || {
454                    errno::set_errno(new_errno);
455                    nonnull_ptr.as_ptr()
456                }),
457                Ok(nonnull_ptr)
458            );
459            prop_assert_eq!(errno::errno(), start_errno);
460            prop_assert_eq!(
461                super::call_hwloc_ptr_mut(api, || {
462                    errno::set_errno(new_errno);
463                    nonnull_ptr.as_ptr()
464                }),
465                Ok(nonnull_ptr)
466            );
467            prop_assert_eq!(errno::errno(), start_errno);
468        }
469
470        #[test]
471        fn ptr_fail_with_errno(start_errno: i32, new_errno: NonZeroU32) {
472            // Test boilerplate
473            let start_errno = Errno(start_errno.wrapping_abs());
474            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
475            let _errno_guard = ErrnoGuard::new(start_errno);
476            let api = "bar";
477
478            // Null output means failure and will lead to an errno check
479            let null_ptr = ptr::null_mut::<Vec<f32>>();
480            prop_assert_eq!(
481                super::call_hwloc_ptr(api, || {
482                    errno::set_errno(new_errno);
483                    null_ptr
484                }),
485                Err(RawHwlocError {
486                    api,
487                    errno: Some(new_errno)
488                })
489            );
490            prop_assert_eq!(errno::errno(), start_errno);
491            prop_assert_eq!(
492                super::call_hwloc_ptr_mut(api, || {
493                    errno::set_errno(new_errno);
494                    null_ptr
495                }),
496                Err(RawHwlocError {
497                    api,
498                    errno: Some(new_errno)
499                })
500            );
501            prop_assert_eq!(errno::errno(), start_errno);
502        }
503
504        #[test]
505        fn ptr_fail_wo_errno(start_errno: i32) {
506            // Test boilerplate
507            let start_errno = Errno(start_errno.wrapping_abs());
508            let _errno_guard = ErrnoGuard::new(start_errno);
509            let api = "baz";
510            let null_ptr = ptr::null_mut::<String>();
511
512            // Not setting errno on failure is handled properly
513            prop_assert_eq!(
514                super::call_hwloc_ptr(api, || { null_ptr }),
515                Err(RawHwlocError { api, errno: None })
516            );
517            prop_assert_eq!(errno::errno(), start_errno);
518            prop_assert_eq!(
519                super::call_hwloc_ptr_mut(api, || { null_ptr }),
520                Err(RawHwlocError { api, errno: None })
521            );
522            prop_assert_eq!(errno::errno(), start_errno);
523        }
524
525        #[test]
526        fn int_normal_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
527            // Test boilerplate
528            let start_errno = Errno(start_errno.wrapping_abs());
529            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
530            let _errno_guard = ErrnoGuard::new(start_errno);
531            let api = "abc";
532
533            // Prepare to call the function
534            let call = || {
535                let res = super::call_hwloc_positive_or_minus1(api, || {
536                    errno::set_errno(new_errno);
537                    output
538                });
539                prop_assert_eq!(errno::errno(), start_errno);
540                Ok(res)
541            };
542
543            // Interpret results
544            match output {
545                bad if bad < -1 => assert_panics(call)?,
546                -1 => prop_assert_eq!(
547                    call()?,
548                    Err(RawHwlocError {
549                        api,
550                        errno: Some(new_errno)
551                    })
552                ),
553                positive => prop_assert_eq!(call()?, Ok(u32::try_from(positive).unwrap())),
554            }
555        }
556
557        #[test]
558        fn int_normal_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
559            // Test boilerplate
560            let start_errno = Errno(start_errno.wrapping_abs());
561            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
562            let _errno_guard = ErrnoGuard::new(start_errno);
563            let api = "def";
564
565            // Returning -1 means failure and will lead to an errno check
566            prop_assert_eq!(
567                super::call_hwloc_positive_or_minus1(api, || {
568                    errno::set_errno(new_errno);
569                    -1
570                }),
571                Err(RawHwlocError {
572                    api,
573                    errno: Some(new_errno)
574                })
575            );
576            prop_assert_eq!(errno::errno(), start_errno);
577        }
578
579        #[test]
580        fn int_normal_err_wo_errno(start_errno: i32) {
581            // Test boilerplate
582            let start_errno = Errno(start_errno.wrapping_abs());
583            let _errno_guard = ErrnoGuard::new(start_errno);
584            let api = "ghi";
585
586            // Not setting errno on failure is handled properly
587            prop_assert_eq!(
588                super::call_hwloc_positive_or_minus1(api, || -1),
589                Err(RawHwlocError { api, errno: None })
590            );
591            prop_assert_eq!(errno::errno(), start_errno);
592        }
593
594        #[test]
595        fn int_raw_with_errno(
596            output: i32,
597            lowest_good_value: i32,
598            start_errno: i32,
599            new_errno: NonZeroU32,
600        ) {
601            // Test boilerplate
602            let start_errno = Errno(start_errno.wrapping_abs());
603            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
604            let _errno_guard = ErrnoGuard::new(start_errno);
605            let api = "jkl";
606
607            // Run the function
608            let result = super::call_hwloc_int_raw(
609                api,
610                || {
611                    errno::set_errno(new_errno);
612                    output
613                },
614                lowest_good_value,
615            );
616            prop_assert_eq!(errno::errno(), start_errno);
617
618            // Interpret outcome
619            if output >= lowest_good_value {
620                prop_assert_eq!(result, Ok(output));
621            } else {
622                prop_assert_eq!(
623                    result,
624                    Err(RawNegIntError {
625                        api,
626                        result: output,
627                        errno: Some(new_errno),
628                    })
629                );
630            }
631        }
632
633        #[test]
634        fn int_raw_wo_errno(output: i32, lowest_good_value: i32, start_errno: i32) {
635            // Test boilerplate
636            let start_errno = Errno(start_errno.wrapping_abs());
637            let _errno_guard = ErrnoGuard::new(start_errno);
638            let api = "opq";
639
640            // Run the function
641            let result = super::call_hwloc_int_raw(api, || output, lowest_good_value);
642            prop_assert_eq!(errno::errno(), start_errno);
643
644            // Interpret outcome
645            if output >= lowest_good_value {
646                prop_assert_eq!(result, Ok(output));
647            } else {
648                prop_assert_eq!(
649                    result,
650                    Err(RawNegIntError {
651                        api,
652                        result: output,
653                        errno: None,
654                    })
655                );
656            }
657        }
658
659        #[test]
660        fn bool_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
661            // Test boilerplate
662            let start_errno = Errno(start_errno.wrapping_abs());
663            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
664            let _errno_guard = ErrnoGuard::new(start_errno);
665            let api = "rst";
666
667            // Prepare to call the function
668            let call = || {
669                let res = super::call_hwloc_bool(api, || {
670                    errno::set_errno(new_errno);
671                    output
672                });
673                prop_assert_eq!(errno::errno(), start_errno);
674                Ok(res)
675            };
676
677            // Interpret outcome
678            match output {
679                -1 => prop_assert_eq!(
680                    call()?,
681                    Err(RawHwlocError {
682                        api,
683                        errno: Some(new_errno)
684                    })
685                ),
686                0 => prop_assert_eq!(call()?, Ok(false)),
687                1 => prop_assert_eq!(call()?, Ok(true)),
688                _ => assert_panics(call)?,
689            }
690        }
691
692        #[test]
693        fn bool_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
694            // Test boilerplate
695            let start_errno = Errno(start_errno.wrapping_abs());
696            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
697            let _errno_guard = ErrnoGuard::new(start_errno);
698            let api = "uvw";
699
700            // Run the function
701            prop_assert_eq!(
702                super::call_hwloc_bool(api, || {
703                    errno::set_errno(new_errno);
704                    -1
705                }),
706                Err(RawHwlocError {
707                    api,
708                    errno: Some(new_errno)
709                })
710            );
711            prop_assert_eq!(errno::errno(), start_errno);
712        }
713
714        #[test]
715        fn bool_err_wo_errno(start_errno: i32) {
716            // Test boilerplate
717            let start_errno = Errno(start_errno.wrapping_abs());
718            let _errno_guard = ErrnoGuard::new(start_errno);
719            let api = "xyz";
720
721            // Run the function
722            prop_assert_eq!(
723                super::call_hwloc_bool(api, || -1),
724                Err(RawHwlocError { api, errno: None })
725            );
726            prop_assert_eq!(errno::errno(), start_errno);
727        }
728
729        #[test]
730        fn bool_success(output: bool, start_errno: i32, new_errno: NonZeroU32) {
731            // Test boilerplate
732            let start_errno = Errno(start_errno.wrapping_abs());
733            let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
734            let _errno_guard = ErrnoGuard::new(start_errno);
735            let api = "cthulhu_phtagn";
736
737            // Run the function
738            prop_assert_eq!(
739                super::call_hwloc_bool(api, || {
740                    errno::set_errno(new_errno);
741                    i32::from(output)
742                }),
743                Ok(output)
744            );
745            prop_assert_eq!(errno::errno(), start_errno);
746        }
747
748        #[test]
749        fn parameter_error_from(x: i128) {
750            prop_assert_eq!(ParameterError::from(x), ParameterError(x));
751        }
752    }
753
754    /// Generate a mostly-arbitrary [`HybridError`]
755    fn any_hybrid_error() -> impl Strategy<Value = HybridError<ParameterError<u64>>> {
756        prop_oneof![
757            any::<u64>().prop_map(|p| HybridError::Rust(ParameterError(p))),
758            any::<Option<i32>>().prop_map(|errno| HybridError::Hwloc(RawHwlocError {
759                api: "hwloc_fancy_api",
760                errno: errno.map(Errno),
761            }))
762        ]
763    }
764    //
765    proptest! {
766        #[test]
767        fn expect_only_hwloc(hybrid in any_hybrid_error()) {
768            match &hybrid {
769                HybridError::Rust(_) => assert_panics(|| hybrid.expect_only_hwloc("will happen"))?,
770                HybridError::Hwloc(h) => prop_assert_eq!(hybrid.expect_only_hwloc("won't happen"), *h),
771            }
772        }
773    }
774}