impl LcovParser {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<(StatementId, usize)>> {
let content = std::fs::read_to_string(path.as_ref())
.map_err(|e| anyhow!("Failed to read LCOV file: {}", e))?;
Self::parse(&content)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn parse(content: &str) -> Result<Vec<(StatementId, usize)>> {
let mut results = Vec::new();
let mut current_file: Option<PathBuf> = None;
for line in content.lines() {
let line = line.trim();
if let Some(path) = line.strip_prefix("SF:") {
current_file = Some(PathBuf::from(path));
} else if let Some(da) = line.strip_prefix("DA:") {
if let Some(ref file) = current_file {
let parts: Vec<&str> = da.split(',').collect();
if parts.len() >= 2 {
if let (Ok(line_num), Ok(count)) =
(parts[0].parse::<usize>(), parts[1].parse::<usize>())
{
results.push((StatementId::new(file.clone(), line_num), count));
}
}
}
} else if line == "end_of_record" {
current_file = None;
}
}
Ok(results)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn combine_coverage(
passed_coverage: &[(StatementId, usize)],
failed_coverage: &[(StatementId, usize)],
) -> Vec<StatementCoverage> {
let mut coverage_map: HashMap<StatementId, (usize, usize)> = HashMap::new();
for (stmt, count) in passed_coverage {
if *count > 0 {
coverage_map.entry(stmt.clone()).or_insert((0, 0)).0 += 1;
}
}
for (stmt, count) in failed_coverage {
if *count > 0 {
coverage_map.entry(stmt.clone()).or_insert((0, 0)).1 += 1;
}
}
coverage_map
.into_iter()
.map(|(id, (passed, failed))| StatementCoverage::new(id, passed, failed))
.collect()
}
}
impl FaultLocalizer {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_coverage_tool_available() -> bool {
std::process::Command::new("cargo")
.args(["llvm-cov", "--version"])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn run_localization(
passed_coverage: &[(StatementId, usize)],
failed_coverage: &[(StatementId, usize)],
total_passed: usize,
total_failed: usize,
formula: SbflFormula,
top_n: usize,
) -> FaultLocalizationResult {
info!(
"Running fault localization: {} passed, {} failed tests",
total_passed, total_failed
);
let combined = LcovParser::combine_coverage(passed_coverage, failed_coverage);
let localizer = SbflLocalizer::new()
.with_formula(formula)
.with_top_n(top_n)
.with_explanations(true);
localizer.localize(&combined, total_passed, total_failed)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn generate_report(
result: &FaultLocalizationResult,
format: ReportFormat,
) -> Result<String> {
match format {
ReportFormat::Yaml => {
serde_yaml_ng::to_string(result).map_err(|e| anyhow!("Failed to generate YAML: {}", e))
}
ReportFormat::Json => serde_json::to_string_pretty(result)
.map_err(|e| anyhow!("Failed to generate JSON: {}", e)),
ReportFormat::Terminal => Ok(Self::format_terminal_report(result)),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_terminal_report(result: &FaultLocalizationResult) -> String {
let mut output = String::new();
output.push_str(
"╔══════════════════════════════════════════════════════════════════════════════╗\n",
);
output.push_str(&format!(
"║ FAULT LOCALIZATION REPORT - {} \n",
result.formula_used
));
output.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
output.push_str(&format!(
"║ Tests: {} passed, {} failed \n",
result.total_passed_tests, result.total_failed_tests
));
output.push_str(&format!(
"║ Confidence: {:.2} \n",
result.confidence
));
output.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
output.push_str(
"║ TOP SUSPICIOUS STATEMENTS ║\n",
);
output.push_str(
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
);
for ranking in &result.rankings {
let bar_len = (ranking.suspiciousness * 20.0).min(20.0) as usize;
let progress_bar = format!("{}{}", "█".repeat(bar_len), "░".repeat(20 - bar_len));
let file_display = ranking.statement.file.display().to_string();
let file_short = if file_display.len() > 30 {
format!(
"...{}",
file_display
.get(file_display.len() - 27..)
.unwrap_or(&file_display)
)
} else {
file_display
};
output.push_str(&format!(
"║ #{:<2} {:30}:{:<5} {} {:.2} ║\n",
ranking.rank,
file_short,
ranking.statement.line,
progress_bar,
ranking.suspiciousness
));
}
output.push_str(
"╚══════════════════════════════════════════════════════════════════════════════╝\n",
);
if !result.rankings.is_empty() {
output.push_str("\n📋 Detailed Analysis:\n");
for ranking in &result.rankings {
output.push_str(&format!(
"\n #{} {} (score: {:.3})\n",
ranking.rank, ranking.statement, ranking.suspiciousness
));
output.push_str(&format!(" {}\n", ranking.explanation));
output.push_str(&format!(
" All scores: tarantula={:.3}, ochiai={:.3}, dstar2={:.3}, dstar3={:.3}\n",
ranking.scores.get("tarantula").unwrap_or(&0.0),
ranking.scores.get("ochiai").unwrap_or(&0.0),
ranking.scores.get("dstar2").unwrap_or(&0.0),
ranking.scores.get("dstar3").unwrap_or(&0.0),
));
}
}
output
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn enrich_with_tdg(
result: &mut FaultLocalizationResult,
tdg_scores: &HashMap<String, f32>,
) {
for ranking in &mut result.rankings {
let file_path = ranking.statement.file.to_string_lossy().to_string();
if let Some(&tdg) = tdg_scores.get(&file_path) {
ranking.scores.insert("tdg".to_string(), tdg);
}
}
}
}