xtask/tests/guard_report.rs
//! The dependency guard's verdict as data: every rule prints its own name, the report
//! prints every finding under its own heading, and the appliance-growth budget is held
//! from both sides (`xtask/src/guard.rs`).
use xtask::guard::{
check_appliance_growth, Report, Rule, Violation, APPLIANCE, APPLIANCE_HTTP, HTTP_GROWTH_BUDGET,
};
fn v(profile: &str, rule: Rule, item: &str) -> Violation {
Violation {
profile: profile.to_string(),
rule,
item: item.to_string(),
why: format!("because of {item}"),
}
}
fn sized(plain: usize, with_http: usize) -> Report {
Report {
sizes: vec![
(APPLIANCE.to_string(), plain + 40, plain),
(APPLIANCE_HTTP.to_string(), with_http + 40, with_http),
],
..Report::default()
}
}
#[test]
fn every_rule_and_violation_prints_what_it_is() {
let names: Vec<String> = [
Rule::VacuousGraph,
Rule::ForbiddenAnywhere,
Rule::ForbiddenInServerGraph,
Rule::GixPorcelainFeature,
Rule::StaleDeviation,
Rule::PhantomRule,
Rule::SlackBudget,
]
.iter()
.map(|r| r.to_string())
.collect();
assert_eq!(
names,
[
"vacuous graph",
"forbidden anywhere",
"forbidden in the S graph",
"gix porcelain feature in the S graph",
"stale tracked deviation",
"phantom rule name",
"slack budget",
]
);
assert_eq!(
v("appliance", Rule::ForbiddenAnywhere, "openssl-sys").to_string(),
"[appliance] forbidden anywhere: openssl-sys — because of openssl-sys"
);
}
/// Each kind of finding is printed under its own heading, and only a report with no
/// violation, no stale deviation and no phantom rule is clean.
#[test]
fn the_report_prints_every_finding_and_only_an_empty_one_is_clean() {
let mut r = sized(310, 310);
assert!(r.is_clean());
assert_eq!(r.size(APPLIANCE), Some((350, 310)));
assert_eq!(r.size("no-such-profile"), None);
r.tracked
.push(v("S", Rule::ForbiddenInServerGraph, "tracked-crate"));
assert!(r.is_clean(), "a tracked deviation is printed, not failed");
r.stale_deviations
.push(v("S", Rule::StaleDeviation, "old-excuse"));
r.phantom_rules
.push(v("*", Rule::PhantomRule, "never-built"));
r.violations.push(v(
"S",
Rule::GixPorcelainFeature,
"gix:blocking-network-client",
));
assert!(!r.is_clean());
let page = r.render();
let lines: Vec<&str> = page.lines().collect();
assert_eq!(lines.len(), 6, "{page}");
assert!(
lines[0].starts_with(" size appliance")
&& lines[0].ends_with("350 crates (310 third-party)"),
"{page}"
);
assert!(
lines[2].starts_with(" tracked [S] forbidden in the S graph: tracked-crate"),
"{page}"
);
assert!(
lines[3].starts_with(" STALE [S] stale tracked deviation: old-excuse"),
"{page}"
);
assert!(
lines[4].starts_with(" PHANTOM [*] phantom rule name: never-built"),
"{page}"
);
assert!(
lines[5].starts_with(" VIOLATION [S] gix porcelain feature"),
"{page}"
);
let mut empty = Report::default();
assert_eq!(empty.render(), "");
empty.violations.push(v("x", Rule::VacuousGraph, "y"));
assert!(!empty.is_clean());
}
/// The smart-HTTP growth budget: a run that did not resolve both graphs checked
/// nothing and says so; growth over the budget is forbidden; growth under it is slack.
#[test]
fn the_growth_budget_is_held_from_both_sides() {
let missing = check_appliance_growth(&Report::default());
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].rule, Rule::VacuousGraph);
assert!(
missing[0].why.contains("not both resolved"),
"{}",
missing[0].why
);
assert_eq!(
check_appliance_growth(&sized(310, 310 + HTTP_GROWTH_BUDGET)),
Vec::new()
);
let over = check_appliance_growth(&sized(310, 311 + HTTP_GROWTH_BUDGET));
assert_eq!(over.len(), 1);
assert_eq!(over[0].rule, Rule::ForbiddenAnywhere);
assert!(
over[0]
.why
.contains(&format!("adds {} third-party", HTTP_GROWTH_BUDGET + 1)),
"{}",
over[0].why
);
// with http resolving FEWER third-party crates the growth saturates at zero
let shrunk = check_appliance_growth(&sized(310, 300));
if HTTP_GROWTH_BUDGET == 0 {
assert_eq!(shrunk, Vec::new());
} else {
assert_eq!(shrunk.len(), 1);
assert_eq!(shrunk[0].rule, Rule::SlackBudget);
}
}