Fix capabilities logic errors

This commit is contained in:
NGnius (Graham) 2023-08-08 20:53:47 -04:00
parent 908df9ec7d
commit fe1f031c33
5 changed files with 116 additions and 15 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "sysfuss" name = "sysfuss"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"] authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
description = "sysfs wrapper for convenience" description = "sysfs wrapper for convenience"

View file

@ -20,9 +20,8 @@ impl BasicEntityPath {
pub(crate) fn all(root: impl AsRef<Path>, class: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> { pub(crate) fn all(root: impl AsRef<Path>, class: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> {
let dir_path = root.as_ref().join(SYS_CLASS_PATH).join(class); let dir_path = root.as_ref().join(SYS_CLASS_PATH).join(class);
dir_path.read_dir() Ok(dir_path.read_dir()?
.map( .filter_map(
|iter| iter.filter_map(
|entry| entry.map( |entry| entry.map(
|entry| if entry.path().is_file() { None } else { Some(Self::new(entry.path())) } |entry| if entry.path().is_file() { None } else { Some(Self::new(entry.path())) }
).transpose())) ).transpose()))
@ -77,6 +76,7 @@ impl crate::SysEntityAttributes<String> for BasicEntityPath {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::SysEntity;
use crate::SysEntityRawExt; use crate::SysEntityRawExt;
#[test] #[test]
@ -89,4 +89,45 @@ mod tests {
} }
Ok(()) Ok(())
} }
#[test]
fn basic_capabilities() -> std::io::Result<()> {
let sys = crate::SysPath::default();
if std::fs::read_to_string("/sys/class/hwmon/hwmon0/name")?.trim() != "nvme" {
// skip if system has different hwmons
// TODO account for different test systems
eprintln!("basic entity test skipped since hwmon0 is not nvme (maybe running on a different PC?)");
return Ok(())
}
let basic = sys.class("hwmon", crate::capability::attributes([
"name".to_string(),
"temp1_alarm".to_string(),
"temp1_crit".to_string(),
].into_iter()))?.next().expect("Missing any hwmon");
let attr_value = crate::SysEntityAttributesExt::attribute::<String, _>(&basic, "name".to_owned()).expect("name capable but also incapable");
println!("Attribute ./name = '{}'", attr_value);
assert!(attr_value == "nvme");
Ok(())
}
#[test]
fn basic_drm_capabilities() -> std::io::Result<()> {
let sys = crate::SysPath::default();
let expected_dev = if let Ok(dev) = std::fs::read_to_string("/sys/class/drm/card1/dev") {
dev.trim().to_owned()
} else {
// skip if system has different hwmons
// TODO account for different test systems
eprintln!("basic entity test skipped since drm card0 does not exist (maybe running on a different PC?)");
return Ok(())
};
let basic = sys.class("drm", crate::capability::attributes([
"dev".to_string(),
"uevent".to_string(),
].into_iter()))?.filter(|basic| basic.name().expect("no name").starts_with("card")).next().expect("Missing drm");
let attr_value = crate::SysEntityAttributesExt::attribute::<String, _>(&basic, "dev".to_owned()).expect("dev capable but also incapable");
println!("Attribute ./dev = '{}'", attr_value);
assert!(attr_value == expected_dev);
Ok(())
}
} }

View file

@ -19,6 +19,26 @@ impl <T1, T2> EitherErr2<T1, T2> {
} }
} }
impl <T> EitherErr2<std::convert::Infallible, T> {
/// Convert to non-infallible error
pub fn map_infallible_first(self) -> T {
match self {
Self::First(_e) => panic!("Infallible error cannot exist"),
Self::Second(e) => e,
}
}
}
impl <T> EitherErr2<T, std::convert::Infallible> {
/// Convert to non-infallible error
pub fn map_infallible_second(self) -> T {
match self {
Self::First(e) => e,
Self::Second(_e) => panic!("Infallible error cannot exist"),
}
}
}
impl <T1: std::fmt::Display, T2: std::fmt::Display> std::fmt::Display for EitherErr2<T1, T2> { impl <T1: std::fmt::Display, T2: std::fmt::Display> std::fmt::Display for EitherErr2<T1, T2> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {

View file

@ -4,6 +4,8 @@ use std::io::Result as IoResult;
const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/"; const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/";
use crate::SysEntityAttributesExt;
/// Attribute representation. /// Attribute representation.
/// Attribute files are usually in the format `<type><number>_<item>`. /// Attribute files are usually in the format `<type><number>_<item>`.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
@ -202,7 +204,11 @@ impl HwMonPath {
pub(crate) fn name(root: &crate::SysPath, name: &str) -> IoResult<Option<Self>> { pub(crate) fn name(root: &crate::SysPath, name: &str) -> IoResult<Option<Self>> {
for entry in Self::all(root)? { for entry in Self::all(root)? {
let entry = entry?; let entry = entry?;
if entry.attribute(HwMonAttribute::name())? == name { let value: String = entry.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| match e {
crate::EitherErr2::First(e) => e,
crate::EitherErr2::Second(_e) => panic!("Infallible"),
})?;
if value == name {
return Ok(Some(entry)) return Ok(Some(entry))
} }
} }
@ -215,11 +221,6 @@ impl HwMonPath {
std::fs::read_to_string(self.path.join(name)) std::fs::read_to_string(self.path.join(name))
} }
/// Get a hwmon attribute (file contents)
fn attribute(&self, attr: HwMonAttribute) -> IoResult<String> {
self.attribute_str(&attr.to_attr_str())
}
/// Get the path to a hwmon attribute. /// Get the path to a hwmon attribute.
/// Use `HwMonPath.as_ref().join(attribute_str)` for non-standard attributes /// Use `HwMonPath.as_ref().join(attribute_str)` for non-standard attributes
pub fn path_to(&self, attr: HwMonAttribute) -> PathBuf { pub fn path_to(&self, attr: HwMonAttribute) -> PathBuf {
@ -239,7 +240,7 @@ impl crate::SysEntity for HwMonPath {
} }
fn name(&self) -> IoResult<String> { fn name(&self) -> IoResult<String> {
self.attribute(HwMonAttribute::name()) self.attribute(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())
} }
} }
@ -268,10 +269,29 @@ mod tests {
assert!(!all_hwmon.is_empty()); assert!(!all_hwmon.is_empty());
for hwmon in all_hwmon.into_iter() { for hwmon in all_hwmon.into_iter() {
let hwmon = hwmon?; let hwmon = hwmon?;
assert!(hwmon.attribute(HwMonAttribute::name())? != ""); assert!(hwmon.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())? != "");
assert!(!hwmon.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())?.ends_with("\n"));
assert!(!hwmon.capabilities().is_empty()); assert!(!hwmon.capabilities().is_empty());
assert!(hwmon.capabilities().contains(&HwMonAttribute::name())) assert!(hwmon.capabilities().contains(&HwMonAttribute::name()))
} }
Ok(()) Ok(())
} }
#[test]
fn hwmon_capabilities() -> std::io::Result<()> {
let sys = crate::SysPath::default();
if !sys.hwmon_by_name("amdgpu").is_ok() {
// skip if system has no AMD GPU
eprintln!("hwmon test skipped since amdgpu does not exist (maybe running on a laptop PC?)");
return Ok(())
}
let hwmon = sys.hwmon(crate::capability::attributes([
HwMonAttribute::name(),
HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Input),
HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Min),
HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Max)
].into_iter()))?.next().expect("Missing capable amdgpu");
assert_eq!(hwmon.attribute::<String, _>(HwMonAttribute::name()).expect("name capable but also incapable"), "amdgpu");
Ok(())
}
} }

View file

@ -2,8 +2,7 @@ use std::path::{Path, PathBuf};
use std::convert::AsRef; use std::convert::AsRef;
use std::io::Result as IoResult; use std::io::Result as IoResult;
use crate::SysEntityRawExt; use crate::SysEntityAttributesExt;
//use crate::SysEntityAttributes;
const PSU_DIR_PATH: &'static str = "sys/class/power_supply/"; const PSU_DIR_PATH: &'static str = "sys/class/power_supply/";
@ -218,7 +217,7 @@ impl PowerSupplyPath {
/// Get the power supply type /// Get the power supply type
pub fn type_str(&self) -> IoResult<String> { pub fn type_str(&self) -> IoResult<String> {
self.attribute_str("type") self.attribute(PowerSupplyAttribute::Type).map_err(|e| e.map_infallible_second())
} }
/// Get the power supply type /// Get the power supply type
@ -267,6 +266,9 @@ impl crate::SysEntityAttributes<PowerSupplyAttribute> for PowerSupplyPath {
PowerSupplyAttribute::CapacityAlertMin, PowerSupplyAttribute::CapacityAlertMin,
PowerSupplyAttribute::CapacityErrorMargin, PowerSupplyAttribute::CapacityErrorMargin,
PowerSupplyAttribute::CapacityLevel, PowerSupplyAttribute::CapacityLevel,
PowerSupplyAttribute::ChargeNow,
PowerSupplyAttribute::ChargeFull,
PowerSupplyAttribute::ChargeFullDesign,
PowerSupplyAttribute::ChargeControlLimit, PowerSupplyAttribute::ChargeControlLimit,
PowerSupplyAttribute::ChargeControlLimitMax, PowerSupplyAttribute::ChargeControlLimitMax,
PowerSupplyAttribute::ChargeControlStartThreshold, PowerSupplyAttribute::ChargeControlStartThreshold,
@ -290,6 +292,8 @@ impl crate::SysEntityAttributes<PowerSupplyAttribute> for PowerSupplyPath {
mod tests { mod tests {
use super::*; use super::*;
use crate::SysEntityAttributes; use crate::SysEntityAttributes;
use crate::SysEntityAttributesExt;
use crate::SysAttribute;
#[test] #[test]
fn power_supply_all() -> std::io::Result<()> { fn power_supply_all() -> std::io::Result<()> {
@ -308,4 +312,20 @@ mod tests {
} }
Ok(()) Ok(())
} }
#[test]
fn power_supply_capabilities() -> std::io::Result<()> {
let sys = crate::SysPath::default();
if !PowerSupplyAttribute::Type.exists(&sys.power_supply_by_name("BAT0")) {
// skip if system has no battery
eprintln!("power_supply test skipped since BAT0 does not exist (maybe running on a desktop PC?)");
return Ok(())
}
let psu = sys.power_supply(crate::capability::attributes([
PowerSupplyAttribute::Type,
PowerSupplyAttribute::Capacity,
].into_iter()))?.next().expect("Missing capable battery");
assert!(psu.attribute::<PowerSupplyType, _>(PowerSupplyAttribute::Type).expect("type capable but also incapable") == PowerSupplyType::Battery);
Ok(())
}
} }