LCOV - code coverage report
Current view: top level - dashql-pack/src - coverage_badge_command.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 100.0 % 45 45
Test Date: 2026-11-06 17:46:50

            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              : }
        

Generated by: LCOV version 2.0-1