From 541c4ab2c2c562f7b35a603928e9dd0dfea984ed Mon Sep 17 00:00:00 2001 From: Charles Hall Date: Wed, 15 Nov 2023 21:59:28 -0800 Subject: [PATCH] add `--fail-under-{functions,regions}` options Not including one for branches since Rust itself doesn't support it. --- README.md | 6 ++++ docs/cargo-llvm-cov-report.txt | 6 ++++ docs/cargo-llvm-cov-run.txt | 6 ++++ docs/cargo-llvm-cov-test.txt | 6 ++++ docs/cargo-llvm-cov.txt | 6 ++++ src/cli.rs | 10 +++++++ src/json.rs | 51 ++++++++++++++++++++++++++++++---- src/main.rs | 30 ++++++++++++++++++-- 8 files changed, 112 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6de49337..7d23c441 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,15 @@ OPTIONS: --no-clean Build without cleaning any old build artifacts + --fail-under-functions + Exit with a status of 1 if the total function coverage is less than MIN percent + --fail-under-lines Exit with a status of 1 if the total line coverage is less than MIN percent + --fail-under-regions + Exit with a status of 1 if the total region coverage is less than MIN percent + --fail-uncovered-lines Exit with a status of 1 if the uncovered lines are greater than MAX diff --git a/docs/cargo-llvm-cov-report.txt b/docs/cargo-llvm-cov-report.txt index 3d048b1c..2e9dcc30 100644 --- a/docs/cargo-llvm-cov-report.txt +++ b/docs/cargo-llvm-cov-report.txt @@ -86,9 +86,15 @@ OPTIONS: --hide-instantiations Hide instantiations from report + --fail-under-functions + Exit with a status of 1 if the total function coverage is less than MIN percent + --fail-under-lines Exit with a status of 1 if the total line coverage is less than MIN percent + --fail-under-regions + Exit with a status of 1 if the total region coverage is less than MIN percent + --fail-uncovered-lines Exit with a status of 1 if the uncovered lines are greater than MAX diff --git a/docs/cargo-llvm-cov-run.txt b/docs/cargo-llvm-cov-run.txt index 66205331..82198fca 100644 --- a/docs/cargo-llvm-cov-run.txt +++ b/docs/cargo-llvm-cov-run.txt @@ -103,9 +103,15 @@ OPTIONS: --no-clean Build without cleaning any old build artifacts + --fail-under-functions + Exit with a status of 1 if the total function coverage is less than MIN percent + --fail-under-lines Exit with a status of 1 if the total line coverage is less than MIN percent + --fail-under-regions + Exit with a status of 1 if the total region coverage is less than MIN percent + --fail-uncovered-lines Exit with a status of 1 if the uncovered lines are greater than MAX diff --git a/docs/cargo-llvm-cov-test.txt b/docs/cargo-llvm-cov-test.txt index 2e38fa3c..a5fce780 100644 --- a/docs/cargo-llvm-cov-test.txt +++ b/docs/cargo-llvm-cov-test.txt @@ -108,9 +108,15 @@ OPTIONS: --no-clean Build without cleaning any old build artifacts + --fail-under-functions + Exit with a status of 1 if the total function coverage is less than MIN percent + --fail-under-lines Exit with a status of 1 if the total line coverage is less than MIN percent + --fail-under-regions + Exit with a status of 1 if the total region coverage is less than MIN percent + --fail-uncovered-lines Exit with a status of 1 if the uncovered lines are greater than MAX diff --git a/docs/cargo-llvm-cov.txt b/docs/cargo-llvm-cov.txt index d0ea3fc7..d520987c 100644 --- a/docs/cargo-llvm-cov.txt +++ b/docs/cargo-llvm-cov.txt @@ -103,9 +103,15 @@ OPTIONS: --no-clean Build without cleaning any old build artifacts + --fail-under-functions + Exit with a status of 1 if the total function coverage is less than MIN percent + --fail-under-lines Exit with a status of 1 if the total line coverage is less than MIN percent + --fail-under-regions + Exit with a status of 1 if the total region coverage is less than MIN percent + --fail-uncovered-lines Exit with a status of 1 if the uncovered lines are greater than MAX diff --git a/src/cli.rs b/src/cli.rs index 67e26a8f..8a3ed44d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -226,7 +226,9 @@ impl Args { let mut no_cfg_coverage = false; let mut no_cfg_coverage_nightly = false; let mut no_report = false; + let mut fail_under_functions = None; let mut fail_under_lines = None; + let mut fail_under_regions = None; let mut fail_uncovered_lines = None; let mut fail_uncovered_regions = None; let mut fail_uncovered_functions = None; @@ -398,7 +400,9 @@ impl Args { Long("no-cfg-coverage") => parse_flag!(no_cfg_coverage), Long("no-cfg-coverage-nightly") => parse_flag!(no_cfg_coverage_nightly), Long("no-report") => parse_flag!(no_report), + Long("fail-under-functions") => parse_opt!(fail_under_functions), Long("fail-under-lines") => parse_opt!(fail_under_lines), + Long("fail-under-regions") => parse_opt!(fail_under_regions), Long("fail-uncovered-lines") => parse_opt!(fail_uncovered_lines), Long("fail-uncovered-regions") => parse_opt!(fail_uncovered_regions), Long("fail-uncovered-functions") => parse_opt!(fail_uncovered_functions), @@ -816,7 +820,9 @@ impl Args { no_cfg_coverage, no_cfg_coverage_nightly, no_report, + fail_under_functions, fail_under_lines, + fail_under_regions, fail_uncovered_lines, fail_uncovered_regions, fail_uncovered_functions, @@ -1025,8 +1031,12 @@ pub(crate) struct LlvmCovOptions { pub(crate) no_cfg_coverage_nightly: bool, /// Run tests, but don't generate coverage report pub(crate) no_report: bool, + /// Exit with a status of 1 if the total function coverage is less than MIN percent. + pub(crate) fail_under_functions: Option, /// Exit with a status of 1 if the total line coverage is less than MIN percent. pub(crate) fail_under_lines: Option, + /// Exit with a status of 1 if the total region coverage is less than MIN percent. + pub(crate) fail_under_regions: Option, /// Exit with a status of 1 if the uncovered lines are greater than MAX. pub(crate) fail_uncovered_lines: Option, /// Exit with a status of 1 if the uncovered regions are greater than MAX. diff --git a/src/json.rs b/src/json.rs index 72eebd57..cd05b414 100644 --- a/src/json.rs +++ b/src/json.rs @@ -146,6 +146,24 @@ impl CodeCovJsonExport { /// Files -> list of uncovered lines. pub(crate) type UncoveredLines = BTreeMap>; +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum CoverageKind { + Functions, + Lines, + Regions, +} + +impl AsRef for CoverageKind { + fn as_ref(&self) -> &'static str { + match self { + CoverageKind::Functions => "functions", + CoverageKind::Lines => "lines", + CoverageKind::Regions => "regions", + } + } +} + impl LlvmCovJsonExport { pub fn demangle(&mut self) { for data in &mut self.data { @@ -165,12 +183,13 @@ impl LlvmCovJsonExport { } /// Gets the minimal lines coverage of all files. - pub fn get_lines_percent(&self) -> Result { + pub fn get_coverage_percent(&self, kind: CoverageKind) -> Result { let mut count = 0_f64; let mut covered = 0_f64; for data in &self.data { let totals = &data.totals.as_object().context("totals is not an object")?; - let lines = &totals["lines"].as_object().context("no lines")?; + let lines = + &totals[kind.as_ref()].as_object().context(format!("no {}", kind.as_ref()))?; count += lines["count"].as_f64().context("no count")?; covered += lines["covered"].as_f64().context("no covered")?; } @@ -555,8 +574,13 @@ mod tests { } } - #[test] - fn test_get_lines_percent() { + fn test_get_coverage_percent(kind: CoverageKind) { + let expected = match kind { + CoverageKind::Functions => 100_f64, + CoverageKind::Lines => 68.181_818_181_818_19, + CoverageKind::Regions => 66.666_666_666_666_67, + }; + // There are 5 different percentages, make sure we pick the correct one. let file = format!( "{}/tests/fixtures/coverage-reports/no_coverage/no_coverage.json", @@ -565,10 +589,25 @@ mod tests { let s = fs::read_to_string(file).unwrap(); let json = serde_json::from_str::(&s).unwrap(); - let percent = json.get_lines_percent().unwrap(); + let actual = json.get_coverage_percent(kind).unwrap(); let error_margin = f64::EPSILON; - assert!((percent - 68.181_818_181_818_19).abs() < error_margin, "{percent}"); + assert!((actual - expected).abs() < error_margin, "{actual}"); + } + + #[test] + fn test_get_functions_percent() { + test_get_coverage_percent(CoverageKind::Functions); + } + + #[test] + fn test_get_lines_percent() { + test_get_coverage_percent(CoverageKind::Lines); + } + + #[test] + fn test_get_regions_percent() { + test_get_coverage_percent(CoverageKind::Regions); } #[test] diff --git a/src/main.rs b/src/main.rs index f9245d2d..1d4ede9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use std::{ use anyhow::{bail, Context as _, Result}; use camino::{Utf8Path, Utf8PathBuf}; use cargo_config2::Flags; -use cargo_llvm_cov::json::{CodeCovJsonExport, LlvmCovJsonExport}; +use cargo_llvm_cov::json::{CodeCovJsonExport, CoverageKind, LlvmCovJsonExport}; use regex::Regex; use walkdir::WalkDir; @@ -485,7 +485,9 @@ fn generate_report(cx: &Context) -> Result<()> { .generate_report(cx, &object_files, ignore_filename_regex.as_deref()) .context("failed to generate report")?; - if cx.args.cov.fail_under_lines.is_some() + if cx.args.cov.fail_under_functions.is_some() + || cx.args.cov.fail_under_lines.is_some() + || cx.args.cov.fail_under_regions.is_some() || cx.args.cov.fail_uncovered_functions.is_some() || cx.args.cov.fail_uncovered_lines.is_some() || cx.args.cov.fail_uncovered_regions.is_some() @@ -496,14 +498,36 @@ fn generate_report(cx: &Context) -> Result<()> { .get_json(cx, &object_files, ignore_filename_regex.as_ref()) .context("failed to get json")?; + if let Some(fail_under_functions) = cx.args.cov.fail_under_functions { + // Handle --fail-under-functions. + let functions_percent = json + .get_coverage_percent(CoverageKind::Functions) + .context("failed to get function coverage")?; + if functions_percent < fail_under_functions { + term::error::set(true); + } + } + if let Some(fail_under_lines) = cx.args.cov.fail_under_lines { // Handle --fail-under-lines. - let lines_percent = json.get_lines_percent().context("failed to get line coverage")?; + let lines_percent = json + .get_coverage_percent(CoverageKind::Lines) + .context("failed to get line coverage")?; if lines_percent < fail_under_lines { term::error::set(true); } } + if let Some(fail_under_regions) = cx.args.cov.fail_under_regions { + // Handle --fail-under-regions. + let regions_percent = json + .get_coverage_percent(CoverageKind::Regions) + .context("failed to get region coverage")?; + if regions_percent < fail_under_regions { + term::error::set(true); + } + } + if let Some(fail_uncovered_functions) = cx.args.cov.fail_uncovered_functions { // Handle --fail-uncovered-functions. let uncovered =