From fe1f031c33ee2872a6e0dba3e022b8012d7b2849 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Tue, 8 Aug 2023 20:53:47 -0400 Subject: [PATCH] Fix capabilities logic errors --- Cargo.toml | 2 +- src/basic.rs | 47 ++++++++++++++++++++++++++++++++++++++++++--- src/errors.rs | 20 +++++++++++++++++++ src/hwmon.rs | 36 ++++++++++++++++++++++++++-------- src/power_supply.rs | 26 ++++++++++++++++++++++--- 5 files changed, 116 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a11575d..d45bdee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sysfuss" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["NGnius (Graham) "] description = "sysfs wrapper for convenience" diff --git a/src/basic.rs b/src/basic.rs index 2174f51..c9998c4 100644 --- a/src/basic.rs +++ b/src/basic.rs @@ -20,9 +20,8 @@ impl BasicEntityPath { pub(crate) fn all(root: impl AsRef, class: impl AsRef) -> IoResult>> { let dir_path = root.as_ref().join(SYS_CLASS_PATH).join(class); - dir_path.read_dir() - .map( - |iter| iter.filter_map( + Ok(dir_path.read_dir()? + .filter_map( |entry| entry.map( |entry| if entry.path().is_file() { None } else { Some(Self::new(entry.path())) } ).transpose())) @@ -77,6 +76,7 @@ impl crate::SysEntityAttributes for BasicEntityPath { #[cfg(test)] mod tests { use super::*; + use crate::SysEntity; use crate::SysEntityRawExt; #[test] @@ -89,4 +89,45 @@ mod tests { } 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::(&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::(&basic, "dev".to_owned()).expect("dev capable but also incapable"); + println!("Attribute ./dev = '{}'", attr_value); + assert!(attr_value == expected_dev); + Ok(()) + } } diff --git a/src/errors.rs b/src/errors.rs index b641af2..61f9940 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -19,6 +19,26 @@ impl EitherErr2 { } } +impl EitherErr2 { + /// 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 EitherErr2 { + /// 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 std::fmt::Display for EitherErr2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/hwmon.rs b/src/hwmon.rs index b3c877e..168d273 100644 --- a/src/hwmon.rs +++ b/src/hwmon.rs @@ -4,6 +4,8 @@ use std::io::Result as IoResult; const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/"; +use crate::SysEntityAttributesExt; + /// Attribute representation. /// Attribute files are usually in the format `_`. #[derive(Clone, Copy, PartialEq, Eq)] @@ -202,7 +204,11 @@ impl HwMonPath { pub(crate) fn name(root: &crate::SysPath, name: &str) -> IoResult> { for entry in Self::all(root)? { let entry = entry?; - if entry.attribute(HwMonAttribute::name())? == name { + let value: String = entry.attribute::(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)) } } @@ -215,11 +221,6 @@ impl HwMonPath { std::fs::read_to_string(self.path.join(name)) } - /// Get a hwmon attribute (file contents) - fn attribute(&self, attr: HwMonAttribute) -> IoResult { - self.attribute_str(&attr.to_attr_str()) - } - /// Get the path to a hwmon attribute. /// Use `HwMonPath.as_ref().join(attribute_str)` for non-standard attributes pub fn path_to(&self, attr: HwMonAttribute) -> PathBuf { @@ -239,7 +240,7 @@ impl crate::SysEntity for HwMonPath { } fn name(&self) -> IoResult { - 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()); for hwmon in all_hwmon.into_iter() { let hwmon = hwmon?; - assert!(hwmon.attribute(HwMonAttribute::name())? != ""); + assert!(hwmon.attribute::(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())? != ""); + assert!(!hwmon.attribute::(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())?.ends_with("\n")); assert!(!hwmon.capabilities().is_empty()); assert!(hwmon.capabilities().contains(&HwMonAttribute::name())) } 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::(HwMonAttribute::name()).expect("name capable but also incapable"), "amdgpu"); + Ok(()) + } } diff --git a/src/power_supply.rs b/src/power_supply.rs index 6e39cb1..3184733 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -2,8 +2,7 @@ use std::path::{Path, PathBuf}; use std::convert::AsRef; use std::io::Result as IoResult; -use crate::SysEntityRawExt; -//use crate::SysEntityAttributes; +use crate::SysEntityAttributesExt; const PSU_DIR_PATH: &'static str = "sys/class/power_supply/"; @@ -218,7 +217,7 @@ impl PowerSupplyPath { /// Get the power supply type pub fn type_str(&self) -> IoResult { - self.attribute_str("type") + self.attribute(PowerSupplyAttribute::Type).map_err(|e| e.map_infallible_second()) } /// Get the power supply type @@ -267,6 +266,9 @@ impl crate::SysEntityAttributes for PowerSupplyPath { PowerSupplyAttribute::CapacityAlertMin, PowerSupplyAttribute::CapacityErrorMargin, PowerSupplyAttribute::CapacityLevel, + PowerSupplyAttribute::ChargeNow, + PowerSupplyAttribute::ChargeFull, + PowerSupplyAttribute::ChargeFullDesign, PowerSupplyAttribute::ChargeControlLimit, PowerSupplyAttribute::ChargeControlLimitMax, PowerSupplyAttribute::ChargeControlStartThreshold, @@ -290,6 +292,8 @@ impl crate::SysEntityAttributes for PowerSupplyPath { mod tests { use super::*; use crate::SysEntityAttributes; + use crate::SysEntityAttributesExt; + use crate::SysAttribute; #[test] fn power_supply_all() -> std::io::Result<()> { @@ -308,4 +312,20 @@ mod tests { } 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::(PowerSupplyAttribute::Type).expect("type capable but also incapable") == PowerSupplyType::Battery); + Ok(()) + } }