use std::path::{Path, PathBuf};
const ALLOWED_MEMBERS: &[&str] = &["Hello", "Reload", "StartUnit", "StopUnit", "ResetFailedUnit", "GetUnit", "Get"];
const FORBIDDEN_IN_CODE: &[&str] = &[
"EnableUnitFiles",
"LinkUnitFiles",
"AddDependencyUnitFiles",
"PresetUnitFiles",
"SetDefaultTarget",
"MaskUnitFiles",
"Command::new(\"systemctl",
"WantedBy=",
"RequiredBy=",
"[Install]",
];
const ALLOWED_CODE: &[(&str, &str)] = &[("service/service_provision.rs", "sockets.target.wants")];
pub(crate) fn lines_that_start_at_login(text: &str) -> Vec<String> {
let mut found: Vec<String> = Vec::new();
for line in text.lines() {
let trimmed: &str = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with(';') {
continue;
}
let lower: String = trimmed.to_ascii_lowercase();
let install: bool = lower == "[install]"
|| ["wantedby=", "requiredby=", "upheldby=", "also=", "alias=", "defaultinstance="].iter().any(|key| { return lower.starts_with(key); });
if install {
found.push(trimmed.to_string());
}
}
return found;
}
fn files_below(root: &Path, extensions: &[&str]) -> Vec<String> {
let mut found: Vec<String> = Vec::new();
let mut pending: Vec<PathBuf> = vec![root.to_path_buf()];
while let Some(directory) = pending.pop() {
for entry in std::fs::read_dir(&directory).expect("a readable source directory").flatten() {
let path: PathBuf = entry.path();
if path.is_dir() {
pending.push(path);
continue;
}
let extension: String = path.extension().map(|text| { return text.to_string_lossy().into_owned(); }).unwrap_or_default();
if extensions.contains(&extension.as_str()) {
let relative: &Path = path.strip_prefix(root).expect("a path below the root");
found.push(relative.to_string_lossy().replace('\\', "/"));
}
}
}
found.sort();
return found;
}
fn is_test_file(relative: &str) -> bool {
let name: &str = relative.rsplit('/').next().unwrap_or(relative);
return name.ends_with("_tests.rs") || name.contains("test_support") || relative.starts_with("testing/") || relative.starts_with("binary/");
}
fn native_root() -> PathBuf {
return PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src").join("native");
}
fn code_lines(text: &str) -> Vec<(usize, &str)> {
return text
.lines()
.enumerate()
.filter(|(_, line)| { let trimmed: &str = line.trim_start(); return !trimmed.is_empty() && !trimmed.starts_with("//"); })
.map(|(index, line)| { return (index + 1, line); })
.collect();
}
#[test]
fn the_unit_templates_have_no_install_section() {
let root: PathBuf = native_root().join("service").join("service_units");
let templates: Vec<String> = files_below(root.as_path(), &["socket", "service"]);
assert!(templates.len() >= 2, "the socket and the service template are found: {templates:?}");
for name in templates {
let text: String = std::fs::read_to_string(root.join(name.as_str())).expect("a template");
assert_eq!(lines_that_start_at_login(text.as_str()), Vec::<String>::new(), "{name}");
}
}
#[test]
fn the_detector_finds_every_install_line_and_skips_comments() {
for bad in ["[Install]", "WantedBy=default.target", "RequiredBy=sockets.target", "UpheldBy=x.target", "Also=y.socket", "Alias=z.service", "DefaultInstance=a", " wantedby=sockets.target"] {
let text: String = format!("[Unit]\nDescription=x\n{bad}\n");
assert_eq!(lines_that_start_at_login(text.as_str()).len(), 1, "{bad}");
}
assert!(lines_that_start_at_login("[Unit]\n# WantedBy=default.target is not written\n; [Install]\n[Socket]\nListenStream=%t/x.sock\n").is_empty());
}
#[test]
fn no_release_code_makes_a_unit_start_at_startup_or_login() {
let root: PathBuf = native_root();
let mut found: Vec<String> = Vec::new();
for relative in files_below(root.as_path(), &["rs"]) {
if is_test_file(relative.as_str()) {
continue;
}
let text: String = std::fs::read_to_string(root.join(relative.as_str())).expect("a source file");
for (number, line) in code_lines(text.as_str()) {
for forbidden in FORBIDDEN_IN_CODE {
if line.contains(forbidden) {
found.push(format!("{relative}:{number}: {forbidden}"));
}
}
let wants: bool = line.contains(".wants") || line.contains(".requires");
let excused: bool = ALLOWED_CODE.iter().any(|(file, allowed_text)| { return *file == relative && line.contains(allowed_text); });
if wants && !excused {
found.push(format!("{relative}:{number}: a wants or requires folder"));
}
}
}
assert_eq!(found, Vec::<String>::new());
let provision: String = std::fs::read_to_string(root.join("service").join("service_provision.rs")).expect("provisioning");
assert!(provision.contains("sockets.target.wants"), "the exception is still needed; remove it from the list when the code goes");
}
#[test]
fn the_manager_is_called_only_with_the_members_on_the_list() {
let root: PathBuf = native_root();
let mut called: Vec<String> = Vec::new();
for relative in files_below(root.as_path(), &["rs"]) {
if is_test_file(relative.as_str()) {
continue;
}
let text: String = std::fs::read_to_string(root.join(relative.as_str())).expect("a source file");
for (_, line) in code_lines(text.as_str()) {
let Some(at) = line.find("member: \"") else { continue };
let rest: &str = &line[at + "member: \"".len()..];
let name: &str = rest.split('"').next().unwrap_or("");
called.push(name.to_string());
}
}
assert!(called.len() >= ALLOWED_MEMBERS.len() - 1, "the client's targets are found: {called:?}");
let outside: Vec<&String> = called.iter().filter(|name| { return !ALLOWED_MEMBERS.contains(&name.as_str()); }).collect();
assert!(outside.is_empty(), "members outside the list: {outside:?}");
}