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
10enum RawData<'a> {
12 TN(&'a str),
14
15 SF(&'a Path),
17
18 FN(usize, &'a str),
20
21 FNDA(u32, &'a str),
23
24 FNF(u32),
26
27 FNH(u32),
29
30 DA(usize, u32),
32
33 LF(u32),
35
36 LH(u32),
38
39 BRDA(usize, usize, usize, bool),
41
42 BRF(u32),
44
45 BRH(u32),
47
48 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 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 }
181
182 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 }