use std::fs;
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;
fn cli() -> Command {
Command::cargo_bin("bca").unwrap()
}
const BRANCHY_RUST: &str = r#"
pub fn classify(n: i32) -> &'static str {
if n < 0 {
"neg"
} else if n == 0 {
"zero"
} else if n < 10 {
"small"
} else if n < 100 {
"medium"
} else {
"large"
}
}
"#;
const WORSER_RUST: &str = r#"
pub fn classify(n: i32) -> &'static str {
if n < 0 {
"neg"
} else if n == 0 {
"zero"
} else if n < 10 {
"small"
} else if n < 100 {
"medium"
} else if n < 1000 {
"big"
} else if n < 10000 {
"huge"
} else {
"massive"
}
}
"#;
const TRIVIAL_RUST: &str = "
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
";
fn write_fixture(dir: &TempDir, name: &str, body: &str) -> String {
let path = dir.path().join(name);
fs::write(&path, body).expect("write fixture");
path.to_str().expect("utf8 fixture path").to_string()
}
fn write_file(dir: &TempDir, name: &str, body: &str) -> std::path::PathBuf {
let path = dir.path().join(name);
fs::write(&path, body).expect("write file");
path
}
#[test]
fn write_baseline_then_recheck_exits_clean() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("wrote 1 baseline entries"));
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn regressed_function_fails_even_when_baselined() {
let dir = TempDir::new().unwrap();
let src_path = dir.path().join("branchy.rs");
fs::write(&src_path, BRANCHY_RUST).unwrap();
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
fs::write(&src_path, WORSER_RUST).unwrap();
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(2)
.stderr(predicate::str::contains("classify"))
.stderr(predicate::str::contains("cyclomatic = 7"));
}
#[test]
fn new_offender_fails_even_with_baseline() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
let new_src = write_fixture(&dir, "extra.rs", BRANCHY_RUST);
cli()
.args([
"--paths",
new_src.as_str(),
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(2)
.stderr(predicate::str::contains("classify"));
}
#[test]
fn improved_function_still_passes() {
let dir = TempDir::new().unwrap();
let src_path = write_file(&dir, "branchy.rs", WORSER_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
fs::write(&src_path, BRANCHY_RUST).unwrap();
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn moved_function_treated_as_new_offender() {
let dir = TempDir::new().unwrap();
let src_path = write_file(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
let shifted = format!("/// Doc comment.\n///\n///\n{BRANCHY_RUST}");
fs::write(&src_path, shifted).unwrap();
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(2)
.stderr(predicate::str::contains("classify"));
}
#[test]
fn write_baseline_excludes_suppressed_functions() {
let dir = TempDir::new().unwrap();
let suppressed_src = r#"
pub fn classify(n: i32) -> &'static str {
// bca: suppress(cyclomatic)
if n < 0 {
"neg"
} else if n == 0 {
"zero"
} else if n < 10 {
"small"
} else if n < 100 {
"medium"
} else {
"large"
}
}
"#;
let src = write_fixture(&dir, "branchy.rs", suppressed_src);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("wrote 0 baseline entries"));
let content = fs::read_to_string(&baseline).unwrap();
assert!(content.contains("version = 2"));
assert!(!content.contains("[[entry]]"));
}
#[test]
fn write_baseline_with_no_suppress_includes_suppressed() {
let dir = TempDir::new().unwrap();
let suppressed_src = r#"
pub fn classify(n: i32) -> &'static str {
// bca: suppress(cyclomatic)
if n < 0 {
"neg"
} else if n == 0 {
"zero"
} else if n < 10 {
"small"
} else if n < 100 {
"medium"
} else {
"large"
}
}
"#;
let src = write_fixture(&dir, "branchy.rs", suppressed_src);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--no-suppress",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("wrote 1 baseline entries"));
}
#[test]
fn baseline_and_write_baseline_conflict_at_arg_parse() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
"a.toml",
"--write-baseline",
"b.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn write_baseline_conflicts_with_output_format() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--output-format",
"sarif",
"--write-baseline",
"b.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn write_baseline_conflicts_with_output() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--output",
"some.xml",
"--write-baseline",
"b.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("cannot be used with"));
}
#[test]
fn missing_baseline_file_fails_with_exit_1() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
"/definitely/does/not/exist.toml",
])
.assert()
.code(1)
.stderr(predicate::str::contains("read baseline"));
}
#[test]
fn malformed_baseline_toml_fails_with_exit_1() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
let baseline = dir.path().join("baseline.toml");
fs::write(&baseline, "this is not = valid toml [[[\n").unwrap();
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(1)
.stderr(predicate::str::contains("parse baseline"));
}
#[test]
fn higher_version_baseline_fails_with_helpful_message() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
let baseline = dir.path().join("baseline.toml");
fs::write(&baseline, "version = 99\n").unwrap();
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(1)
.stderr(predicate::str::contains("upgrade bca").or(predicate::str::contains("regenerate")));
}
#[test]
fn empty_baseline_file_fails_with_missing_version() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
let baseline = dir.path().join("baseline.toml");
fs::write(&baseline, "").unwrap();
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(1)
.stderr(predicate::str::contains("missing version field"));
}
#[test]
fn no_fail_overrides_baseline_fail() {
let dir = TempDir::new().unwrap();
let src_path = write_file(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
fs::write(&src_path, WORSER_RUST).unwrap();
cli()
.args([
"--paths",
src_path.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
"--no-fail",
])
.assert()
.success()
.stderr(predicate::str::contains("classify"));
}
#[test]
fn stale_baseline_entries_do_not_cover_unrelated_violations() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
fs::write(
&baseline,
"version = 2\n[[entry]]\npath = \"nonexistent.rs\"\nfunction = \"ghost\"\nstart_line = 1\nmetric = \"cyclomatic\"\nvalue = 100.0\n",
)
.unwrap();
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(2)
.stderr(predicate::str::contains("classify"))
.stderr(predicate::str::contains("cyclomatic = 5"));
}
#[test]
fn write_baseline_byte_equal_across_two_runs() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline_a = dir.path().join("a.toml");
let baseline_b = dir.path().join("b.toml");
for out in [&baseline_a, &baseline_b] {
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
out.to_str().unwrap(),
])
.assert()
.success();
}
let a = fs::read(&baseline_a).unwrap();
let b = fs::read(&baseline_b).unwrap();
assert_eq!(
a, b,
"two --write-baseline runs over an unchanged tree must produce byte-identical output"
);
}
#[test]
fn filter_emits_summary_when_any_filtered() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains(
"filtered 1 violations via baseline",
));
}
#[test]
fn write_baseline_creates_parent_directory() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("nested/sub/baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
assert!(
baseline.exists(),
"atomic write must create the parent directory"
);
}
#[test]
fn top_level_file_metric_baselined() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "branchy.rs", BRANCHY_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"loc.sloc=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
let content = fs::read_to_string(&baseline).unwrap();
assert!(
content.contains(r#"function = "<file>""#),
"expected `<file>` sentinel entry in baseline; got:\n{content}",
);
assert!(content.contains(r#"metric = "loc.sloc""#));
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"loc.sloc=1",
"--baseline",
baseline.to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn write_baseline_with_no_matching_files_fails_with_exit_1() {
let dir = TempDir::new().unwrap();
let empty = dir.path().join("empty");
fs::create_dir(&empty).unwrap();
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
empty.to_str().unwrap(),
"check",
"--threshold",
"cyclomatic=1",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.code(1)
.stderr(predicate::str::contains("no input files matched"));
assert!(
!baseline.exists(),
"no baseline file should be created when --paths matches nothing"
);
}
#[test]
fn clean_tree_write_baseline_produces_empty_versioned_file() {
let dir = TempDir::new().unwrap();
let src = write_fixture(&dir, "trivial.rs", TRIVIAL_RUST);
let baseline = dir.path().join("baseline.toml");
cli()
.args([
"--paths",
&src,
"check",
"--threshold",
"cyclomatic=10",
"--write-baseline",
baseline.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("wrote 0 baseline entries"));
let content = fs::read_to_string(&baseline).unwrap();
assert!(content.contains("version = 2"));
assert!(!content.contains("[[entry]]"));
}