async fn handle_resolve_paths(input: &Path, output: &Path, project_path: &Path) -> Result<()> {
println!("🔍 Resolving test file paths");
println!(" Project: {}", project_path.display());
println!();
let content = std::fs::read_to_string(input).context("Failed to read discovery report")?;
let mut report: DiscoveryReport =
serde_json::from_str(&content).context("Failed to parse discovery report")?;
println!(
" Found {} failures to resolve",
report.test_failures.len()
);
let test_file_map = build_test_file_map(project_path)?;
println!(" Found {} test files", test_file_map.len());
let mut resolved = 0;
for failure in &mut report.test_failures {
if let Some(path) = resolve_test_path(&failure.name, &test_file_map) {
failure.file = path;
resolved += 1;
}
}
println!(
" Resolved {} of {} paths",
resolved,
report.test_failures.len()
);
let json = serde_json::to_string_pretty(&report)?;
std::fs::write(output, json)?;
println!("\n✅ Output written to: {}", output.display());
Ok(())
}
fn build_test_file_map(project_path: &Path) -> Result<std::collections::HashMap<String, PathBuf>> {
use std::collections::HashMap;
let mut map = HashMap::new();
for entry in walkdir::WalkDir::new(project_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
let path = entry.path();
if let Ok(content) = std::fs::read_to_string(path) {
for line in content.lines() {
if line.trim().starts_with("fn ") && line.contains("(") {
let trimmed = line.trim().strip_prefix("fn ").unwrap_or(line);
if let Some(name_end) = trimmed.find('(') {
let fn_name = trimmed.get(..name_end).unwrap_or_default();
map.insert(fn_name.to_string(), path.to_path_buf());
}
}
}
}
}
Ok(map)
}
fn resolve_test_path(
test_name: &str,
map: &std::collections::HashMap<String, PathBuf>,
) -> Option<PathBuf> {
if let Some(path) = map.get(test_name) {
return Some(path.clone());
}
if let Some(fn_name) = test_name.split("::").last() {
if let Some(path) = map.get(fn_name) {
return Some(path.clone());
}
}
None
}
fn parse_test_summary(stdout: &str, stderr: &str) -> (usize, usize, usize) {
let combined = format!("{}\n{}", stdout, stderr);
let mut passed = 0;
let mut failed = 0;
let mut ignored = 0;
for line in combined.lines() {
if line.contains("passed") && line.contains("filtered") {
if let Some(p) = extract_number(line, "passed") {
passed = p;
}
if let Some(f) = extract_number(line, "failed") {
failed = f;
}
if let Some(i) = extract_number(line, "ignored") {
ignored = i;
}
}
}
(passed, failed, ignored)
}
fn extract_number(line: &str, keyword: &str) -> Option<usize> {
if let Some(pos) = line.find(keyword) {
let before = line.get(..pos).unwrap_or_default();
let parts: Vec<&str> = before.split_whitespace().collect();
if let Some(num_str) = parts.last() {
return num_str.parse().ok();
}
}
None
}