Line data Source code
1 : use anyhow::{bail, Context, Result};
2 : use clap::Parser;
3 : use std::path::PathBuf;
4 :
5 : use crate::badge::build_coverage_badge;
6 :
7 : /// Write the coverage badge of LCOV tracefiles
8 : #[derive(Parser, Debug)]
9 : pub struct CoverageBadgeArgs {
10 : /// The LCOV tracefiles
11 : #[arg(required = true)]
12 : tracefiles: Vec<PathBuf>,
13 : /// The SVG file to write
14 : #[arg(long)]
15 : output: PathBuf,
16 : }
17 :
18 : /// The lines hit and the lines found of a tracefile
19 6 : fn count_lines(tracefile: &str) -> Result<(u64, u64)> {
20 6 : let (mut hit, mut found) = (0, 0);
21 15 : for line in tracefile.lines() {
22 15 : if let Some(count) = line.strip_prefix("LH:") {
23 4 : hit += count.parse::<u64>().with_context(|| format!("invalid record: {line}"))?;
24 11 : } else if let Some(count) = line.strip_prefix("LF:") {
25 5 : found += count.parse::<u64>().with_context(|| format!("invalid record: {line}"))?;
26 6 : }
27 : }
28 5 : Ok((hit, found))
29 6 : }
30 :
31 2 : pub fn coverage_badge(args: CoverageBadgeArgs) -> Result<()> {
32 2 : let (mut hit, mut found) = (0, 0);
33 3 : for path in &args.tracefiles {
34 3 : let tracefile = std::fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
35 3 : let (file_hit, file_found) = count_lines(&tracefile).with_context(|| format!("failed to parse {}", path.display()))?;
36 3 : hit += file_hit;
37 3 : found += file_found;
38 : }
39 2 : if found == 0 {
40 1 : bail!("the tracefiles have no lines");
41 1 : }
42 1 : let percentage = hit as f64 * 100.0 / found as f64;
43 1 : log::info!("line coverage: {percentage:.1}% ({hit} of {found} lines)");
44 1 : std::fs::write(&args.output, build_coverage_badge(percentage))
45 1 : .with_context(|| format!("failed to write {}", args.output.display()))
46 2 : }
47 :
48 : #[cfg(test)]
49 : mod tests {
50 : use super::*;
51 : use crate::test_s3::TempDir;
52 :
53 : #[test]
54 1 : fn counts_lines_of_all_records() {
55 1 : let tracefile = "SF:a.rs\nFN:1,a\nDA:1,1\nLH:3\nLF:4\nend_of_record\nSF:b.rs\nLH:5\nLF:6\nend_of_record\n";
56 1 : assert_eq!(count_lines(tracefile).unwrap(), (8, 10));
57 1 : assert_eq!(count_lines("").unwrap(), (0, 0));
58 1 : assert!(count_lines("LF:x\n").is_err());
59 1 : }
60 :
61 : #[test]
62 1 : fn writes_the_badge_of_all_tracefiles() {
63 1 : let dir = TempDir::new();
64 1 : let tracefiles = vec![dir.write("a.dat", b"LH:90\nLF:100\n"), dir.write("b.dat", b"LH:0\nLF:100\n")];
65 1 : let output = dir.path().join("badge.svg");
66 1 : coverage_badge(CoverageBadgeArgs { tracefiles, output: output.clone() }).unwrap();
67 1 : assert!(std::fs::read_to_string(output).unwrap().contains("aria-label=\"coverage: 45%\""));
68 1 : }
69 :
70 : #[test]
71 1 : fn fails_without_lines() {
72 1 : let dir = TempDir::new();
73 1 : let tracefiles = vec![dir.write("a.dat", b"")];
74 1 : let output = dir.path().join("badge.svg");
75 1 : assert!(coverage_badge(CoverageBadgeArgs { tracefiles, output: output.clone() }).is_err());
76 1 : assert!(!output.exists());
77 1 : }
78 : }
|