1#![cfg_attr(docsrs, feature(doc_cfg))]
29#![warn(missing_docs)]
30#![warn(rust_2018_idioms)]
31
32use dev_report::{CheckResult, Severity};
33
34#[derive(Debug, Clone)]
36pub struct CoverageRun {
37 name: String,
38 version: String,
39}
40
41impl CoverageRun {
42 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
44 Self {
45 name: name.into(),
46 version: version.into(),
47 }
48 }
49
50 pub fn execute(&self) -> Result<CoverageResult, CoverageError> {
55 Ok(CoverageResult {
57 name: self.name.clone(),
58 version: self.version.clone(),
59 line_pct: 0.0,
60 function_pct: 0.0,
61 region_pct: 0.0,
62 total_lines: 0,
63 covered_lines: 0,
64 })
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct CoverageResult {
71 pub name: String,
73 pub version: String,
75 pub line_pct: f64,
77 pub function_pct: f64,
79 pub region_pct: f64,
81 pub total_lines: u64,
83 pub covered_lines: u64,
85}
86
87#[derive(Debug, Clone, Copy)]
89pub enum CoverageThreshold {
90 MinLinePct(f64),
92 MinFunctionPct(f64),
94 MinRegionPct(f64),
96}
97
98impl CoverageThreshold {
99 pub fn min_line_pct(pct: f64) -> Self {
101 Self::MinLinePct(pct)
102 }
103
104 pub fn min_function_pct(pct: f64) -> Self {
106 Self::MinFunctionPct(pct)
107 }
108
109 pub fn min_region_pct(pct: f64) -> Self {
111 Self::MinRegionPct(pct)
112 }
113}
114
115impl CoverageResult {
116 pub fn into_check_result(self, threshold: CoverageThreshold) -> CheckResult {
118 let name = format!("coverage::{}", self.name);
119 let (actual, target, label) = match threshold {
120 CoverageThreshold::MinLinePct(p) => (self.line_pct, p, "line"),
121 CoverageThreshold::MinFunctionPct(p) => (self.function_pct, p, "function"),
122 CoverageThreshold::MinRegionPct(p) => (self.region_pct, p, "region"),
123 };
124 let detail = format!("{label} coverage {actual:.2}% (threshold {target:.2}%)");
125 if actual < target {
126 CheckResult::fail(name, Severity::Warning).with_detail(detail)
127 } else {
128 CheckResult::pass(name).with_detail(detail)
129 }
130 }
131}
132
133#[derive(Debug)]
135pub enum CoverageError {
136 ToolNotInstalled,
138 SubprocessFailed(String),
140 ParseError(String),
142}
143
144impl std::fmt::Display for CoverageError {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::ToolNotInstalled => write!(f, "cargo-llvm-cov is not installed"),
148 Self::SubprocessFailed(s) => write!(f, "subprocess failed: {s}"),
149 Self::ParseError(s) => write!(f, "parse error: {s}"),
150 }
151 }
152}
153
154impl std::error::Error for CoverageError {}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn run_returns_a_result() {
162 let run = CoverageRun::new("x", "0.1.0");
163 let r = run.execute().unwrap();
164 assert_eq!(r.name, "x");
165 }
166
167 #[test]
168 fn threshold_pass() {
169 let r = CoverageResult {
170 name: "x".into(),
171 version: "0.1.0".into(),
172 line_pct: 90.0,
173 function_pct: 85.0,
174 region_pct: 80.0,
175 total_lines: 100,
176 covered_lines: 90,
177 };
178 let c = r.into_check_result(CoverageThreshold::min_line_pct(80.0));
179 assert!(matches!(c.verdict, dev_report::Verdict::Pass));
180 }
181
182 #[test]
183 fn threshold_fail() {
184 let r = CoverageResult {
185 name: "x".into(),
186 version: "0.1.0".into(),
187 line_pct: 50.0,
188 function_pct: 60.0,
189 region_pct: 40.0,
190 total_lines: 100,
191 covered_lines: 50,
192 };
193 let c = r.into_check_result(CoverageThreshold::min_line_pct(80.0));
194 assert!(matches!(c.verdict, dev_report::Verdict::Fail));
195 }
196}