use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
lazy_static::lazy_static! {
static ref TEST_RESULTS: Mutex<BTreeMap<String, TestResult>> = Mutex::new(BTreeMap::new());
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct TestResult {
passed: bool,
expected_to_fail: bool,
}
pub fn record_test_result(test_name: &str, passed: bool, expected_to_fail: bool) {
let mut results = TEST_RESULTS.lock().unwrap_or_else(|e| e.into_inner());
results.insert(
test_name.to_string(),
TestResult {
passed,
expected_to_fail,
},
);
}
#[cfg(test)]
#[ctor::dtor]
fn generate_test_report() {
if let Err(e) = create_test_summary_report() {
eprintln!("Warning: Failed to generate test summary report: {}", e);
}
}
fn create_test_summary_report() -> Result<(), Box<dyn std::error::Error>> {
let cert_c_dir = PathBuf::from("src/rules/cert_c");
let output_path = PathBuf::from("docs/test-summary.md");
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent)?;
}
let mut categories: BTreeMap<String, Vec<RuleInfo>> = BTreeMap::new();
let mut total_rules = 0;
let mut implemented_rules = 0;
let mut total_tests = 0;
for category_entry in fs::read_dir(&cert_c_dir)? {
let category_entry = category_entry?;
let category_path = category_entry.path();
if !category_path.is_dir() {
continue;
}
let category_name = category_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("Unknown")
.to_string();
if category_name.starts_with('.') {
continue;
}
for rule_entry in fs::read_dir(&category_path)? {
let rule_entry = rule_entry?;
let rule_path = rule_entry.path();
if !rule_path.is_dir() {
continue;
}
let rule_id = rule_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("Unknown")
.to_string();
let toml_path = rule_path.join(format!("{}.toml", rule_id));
let (title, description, is_enabled) = if let Ok(content) =
fs::read_to_string(&toml_path)
{
let title = content
.lines()
.find(|line| line.trim().starts_with("title = "))
.and_then(|line| line.split('=').nth(1))
.map(|s| s.trim().trim_matches('"').to_string())
.unwrap_or_else(|| "No title".to_string());
let description = if let Some(desc_start) = content.find("description = \"\"\"") {
let desc_content = &content[desc_start + 18..];
if let Some(desc_end) = desc_content.find("\"\"\"") {
desc_content[..desc_end].trim().to_string()
} else {
"No description".to_string()
}
} else {
"No description".to_string()
};
let enabled = content.contains("enabled = true");
(title, description, enabled)
} else {
("No title".to_string(), "No description".to_string(), false)
};
let tests_dir = rule_path.join("tests");
let mut fail_tests = Vec::new();
let mut pass_tests = Vec::new();
let results = TEST_RESULTS.lock().unwrap_or_else(|e| e.into_inner());
if tests_dir.exists() {
let fail_dir = tests_dir.join("fail");
if fail_dir.exists() {
for test_file in fs::read_dir(&fail_dir)? {
let test_file = test_file?;
if test_file.path().extension().is_some_and(|e| e == "c") {
let file_name = test_file.file_name().to_string_lossy().to_string();
let test_name = file_name.trim_end_matches(".c");
let func_name = format!(
"test_{}_fail_{}",
rule_id.to_lowercase().replace("-", "_"),
test_name.replace("-", "_").replace(".", "_")
);
let result = results.get(&func_name).cloned();
fail_tests.push(TestCaseInfo {
name: file_name,
result,
});
}
}
}
let pass_dir = tests_dir.join("pass");
if pass_dir.exists() {
for test_file in fs::read_dir(&pass_dir)? {
let test_file = test_file?;
if test_file.path().extension().is_some_and(|e| e == "c") {
let file_name = test_file.file_name().to_string_lossy().to_string();
let test_name = file_name.trim_end_matches(".c");
let func_name = format!(
"test_{}_pass_{}",
rule_id.to_lowercase().replace("-", "_"),
test_name.replace("-", "_").replace(".", "_")
);
let result = results.get(&func_name).cloned();
pass_tests.push(TestCaseInfo {
name: file_name,
result,
});
}
}
}
}
fail_tests.sort_by(|a, b| a.name.cmp(&b.name));
pass_tests.sort_by(|a, b| a.name.cmp(&b.name));
let test_count = fail_tests.len() + pass_tests.len();
total_tests += test_count;
total_rules += 1;
if is_enabled {
implemented_rules += 1;
}
let rule_info = RuleInfo {
id: rule_id.clone(),
category: category_name.clone(),
title,
description,
is_enabled,
fail_tests,
pass_tests,
};
categories
.entry(category_name.clone())
.or_default()
.push(rule_info);
}
}
let mut report = String::new();
report.push_str("# CERT C Rules Test Summary\n\n");
report.push_str("## Overview\n\n");
report.push_str(&format!("- **Total Rules:** {}\n", total_rules));
report.push_str(&format!(
"- **Implemented Rules:** {} ({:.1}%)\n",
implemented_rules,
(implemented_rules as f64 / total_rules as f64) * 100.0
));
report.push_str(&format!("- **Total Test Cases:** {}\n", total_tests));
report.push_str(&format!(
"- **Average Tests per Rule:** {:.1}\n\n",
total_tests as f64 / total_rules as f64
));
report.push_str("## Table of Contents\n\n");
for (category, rules) in &categories {
let implemented_count = rules.iter().filter(|r| r.is_enabled).count();
report.push_str(&format!(
"- [{}](#category-{}) ({} implemented / {} total)\n",
category,
category.to_lowercase(),
implemented_count,
rules.len()
));
for rule in rules {
let (passed, total, not_run) = calculate_rule_metrics(rule);
let status_icon = if rule.is_enabled {
"✅"
} else if total > 0 {
"🔶" } else {
"⚫" };
let status_text = if rule.is_enabled {
"Implemented"
} else if total > 0 {
"Not Implemented (has tests)"
} else {
"Not Implemented (no tests)"
};
let pass_rate = if total > 0 {
format!(
"{}/{} ({:.1}%)",
passed,
total,
(passed as f64 / total as f64) * 100.0
)
} else {
"0/0 (N/A)".to_string()
};
let not_run_str = if not_run > 0 {
format!(" [{} not run]", not_run)
} else {
String::new()
};
report.push_str(&format!(
" - {} [{}](#rule-{}) - {}: Pass {}{}\n",
status_icon,
rule.id,
rule.id.to_lowercase().replace("-", ""),
status_text,
pass_rate,
not_run_str
));
}
}
report.push('\n');
for (category, rules) in &categories {
report.push_str(&format!("## Category: {}\n\n", category));
report.push_str(&format!(
"<a id=\"category-{}\"></a>\n\n",
category.to_lowercase()
));
let implemented_count = rules.iter().filter(|r| r.is_enabled).count();
report.push_str(&format!(
"**Implementation Status:** {} / {} rules ({:.1}%)\n\n",
implemented_count,
rules.len(),
(implemented_count as f64 / rules.len() as f64) * 100.0
));
for rule in rules {
let (passed, total, not_run) = calculate_rule_metrics(rule);
let test_count = rule.fail_tests.len() + rule.pass_tests.len();
let status_icon = if rule.is_enabled {
"✅"
} else if total > 0 {
"🔶"
} else {
"⚫"
};
let status_text = if rule.is_enabled {
"Implemented"
} else if total > 0 {
"Not Implemented (has tests)"
} else {
"Not Implemented (no tests)"
};
report.push_str(&format!(
"### {} {} - {}\n\n",
status_icon, rule.id, status_text
));
report.push_str(&format!(
"<a id=\"rule-{}\"></a>\n\n",
rule.id.to_lowercase().replace("-", "")
));
report.push_str(&format!("**Title:** {}\n\n", rule.title));
report.push_str(&format!("**Description:** {}\n\n", rule.description));
report.push_str(&format!(
"**Test Coverage:** {} tests ({} fail, {} pass)\n\n",
test_count,
rule.fail_tests.len(),
rule.pass_tests.len()
));
if total > 0 {
let pass_rate = (passed as f64 / total as f64) * 100.0;
report.push_str(&format!(
"**Test Results:** {}/{} passed ({:.1}%)",
passed, total, pass_rate
));
if not_run > 0 {
report.push_str(&format!(", {} not run", not_run));
}
report.push_str("\n\n");
}
if !rule.fail_tests.is_empty() {
report.push_str("#### Fail Tests (Should Detect Violations)\n\n");
for test in &rule.fail_tests {
let test_name = test.name.trim_end_matches(".c");
let func_name = format!(
"test_{}_fail_{}",
rule.id.to_lowercase().replace("-", "_"),
test_name.replace("-", "_").replace(".", "_")
);
let status = if let Some(result) = &test.result {
if result.passed {
"✅ PASS"
} else {
"❌ FAIL"
}
} else {
"⏭️ NOT RUN"
};
report.push_str(&format!("- {} `{}` → `{}`\n", status, test.name, func_name));
}
report.push('\n');
}
if !rule.pass_tests.is_empty() {
report.push_str("#### Pass Tests (Should NOT Detect Violations)\n\n");
for test in &rule.pass_tests {
let test_name = test.name.trim_end_matches(".c");
let func_name = format!(
"test_{}_pass_{}",
rule.id.to_lowercase().replace("-", "_"),
test_name.replace("-", "_").replace(".", "_")
);
let status = if let Some(result) = &test.result {
if result.passed {
"✅ PASS"
} else {
"❌ FAIL"
}
} else {
"⏭️ NOT RUN"
};
report.push_str(&format!("- {} `{}` → `{}`\n", status, test.name, func_name));
}
report.push('\n');
}
report.push_str("---\n\n");
}
}
report.push_str("## Summary by Category\n\n");
report.push_str("| Category | Rules | Implemented | Tests | Avg Tests/Rule |\n");
report.push_str("|----------|-------|-------------|-------|----------------|\n");
for (category, rules) in &categories {
let implemented = rules.iter().filter(|r| r.is_enabled).count();
let tests: usize = rules
.iter()
.map(|r| r.fail_tests.len() + r.pass_tests.len())
.sum();
let avg = tests as f64 / rules.len() as f64;
report.push_str(&format!(
"| {} | {} | {} | {} | {:.1} |\n",
category,
rules.len(),
implemented,
tests,
avg
));
}
report.push('\n');
fs::write(&output_path, report)?;
println!("Generated test summary report: {}", output_path.display());
Ok(())
}
fn calculate_rule_metrics(rule: &RuleInfo) -> (usize, usize, usize) {
let mut passed = 0;
let mut total = 0;
let mut not_run = 0;
for test in &rule.fail_tests {
total += 1;
if let Some(result) = &test.result {
if result.passed {
passed += 1;
}
} else {
not_run += 1;
}
}
for test in &rule.pass_tests {
total += 1;
if let Some(result) = &test.result {
if result.passed {
passed += 1;
}
} else {
not_run += 1;
}
}
(passed, total, not_run)
}
#[derive(Debug, Clone)]
struct TestCaseInfo {
name: String,
result: Option<TestResult>,
}
#[derive(Debug)]
#[allow(dead_code)]
struct RuleInfo {
id: String,
category: String,
title: String,
description: String,
is_enabled: bool,
fail_tests: Vec<TestCaseInfo>,
pass_tests: Vec<TestCaseInfo>,
}
include!(concat!(env!("OUT_DIR"), "/integration_tests.rs"));