#![cfg_attr(coverage_nightly, coverage(off))]
use super::super::types::QueryResult;
use super::enrichment::enrich_with_coverage;
use super::parsing::build_coverage_map;
use super::profdata::{
load_coverage_from_cache, run_cargo_llvm_cov_and_cache, write_negative_coverage_cache,
};
use super::types::CoverageCache;
use std::collections::HashMap;
use std::path::Path;
#[cfg_attr(coverage_nightly, coverage(off))]
#[allow(clippy::type_complexity)]
fn try_load_coverage_from_explicit_path(
path: &Path,
project_root: &Path,
) -> Result<Option<HashMap<String, HashMap<usize, u64>>>, String> {
let json = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read coverage file {}: {e}", path.display()))?;
Ok(Some(build_coverage_map(&json, project_root)?))
}
#[allow(clippy::type_complexity)]
fn try_load_coverage_from_env(
project_root: &Path,
) -> Result<Option<HashMap<String, HashMap<usize, u64>>>, String> {
let env_path = match std::env::var("PMAT_COVERAGE_FILE") {
Ok(p) => p,
Err(_) => return Ok(None),
};
let path = std::path::PathBuf::from(&env_path);
if !path.exists() {
return Ok(None);
}
try_load_coverage_from_explicit_path(&path, project_root)
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub async fn enrich_results_with_coverage(
results: &mut [QueryResult],
project_root: &Path,
coverage_file: Option<&Path>,
) -> Result<(), String> {
use std::process::Command;
if results.is_empty() {
return Ok(());
}
let file_coverage = if let Some(path) = coverage_file {
try_load_coverage_from_explicit_path(path, project_root)?
} else {
try_load_coverage_from_env(project_root)?
};
if let Some(cov) = file_coverage {
enrich_with_coverage(results, &cov);
return Ok(());
}
let cache_path = project_root.join(".pmat/coverage-cache.json");
let head_hash = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(project_root)
.output()
.map_err(|e| format!("git rev-parse failed: {e}"))?;
let head_hash = String::from_utf8_lossy(&head_hash.stdout)
.trim()
.to_string();
if let Some(cached) = load_coverage_from_cache(&cache_path, &head_hash, project_root) {
if cached.is_empty() {
return Err(
"No coverage data available (cached from previous attempt).\n\n\
To generate it, run:\n \
cargo llvm-cov test --lib --no-report\n\n\
Then re-run with --coverage-gaps.\n\
Or pass --coverage-file <path> to use existing coverage JSON."
.to_string(),
);
}
enrich_with_coverage(results, &cached);
return Ok(());
}
match run_cargo_llvm_cov_and_cache(project_root, &cache_path, &head_hash) {
Ok(cov) => {
enrich_with_coverage(results, &cov);
Ok(())
}
Err(e) => {
write_negative_coverage_cache(&cache_path, &head_hash, project_root);
Err(e)
}
}
}
pub fn load_workspace_coverage(
siblings: &[(std::path::PathBuf, String)],
) -> HashMap<String, HashMap<usize, u64>> {
let mut merged: HashMap<String, HashMap<usize, u64>> = HashMap::new();
for (idx_path, project_name) in siblings {
let pmat_dir = idx_path.parent().unwrap_or(Path::new("."));
let cache_path = pmat_dir.join("coverage-cache.json");
let cache_json = match std::fs::read_to_string(&cache_path) {
Ok(j) => j,
Err(_) => continue, };
let cache: CoverageCache = match serde_json::from_str(&cache_json) {
Ok(c) => c,
Err(_) => continue,
};
for (file_path, line_hits) in cache.files {
let prefixed = format!("{}/{}", project_name, file_path);
merged.insert(prefixed, line_hits);
}
}
merged
}