rust_covfix/
lcov.rs

1use std::io::{BufRead, Write};
2use std::path::{Path, PathBuf};
3
4use crate::coverage::{
5    BranchCoverage, CoverageReader, CoverageWriter, FileCoverage, LineCoverage, PackageCoverage,
6    TotalCoverage,
7};
8use crate::error::*;
9
10/// Enumeration representing each line in 'lcov.info'
11enum RawData<'a> {
12    /// Test Name
13    TN(&'a str),
14
15    /// Source File
16    SF(&'a Path),
17
18    /// Function
19    FN(usize, &'a str),
20
21    /// Fn Data
22    FNDA(u32, &'a str),
23
24    /// \# FN Found
25    FNF(u32),
26
27    /// \# Fn Executed
28    FNH(u32),
29
30    /// Executions for some Line
31    DA(usize, u32),
32
33    /// \# Lines Found
34    LF(u32),
35
36    /// \# Lines Executed
37    LH(u32),
38
39    /// Branch coverage information
40    BRDA(usize, usize, usize, bool),
41
42    /// \# Branches Found
43    BRF(u32),
44
45    /// \# Branches Executed
46    BRH(u32),
47
48    /// End of Record
49    EndOfRecord,
50}
51
52pub struct LcovParser {
53    root: PathBuf,
54}
55
56impl CoverageReader for LcovParser {
57    fn read<R: BufRead>(&self, reader: &mut R) -> Result<PackageCoverage, Error> {
58        let mut line_buf = String::with_capacity(120);
59        let mut line_coverages = Vec::new();
60        let mut branch_coverages = Vec::new();
61        let mut file_coverages = Vec::new();
62        let mut filename = PathBuf::new();
63        let mut testname = String::new();
64
65        while let Ok(n) = reader.read_line(&mut line_buf) {
66            if n == 0 {
67                break;
68            }
69
70            let raw_data = match self.parse_line(&line_buf) {
71                Some(raw_data) => raw_data,
72                None => {
73                    line_buf.clear();
74                    continue;
75                }
76            };
77
78            match raw_data {
79                RawData::TN(name) => testname = name.into(),
80                RawData::SF(file) => {
81                    filename = file.into();
82                }
83                RawData::DA(line, count) => {
84                    if line > 0 {
85                        line_coverages.push(LineCoverage {
86                            line_number: line,
87                            count: Some(count),
88                        });
89                    }
90                }
91                RawData::BRDA(line, block, _, taken) => {
92                    if line > 0 {
93                        branch_coverages.push(BranchCoverage {
94                            line_number: line,
95                            block_number: Some(block),
96                            taken: Some(taken),
97                        });
98                    }
99                }
100                RawData::EndOfRecord => {
101                    let filepath = self.root.join(&filename);
102
103                    let file_coverage = FileCoverage::new(
104                        filepath,
105                        line_coverages.drain(..).collect(),
106                        branch_coverages.drain(..).collect(),
107                    );
108                    file_coverages.push(file_coverage);
109                }
110                _ => {}
111            }
112
113            line_buf.clear();
114        }
115
116        Ok(PackageCoverage::with_test_name(testname, file_coverages))
117    }
118}
119
120impl CoverageWriter for LcovParser {
121    fn write<W: Write>(&self, data: &PackageCoverage, writer: &mut W) -> Result<(), Error> {
122        self.write_package_coverage(writer, data)
123    }
124}
125
126impl LcovParser {
127    pub fn new<P: Into<PathBuf>>(root: P) -> Self {
128        Self { root: root.into() }
129    }
130
131    fn parse_line<'a>(&self, line: &'a str) -> Option<RawData<'a>> {
132        let line = line.trim_end();
133        if line == "end_of_record" {
134            return Some(RawData::EndOfRecord);
135        }
136
137        let end = match line.find(':') {
138            Some(end) => end,
139            None => return None,
140        };
141
142        let prefix = &line[0..end];
143        let mut contents = line[end + 1..].split(',');
144
145        // cov:begin-ignore-branch
146        match prefix {
147            "TN" => Some(RawData::TN(contents.next().unwrap_or(""))),
148            "SF" => Some(RawData::SF(Path::new(contents.next()?))),
149            "FN" => {
150                let line = contents.next()?.parse().ok()?;
151                let name = contents.next()?;
152                Some(RawData::FN(line, name))
153            }
154            "FNDA" => {
155                let count = contents.next()?.parse().ok()?;
156                let name = contents.next()?;
157                Some(RawData::FNDA(count, name))
158            }
159            "FNF" => Some(RawData::FNF(contents.next()?.parse().ok()?)),
160            "FNH" => Some(RawData::FNH(contents.next()?.parse().ok()?)),
161            "DA" => {
162                let line = contents.next()?.parse().ok()?;
163                let count = contents.next()?.parse().ok()?;
164                Some(RawData::DA(line, count))
165            }
166            "LF" => Some(RawData::LF(contents.next()?.parse().ok()?)),
167            "LH" => Some(RawData::LH(contents.next()?.parse().ok()?)),
168            "BRDA" => {
169                let line = contents.next()?.parse().ok()?;
170                let block = contents.next()?.parse().ok()?;
171                let branch = contents.next()?.parse().ok()?;
172                let taken = contents.next()? != "-";
173                Some(RawData::BRDA(line, block, branch, taken))
174            }
175            "BRF" => Some(RawData::BRF(contents.next()?.parse().ok()?)),
176            "BRH" => Some(RawData::BRH(contents.next()?.parse().ok()?)),
177            _ => None,
178        }
179        // cov:end-ignore-branch
180    }
181
182    // cov:begin-ignore-branch
183    fn write_package_coverage<W: Write>(
184        &self,
185        writer: &mut W,
186        data: &PackageCoverage,
187    ) -> Result<(), Error> {
188        writeln!(writer, "TN:{}", data.name())?;
189
190        for cov in data.file_coverages() {
191            self.write_file_coverage(writer, cov)?;
192        }
193
194        Ok(())
195    }
196
197    fn write_file_coverage<W: Write>(
198        &self,
199        writer: &mut W,
200        data: &FileCoverage,
201    ) -> Result<(), Error> {
202        let path = data.path().strip_prefix(&self.root).unwrap();
203        writeln!(writer, "SF:{}", path.display())?;
204
205        let mut current_line = 1;
206        let mut count = 0;
207        for cov in data.branch_coverages() {
208            if cov.line_number == current_line {
209                count += 1;
210            } else {
211                count = 0;
212            }
213            current_line = cov.line_number;
214
215            self.write_branch_coverage(writer, cov, count)?;
216        }
217
218        writeln!(writer, "BRF:{}", data.branch_total())?;
219        writeln!(writer, "BRH:{}", data.branch_executed())?;
220
221        for cov in data.line_coverages() {
222            self.write_line_coverage(writer, cov)?;
223        }
224
225        writeln!(writer, "LF:{}", data.line_total())?;
226        writeln!(writer, "LH:{}", data.line_executed())?;
227
228        writeln!(writer, "end_of_record")?;
229
230        Ok(())
231    }
232
233    fn write_branch_coverage<W: Write>(
234        &self,
235        writer: &mut W,
236        data: &BranchCoverage,
237        branch_number: usize,
238    ) -> Result<(), Error> {
239        if let Some(taken) = data.taken {
240            writeln!(
241                writer,
242                "BRDA:{},{},{},{}",
243                data.line_number,
244                data.block_number.unwrap_or(0),
245                branch_number,
246                if taken { "1" } else { "-" }
247            )?;
248        }
249
250        Ok(())
251    }
252
253    fn write_line_coverage<W: Write>(
254        &self,
255        writer: &mut W,
256        data: &LineCoverage,
257    ) -> Result<(), Error> {
258        if let Some(count) = data.count {
259            writeln!(writer, "DA:{},{}", data.line_number, count)?;
260        }
261
262        Ok(())
263    }
264    // cov:end-ignore-branch
265}