use stern4rust::offence::Offence;
use stern4rust::offence_threshold::OffenceThreshold;
use stern4rust::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_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 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_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()],
)
.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 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 rules_broken=1"),
"got {report}"
);
}