rust_covfix/
coverage.rs

1use std::fs;
2use std::io::{BufRead, BufReader, BufWriter, Write};
3use std::path::{Path, PathBuf};
4
5use crate::error::*;
6
7/// Coverage information for a single line
8#[derive(Clone, Debug, PartialEq)]
9pub struct LineCoverage {
10    /// 1-indexed line in the source file
11    pub line_number: usize,
12    /// execution count of line. `None` means this line is not executable.
13    /// `None` value is used when the fixer detects non-executable line.
14    pub count: Option<u32>,
15}
16
17/// Coverage information for a single branch
18#[derive(Clone, Debug, PartialEq)]
19pub struct BranchCoverage {
20    /// 1-indexed line in the source file
21    pub line_number: usize,
22    /// block id which contains this branch
23    pub block_number: Option<usize>,
24    /// whether this branch was executed.
25    /// `None` value is used when the fixer detects non-executable branch.
26    pub taken: Option<bool>,
27}
28
29/// Coverage information for a single file
30///
31/// `FileCoverage` holds coverage information for lines and branches in the source file.
32#[derive(Debug, PartialEq)]
33pub struct FileCoverage {
34    path: PathBuf,
35    #[doc(hidden)]
36    pub line_coverages: Vec<LineCoverage>,
37    #[doc(hidden)]
38    pub branch_coverages: Vec<BranchCoverage>,
39}
40
41impl FileCoverage {
42    pub fn new<P: Into<PathBuf>>(
43        path: P,
44        line_coverages: Vec<LineCoverage>,
45        branch_coverages: Vec<BranchCoverage>,
46    ) -> Self {
47        Self {
48            path: path.into(),
49            line_coverages,
50            branch_coverages,
51        }
52    }
53
54    pub fn path(&self) -> &Path {
55        &self.path
56    }
57
58    pub fn line_coverages(&self) -> &[LineCoverage] {
59        &self.line_coverages
60    }
61
62    pub fn branch_coverages(&self) -> &[BranchCoverage] {
63        &self.branch_coverages
64    }
65}
66
67/// Coverage information for package
68#[derive(Debug, PartialEq)]
69pub struct PackageCoverage {
70    name: String,
71    #[doc(hidden)]
72    pub file_coverages: Vec<FileCoverage>,
73}
74
75impl PackageCoverage {
76    pub fn new(file_coverages: Vec<FileCoverage>) -> Self {
77        Self::with_test_name("", file_coverages)
78    }
79
80    pub fn with_test_name<T: Into<String>>(name: T, file_coverages: Vec<FileCoverage>) -> Self {
81        Self {
82            name: name.into(),
83            file_coverages,
84        }
85    }
86
87    pub fn name(&self) -> &str {
88        &self.name
89    }
90
91    pub fn file_coverages(&self) -> &[FileCoverage] {
92        &self.file_coverages
93    }
94}
95
96#[doc(hidden)]
97pub trait TotalCoverage {
98    fn line_executed(&self) -> usize;
99    fn line_total(&self) -> usize;
100    fn branch_executed(&self) -> usize;
101    fn branch_total(&self) -> usize;
102}
103
104#[doc(hidden)]
105impl TotalCoverage for FileCoverage {
106    fn line_executed(&self) -> usize {
107        self.line_coverages
108            .iter()
109            .filter(|&v| v.count.map_or(false, |c| c > 0))
110            .count()
111    }
112
113    fn line_total(&self) -> usize {
114        self.line_coverages
115            .iter()
116            .filter(|&v| v.count.is_some())
117            .count()
118    }
119
120    fn branch_executed(&self) -> usize {
121        self.branch_coverages
122            .iter()
123            .filter(|&v| v.taken.unwrap_or(false))
124            .count()
125    }
126
127    fn branch_total(&self) -> usize {
128        self.branch_coverages
129            .iter()
130            .filter(|&v| v.taken.is_some())
131            .count()
132    }
133}
134
135#[doc(hidden)]
136impl TotalCoverage for PackageCoverage {
137    fn line_executed(&self) -> usize {
138        self.file_coverages
139            .iter()
140            .fold(0, |sum, a| sum + a.line_executed())
141    }
142
143    fn line_total(&self) -> usize {
144        self.file_coverages
145            .iter()
146            .fold(0, |sum, a| sum + a.line_total())
147    }
148
149    fn branch_executed(&self) -> usize {
150        self.file_coverages
151            .iter()
152            .fold(0, |sum, a| sum + a.branch_executed())
153    }
154
155    fn branch_total(&self) -> usize {
156        self.file_coverages
157            .iter()
158            .fold(0, |sum, a| sum + a.branch_total())
159    }
160}
161
162pub trait CoverageReader {
163    /// fetch the coverage information from the reader
164    fn read<R: BufRead>(&self, reader: &mut R) -> Result<PackageCoverage, Error>;
165
166    /// fetch the coverage information from file
167    fn read_from_file(&self, path: &Path) -> Result<PackageCoverage, Error> {
168        let f = fs::File::open(path)
169            .chain_err(|| format!("Failed to open coverage file {:?}", path))?;
170        let capacity = f.metadata().map(|m| m.len() as usize + 1).unwrap_or(8192);
171        let mut reader = BufReader::with_capacity(capacity, f);
172        self.read(&mut reader)
173    }
174}
175
176pub trait CoverageWriter {
177    /// save coverage information into the writer
178    fn write<W: Write>(&self, data: &PackageCoverage, writer: &mut W) -> Result<(), Error>;
179
180    /// save coverage information into the file
181    fn write_to_file(&self, data: &PackageCoverage, path: &Path) -> Result<(), Error> {
182        let f = fs::File::create(path).chain_err(|| format!("Failed to open file {:?}", path))?;
183        let mut writer = BufWriter::new(f);
184        self.write(&data, &mut writer)
185    }
186}