Skip to content

Commit b6ecea9

Browse files
committed
chore(Test): add test to ensure auto-managed devices have a hwdb entry
1 parent 644d596 commit b6ecea9

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,13 @@ format: ## Run rustfmt on all source files
110110
rustfmt --edition 2021 $(ALL_RS)
111111

112112
.PHONY: test
113-
test: ## Run all tests
113+
test: test-autostart-rules ## Run all tests
114114
cargo test -- --show-output
115115

116+
.PHONY: test-autostart-rules
117+
test-autostart-rules: ## Test to ensure autostart rules are up-to-date
118+
RUST_BACKTRACE=0 cargo test config::config_test::check_autostart_rules -- --exact --show-output
119+
116120
.PHONY: generate
117121
generate: ## Generate schema definitions for configs
118122
cargo run --bin generate

src/config/config_test.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::{
2+
collections::{HashMap, HashSet},
3+
error::Error,
4+
path::PathBuf,
5+
};
6+
7+
use glob_match::glob_match;
8+
use tokio::fs;
9+
10+
use crate::config::CompositeDeviceConfig;
11+
12+
const AUTOSTART_HWDB_FILE: &str = "./rootfs/usr/lib/udev/hwdb.d/60-inputplumber-autostart.hwdb";
13+
const DEVICE_CONFIG_DIR: &str = "./rootfs/usr/share/inputplumber/devices";
14+
15+
const RED: &str = "\x1b[31m";
16+
const YELLOW: &str = "\x1b[33m";
17+
const PURPLE: &str = "\x1b[35m";
18+
const CYAN: &str = "\x1b[36m";
19+
const ENDCOLOR: &str = "\x1b[0m";
20+
21+
/// Test for validating that there is a device entry in the autostart hwdb file
22+
/// for every device with `auto_manage: true`.
23+
#[tokio::test]
24+
async fn check_autostart_rules() -> Result<(), Box<dyn Error>> {
25+
// Find all config files
26+
let mut configs = HashMap::new();
27+
let mut config_dir = fs::read_dir(DEVICE_CONFIG_DIR).await?;
28+
while let Some(entry) = config_dir.next_entry().await? {
29+
if !entry.file_type().await?.is_file() {
30+
continue;
31+
}
32+
33+
// Load the config file
34+
let path = entry.path();
35+
let Ok(config) = CompositeDeviceConfig::from_yaml_file(path.display().to_string()) else {
36+
continue;
37+
};
38+
39+
// Only consider configs with auto_manage enabled
40+
let Some(options) = config.options.as_ref() else {
41+
continue;
42+
};
43+
if !options.auto_manage.unwrap_or_default() {
44+
continue;
45+
}
46+
47+
configs.insert(path, config);
48+
}
49+
50+
// Check each config
51+
let mut failures = Vec::new();
52+
let mut failed_configs = HashSet::new();
53+
for (path, config) in configs {
54+
println!("Checking config {CYAN}{path:?}{ENDCOLOR}");
55+
56+
// Validate each DMI match
57+
for entry in config.matches {
58+
let Some(dmi) = entry.dmi_data else {
59+
continue;
60+
};
61+
62+
// Build the glob pattern to match in the file
63+
let mut patterns = Vec::new();
64+
if let Some(vendor) = dmi.sys_vendor.as_ref() {
65+
let vendor = vendor.replace(" ", ""); // Remove spaces
66+
let pattern_part = format!("svn{vendor}:");
67+
patterns.push(pattern_part);
68+
}
69+
if let Some(product) = dmi.product_name.as_ref() {
70+
let product = product.replace(" ", ""); // Remove spaces
71+
let pattern_part = format!("pn{product}:");
72+
patterns.push(pattern_part);
73+
}
74+
if let Some(board_name) = dmi.board_name.as_ref() {
75+
let board_name = board_name.replace(" ", ""); // Remove spaces
76+
let pattern_part = format!("rn{board_name}:");
77+
patterns.push(pattern_part);
78+
}
79+
80+
let pattern = patterns.join("*");
81+
let pattern = format!("dmi:*{pattern}*");
82+
println!(
83+
" Checking for autostart rule with glob pattern: {YELLOW}{pattern}{ENDCOLOR}"
84+
);
85+
86+
// Check to see if the pattern matches any lines in the hwdb file
87+
let mut has_hwdb_entry = false;
88+
let hwdb_file = fs::read_to_string(PathBuf::from(AUTOSTART_HWDB_FILE)).await?;
89+
for line in hwdb_file.lines() {
90+
//println!("Line: {line}");
91+
if glob_match(pattern.as_str(), line) {
92+
//println!(" Line matches pattern: {pattern}");
93+
has_hwdb_entry = true;
94+
}
95+
}
96+
97+
if has_hwdb_entry {
98+
continue;
99+
}
100+
101+
println!(
102+
" {RED}Failed to find pattern {YELLOW}'{pattern}'{RED} in hwdb config{ENDCOLOR}"
103+
);
104+
failures.push(format!("Unable to find pattern '{pattern}' generated from config {path:?} in hwdb file: {AUTOSTART_HWDB_FILE}"));
105+
failed_configs.insert(path.clone());
106+
}
107+
}
108+
109+
// Print the results
110+
println!();
111+
112+
if failures.is_empty() {
113+
println!("Total errors: 0");
114+
println!();
115+
println!("Success!");
116+
return Ok(());
117+
}
118+
119+
println!("Errors:");
120+
for failure in failures.iter() {
121+
let msg = format!(" {RED}* {failure}{ENDCOLOR}");
122+
println!("{msg}");
123+
}
124+
println!("Total errors: {}", failures.len());
125+
println!();
126+
127+
println!("Configs with failures:");
128+
let mut failed_configs: Vec<PathBuf> = failed_configs.into_iter().collect();
129+
failed_configs.sort();
130+
for config in failed_configs {
131+
println!(" {config:?}");
132+
}
133+
134+
println!();
135+
println!("{PURPLE}ERROR: The above device configurations have `auto_manage: true`, but do not have a matching entry in the `inputplumber-autostart.hwdb` file. Please add an entry to the hwdb file so the inputplumber service will start when the device is detected.{ENDCOLOR}");
136+
println!();
137+
println!("Failed!");
138+
139+
assert_eq!(failures.len(), 0);
140+
141+
Ok(())
142+
}

src/config/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
pub mod capability_map;
2+
#[cfg(test)]
3+
pub mod config_test;
24
pub mod path;
35

46
use std::io;

0 commit comments

Comments
 (0)