use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=tests/");
println!("cargo:rerun-if-changed=src/ptop/");
println!("cargo:rerun-if-changed=src/widgets/");
if env::var("DOCS_RS").is_ok() {
return;
}
let ptop_enabled = env::var("CARGO_FEATURE_PTOP").is_ok();
if ptop_enabled {
enforce_ptop_tests();
}
enforce_widget_tests();
}
#[allow(clippy::too_many_lines)]
fn enforce_ptop_tests() {
let tests_dir = Path::new("tests");
let async_test = tests_dir.join("cpu_exploded_async.rs");
assert!(
async_test.exists(),
"\n\
╔══════════════════════════════════════════════════════════════════════════════╗\n\
║ SPEC-024 ENFORCEMENT FAILURE: MISSING INTERFACE TEST ║\n\
╠══════════════════════════════════════════════════════════════════════════════╣\n\
║ ║\n\
║ The file tests/cpu_exploded_async.rs is MANDATORY for ptop builds. ║\n\
║ ║\n\
║ This test DEFINES the interface for async CPU data updates. ║\n\
║ Without it, the async update system has no contract. ║\n\
║ ║\n\
║ TESTS DEFINE INTERFACE. IMPLEMENTATION FOLLOWS. ║\n\
║ ║\n\
║ See: docs/specifications/pixel-by-pixel-demo-ptop-ttop.md Part 0 ║\n\
║ ║\n\
╚══════════════════════════════════════════════════════════════════════════════╝\n"
);
let async_test_content = fs::read_to_string(&async_test).unwrap_or_default();
let required_patterns = [
(
"test_metrics_snapshot_includes_per_core_freq",
"MetricsSnapshot must have per_core_freq field",
),
(
"test_metrics_snapshot_includes_per_core_temp",
"MetricsSnapshot must have per_core_temp field",
),
(
"test_app_has_per_core_freq_field",
"App must have per_core_freq field",
),
(
"test_app_has_per_core_temp_field",
"App must have per_core_temp field",
),
(
"test_apply_snapshot_updates_freq_temp",
"apply_snapshot must transfer freq/temp data",
),
(
"test_render_uses_async_updated_data",
"Render must use async-updated data",
),
];
for (pattern, description) in required_patterns {
assert!(
async_test_content.contains(pattern),
"\n\
╔══════════════════════════════════════════════════════════════════════════════╗\n\
║ SPEC-024 ENFORCEMENT FAILURE: MISSING REQUIRED TEST ║\n\
╠══════════════════════════════════════════════════════════════════════════════╣\n\
║ ║\n\
║ Missing test: {pattern:<50} ║\n\
║ ║\n\
║ Purpose: {description:<54} ║\n\
║ ║\n\
║ This test is REQUIRED by SPEC-024 to define the async data flow interface. ║\n\
║ ║\n\
╚══════════════════════════════════════════════════════════════════════════════╝\n"
);
}
let visibility_test = tests_dir.join("cbtop_visibility.rs");
assert!(
visibility_test.exists(),
"\n\
╔══════════════════════════════════════════════════════════════════════════════╗\n\
║ SPEC-024 ENFORCEMENT FAILURE: MISSING VISIBILITY TEST ║\n\
╠══════════════════════════════════════════════════════════════════════════════╣\n\
║ ║\n\
║ The file tests/cbtop_visibility.rs is MANDATORY for ptop builds. ║\n\
║ ║\n\
║ This test ensures panels render actual data, not placeholder content. ║\n\
║ ║\n\
╚══════════════════════════════════════════════════════════════════════════════╝\n"
);
let app_interface = tests_dir.join("ptop_app_interface.rs");
assert!(
app_interface.exists(),
"\n\
╔══════════════════════════════════════════════════════════════════════════════╗\n\
║ SPEC-024 ENFORCEMENT FAILURE: MISSING APP INTERFACE TEST ║\n\
╠══════════════════════════════════════════════════════════════════════════════╣\n\
║ ║\n\
║ The file tests/ptop_app_interface.rs is MANDATORY for ptop builds. ║\n\
║ ║\n\
║ This test defines the App, MetricsSnapshot, and MetricsCollector interface. ║\n\
║ ║\n\
╚══════════════════════════════════════════════════════════════════════════════╝\n"
);
let panels_interface = tests_dir.join("ptop_panels_interface.rs");
assert!(
panels_interface.exists(),
"\n\
╔══════════════════════════════════════════════════════════════════════════════╗\n\
║ SPEC-024 ENFORCEMENT FAILURE: MISSING PANELS INTERFACE TEST ║\n\
╠══════════════════════════════════════════════════════════════════════════════╣\n\
║ ║\n\
║ The file tests/ptop_panels_interface.rs is MANDATORY for ptop builds. ║\n\
║ ║\n\
║ This test defines the interface for all panel types. ║\n\
║ ║\n\
╚══════════════════════════════════════════════════════════════════════════════╝\n"
);
}
fn enforce_widget_tests() {
let widgets_dir = Path::new("src/widgets");
let tests_dir = Path::new("tests");
if !widgets_dir.exists() {
return;
}
let widget_files: HashSet<String> = fs::read_dir(widgets_dir)
.into_iter()
.flatten()
.filter_map(std::result::Result::ok)
.filter(|e| e.path().extension().is_some_and(|x| x == "rs"))
.filter(|e| e.file_name() != "mod.rs")
.map(|e| e.file_name().to_string_lossy().replace(".rs", ""))
.collect();
let critical_widgets = [
"process_table",
"graph",
"gauge",
"sparkline",
"border",
"display_rules",
];
for widget in critical_widgets {
if widget_files.contains(widget) {
let widget_path = widgets_dir.join(format!("{widget}.rs"));
let content = fs::read_to_string(&widget_path).unwrap_or_default();
let has_inline_tests = content.contains("#[cfg(test)]")
&& (content.contains("#[test]") || content.contains("#[path ="));
let external_test_patterns = [
format!("f_*_{widget}*.rs"),
format!("{widget}_test.rs"),
format!("widget_{widget}.rs"),
];
let has_external_tests = fs::read_dir(tests_dir)
.into_iter()
.flatten()
.filter_map(std::result::Result::ok)
.any(|e| {
let name = e.file_name().to_string_lossy().to_lowercase();
name.contains(widget)
|| external_test_patterns.iter().any(|p| {
let pattern = p.replace('*', "");
name.contains(&pattern.replace(".rs", ""))
})
});
if !has_inline_tests && !has_external_tests {
println!(
"cargo:warning=SPEC-024: Widget '{widget}' has no tests. \
Add #[cfg(test)] module or tests/{widget}_test.rs"
);
}
}
}
}