Skip to main content

bcore_mutation/
coverage.rs

1use crate::error::{MutationError, Result};
2use regex::Regex;
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6
7pub fn parse_coverage_file(coverage_file_path: &Path) -> Result<HashMap<String, Vec<usize>>> {
8    let content = fs::read_to_string(coverage_file_path)?;
9    let mut coverage_data: HashMap<String, Vec<usize>> = HashMap::new();
10    let mut current_file: Option<String> = None;
11
12    // Regular expressions for parsing lines
13    let file_pattern = Regex::new(r"^SF:(.+)$")?; // Source file
14    let line_pattern = Regex::new(r"^DA:(\d+),(\d+)$")?; // Line coverage
15
16    for line in content.lines() {
17        let line = line.trim();
18
19        // Check for source file
20        if let Some(captures) = file_pattern.captures(line) {
21            let full_path = &captures[1];
22
23            // Extract from "src/" onwards
24            let relative_path = if let Some(pos) = full_path.find("src/") {
25                &full_path[pos..]
26            } else {
27                full_path // fallback to full path if "src/" not found
28            };
29
30            current_file = Some(relative_path.to_string());
31            coverage_data.insert(relative_path.to_string(), Vec::new());
32            continue;
33        }
34
35        // Check for line coverage (DA:line_number,hits)
36        if let Some(captures) = line_pattern.captures(line) {
37            if let Some(ref file) = current_file {
38                let line_number: usize = captures[1]
39                    .parse()
40                    .map_err(|_| MutationError::Coverage("Invalid line number".to_string()))?;
41                let hits: usize = captures[2]
42                    .parse()
43                    .map_err(|_| MutationError::Coverage("Invalid hit count".to_string()))?;
44
45                if hits > 0 {
46                    if let Some(lines) = coverage_data.get_mut(file) {
47                        if !lines.contains(&line_number) {
48                            lines.push(line_number);
49                        }
50                    }
51                }
52            }
53        }
54    }
55
56    Ok(coverage_data)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use std::io::Write;
63    use tempfile::NamedTempFile;
64
65    #[test]
66    fn test_parse_coverage_file() {
67        let mut temp_file = NamedTempFile::new().unwrap();
68        writeln!(temp_file, "SF:/path/to/file1.cpp").unwrap();
69        writeln!(temp_file, "DA:1,5").unwrap();
70        writeln!(temp_file, "DA:2,0").unwrap();
71        writeln!(temp_file, "DA:3,10").unwrap();
72        writeln!(temp_file, "SF:/path/to/file2.cpp").unwrap();
73        writeln!(temp_file, "DA:10,1").unwrap();
74        writeln!(temp_file, "DA:11,0").unwrap();
75
76        let result = parse_coverage_file(temp_file.path()).unwrap();
77
78        assert_eq!(result.len(), 2);
79        assert_eq!(result["/path/to/file1.cpp"], vec![1, 3]);
80        assert_eq!(result["/path/to/file2.cpp"], vec![10]);
81    }
82}