use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use crate::coverage::{
BranchCoverage, CoverageReader, CoverageWriter, FileCoverage, LineCoverage, PackageCoverage,
TotalCoverage,
};
use crate::error::*;
enum RawData<'a> {
TN(&'a str),
SF(&'a Path),
FN(usize, &'a str),
FNDA(u32, &'a str),
FNF(u32),
FNH(u32),
DA(usize, u32),
LF(u32),
LH(u32),
BRDA(usize, usize, usize, bool),
BRF(u32),
BRH(u32),
EndOfRecord,
}
pub struct LcovParser {
root: PathBuf,
}
impl CoverageReader for LcovParser {
fn read<R: BufRead>(&self, reader: &mut R) -> Result<PackageCoverage, Error> {
let mut line_buf = String::with_capacity(120);
let mut line_coverages = Vec::new();
let mut branch_coverages = Vec::new();
let mut file_coverages = Vec::new();
let mut filename = PathBuf::new();
let mut testname = String::new();
while let Ok(n) = reader.read_line(&mut line_buf) {
if n == 0 {
break;
}
let raw_data = match self.parse_line(&line_buf) {
Some(raw_data) => raw_data,
None => {
line_buf.clear();
continue;
}
};
match raw_data {
RawData::TN(name) => testname = name.into(),
RawData::SF(file) => {
filename = file.into();
}
RawData::DA(line, count) => {
if line > 0 {
line_coverages.push(LineCoverage {
line_number: line,
count: Some(count),
});
}
}
RawData::BRDA(line, block, _, taken) => {
if line > 0 {
branch_coverages.push(BranchCoverage {
line_number: line,
block_number: Some(block),
taken: Some(taken),
});
}
}
RawData::EndOfRecord => {
let filepath = self.root.join(&filename);
let file_coverage = FileCoverage::new(
filepath,
line_coverages.drain(..).collect(),
branch_coverages.drain(..).collect(),
);
file_coverages.push(file_coverage);
}
_ => {}
}
line_buf.clear();
}
Ok(PackageCoverage::with_test_name(testname, file_coverages))
}
}
impl CoverageWriter for LcovParser {
fn write<W: Write>(&self, data: &PackageCoverage, writer: &mut W) -> Result<(), Error> {
self.write_package_coverage(writer, data)
}
}
impl LcovParser {
pub fn new<P: Into<PathBuf>>(root: P) -> Self {
Self { root: root.into() }
}
fn parse_line<'a>(&self, line: &'a str) -> Option<RawData<'a>> {
let line = line.trim_end();
if line == "end_of_record" {
return Some(RawData::EndOfRecord);
}
let end = match line.find(':') {
Some(end) => end,
None => return None,
};
let prefix = &line[0..end];
let mut contents = line[end + 1..].split(',');
match prefix {
"TN" => Some(RawData::TN(contents.next().unwrap_or(""))),
"SF" => Some(RawData::SF(Path::new(contents.next()?))),
"FN" => {
let line = contents.next()?.parse().ok()?;
let name = contents.next()?;
Some(RawData::FN(line, name))
}
"FNDA" => {
let count = contents.next()?.parse().ok()?;
let name = contents.next()?;
Some(RawData::FNDA(count, name))
}
"FNF" => Some(RawData::FNF(contents.next()?.parse().ok()?)),
"FNH" => Some(RawData::FNH(contents.next()?.parse().ok()?)),
"DA" => {
let line = contents.next()?.parse().ok()?;
let count = contents.next()?.parse().ok()?;
Some(RawData::DA(line, count))
}
"LF" => Some(RawData::LF(contents.next()?.parse().ok()?)),
"LH" => Some(RawData::LH(contents.next()?.parse().ok()?)),
"BRDA" => {
let line = contents.next()?.parse().ok()?;
let block = contents.next()?.parse().ok()?;
let branch = contents.next()?.parse().ok()?;
let taken = contents.next()? != "-";
Some(RawData::BRDA(line, block, branch, taken))
}
"BRF" => Some(RawData::BRF(contents.next()?.parse().ok()?)),
"BRH" => Some(RawData::BRH(contents.next()?.parse().ok()?)),
_ => None,
}
}
fn write_package_coverage<W: Write>(
&self,
writer: &mut W,
data: &PackageCoverage,
) -> Result<(), Error> {
writeln!(writer, "TN:{}", data.name())?;
for cov in data.file_coverages() {
self.write_file_coverage(writer, cov)?;
}
Ok(())
}
fn write_file_coverage<W: Write>(
&self,
writer: &mut W,
data: &FileCoverage,
) -> Result<(), Error> {
let path = data.path().strip_prefix(&self.root).unwrap();
writeln!(writer, "SF:{}", path.display())?;
let mut current_line = 1;
let mut count = 0;
for cov in data.branch_coverages() {
if cov.line_number == current_line {
count += 1;
} else {
count = 0;
}
current_line = cov.line_number;
self.write_branch_coverage(writer, cov, count)?;
}
writeln!(writer, "BRF:{}", data.branch_total())?;
writeln!(writer, "BRH:{}", data.branch_executed())?;
for cov in data.line_coverages() {
self.write_line_coverage(writer, cov)?;
}
writeln!(writer, "LF:{}", data.line_total())?;
writeln!(writer, "LH:{}", data.line_executed())?;
writeln!(writer, "end_of_record")?;
Ok(())
}
fn write_branch_coverage<W: Write>(
&self,
writer: &mut W,
data: &BranchCoverage,
branch_number: usize,
) -> Result<(), Error> {
if let Some(taken) = data.taken {
writeln!(
writer,
"BRDA:{},{},{},{}",
data.line_number,
data.block_number.unwrap_or(0),
branch_number,
if taken { "1" } else { "-" }
)?;
}
Ok(())
}
fn write_line_coverage<W: Write>(
&self,
writer: &mut W,
data: &LineCoverage,
) -> Result<(), Error> {
if let Some(count) = data.count {
writeln!(writer, "DA:{},{}", data.line_number, count)?;
}
Ok(())
}
}