use clap::Parser;
use std::env;
use std::fs;
use std::path::PathBuf;
use stern4rust::reporting::run_outcome::RunOutcome;
use stern4rust::runner::Runner;
use stern4rust::settings::args::Args;
const THIS_CRATE: &str = "cargo-stern4rust";
fn args_from(parts: &[&str]) -> Args {
Args::parse_from(parts.iter().map(|part| (*part).to_string()))
}
fn config_directory(name: &str, contents: &str) -> PathBuf {
let path = probe_package(name);
fs::write(path.join("stern4rust.toml"), contents).expect("write the config");
path
}
fn header_file(name: &str, contents: &str) -> PathBuf {
let path = env::temp_dir().join(format!("stern4rust_header_{name}.txt"));
fs::write(&path, contents).expect("write the header file");
path
}
fn probe_package(name: &str) -> PathBuf {
let path = env::temp_dir().join(format!("stern4rust_run_{name}"));
let _ = fs::remove_dir_all(&path);
fs::create_dir_all(path.join("src")).expect("create the package");
fs::write(
path.join("Cargo.toml"),
"[package]
name = \"probe\"
version = \"0.1.0\"
edition = \"2021\"
",
)
.expect("write the manifest");
fs::write(
path.join("src/lib.rs"),
"pub mod widget;
",
)
.expect("write the registry");
fs::write(
path.join("src/widget.rs"),
"pub struct Widget;
",
)
.expect("write the module");
path
}
fn run_with_header(name: &str, contents: &str) -> RunOutcome {
let path = header_file(name, contents);
Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--package",
THIS_CRATE,
"--header-file",
&path.to_string_lossy(),
]))
.expect("the run itself should succeed")
}
#[test]
fn run_against_an_unknown_package_is_an_error() {
let path = header_file("unknown_package", "// Copyright 2025\n");
let args = args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--package",
"no-such-package",
"--header-file",
&path.to_string_lossy(),
]);
let result = Runner::run(args);
assert!(result.is_err());
}
#[test]
fn run_against_this_crate_with_a_header_it_does_not_carry_reports_rules_broken() {
let outcome = run_with_header("foreign", "// Copyright 1999 Someone Else\n");
assert_eq!(outcome, RunOutcome::RulesBroken);
}
#[test]
fn run_against_this_crate_with_its_own_header_is_clean() {
let outcome = run_with_header(
"own",
"// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>\n\
// Licensed under the MIT License\n\
// SPDX-License-Identifier: MIT\n",
);
assert_eq!(outcome, RunOutcome::Clean);
}
#[test]
fn run_with_a_config_file_applies_its_settings() {
let path = config_directory(
"applies",
"rules = [\"tests-layout\"]
",
);
let outcome = Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
&path.join("Cargo.toml").to_string_lossy(),
]))
.expect("the run itself should succeed");
assert_eq!(outcome, RunOutcome::Clean);
}
#[test]
fn run_with_a_written_baseline_forgives_the_old_and_still_fails_on_the_new() {
let path = config_directory(
"baseline",
"rules = [\"imported-paths\"]
",
);
let widget = path.join("src/widget.rs");
fs::write(
&widget,
"pub struct W;
impl W { pub fn go() { let _ = std::env::args(); } }
",
)
.expect("write the offence");
let manifest = path.join("Cargo.toml").to_string_lossy().to_string();
let judge = || {
Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
&manifest,
]))
.expect("the run itself should succeed")
};
assert_eq!(
judge(),
RunOutcome::RulesBroken,
"the offence should be seen"
);
Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
&manifest,
"--write-baseline",
]))
.expect("writing the baseline should succeed");
assert_eq!(
judge(),
RunOutcome::Clean,
"the recorded offence is forgiven"
);
fs::write(
&widget,
"pub struct W;
impl W { pub fn go() { let _ = std::env::args(); let _ = std::env::vars(); } }
",
)
.expect("introduce a new offence");
assert_eq!(
judge(),
RunOutcome::RulesBroken,
"a new offence still fails"
);
}
#[test]
fn run_with_an_exclusion_covering_every_file_finds_nothing_to_judge() {
let path = header_file(
"excluded",
"// nobody carries this header
",
);
let outcome = Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--package",
THIS_CRATE,
"--header-file",
&path.to_string_lossy(),
"--exclude",
"**/*.rs",
]))
.expect("the run itself should succeed");
assert_eq!(outcome, RunOutcome::Clean);
}
#[test]
fn run_with_an_invalid_config_file_is_an_error() {
let path = config_directory(
"invalid",
"rules = 7
",
);
let outcome = Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
&path.join("Cargo.toml").to_string_lossy(),
]));
assert!(outcome.is_err());
}
#[test]
fn run_with_an_unknown_rule_name_is_an_error() {
let args = args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--skip",
"test-file-strucutre",
]);
let result = Runner::run(args);
assert!(result.is_err());
}
#[test]
fn run_with_an_unreadable_header_file_is_an_error() {
let absent = env::temp_dir().join("stern4rust_header_absent.txt");
let _ = fs::remove_file(&absent);
let args = args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--header-file",
&absent.to_string_lossy(),
]);
let result = Runner::run(args);
assert!(result.is_err());
}
#[test]
fn run_with_an_unusable_exclude_pattern_is_an_error() {
let outcome = Runner::run(args_from(&[
"cargo-stern4rust",
"--manifest-path",
"Cargo.toml",
"--package",
THIS_CRATE,
"--exclude",
"fixture/[",
]));
assert!(outcome.is_err());
}
#[test]
fn run_with_the_header_rule_selected_but_no_header_file_is_an_error() {
let path = probe_package("header_rule_with_no_header_file");
let args = args_from(&[
"cargo-stern4rust",
"--manifest-path",
path.join("Cargo.toml").to_str().expect("manifest path"),
"--rule",
"header",
]);
let result = Runner::run(args);
assert!(result.is_err());
}
#[test]
fn run_without_a_header_file_still_applies_the_rules_that_need_no_configuration() {
let args = args_from(&["cargo-stern4rust", "--manifest-path", "Cargo.toml"]);
let result = Runner::run(args);
assert!(result.is_ok());
}