clock_bound_d/
phc_utils.rs1#[cfg_attr(any(test, feature = "test"), mockall::automock)]
2mod get_pci_slot {
3 pub(crate) fn get_pci_slot_name(uevent_file_path: &str) -> anyhow::Result<String> {
9 let contents = std::fs::read_to_string(uevent_file_path).map_err(|e| {
10 anyhow::anyhow!(
11 "Failed to open uevent file {:?} for PHC network interface specified: {}",
12 uevent_file_path,
13 e
14 )
15 })?;
16
17 Ok(contents
18 .lines()
19 .find_map(|line| line.strip_prefix("PCI_SLOT_NAME="))
20 .ok_or(anyhow::anyhow!(
21 "Failed to find PCI_SLOT_NAME at uevent file path {:?}",
22 uevent_file_path
23 ))?
24 .to_string())
25 }
26}
27
28#[cfg(not(any(test, feature = "test")))]
29pub(crate) use get_pci_slot::get_pci_slot_name;
30#[cfg(any(test, feature = "test"))]
31pub(crate) use mock_get_pci_slot::get_pci_slot_name;
32
33pub fn get_error_bound_sysfs_path(interface: &str) -> anyhow::Result<std::path::PathBuf> {
39 let uevent_file_path = format!("/sys/class/net/{}/device/uevent", interface);
40 let pci_slot_name = get_pci_slot_name(&uevent_file_path)?;
41 Ok(std::path::PathBuf::from(format!(
42 "/sys/bus/pci/devices/{}/phc_error_bound",
43 pci_slot_name
44 )))
45}
46
47pub struct PhcWithSysfsErrorBound {
48 sysfs_phc_error_bound_path: std::path::PathBuf,
49 phc_ref_id: u32,
50}
51
52#[cfg_attr(any(test, feature = "test"), mockall::automock)]
53impl PhcWithSysfsErrorBound {
54 pub(crate) fn new(phc_error_bound_path: std::path::PathBuf, phc_ref_id: u32) -> Self {
55 Self {
56 sysfs_phc_error_bound_path: phc_error_bound_path,
57 phc_ref_id,
58 }
59 }
60
61 pub(crate) fn read_phc_error_bound(&self) -> anyhow::Result<i64> {
62 std::fs::read_to_string(&self.sysfs_phc_error_bound_path)?
63 .trim()
64 .parse::<i64>()
65 .map_err(|e| anyhow::anyhow!("Failed to parse PHC error bound value to i64: {}", e))
66 }
67
68 pub(crate) fn get_phc_ref_id(&self) -> u32 {
69 self.phc_ref_id
70 }
71}
72
73#[cfg(test)]
74mod test {
75 use rstest::rstest;
76 use tempfile::NamedTempFile;
77
78 use super::*;
79
80 use std::io::Write;
81
82 #[rstest]
83 #[case::happy_path("PCI_SLOT_NAME=12345", "12345")]
84 #[case::happy_path_multi_line(
85 "
86oneline
87PCI_SLOT_NAME=23456
88twoline",
89 "23456"
90 )]
91 fn test_get_pci_slot_name_success(
92 #[case] file_contents_to_write: &str,
93 #[case] return_value: &str,
94 ) {
95 let mut test_uevent_file = NamedTempFile::new().expect("create mock uevent file failed");
96 test_uevent_file
97 .write_all(file_contents_to_write.as_bytes())
98 .expect("write to mock uevent file failed");
99
100 let rt = get_pci_slot::get_pci_slot_name(test_uevent_file.path().to_str().unwrap());
101 assert!(rt.is_ok());
102 assert_eq!(rt.unwrap(), return_value.to_string());
103 }
104
105 #[rstest]
106 #[case::missing_pci_slot_name("no pci slot name")]
107 fn test_get_pci_slot_name_failure(#[case] file_contents_to_write: &str) {
108 let mut test_uevent_file = NamedTempFile::new().expect("create mock uevent file failed");
109 test_uevent_file
110 .write_all(file_contents_to_write.as_bytes())
111 .expect("write to mock uevent file failed");
112
113 let rt = get_pci_slot::get_pci_slot_name(test_uevent_file.path().to_str().unwrap());
114 assert!(rt.is_err());
115 assert!(rt
116 .unwrap_err()
117 .to_string()
118 .contains("Failed to find PCI_SLOT_NAME at uevent file path"));
119 }
120
121 #[test]
122 fn test_get_pci_slot_name_file_does_not_exist() {
123 let rt = get_pci_slot::get_pci_slot_name("/does/not/exist");
124 assert!(rt.is_err());
125 }
126
127 #[rstest]
128 #[case::happy_path("12345", 12345)]
129 fn test_read_phc_error_bound_success(
130 #[case] file_contents_to_write: &str,
131 #[case] return_value: i64,
132 ) {
133 let mut test_phc_error_bound_file =
134 NamedTempFile::new().expect("create mock phc error bound file failed");
135 test_phc_error_bound_file
136 .write_all(file_contents_to_write.as_bytes())
137 .expect("write to mock phc error bound file failed");
138
139 let phc_error_bound_reader =
140 PhcWithSysfsErrorBound::new(test_phc_error_bound_file.path().to_path_buf(), 0);
141 let rt = phc_error_bound_reader.read_phc_error_bound();
142 assert!(rt.is_ok());
143 assert_eq!(rt.unwrap(), return_value);
144 }
145
146 #[rstest]
147 #[case::parsing_fail("asdf_not_an_i64")]
148 fn test_read_phc_error_bound_bad_file_contents(#[case] file_contents_to_write: &str) {
149 let mut test_phc_error_bound_file =
150 NamedTempFile::new().expect("create mock phc error bound file failed");
151 test_phc_error_bound_file
152 .write_all(file_contents_to_write.as_bytes())
153 .expect("write to mock phc error bound file failed");
154
155 let phc_error_bound_reader =
156 PhcWithSysfsErrorBound::new(test_phc_error_bound_file.path().to_path_buf(), 0);
157 let rt = phc_error_bound_reader.read_phc_error_bound();
158 assert!(rt.is_err());
159 assert!(rt
160 .unwrap_err()
161 .to_string()
162 .contains("Failed to parse PHC error bound value to i64"));
163 }
164
165 #[test]
166 fn test_read_phc_error_bound_file_does_not_exist() {
167 let phc_error_bound_reader = PhcWithSysfsErrorBound::new("/does/not/exist".into(), 0);
168 let rt = phc_error_bound_reader.read_phc_error_bound();
169 assert!(rt.is_err());
170 }
171
172 #[test]
173 fn test_get_phc_ref_id() {
174 let phc_error_bound_reader = PhcWithSysfsErrorBound::new("/does/not/matter".into(), 12345);
175 assert_eq!(phc_error_bound_reader.get_phc_ref_id(), 12345);
176 }
177
178 #[test]
179 fn test_get_error_bound_sysfs_path() {
180 let ctx = mock_get_pci_slot::get_pci_slot_name_context();
181 ctx.expect().returning(|_| Ok("12345".to_string()));
182 let rt = get_error_bound_sysfs_path("arbitrary_interface");
183 assert!(rt.is_ok());
184 assert_eq!(
185 rt.unwrap().to_str().unwrap(),
186 "/sys/bus/pci/devices/12345/phc_error_bound"
187 );
188 }
189}