pub(crate) mod markdown;
pub(crate) mod text;
use crate::threshold::ThresholdSource;
use crate::verdict::{PackageOutcome, Status};
fn format_lines(outcome: &PackageOutcome) -> String {
outcome.percent().map_or_else(|| "(no data)".to_owned(), |p| format!("{p:.1}%"))
}
fn format_threshold(outcome: &PackageOutcome) -> String {
format!("{:.1}%", outcome.threshold.min_lines_percent)
}
fn format_delta(outcome: &PackageOutcome) -> String {
let Some(pct) = outcome.percent() else {
return "—".to_owned();
};
let delta = pct - outcome.threshold.min_lines_percent;
let rounded = (delta * 10.0).round() / 10.0;
if rounded > 0.0 {
format!("+{rounded:.1}pp")
} else if rounded < 0.0 {
format!("{rounded:.1}pp")
} else {
"0.0pp".to_owned()
}
}
fn format_status_text(status: Status) -> &'static str {
match status {
Status::Ok => "OK",
Status::Fail => "FAIL",
Status::NoData => "NO DATA",
}
}
fn format_status_markdown(status: Status) -> &'static str {
match status {
Status::Ok => "✅",
Status::Fail => "❌",
Status::NoData => "💥",
}
}
fn format_source(source: ThresholdSource) -> &'static str {
source.as_str()
}
fn count_failures(outcomes: &[PackageOutcome]) -> usize {
outcomes.iter().filter(|o| o.status == Status::Fail).count()
}
fn count_no_data(outcomes: &[PackageOutcome]) -> usize {
outcomes.iter().filter(|o| o.status == Status::NoData).count()
}
fn plural(n: usize, singular: &str, plural: &str) -> String {
if n == 1 {
format!("{n} {singular}")
} else {
format!("{n} {plural}")
}
}
fn packages(n: usize) -> String {
plural(n, "package", "packages")
}
fn files(n: usize) -> String {
plural(n, "file", "files")
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::aggregate::LineTotals;
use crate::threshold::Threshold;
fn outcome(count: u32, covered: u32, threshold: f64) -> PackageOutcome {
PackageOutcome {
name: "x".to_owned(),
threshold: Threshold {
min_lines_percent: threshold,
source: ThresholdSource::Package,
},
totals: LineTotals { count, covered },
status: Status::Ok,
}
}
#[test]
fn format_delta_renders_positive_with_sign() {
assert_eq!(format_delta(&outcome(100, 95, 80.0)), "+15.0pp");
}
#[test]
fn format_delta_renders_negative_with_sign() {
assert_eq!(format_delta(&outcome(100, 60, 80.0)), "-20.0pp");
}
#[test]
fn format_delta_collapses_sub_precision_noise_to_unsigned_zero() {
let o = outcome(100, 82, 82.0);
assert_eq!(format_delta(&o), "0.0pp");
let mut o = outcome(100, 82, 82.0);
o.totals = LineTotals {
count: 100_000_000,
covered: 82_000_001,
};
assert_eq!(format_delta(&o), "0.0pp");
let mut o = outcome(100, 82, 82.0);
o.totals = LineTotals {
count: 100_000_000,
covered: 81_999_999,
};
assert_eq!(format_delta(&o), "0.0pp");
}
#[test]
fn format_delta_returns_dash_for_no_data() {
let o = outcome(0, 0, 80.0);
assert_eq!(format_delta(&o), "—");
}
#[test]
fn plural_helpers_pick_singular_only_for_one() {
assert_eq!(packages(0), "0 packages");
assert_eq!(packages(1), "1 package");
assert_eq!(packages(2), "2 packages");
assert_eq!(files(0), "0 files");
assert_eq!(files(1), "1 file");
assert_eq!(files(2), "2 files");
}
}