use crate::effects::AnalysisEffect;
use crate::env::{AnalysisEnv, RealEnv};
use crate::errors::AnalysisError;
use crate::io::traits::CoverageData;
use std::path::PathBuf;
use stillwater::effect::prelude::*;
pub fn load_lcov_effect(path: PathBuf) -> AnalysisEffect<CoverageData> {
let path_display = path.display().to_string();
from_fn(move |env: &RealEnv| {
env.coverage_loader().load_lcov(&path).map_err(|e| {
AnalysisError::coverage_with_path(
format!(
"Failed to load LCOV from '{}': {}",
path_display,
e.message()
),
&path,
)
})
})
.boxed()
}
pub fn load_cobertura_effect(path: PathBuf) -> AnalysisEffect<CoverageData> {
let path_display = path.display().to_string();
from_fn(move |env: &RealEnv| {
env.coverage_loader().load_cobertura(&path).map_err(|e| {
AnalysisError::coverage_with_path(
format!(
"Failed to load Cobertura from '{}': {}",
path_display,
e.message()
),
&path,
)
})
})
.boxed()
}
pub fn load_coverage_effect(
primary_path: PathBuf,
project_root: PathBuf,
) -> AnalysisEffect<CoverageData> {
let fallback_paths = vec![
primary_path.clone(),
project_root.join("target/llvm-cov-target/debug/coverage/lcov.info"),
project_root.join("target/coverage/lcov.info"),
project_root.join("lcov.info"),
];
let cobertura_path = project_root.join("target/coverage/cobertura.xml");
try_coverage_paths(fallback_paths, cobertura_path)
}
fn try_coverage_paths(
lcov_paths: Vec<PathBuf>,
cobertura_path: PathBuf,
) -> AnalysisEffect<CoverageData> {
let paths_display: Vec<String> = lcov_paths.iter().map(|p| p.display().to_string()).collect();
let cobertura_display = cobertura_path.display().to_string();
from_fn(move |env: &RealEnv| {
let mut last_error = None;
for path in &lcov_paths {
if env.file_system().exists(path) {
match env.coverage_loader().load_lcov(path) {
Ok(data) => return Ok(data),
Err(e) => last_error = Some(e),
}
}
}
if env.file_system().exists(&cobertura_path) {
match env.coverage_loader().load_cobertura(&cobertura_path) {
Ok(data) => return Ok(data),
Err(e) => last_error = Some(e),
}
}
Err(AnalysisError::coverage(format!(
"No coverage data found. Tried:\n LCOV: {}\n Cobertura: {}\n\n\
Generate coverage with:\n cargo llvm-cov --lcov --output-path coverage.lcov\n\
Last error: {}",
paths_display.join(", "),
cobertura_display,
last_error
.map(|e| e.to_string())
.unwrap_or_else(|| "none".to_string())
)))
})
.boxed()
}
pub fn load_coverage_optional_effect(path: PathBuf) -> AnalysisEffect<CoverageData> {
let path_clone = path.clone();
from_fn(move |env: &RealEnv| {
if !env.file_system().exists(&path_clone) {
return Ok(CoverageData::default());
}
match env.coverage_loader().load_lcov(&path_clone) {
Ok(data) => Ok(data),
Err(_) => {
Ok(CoverageData::default())
}
}
})
.boxed()
}
pub fn has_coverage_effect(project_root: PathBuf) -> AnalysisEffect<bool> {
from_fn(move |env: &RealEnv| {
let paths = [
project_root.join("target/llvm-cov-target/debug/coverage/lcov.info"),
project_root.join("target/coverage/lcov.info"),
project_root.join("target/coverage/cobertura.xml"),
project_root.join("lcov.info"),
project_root.join("coverage.lcov"),
];
Ok(paths.iter().any(|p| env.file_system().exists(p)))
})
.boxed()
}
pub fn find_coverage_path_effect(project_root: PathBuf) -> AnalysisEffect<Option<PathBuf>> {
from_fn(move |env: &RealEnv| {
let paths = [
project_root.join("target/llvm-cov-target/debug/coverage/lcov.info"),
project_root.join("target/coverage/lcov.info"),
project_root.join("target/coverage/cobertura.xml"),
project_root.join("lcov.info"),
project_root.join("coverage.lcov"),
];
Ok(paths.into_iter().find(|p| env.file_system().exists(p)))
})
.boxed()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::DebtmapConfig;
use crate::effects::run_effect;
use tempfile::TempDir;
fn create_test_env() -> (TempDir, DebtmapConfig) {
let temp_dir = TempDir::new().unwrap();
(temp_dir, DebtmapConfig::default())
}
fn create_lcov_content() -> &'static str {
r#"SF:src/main.rs
DA:1,5
DA:2,5
DA:3,0
end_of_record
SF:src/lib.rs
DA:1,1
DA:2,0
end_of_record
"#
}
#[test]
fn test_load_lcov_effect_success() {
let (temp_dir, config) = create_test_env();
let lcov_path = temp_dir.path().join("coverage.lcov");
std::fs::write(&lcov_path, create_lcov_content()).unwrap();
let effect = load_lcov_effect(lcov_path);
let result = run_effect(effect, config);
assert!(result.is_ok());
let coverage = result.unwrap();
let main_coverage = coverage
.get_file_coverage(std::path::Path::new("src/main.rs"))
.unwrap();
assert!((main_coverage - 66.67).abs() < 1.0);
}
#[test]
fn test_load_lcov_effect_not_found() {
let config = DebtmapConfig::default();
let effect = load_lcov_effect("/nonexistent/coverage.lcov".into());
let result = run_effect(effect, config);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("Failed to load LCOV"));
}
#[test]
fn test_load_coverage_optional_effect_missing() {
let config = DebtmapConfig::default();
let effect = load_coverage_optional_effect("/nonexistent/coverage.lcov".into());
let result = run_effect(effect, config);
assert!(result.is_ok());
let coverage = result.unwrap();
assert!(coverage.files().next().is_none());
}
#[test]
fn test_load_coverage_optional_effect_exists() {
let (temp_dir, config) = create_test_env();
let lcov_path = temp_dir.path().join("coverage.lcov");
std::fs::write(&lcov_path, create_lcov_content()).unwrap();
let effect = load_coverage_optional_effect(lcov_path);
let result = run_effect(effect, config);
assert!(result.is_ok());
let coverage = result.unwrap();
assert!(coverage
.get_file_coverage(std::path::Path::new("src/main.rs"))
.is_some());
}
#[test]
fn test_has_coverage_effect_no_files() {
let (temp_dir, config) = create_test_env();
let effect = has_coverage_effect(temp_dir.path().to_path_buf());
let result = run_effect(effect, config);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_has_coverage_effect_with_files() {
let (temp_dir, config) = create_test_env();
let coverage_dir = temp_dir.path().join("target/coverage");
std::fs::create_dir_all(&coverage_dir).unwrap();
std::fs::write(coverage_dir.join("lcov.info"), create_lcov_content()).unwrap();
let effect = has_coverage_effect(temp_dir.path().to_path_buf());
let result = run_effect(effect, config);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_find_coverage_path_effect_found() {
let (temp_dir, config) = create_test_env();
let coverage_dir = temp_dir.path().join("target/coverage");
std::fs::create_dir_all(&coverage_dir).unwrap();
let lcov_path = coverage_dir.join("lcov.info");
std::fs::write(&lcov_path, create_lcov_content()).unwrap();
let effect = find_coverage_path_effect(temp_dir.path().to_path_buf());
let result = run_effect(effect, config);
assert!(result.is_ok());
let found_path = result.unwrap();
assert!(found_path.is_some());
assert_eq!(found_path.unwrap(), lcov_path);
}
#[test]
fn test_find_coverage_path_effect_not_found() {
let (temp_dir, config) = create_test_env();
let effect = find_coverage_path_effect(temp_dir.path().to_path_buf());
let result = run_effect(effect, config);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_load_coverage_effect_with_fallback() {
let (temp_dir, config) = create_test_env();
let coverage_dir = temp_dir.path().join("target/coverage");
std::fs::create_dir_all(&coverage_dir).unwrap();
std::fs::write(coverage_dir.join("lcov.info"), create_lcov_content()).unwrap();
let effect = load_coverage_effect(
temp_dir.path().join("nonexistent.lcov"),
temp_dir.path().to_path_buf(),
);
let result = run_effect(effect, config);
assert!(result.is_ok());
let coverage = result.unwrap();
assert!(coverage
.get_file_coverage(std::path::Path::new("src/main.rs"))
.is_some());
}
}