use super::*;
use crate::cli::OutputMode;
use crate::walk::{ExcludeFilter, WalkConfig};
use git2::Repository;
use std::fs;
use std::path::Path;
#[test]
fn run_on_empty_dir() {
let dir = tempfile::tempdir().unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
run(&cfg, OutputMode::Table, 10, 6, "cogcom").unwrap();
}
#[test]
fn run_on_rust_file() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("main.rs"),
"// a module\nfn main() {\n let x = 1;\n let y = x + 2;\n println!(\"{}\", y);\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert!(
score.score > 50.0,
"simple code should score well, got {}",
score.score
);
assert_eq!(score.files_analyzed, 1);
assert!(score.total_loc > 0);
assert_eq!(score.dimensions.len(), 5);
}
#[test]
fn run_on_rust_file_legacy_model() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("main.rs"),
"// a module\nfn main() {\n let x = 1;\n let y = x + 2;\n println!(\"{}\", y);\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Legacy;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert!(
score.score > 50.0,
"simple code should score well with legacy model, got {}",
score.score
);
assert_eq!(score.files_analyzed, 1);
assert!(score.total_loc > 0);
assert_eq!(score.dimensions.len(), 6);
assert_eq!(score.dimensions[0].name, "Maintainability Index");
assert_eq!(score.dimensions[1].name, "Cyclomatic Complexity");
}
#[test]
fn run_json_output() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("main.rs"),
"fn main() {\n let x = 1;\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
run(&cfg, OutputMode::Json, 10, 6, "cogcom").unwrap();
}
#[test]
fn run_includes_tests_with_flag() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir(dir.path().join("tests")).unwrap();
fs::write(
dir.path().join("tests/integration.rs"),
"fn test() {\n assert!(true);\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), true, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 1);
}
#[test]
fn run_excludes_tests_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir(dir.path().join("tests")).unwrap();
fs::write(
dir.path().join("tests/integration.rs"),
"fn test() {\n assert!(true);\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 0);
}
#[test]
fn run_on_current_repo() {
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(Path::new("."), false, &filter);
run(&cfg, OutputMode::Table, 5, 6, "cogcom").unwrap();
}
#[test]
fn run_on_current_repo_legacy() {
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(Path::new("."), false, &filter);
run(&cfg, OutputMode::Table, 5, 6, "legacy").unwrap();
}
#[test]
fn excludes_markdown_files() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("README.md"), "# Hello\n\nWorld\n").unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 0, "Markdown should be excluded");
}
#[test]
fn excludes_toml_files() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 0, "TOML should be excluded");
}
#[test]
fn excludes_json_files() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("data.json"), "{\"key\": \"value\"}\n").unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 0, "JSON should be excluded");
}
#[test]
fn run_on_single_file() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("lib.rs");
fs::write(
&file,
"/// Docs\nfn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(&file, false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
assert_eq!(score.files_analyzed, 1);
assert!(score.total_loc > 0);
}
#[test]
fn dimensions_sum_to_100_percent() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("main.rs"),
"fn main() {\n let x = 1;\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Cognitive;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
let total_weight: f64 = score.dimensions.iter().map(|d| d.weight).sum();
assert!(
(total_weight - 1.0).abs() < 0.001,
"weights should sum to 1.0, got {total_weight}"
);
}
#[test]
fn legacy_dimensions_sum_to_100_percent() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("main.rs"),
"fn main() {\n let x = 1;\n}\n",
)
.unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let model = ScoringModel::Legacy;
let score = compute_score(&cfg, 10, 6, &model).unwrap();
let total_weight: f64 = score.dimensions.iter().map(|d| d.weight).sum();
assert!(
(total_weight - 1.0).abs() < 0.001,
"legacy weights should sum to 1.0, got {total_weight}"
);
}
#[test]
fn score_gate_error_message_shows_scores_delta_and_tolerance() {
let before = 86.93_f64;
let after = 86.89_f64;
let msg = format_gate_error(before, after, after - before, 0.01);
assert!(
msg.contains("86.93 → 86.89"),
"scores should show two decimal places, got: {msg}"
);
assert!(
msg.contains("(-0.0400)"),
"delta should show four decimal places, got: {msg}"
);
assert!(
msg.contains("0.01 tolerance"),
"message should name the tolerance, got: {msg}"
);
}
#[test]
fn score_gate_error_message_separates_drop_just_over_tolerance() {
let msg = format_gate_error(77.7800, 77.7696, 77.7696 - 77.7800, 0.01);
assert!(
msg.contains("(-0.0104)"),
"a drop just over the tolerance must not print as -0.01, got: {msg}"
);
}
#[test]
fn gate_score_worsened_ignores_drop_within_tolerance() {
assert!(!gate_score_worsened(87.814, 87.811, 0.01));
assert!(!gate_score_worsened(90.0, 90.0, 0.01));
}
#[test]
fn gate_score_worsened_ignores_sub_tolerance_drop_across_rounding_boundary() {
assert!(!gate_score_worsened(77.77897, 77.77122, 0.01));
}
#[test]
fn gate_score_worsened_catches_drop_over_tolerance() {
assert!(gate_score_worsened(87.81, 87.79, 0.01));
assert!(gate_score_worsened(90.0, 89.5, 0.01));
}
#[test]
fn gate_score_worsened_honors_custom_tolerance() {
assert!(!gate_score_worsened(90.0, 89.96, 0.05));
assert!(gate_score_worsened(90.0, 89.9, 0.05));
}
#[test]
fn gate_score_worsened_zero_tolerance_catches_any_drop() {
assert!(gate_score_worsened(90.0, 89.999, 0.0));
assert!(!gate_score_worsened(90.0, 90.0, 0.0));
}
#[test]
fn gate_score_worsened_allows_improvement() {
assert!(!gate_score_worsened(87.80, 87.81, 0.01));
assert!(!gate_score_worsened(89.5, 90.0, 0.0));
}
#[test]
fn score_gate_fail_if_worse_error_contains_decimal_delta() {
let dir = tempfile::tempdir().unwrap();
let repo = git2::Repository::init(dir.path()).unwrap();
let mut config = repo.config().unwrap();
config.set_str("user.name", "Test").unwrap();
config.set_str("user.email", "test@test.com").unwrap();
let sig =
git2::Signature::new("Test", "test@test.com", &git2::Time::new(1_700_000_000, 0)).unwrap();
let simple = "fn clean() -> i32 { 42 }\n";
std::fs::write(dir.path().join("lib.rs"), simple).unwrap();
let oid1 = {
let mut index = repo.index().unwrap();
index.add_path(std::path::Path::new("lib.rs")).unwrap();
index.write().unwrap();
let tree = repo.find_tree(index.write_tree().unwrap()).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[])
.unwrap()
};
let complex = r#"fn clean() -> i32 { 42 }
fn deeply_nested(x: i32) -> i32 {
if x > 0 {
if x > 1 {
if x > 2 {
if x > 3 {
if x > 4 {
if x > 5 {
if x > 6 {
if x > 7 {
if x > 8 {
if x > 9 {
return x;
}
}
}
}
}
}
}
}
}
}
0
}
"#;
std::fs::write(dir.path().join("lib.rs"), complex).unwrap();
{
let mut index = repo.index().unwrap();
index.add_path(std::path::Path::new("lib.rs")).unwrap();
index.write().unwrap();
let tree = repo.find_tree(index.write_tree().unwrap()).unwrap();
let parent = repo.find_commit(oid1).unwrap();
repo.commit(
Some("HEAD"),
&sig,
&sig,
"add deeply nested fn",
&tree,
&[&parent],
)
.unwrap();
}
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), true, &filter);
let gate = ScoreGate {
max_drop: Some(0.01),
fail_below: None,
..ScoreGate::default()
};
let result = run_diff(&cfg, "HEAD~1", OutputMode::Table, 10, 6, "cogcom", gate);
let msg = result
.expect_err(
"gate should fire: 10-level nesting must produce a score drop vs trivial baseline",
)
.to_string();
let paren_start = msg.rfind('(').expect("message should contain '('");
let paren_end = msg.rfind(')').expect("message should contain ')'");
let delta_str = &msg[paren_start + 1..paren_end];
let dot_idx = delta_str.find('.').unwrap_or_else(|| {
panic!("delta '{delta_str}' should contain a decimal point, full msg: {msg}")
});
let decimals = &delta_str[dot_idx + 1..];
assert_eq!(
decimals.len(),
4,
"delta should have exactly 4 decimal places, got '{delta_str}' in: {msg}"
);
}
#[test]
fn scoring_model_from_arg_cogcom() {
assert_eq!(ScoringModel::from_arg("cogcom"), ScoringModel::Cognitive);
}
#[test]
fn scoring_model_from_arg_legacy() {
assert_eq!(ScoringModel::from_arg("legacy"), ScoringModel::Legacy);
}
fn create_test_repo_with_rust_file() -> (tempfile::TempDir, Repository) {
let dir = tempfile::tempdir().unwrap();
let repo = Repository::init(dir.path()).unwrap();
let mut config = repo.config().unwrap();
config.set_str("user.name", "Test").unwrap();
config.set_str("user.email", "test@test.com").unwrap();
let rs_path = dir.path().join("main.rs");
fs::write(&rs_path, "fn main() {\n let x = 1;\n}\n").unwrap();
let sig =
git2::Signature::new("Test", "test@test.com", &git2::Time::new(1_700_000_000, 0)).unwrap();
{
let mut index = repo.index().unwrap();
index.add_path(Path::new("main.rs")).unwrap();
index.write().unwrap();
let tree_oid = index.write_tree().unwrap();
let tree = repo.find_tree(tree_oid).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
.unwrap();
}
(dir, repo)
}
fn make_git_repo_with_file(content: &str) -> tempfile::TempDir {
make_git_repo_with_files(&[("main.rs", content)])
}
fn make_git_repo_with_files(files: &[(&str, &str)]) -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let repo = git2::Repository::init(dir.path()).unwrap();
let mut config = repo.config().unwrap();
config.set_str("user.name", "Test").unwrap();
config.set_str("user.email", "test@test.com").unwrap();
let sig =
git2::Signature::new("Test", "test@test.com", &git2::Time::new(1_700_000_000, 0)).unwrap();
let mut index = repo.index().unwrap();
for (name, content) in files {
fs::write(dir.path().join(name), content).unwrap();
index.add_path(Path::new(name)).unwrap();
}
index.write().unwrap();
let tree_oid = index.write_tree().unwrap();
let tree = repo.find_tree(tree_oid).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[])
.unwrap();
drop(tree);
dir
}
#[test]
fn run_diff_on_git_repo() {
let (dir, _repo) = create_test_repo_with_rust_file();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), true, &filter);
let result = run_diff(
&cfg,
"HEAD",
OutputMode::Table,
5,
6,
"cogcom",
ScoreGate::default(),
);
assert!(result.is_ok(), "run_diff should succeed: {:?}", result);
}
#[test]
fn run_diff_json_on_git_repo() {
let (dir, _repo) = create_test_repo_with_rust_file();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), true, &filter);
let result = run_diff(
&cfg,
"HEAD",
OutputMode::Json,
5,
6,
"cogcom",
ScoreGate::default(),
);
assert!(result.is_ok(), "run_diff JSON should succeed: {:?}", result);
}
#[test]
fn run_diff_legacy_model_on_git_repo() {
let (dir, _repo) = create_test_repo_with_rust_file();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), true, &filter);
let result = run_diff(
&cfg,
"HEAD",
OutputMode::Table,
5,
6,
"legacy",
ScoreGate::default(),
);
assert!(
result.is_ok(),
"run_diff legacy should succeed: {:?}",
result
);
}
#[test]
fn score_gate_no_flags_succeeds() {
let dir = make_git_repo_with_file("fn main() {}\n");
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let gate = ScoreGate::default();
let result = run_diff(&cfg, "HEAD", OutputMode::Table, 10, 6, "cogcom", gate);
assert!(result.is_ok(), "no gates should always succeed: {result:?}");
}
#[test]
fn score_gate_fail_below_passes_when_above_threshold() {
let dir = make_git_repo_with_file("fn main() {}\n");
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let gate = ScoreGate {
max_drop: None,
fail_below: Some(analyzer::Grade::FMinusMinus),
..ScoreGate::default()
};
let result = run_diff(&cfg, "HEAD", OutputMode::Table, 10, 6, "cogcom", gate);
assert!(
result.is_ok(),
"F-- threshold should not trigger on clean code"
);
}
#[test]
fn score_gate_fail_below_unit() {
let before = analyzer::score_to_grade(80.0); let after = analyzer::score_to_grade(70.0); let diff = diff::ScoreDiff {
git_ref: "HEAD".to_string(),
overall: diff::ScoreDelta {
before: 80.0,
after: 70.0,
delta: -10.0,
},
before_grade: before,
after_grade: after,
files_before: 1,
files_after: 1,
loc_before: 10,
loc_after: 10,
dimensions: vec![],
changed: None,
};
let threshold = analyzer::Grade::B;
assert!(
diff.after_grade.numeric_rank() < threshold.numeric_rank(),
"C should be below B"
);
let low_threshold = analyzer::Grade::FMinusMinus;
assert!(
diff.after_grade.numeric_rank() >= low_threshold.numeric_rank(),
"C should not be below F--"
);
assert!(diff.overall.delta < 0.0, "80→70 is a negative delta");
}
#[test]
fn score_gate_fail_if_worse_same_ref_passes() {
let dir = make_git_repo_with_file("fn main() {}\n");
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let gate = ScoreGate {
max_drop: Some(0.01),
fail_below: None,
..ScoreGate::default()
};
let result = run_diff(&cfg, "HEAD", OutputMode::Table, 10, 6, "cogcom", gate);
assert!(
result.is_ok(),
"same ref comparison should not trigger fail-if-worse"
);
}
#[test]
fn run_on_current_repo_with_target() {
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(Path::new("src"), false, &filter);
run(&cfg, OutputMode::Table, 5, 6, "cogcom").unwrap();
}
#[test]
fn run_json_with_target_path() {
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(Path::new("src"), false, &filter);
run(&cfg, OutputMode::Json, 5, 6, "cogcom").unwrap();
}
#[test]
fn run_short_format() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("main.rs"), "fn main() { let x = 1; }\n").unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
run(&cfg, OutputMode::Short, 10, 6, "cogcom").unwrap();
}
#[test]
fn run_terse_format() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("main.rs"), "fn main() { let x = 1; }\n").unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
run(&cfg, OutputMode::Terse, 10, 6, "cogcom").unwrap();
}
#[test]
fn run_diff_short_format() {
let dir = make_git_repo_with_file("fn main() { let x = 1; }\n");
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let gate = ScoreGate {
max_drop: None,
fail_below: None,
..ScoreGate::default()
};
run_diff(&cfg, "HEAD", OutputMode::Short, 10, 6, "cogcom", gate).unwrap();
}
#[test]
fn run_diff_terse_format() {
let dir = make_git_repo_with_file("fn main() { let x = 1; }\n");
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
let gate = ScoreGate {
max_drop: None,
fail_below: None,
..ScoreGate::default()
};
run_diff(&cfg, "HEAD", OutputMode::Terse, 10, 6, "cogcom", gate).unwrap();
}
const SIMPLE_FNS: &str = "fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n\n\
fn sub(a: i32, b: i32) -> i32 {\n a - b\n}\n\n\
fn mul(a: i32, b: i32) -> i32 {\n a * b\n}\n";
const NESTED_FN: &str = "fn tangle(a: i32, b: i32, c: i32) -> i32 {
let mut r = 0;
if a > 0 {
for i in 0..a {
if i % 2 == 0 {
while r < b {
if c > 3 && b > 2 || a < 1 {
match i {
0 => { if b > 1 { r += 1; } else { r += 2; } }
_ => { r += 3; }
}
}
r += 1;
}
}
}
}
r
}
";
fn changed_scope_gate() -> ScoreGate {
ScoreGate {
max_drop: Some(GateScope::Changed.default_tolerance()),
scope: GateScope::Changed,
..ScoreGate::default()
}
}
fn run_gate(dir: &Path, gate: ScoreGate) -> Result<(), Box<dyn std::error::Error>> {
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir, false, &filter);
run_diff(&cfg, "HEAD", OutputMode::Table, 10, 6, "cogcom", gate)
}
#[test]
fn changed_scope_passes_when_healthy_code_is_deleted() {
let dir = make_git_repo_with_files(&[("good.rs", SIMPLE_FNS), ("bad.rs", NESTED_FN)]);
fs::write(
dir.path().join("good.rs"),
"fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
)
.unwrap();
let project = ScoreGate {
max_drop: Some(0.01),
..ScoreGate::default()
};
assert!(
run_gate(dir.path(), project).is_err(),
"the project aggregate drops when healthy code leaves the denominator"
);
run_gate(dir.path(), changed_scope_gate()).expect("no changed file got worse");
}
#[test]
fn changed_scope_ignores_deleted_files() {
let dir = make_git_repo_with_files(&[("good.rs", SIMPLE_FNS), ("bad.rs", NESTED_FN)]);
fs::remove_file(dir.path().join("good.rs")).unwrap();
run_gate(dir.path(), changed_scope_gate()).expect("deleting a file never fails");
}
#[test]
fn changed_scope_fails_naming_the_file_that_got_worse() {
let dir = make_git_repo_with_files(&[("good.rs", SIMPLE_FNS), ("other.rs", SIMPLE_FNS)]);
fs::write(
dir.path().join("good.rs"),
format!("{SIMPLE_FNS}\n{NESTED_FN}"),
)
.unwrap();
let msg = run_gate(dir.path(), changed_scope_gate())
.expect_err("good.rs gained a deeply nested function")
.to_string();
assert!(msg.contains("good.rs dropped"), "{msg}");
assert!(!msg.contains("other.rs"), "{msg}");
}
#[test]
fn changed_scope_does_not_fail_on_new_files() {
let dir = make_git_repo_with_files(&[("good.rs", SIMPLE_FNS)]);
fs::write(dir.path().join("bad.rs"), NESTED_FN).unwrap();
run_gate(dir.path(), changed_scope_gate()).expect("new files have no score to fall from");
}
#[test]
fn changed_scope_fails_when_duplicated_lines_grow() {
let dir = make_git_repo_with_files(&[("a.rs", NESTED_FN)]);
fs::write(
dir.path().join("b.rs"),
NESTED_FN.replace("tangle", "tangle_copy"),
)
.unwrap();
let msg = run_gate(dir.path(), changed_scope_gate())
.expect_err("b.rs duplicates a.rs")
.to_string();
assert!(msg.contains("duplicated lines grew"), "{msg}");
}
#[test]
fn changed_scope_json_includes_changed_files() {
let dir = make_git_repo_with_files(&[("good.rs", SIMPLE_FNS)]);
fs::write(dir.path().join("new.rs"), "fn new() {}\n").unwrap();
let filter = ExcludeFilter::default();
let cfg = WalkConfig::new(dir.path(), false, &filter);
run_diff(
&cfg,
"HEAD",
OutputMode::Json,
10,
6,
"cogcom",
changed_scope_gate(),
)
.unwrap();
}
#[test]
fn changed_scope_maps_paths_when_analyzing_a_subdirectory() {
let dir = make_git_repo_with_files(&[("root.rs", SIMPLE_FNS)]);
let lib = dir.path().join("lib");
fs::create_dir(&lib).unwrap();
fs::write(lib.join("good.rs"), SIMPLE_FNS).unwrap();
let repo = Repository::open(dir.path()).unwrap();
let mut index = repo.index().unwrap();
index.add_path(Path::new("lib/good.rs")).unwrap();
index.write().unwrap();
let tree = repo.find_tree(index.write_tree().unwrap()).unwrap();
let head = repo.head().unwrap().peel_to_commit().unwrap();
let sig = git2::Signature::now("Test", "test@test.com").unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "lib", &tree, &[&head])
.unwrap();
fs::write(lib.join("good.rs"), format!("{SIMPLE_FNS}\n{NESTED_FN}")).unwrap();
fs::write(dir.path().join("root.rs"), NESTED_FN).unwrap();
let msg = run_gate(&lib, changed_scope_gate())
.expect_err("lib/good.rs got worse")
.to_string();
assert!(msg.contains("lib/good.rs dropped"), "{msg}");
assert!(!msg.contains("root.rs"), "{msg}");
}
#[test]
fn a_file_is_scored_without_its_inline_tests_when_they_are_excluded() {
let (_dir, path) = crate::test_blocks::tests::source_with_test_module();
let spec = crate::loc::language::detect(&path).unwrap();
for model in [ScoringModel::Cognitive, ScoringModel::Legacy] {
let of = |exclude_tests| {
analyze_single_file(&path, spec, exclude_tests, &model)
.unwrap()
.metrics
};
assert_eq!(of(true).code_lines, 3);
assert_eq!(of(false).code_lines, 14);
assert!(of(true).halstead_effort < of(false).halstead_effort);
}
}