use stern4rust::reporting::offence::Offence;
use stern4rust::reporting::offence_threshold::OffenceThreshold;
use stern4rust::reporting::package_roster::PackageRoster;
use stern4rust::reporting::report_printer::ReportPrinter;
fn many(count: usize) -> Vec<Offence> {
(1..=count)
.map(|line| offence("src/a.rs", line, "header"))
.collect()
}
fn offence(file: &str, line: usize, rule: &'static str) -> Offence {
Offence::new(
file,
line,
rule,
"something is wrong".to_string(),
"do the thing that makes it right".to_string(),
)
}
#[test]
fn print_with_a_path_far_wider_than_its_heading_does_not_panic() {
let printer = ReportPrinter::new(1);
let long = "crates/deeply/nested/package/src/module/submodule/subject.rs";
printer.print(&[offence(long, 1234, "header")]);
}
#[test]
fn print_with_no_offences_does_not_panic() {
let printer = ReportPrinter::new(0);
printer.print(&[]);
}
#[test]
fn print_with_offences_from_several_rules_does_not_panic() {
let printer = ReportPrinter::new(3);
printer.print(&[
offence("src/a.rs", 1, "header"),
offence("tests/b_tests.rs", 42, "test-file-structure"),
offence("src/c.rs", 7, "header"),
]);
}
#[test]
fn print_with_one_offence_does_not_panic() {
let printer = ReportPrinter::new(1);
printer.print(&[offence("src/a.rs", 1, "header")]);
}
#[test]
fn render_names_a_pattern_that_matched_nothing_as_such() {
let printer = ReportPrinter::new(4)
.with_exclusions(vec![("gone/**".to_string(), 0), ("live/**".to_string(), 3)]);
let report = printer.render(&[]);
assert!(report.contains("matched nothing: gone/**"), "{report}");
assert!(!report.contains("matched nothing: live/**"), "{report}");
}
#[test]
fn render_names_each_exclusion_with_the_files_it_removed() {
let printer = ReportPrinter::new(4).with_exclusions(vec![("fixture/**".to_string(), 27)]);
let report = printer.render(&[]);
assert!(
report.contains("excluded: fixture/** (27 files)"),
"{report}"
);
assert!(report.contains("files_excluded=27"), "{report}");
}
#[test]
fn render_names_the_rules_that_were_applied() {
let report = ReportPrinter::new(1)
.with_rules(
vec!["header".to_string(), "tests-layout".to_string()],
Vec::new(),
Vec::new(),
)
.render(&[]);
assert!(
report.contains("applied: header, tests-layout"),
"got {report}"
);
}
#[test]
fn render_names_the_rules_that_were_not_applied() {
let report = ReportPrinter::new(1)
.with_rules(
vec!["header".to_string()],
vec!["tests-layout".to_string(), "test-free-source".to_string()],
Vec::new(),
)
.render(&[offence("src/a.rs", 1, "header")]);
assert!(
report.contains("not applied: tests-layout (skipped), test-free-source (skipped)"),
"got {report}"
);
assert!(
report.contains("rules_applied=1 rules_skipped=2"),
"got {report}"
);
}
#[test]
fn render_of_a_package_whose_rule_could_not_run_names_the_requirement() {
let printer = ReportPrinter::new(9).with_package_rosters(vec![
PackageRoster::new("node", vec!["header".to_string()], Vec::new(), Vec::new()),
PackageRoster::new(
"fixture",
vec!["header".to_string()],
Vec::new(),
vec![(
"spdx-matches-manifest".to_string(),
"needs a `license` field in Cargo.toml".to_string(),
)],
),
]);
let report = printer.render(&[]);
assert!(report.contains("spdx-matches-manifest (needs a `license` field in Cargo.toml)"));
assert!(report.contains("fixture:"));
}
#[test]
fn render_of_a_single_package_states_one_roster_without_naming_it() {
let printer = ReportPrinter::new(9).with_package_rosters(vec![PackageRoster::new(
"cargo-stern4rust",
vec!["header".to_string()],
Vec::new(),
Vec::new(),
)]);
let report = printer.render(&[]);
assert!(report.contains("applied: header"));
assert!(!report.contains("cargo-stern4rust:"));
}
#[test]
fn render_of_more_offences_than_the_threshold_says_how_many_are_not_shown() {
let report = ReportPrinter::new(1)
.with_threshold(OffenceThreshold::new(3))
.render(&many(10));
assert!(report.contains("7 more"), "got {report}");
assert!(report.contains("--offence-threshold"), "got {report}");
}
#[test]
fn render_of_more_offences_than_the_threshold_shows_only_the_threshold() {
let report = ReportPrinter::new(1)
.with_threshold(OffenceThreshold::new(3))
.render(&many(10));
assert_eq!(report.matches("something is wrong").count(), 3);
}
#[test]
fn render_of_no_offences_says_every_rule_is_satisfied() {
let report = ReportPrinter::new(9).render(&[]);
assert!(report.contains("All rules are satisfied."));
assert!(report.contains(
"files_scanned=9 files_excluded=0 offences=0 baselined=0 fixed=0 rules_broken=0"
));
}
#[test]
fn render_of_no_offences_with_a_skipped_rule_says_applied_rules_are_satisfied() {
let report = ReportPrinter::new(9)
.with_rules(
vec!["header".to_string()],
vec!["tests-layout".to_string()],
Vec::new(),
)
.render(&[]);
assert!(
report.contains("All applied rules are satisfied."),
"got {report}"
);
assert!(!report.contains("All rules are satisfied."), "got {report}");
assert!(report.contains("not applied: tests-layout"), "got {report}");
}
#[test]
fn render_of_packages_that_agree_states_one_roster() {
let printer = ReportPrinter::new(9).with_package_rosters(vec![
PackageRoster::new(
"node",
vec!["header".to_string(), "test-naming".to_string()],
Vec::new(),
Vec::new(),
),
PackageRoster::new(
"node-infra",
vec!["header".to_string(), "test-naming".to_string()],
Vec::new(),
Vec::new(),
),
]);
let report = printer.render(&[]);
assert!(report.contains("applied: header, test-naming"));
assert!(!report.contains("node"));
assert_eq!(report.matches("applied:").count(), 1);
}
#[test]
fn render_of_packages_that_differ_states_a_roster_each() {
let printer = ReportPrinter::new(9).with_package_rosters(vec![
PackageRoster::new(
"node",
vec!["header".to_string(), "test-naming".to_string()],
Vec::new(),
Vec::new(),
),
PackageRoster::new(
"validation",
vec!["header".to_string()],
vec!["test-naming".to_string()],
Vec::new(),
),
]);
let report = printer.render(&[]);
assert!(report.contains("node:"));
assert!(report.contains("validation:"));
assert!(report.contains("not applied: test-naming (skipped)"));
}
#[test]
fn render_puts_a_correction_under_every_offence() {
let offences = [
offence("src/a.rs", 1, "header"),
offence("src/b.rs", 2, "header"),
];
let report = ReportPrinter::new(2).render(&offences);
assert_eq!(
report
.matches("fix: do the thing that makes it right")
.count(),
2
);
}
#[test]
fn render_puts_the_correction_on_its_own_line_after_the_offence() {
let report = ReportPrinter::new(1).render(&[offence("src/a.rs", 1, "header")]);
let lines: Vec<&str> = report.lines().collect();
let row = lines
.iter()
.position(|line| line.contains("something is wrong"))
.expect("the offence row");
assert!(lines[row + 1].trim_start().starts_with("fix: "));
assert!(
lines[row + 1].starts_with(" "),
"got {:?}",
lines[row + 1]
);
}
#[test]
fn render_says_why_each_rule_was_not_applied() {
let report = ReportPrinter::new(1)
.with_rules(
vec!["readable-source".to_string()],
vec!["tests-layout".to_string()],
vec![("header".to_string(), "needs --header-file".to_string())],
)
.render(&[]);
assert!(report.contains("tests-layout (skipped)"), "got {report}");
assert!(
report.contains("header (needs --header-file)"),
"got {report}"
);
assert!(
report.contains("rules_applied=1 rules_skipped=1 rules_unconfigured=1"),
"got {report}"
);
}
#[test]
fn render_shows_the_file_line_and_rule_of_every_offence() {
let report = ReportPrinter::new(2).render(&[
offence("src/a.rs", 1, "header"),
offence("tests/b_tests.rs", 42, "test-file-structure"),
]);
assert!(report.contains("src/a.rs"));
assert!(report.contains("tests/b_tests.rs"));
assert!(report.contains("42"));
assert!(report.contains("test-file-structure"));
}
#[test]
fn render_summarises_two_offences_of_one_rule_as_one_broken_rule() {
let report = ReportPrinter::new(5).render(&[
offence("src/a.rs", 1, "header"),
offence("src/b.rs", 2, "header"),
]);
assert!(report.contains(
"files_scanned=5 files_excluded=0 offences=2 baselined=0 fixed=0 rules_broken=1"
));
}
#[test]
fn render_summary_counts_every_offence_even_when_some_are_not_shown() {
let report = ReportPrinter::new(1)
.with_threshold(OffenceThreshold::new(3))
.render(&many(10));
assert!(
report.contains(
"files_scanned=1 files_excluded=0 offences=10 baselined=0 fixed=0 rules_broken=1"
),
"got {report}"
);
}
#[test]
fn render_with_differing_rosters_keeps_the_summary_line_intact() {
let printer = ReportPrinter::new(9).with_package_rosters(vec![
PackageRoster::new("node", vec!["header".to_string()], Vec::new(), Vec::new()),
PackageRoster::new(
"validation",
Vec::new(),
vec!["header".to_string()],
Vec::new(),
),
]);
let report = printer.render(&[]);
assert!(report.contains("summary: files_scanned=9"));
assert_eq!(report.matches("summary:").count(), 1);
}
#[test]
fn with_baseline_puts_the_suppressed_count_in_the_report() {
let printer = ReportPrinter::new(1).with_baseline(Some("bl.json".to_string()), 7, 2);
let report = printer.render(&[]);
assert!(
report.contains("baseline: bl.json (7 suppressed)"),
"{report}"
);
assert!(
report.contains("2 baseline entries matched nothing"),
"{report}"
);
assert!(report.contains("baselined=7"), "{report}");
}
#[test]
fn with_config_file_names_the_config_the_run_used() {
let printer = ReportPrinter::new(1).with_config_file(Some("stern4rust.toml".to_string()));
let report = printer.render(&[]);
assert!(report.contains("config: stern4rust.toml"), "{report}");
}
#[test]
fn with_fixed_reports_how_many_files_were_rewritten() {
let printer = ReportPrinter::new(1).with_fixed(12);
let report = printer.render(&[]);
assert!(report.contains("fixed: 12 file(s) rewritten"), "{report}");
assert!(report.contains("fixed=12"), "{report}");
}