use std::collections::BTreeMap;
use crate::core::TestRun;
#[derive(Debug)]
pub struct TestGap {
pub location: String,
pub kind: GapKind,
pub suggestion: String,
}
#[derive(Debug)]
pub enum GapKind {
Undescribed,
UntestedFunction,
}
pub fn analyze_gaps(run: &TestRun, src_dir: Option<&std::path::Path>) -> Vec<TestGap> {
let mut gaps = Vec::new();
let test_descriptions: BTreeMap<String, bool> = run.suites
.iter()
.flat_map(|s| s.tests.iter())
.map(|t| (t.name.clone(), t.suite.is_some()))
.collect();
for suite in &run.suites {
if suite.description.is_none() && !suite.tests.is_empty() {
if suite.name.contains("::") || suite.name.chars().any(|c| c.is_uppercase()) {
gaps.push(TestGap {
location: suite.name.clone(),
kind: GapKind::Undescribed,
suggestion: format!("Add .description() to describe(\"{}\") block", suite.name),
});
}
}
}
if let Some(dir) = src_dir {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map_or(false, |e| e == "rs") {
if let Ok(content) = std::fs::read_to_string(&path) {
for line in content.lines() {
let trimmed = line.trim();
if let Some(name) = trimmed.strip_prefix("pub fn ") {
let fn_name = name.split('(').next().unwrap_or(name).trim();
let has_test = test_descriptions.keys().any(|t| t.contains(fn_name));
if !has_test && fn_name != "main" && !fn_name.starts_with('_') {
gaps.push(TestGap {
location: format!("{}::{}", path.file_stem().unwrap_or_default().to_string_lossy(), fn_name),
kind: GapKind::UntestedFunction,
suggestion: format!("Add a test for `pub fn {}`", fn_name),
});
}
}
}
}
}
}
}
}
gaps
}