qlty_coverage/parser/
lcov.rs

1use crate::Parser;
2use anyhow::Result;
3use qlty_types::tests::v1::FileCoverage;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub struct Lcov {}
8
9impl Lcov {
10    pub fn new() -> Self {
11        Self {}
12    }
13}
14
15impl Parser for Lcov {
16    fn parse_text(&self, text: &str) -> Result<Vec<FileCoverage>> {
17        let mut file_coverages = vec![];
18        let mut lcov_lines = text.lines();
19
20        while let Some(lcov_line) = lcov_lines.next() {
21            if let Some(path_string) = lcov_line.strip_prefix(SF) {
22                let file_coverage_path = path_string.to_string();
23
24                let mut file_coverage = FileCoverage {
25                    path: file_coverage_path,
26                    ..Default::default()
27                };
28
29                let mut line_numbers_to_hits = HashMap::new();
30
31                for lcov_line in lcov_lines.by_ref() {
32                    if let Some(lcov_line) = lcov_line.strip_prefix(DA) {
33                        let mut split = lcov_line.split(',');
34
35                        let line_number = split.next().unwrap();
36                        let line_number = line_number.parse::<u32>().unwrap();
37
38                        let hits_count = split.next().unwrap();
39                        let hits_count = hits_count.parse::<u64>().unwrap();
40
41                        *line_numbers_to_hits.entry(line_number).or_insert(0) += hits_count;
42                    } else if lcov_line.starts_with(END_OF_RECORD) {
43                        break;
44                    }
45                }
46
47                let maximum_line_number = line_numbers_to_hits.keys().max().unwrap();
48                let mut line_hits: Vec<i64> = vec![-1; *maximum_line_number as usize];
49
50                for (line_number, hits) in line_numbers_to_hits {
51                    line_hits[(line_number - 1) as usize] = hits as i64;
52                }
53
54                file_coverage.hits = line_hits;
55                file_coverages.push(file_coverage);
56            }
57        }
58
59        Ok(file_coverages)
60    }
61}
62
63const SF: &str = "SF:";
64const DA: &str = "DA:";
65const END_OF_RECORD: &str = "end_of_record";
66
67#[cfg(test)]
68mod test {
69    use super::*;
70
71    #[test]
72    fn simple() {
73        let input = r#"
74SF:src/lib.rs
75DA:1,1
76DA:2,0
77DA:5,10
78"#;
79
80        insta::assert_yaml_snapshot!(Lcov::new().parse_text(input).unwrap(), @r#"
81        - path: src/lib.rs
82          hits:
83            - "1"
84            - "0"
85            - "-1"
86            - "-1"
87            - "10"
88        "#);
89    }
90}