use crate::ffi::{int::PositiveInt, unknown::UnknownVariant};
#[cfg(doc)]
use crate::object::{TopologyObject, types::ObjectType};
#[cfg(feature = "hwloc-2_1_0")]
use hwlocality_sys::HWLOC_TYPE_DEPTH_MEMCACHE;
use hwlocality_sys::{
HWLOC_TYPE_DEPTH_BRIDGE, HWLOC_TYPE_DEPTH_MISC, HWLOC_TYPE_DEPTH_MULTIPLE,
HWLOC_TYPE_DEPTH_NUMANODE, HWLOC_TYPE_DEPTH_OS_DEVICE, HWLOC_TYPE_DEPTH_PCI_DEVICE,
HWLOC_TYPE_DEPTH_UNKNOWN, hwloc_get_type_depth_e,
};
#[cfg(any(test, feature = "proptest"))]
use proptest::prelude::*;
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{fmt, num::TryFromIntError};
use thiserror::Error;
pub type NormalDepth = PositiveInt;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[doc(alias = "hwloc_get_type_depth_e")]
pub enum Depth {
Normal(NormalDepth),
#[doc(alias = "HWLOC_TYPE_DEPTH_NUMANODE")]
NUMANode,
#[doc(alias = "HWLOC_TYPE_DEPTH_BRIDGE")]
Bridge,
#[doc(alias = "HWLOC_TYPE_DEPTH_PCI_DEVICE")]
PCIDevice,
#[doc(alias = "HWLOC_TYPE_DEPTH_OS_DEVICE")]
OSDevice,
#[doc(alias = "HWLOC_TYPE_DEPTH_MISC")]
Misc,
#[cfg(feature = "hwloc-2_1_0")]
#[doc(alias = "HWLOC_TYPE_DEPTH_MEMCACHE")]
MemCache,
Unknown(UnknownVariant<hwloc_get_type_depth_e>),
}
impl Depth {
pub fn expect_normal(self) -> NormalDepth {
NormalDepth::try_from(self).expect("Not a normal object depth")
}
pub const VIRTUAL_DEPTHS: &'static [Self] = &[
#[cfg(feature = "hwloc-2_1_0")]
Self::MemCache,
Self::NUMANode,
Self::Bridge,
Self::PCIDevice,
Self::OSDevice,
Self::Misc,
];
pub const MEMORY_DEPTHS: &'static [Self] = &[
#[cfg(feature = "hwloc-2_1_0")]
Self::MemCache,
Self::NUMANode,
];
pub const IO_DEPTHS: &'static [Self] = &[Self::Bridge, Self::PCIDevice, Self::OSDevice];
pub(crate) unsafe fn from_hwloc(
value: hwloc_get_type_depth_e,
) -> Result<Self, TypeToDepthError> {
match value {
normal if normal >= 0 => {
let normal = NormalDepth::try_from_c_int(normal)
.expect("NormalDepth should support all positive depths");
Ok(normal.into())
}
HWLOC_TYPE_DEPTH_UNKNOWN => Err(TypeToDepthError::Nonexistent),
HWLOC_TYPE_DEPTH_MULTIPLE => Err(TypeToDepthError::Multiple),
HWLOC_TYPE_DEPTH_NUMANODE => Ok(Self::NUMANode),
HWLOC_TYPE_DEPTH_BRIDGE => Ok(Self::Bridge),
HWLOC_TYPE_DEPTH_PCI_DEVICE => Ok(Self::PCIDevice),
HWLOC_TYPE_DEPTH_OS_DEVICE => Ok(Self::OSDevice),
HWLOC_TYPE_DEPTH_MISC => Ok(Self::Misc),
#[cfg(feature = "hwloc-2_1_0")]
HWLOC_TYPE_DEPTH_MEMCACHE => Ok(Self::MemCache),
other => Ok(Self::Unknown(UnknownVariant(other))),
}
}
pub(crate) fn to_hwloc(self) -> hwloc_get_type_depth_e {
match self {
Self::Normal(value) => value.to_c_int(),
Self::NUMANode => HWLOC_TYPE_DEPTH_NUMANODE,
Self::Bridge => HWLOC_TYPE_DEPTH_BRIDGE,
Self::PCIDevice => HWLOC_TYPE_DEPTH_PCI_DEVICE,
Self::OSDevice => HWLOC_TYPE_DEPTH_OS_DEVICE,
Self::Misc => HWLOC_TYPE_DEPTH_MISC,
#[cfg(feature = "hwloc-2_1_0")]
Self::MemCache => HWLOC_TYPE_DEPTH_MEMCACHE,
Self::Unknown(unknown) => unknown.get(),
}
}
}
#[cfg(any(test, feature = "proptest"))]
impl Arbitrary for Depth {
type Parameters = <NormalDepth as Arbitrary>::Parameters;
type Strategy = prop::strategy::TupleUnion<(
prop::strategy::WA<
prop::strategy::Map<<NormalDepth as Arbitrary>::Strategy, fn(NormalDepth) -> Self>,
>,
prop::strategy::WA<prop::sample::Select<Self>>,
)>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
prop_oneof![
4 => NormalDepth::arbitrary_with(args).prop_map(Self::Normal),
1 => prop::sample::select(Self::VIRTUAL_DEPTHS)
]
}
}
impl Default for Depth {
fn default() -> Self {
Self::from(NormalDepth::default())
}
}
impl fmt::Display for Depth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(clippy::wildcard_enum_match_arm)]
match self {
Self::Normal(d) => <NormalDepth as fmt::Display>::fmt(d, f),
abnormal => {
let s = format!("<{abnormal:?}>");
f.pad(&s)
}
}
}
}
impl From<NormalDepth> for Depth {
fn from(value: NormalDepth) -> Self {
Self::Normal(value)
}
}
impl PartialEq<NormalDepth> for Depth {
fn eq(&self, other: &NormalDepth) -> bool {
*self == Self::Normal(*other)
}
}
impl PartialEq<Depth> for NormalDepth {
fn eq(&self, other: &Depth) -> bool {
other == self
}
}
impl PartialEq<usize> for Depth {
fn eq(&self, other: &usize) -> bool {
Self::try_from(*other) == Ok(*self)
}
}
impl PartialEq<Depth> for usize {
fn eq(&self, other: &Depth) -> bool {
other == self
}
}
impl TryFrom<usize> for Depth {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, TryFromIntError> {
NormalDepth::try_from(value).map(Self::from)
}
}
impl TryFrom<Depth> for NormalDepth {
type Error = Depth;
fn try_from(value: Depth) -> Result<Self, Depth> {
if let Depth::Normal(depth) = value {
Ok(depth)
} else {
Err(value)
}
}
}
impl TryFrom<Depth> for usize {
type Error = Depth;
fn try_from(value: Depth) -> Result<Self, Depth> {
NormalDepth::try_from(value).map(Self::from)
}
}
#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq)]
pub enum TypeToDepthError {
#[doc(alias = "HWLOC_TYPE_DEPTH_UNKNOWN")]
#[error("no object of requested type exists in the topology")]
Nonexistent,
#[doc(alias = "HWLOC_TYPE_DEPTH_MULTIPLE")]
#[error("objects of requested type exist at different depths in the topology")]
Multiple,
}
#[cfg(test)]
mod tests {
use crate::tests::assert_panics;
use super::*;
#[allow(unused)]
use similar_asserts::assert_eq;
use static_assertions::{assert_impl_all, assert_not_impl_any, assert_type_eq_all};
use std::{
error::Error,
fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
hash::Hash,
io::{self, Read},
ops::Deref,
panic::UnwindSafe,
};
assert_impl_all!(Depth:
Copy, Debug, Default, Display, From<NormalDepth>, Hash,
PartialEq<NormalDepth>, PartialEq<usize>, Sized, Sync, TryFrom<usize>,
TryInto<NormalDepth>, TryInto<usize>, Unpin, UnwindSafe
);
assert_not_impl_any!(Depth:
Binary, Deref, Drop, Error, IntoIterator, LowerExp, LowerHex,
Octal, PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write,
io::Write
);
assert_type_eq_all!(NormalDepth, PositiveInt);
assert_impl_all!(TypeToDepthError:
Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(TypeToDepthError:
Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
);
#[test]
fn special_values() {
assert_eq!(Depth::default(), Depth::from(NormalDepth::default()));
assert_eq!(
unsafe { Depth::from_hwloc(HWLOC_TYPE_DEPTH_UNKNOWN) },
Err(TypeToDepthError::Nonexistent)
);
assert_eq!(
unsafe { Depth::from_hwloc(HWLOC_TYPE_DEPTH_MULTIPLE) },
Err(TypeToDepthError::Multiple)
);
const RAW_DEPTHS: &[hwloc_get_type_depth_e] = &[
#[cfg(feature = "hwloc-2_1_0")]
{
HWLOC_TYPE_DEPTH_MEMCACHE
},
HWLOC_TYPE_DEPTH_NUMANODE,
HWLOC_TYPE_DEPTH_BRIDGE,
HWLOC_TYPE_DEPTH_PCI_DEVICE,
HWLOC_TYPE_DEPTH_OS_DEVICE,
HWLOC_TYPE_DEPTH_MISC,
];
assert_eq!(RAW_DEPTHS.len(), Depth::VIRTUAL_DEPTHS.len());
for (&raw, &depth) in RAW_DEPTHS.iter().zip(Depth::VIRTUAL_DEPTHS) {
assert_eq!(unsafe { Depth::from_hwloc(raw) }, Ok(depth))
}
}
proptest! {
#[test]
fn unary(depth: Depth) {
prop_assert!(matches!(depth, Depth::Normal(_)) || Depth::VIRTUAL_DEPTHS.contains(&depth));
if let Depth::Normal(normal) = depth {
prop_assert_eq!(depth.to_string(), normal.to_string());
prop_assert_eq!(NormalDepth::try_from(depth), Ok(normal));
prop_assert_eq!(usize::try_from(depth).unwrap(), normal);
prop_assert_eq!(depth.expect_normal(), normal);
prop_assert!(depth.to_hwloc() >= 0);
} else {
prop_assert_eq!(depth.to_string(), format!("<{depth:?}>"));
prop_assert!(NormalDepth::try_from(depth).is_err());
prop_assert!(usize::try_from(depth).is_err());
assert_panics(|| depth.expect_normal())?;
prop_assert!(depth.to_hwloc() <= HWLOC_TYPE_DEPTH_NUMANODE);
}
}
#[test]
fn from_normal(normal: NormalDepth) {
prop_assert_eq!(Depth::from(normal), normal);
prop_assert_eq!(normal, Depth::from(normal));
}
}
fn mostly_small_usize() -> impl Strategy<Value = usize> {
prop_oneof![
4 => usize::from(NormalDepth::MIN)..usize::from(NormalDepth::MAX),
1 => any::<usize>()
]
}
proptest! {
#[test]
fn from_usize(value in mostly_small_usize()) {
if value < usize::from(NormalDepth::MAX) {
prop_assert_eq!(Depth::try_from(value).unwrap(), value);
prop_assert_eq!(value, Depth::try_from(value).unwrap());
} else {
prop_assert!(Depth::try_from(value).is_err());
}
}
#[test]
fn from_raw(value: hwloc_get_type_depth_e) {
let depth_res = unsafe { Depth::from_hwloc(value) };
if value >= 0 {
prop_assert!(depth_res.is_ok());
prop_assert_eq!(depth_res.unwrap(), usize::try_from(value).unwrap());
} else if value == HWLOC_TYPE_DEPTH_UNKNOWN {
prop_assert_eq!(depth_res, Err(TypeToDepthError::Nonexistent));
} else if value == HWLOC_TYPE_DEPTH_MULTIPLE {
prop_assert_eq!(depth_res, Err(TypeToDepthError::Multiple));
} else if value
> -2 - hwloc_get_type_depth_e::try_from(Depth::VIRTUAL_DEPTHS.len()).unwrap()
{
prop_assert!(depth_res.is_ok());
} else {
prop_assert_eq!(depth_res, Ok(Depth::Unknown(UnknownVariant(value))));
}
}
#[test]
fn eq_int(depth: Depth, normal: NormalDepth) {
prop_assert_eq!(depth == normal, depth == Depth::Normal(normal));
}
#[test]
fn eq_usize(depth: Depth, value: usize) {
prop_assert_eq!(
depth == value,
PositiveInt::try_from(value).is_ok_and(|value| depth == value)
);
}
}
}