use std::path::Path;
use codelore_lib::Options;
use codelore_lib::cache::repo_cache_dir;
use codelore_lib::change_set::{build_change_set_report, project_health};
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::{GixRepo, Repo, WorktreeChange, WorktreeChangeKind};
use codelore_lib::test_support::coupling_repo;
use codelore_lib::test_support::differential_repo::{self, DifferentialRepo};
const MONSTER_FN: &str = r"
fn monster(x: i32) -> i32 {
let mut acc = 0;
for a in 0..x {
if a % 2 == 0 && a % 3 == 0 || a % 5 == 0 {
for b in 0..a {
if b > 1 {
match b % 4 {
0 => { if b > 10 { acc += 1; } else { acc += 2; } }
1 => { while acc < 100 { acc += 1; if acc % 7 == 0 { break; } } }
2 => { for c in 0..b { if c > 3 && c < 9 || c == 5 { acc += c; } } }
_ => { if a > b { acc -= 1; } else { acc += 1; } }
}
}
}
}
}
acc
}
";
fn fresh() -> (DifferentialRepo, GixRepo, FactsDb, Options) {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("open fact store");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
(fx, repo, db, opts)
}
fn modified(path: &str) -> WorktreeChange {
WorktreeChange {
path: path.to_string(),
kind: WorktreeChangeKind::Modified,
rename_from: None,
}
}
#[test]
fn modified_file_gets_baseline_and_projected_scores() {
let (fx, repo, db, opts) = fresh();
let main_path = fx.dir.path().join("src/main.rs");
let mut content = std::fs::read_to_string(&main_path).expect("read main.rs");
content.push_str(MONSTER_FN);
std::fs::write(&main_path, content).expect("write main.rs");
let projection =
project_health(&db, &repo, &opts, &[modified("src/main.rs")]).expect("project_health");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.expect("src/main.rs has a delta row");
let baseline = delta.baseline_score.expect("baseline scored");
let projected = delta.projected_score.expect("projection scored");
assert!(
projected < baseline,
"a deeply-nested high-complexity append must lower the score: \
baseline {baseline}, projected {projected}",
);
assert!(
delta.delta.expect("delta present") < 0.0,
"delta must be negative: {:?}",
delta.delta,
);
assert!(
delta.reason.is_none(),
"a fully-scored file needs no reason"
);
}
#[test]
fn baseline_median_reads_code_health_score_not_hotspots_cognitive_health() {
let (_fx, repo, db, opts) = fresh();
let projection = project_health(&db, &repo, &opts, &[]).expect("project_health");
let baseline_median = projection
.baseline_median
.expect("delivery-scale fixture has scoreable files");
let opts_scan = {
let mut o = opts.with_no_row_limit();
o.min_revs = 1;
o
};
let health_rows = codelore_lib::analyses::code_health::run_code_health(&db, &opts_scan)
.expect("run_code_health");
let code_health_median = median_of(health_rows.iter().map(|r| r.score));
assert!(
(baseline_median - code_health_median).abs() < 1e-9,
"baseline_median must equal the code-health composite score median: \
baseline_median={baseline_median}, code_health_median={code_health_median}"
);
let hotspot_rows =
codelore_lib::analyses::hotspots::run_hotspots(&db, &opts_scan).expect("run_hotspots");
let hotspots_median = median_of(hotspot_rows.iter().map(|r| r.cognitive_health));
assert!(
(baseline_median - hotspots_median).abs() > 1e-6,
"baseline_median must NOT equal the hotspots cognitive_health median — \
the two are different metrics on different scales, and coincidental \
equality here would hide a future accidental field swap: \
baseline_median={baseline_median}, hotspots_median={hotspots_median}"
);
}
fn median_of(values: impl Iterator<Item = f64>) -> f64 {
let mut v: Vec<f64> = values.collect();
v.sort_by(|a, b| a.partial_cmp(b).expect("no NaNs in health scores"));
let mid = v.len() / 2;
if v.len() % 2 == 1 {
v[mid]
} else {
f64::midpoint(v[mid - 1], v[mid])
}
}
#[test]
fn unchanged_repo_projects_zero_delta() {
let (fx, repo, db, opts) = fresh();
let head_bytes = repo
.read_blob_at_head("src/main.rs")
.expect("read blob")
.expect("main.rs tracked at HEAD");
std::fs::write(fx.dir.path().join("src/main.rs"), &head_bytes).expect("restore main.rs");
let projection =
project_health(&db, &repo, &opts, &[modified("src/main.rs")]).expect("project_health");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.expect("src/main.rs has a delta row");
assert_eq!(
delta.delta,
Some(0.0),
"an identical re-parse must project exactly zero delta",
);
}
#[test]
fn added_file_reports_no_history_baseline() {
let (fx, repo, db, opts) = fresh();
std::fs::write(
fx.dir.path().join("src/added_new.rs"),
"pub fn helper(a: i32) -> i32 { a + 1 }\n",
)
.expect("write added file");
let change = WorktreeChange {
path: "src/added_new.rs".to_string(),
kind: WorktreeChangeKind::Added,
rename_from: None,
};
let projection = project_health(&db, &repo, &opts, &[change]).expect("project_health");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "src/added_new.rs")
.expect("added file has a delta row");
assert_eq!(
delta.reason.as_deref(),
Some("new file (no history baseline)")
);
assert!(
delta.baseline_score.is_none(),
"an added file has no baseline"
);
assert!(delta.delta.is_none(), "an added file has no delta");
}
#[test]
fn non_tier1_file_reports_reason() {
let (fx, repo, db, opts) = fresh();
std::fs::write(fx.dir.path().join("README.md"), "# changed heading\n").expect("edit README");
let projection =
project_health(&db, &repo, &opts, &[modified("README.md")]).expect("project_health");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "README.md")
.expect("README.md has a delta row");
assert_eq!(delta.reason.as_deref(), Some("not a Tier-1 source file"));
assert!(delta.delta.is_none(), "a non-source file has no delta");
}
#[test]
fn deleted_red_file_is_handled_honestly() {
let fx = differential_repo::build();
let main_path = fx.dir.path().join("src/main.rs");
append(&main_path, MONSTER_FN);
let git = |args: &[&str]| {
std::process::Command::new("git")
.arg("-C")
.arg(fx.dir.path())
.args(["-c", "user.email=codelore-test@example.com"])
.args(["-c", "user.name=CodeLore Test"])
.args(args)
.status()
.expect("spawn git")
};
assert!(
git(&["commit", "-aqm", "worsen main.rs"]).success(),
"commit must succeed"
);
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("open fact store");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let baseline_probe =
project_health(&db, &repo, &opts, &[modified("src/main.rs")]).expect("baseline probe");
let baseline_delta = baseline_probe
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.expect("src/main.rs has a delta row");
let baseline_score = baseline_delta
.baseline_score
.expect("committed main.rs is scored");
assert_eq!(
baseline_delta.baseline_band.as_deref(),
Some("red"),
"the monster-fn commit must make src/main.rs red at HEAD: {baseline_delta:?}",
);
std::fs::remove_file(&main_path).expect("delete main.rs");
let deletion = WorktreeChange {
path: "src/main.rs".to_string(),
kind: WorktreeChangeKind::Deleted,
rename_from: None,
};
let projection = project_health(&db, &repo, &opts, &[deletion]).expect("project deletion");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.expect("a deleted file still gets a delta row");
assert_eq!(delta.reason.as_deref(), Some("deleted at gate time"));
assert_eq!(
delta.projected_score, None,
"a deleted file has no projected score"
);
assert_eq!(
delta.delta, None,
"a deleted file reports no numeric per-file delta — excluded from \
delta_code_health_min_per_file and new_file_health_min by construction"
);
assert_eq!(
delta.baseline_score,
Some(baseline_score),
"the baseline score is preserved even though the file left the projection"
);
assert!(
projection.baseline_median.is_some() && projection.projected_median.is_some(),
"the whole-repo medians must still resolve deterministically around a deletion: {projection:?}",
);
}
#[test]
fn project_health_leaves_the_fact_tables_untouched() {
let (fx, repo, db, opts) = fresh();
let main_path = fx.dir.path().join("src/main.rs");
let mut content = std::fs::read_to_string(&main_path).expect("read main.rs");
content.push_str(MONSTER_FN);
std::fs::write(&main_path, content).expect("write main.rs");
let cm_before = complexity_metrics_count(&db);
let perm_before = permanent_table_count(&db);
let _ = project_health(&db, &repo, &opts, &[modified("src/main.rs")]).expect("project_health");
assert_eq!(
complexity_metrics_count(&db),
cm_before,
"complexity_metrics row count must be unchanged",
);
assert_eq!(
permanent_table_count(&db),
perm_before,
"no new permanent tables may be created",
);
}
fn append(path: &Path, text: &str) {
let mut content = std::fs::read_to_string(path).expect("read file");
content.push_str(text);
std::fs::write(path, content).expect("write file");
}
fn sidecar_count(dir: &Path) -> usize {
std::fs::read_dir(dir).map_or(0, |entries| {
entries
.flatten()
.filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("json"))
.count()
})
}
#[test]
fn newly_cyclic_detected_when_edit_introduces_cycle() {
let (fx, repo, db, opts) = fresh();
append(&fx.dir.path().join("src/main.rs"), "use crate::lib;\n");
append(&fx.dir.path().join("src/lib.rs"), "use crate::main;\n");
let cache_root = tempfile::tempdir().expect("cache root");
let report = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("report");
assert_eq!(
report.newly_cyclic_paths,
vec!["src/lib.rs".to_string(), "src/main.rs".to_string()],
"the edit-introduced cycle members must be newly cyclic, sorted",
);
for path in ["src/lib.rs", "src/main.rs"] {
assert!(
report
.findings
.iter()
.any(|f| f.kind == "newly-cyclic" && f.path == path),
"a newly-cyclic finding must name {path}: {:?}",
report.findings,
);
}
}
#[test]
fn unchanged_tree_projects_zero_delta_with_clones_present() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("open fact store");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
min_clone_node_count: 0,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let head_bytes = repo
.read_blob_at_head("src/main.rs")
.expect("read blob")
.expect("main.rs tracked at HEAD");
std::fs::write(fx.dir.path().join("src/main.rs"), &head_bytes).expect("restore main.rs");
let projection =
project_health(&db, &repo, &opts, &[modified("src/main.rs")]).expect("project_health");
let delta = projection
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.expect("src/main.rs has a delta row");
assert_eq!(
delta.delta,
Some(0.0),
"an unchanged tree must project exactly zero delta even with clones present",
);
}
#[test]
fn worktree_clone_introduction_surfaces_a_finding() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("open fact store");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
min_clone_node_count: 0,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
append(
&fx.dir.path().join("src/main.rs"),
"\nfn dup_alpha(a: i32) -> i32 { let p = a + 1; let q = p * 2; let r = q - 3; r + p + q }\n\
fn dup_beta(b: i32) -> i32 { let s = b + 1; let t = s * 2; let u = t - 3; u + s + t }\n",
);
let cache_root = tempfile::tempdir().expect("cache root");
let report = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("report");
assert!(
report
.findings
.iter()
.any(|f| f.kind == "clone-introduction" && f.path == "src/main.rs"),
"a working-tree-introduced duplicate must raise a clone-introduction finding: {:?}",
report.findings,
);
if let Some(delta) = report
.health
.deltas
.iter()
.find(|d| d.path == "src/main.rs")
.and_then(|d| d.delta)
{
assert!(
delta <= 0.0,
"an introduced duplicate must not raise the projected health: {delta}",
);
}
}
#[test]
fn absence_fires_for_historical_partner() {
let fx = coupling_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("open fact store");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
append(
&fx.dir.path().join("src/alpha/svc.rs"),
"\npub fn gate_probe() {}\n",
);
let cache_root = tempfile::tempdir().expect("cache root");
let report = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("report");
let absence = report
.coupling_absences
.iter()
.find(|a| a.touched_file == "src/alpha/svc.rs" && a.expected_partner == "src/beta/svc.rs")
.expect("the historically-coupled partner must be flagged absent");
assert!(
absence.historical_shared_revs >= 5,
"the pair's shared revisions carry the signal strength: {absence:?}",
);
assert!(
report
.findings
.iter()
.any(|f| f.kind == "coupling-absence" && f.path == "src/alpha/svc.rs"),
"a coupling-absence finding must name the touched file: {:?}",
report.findings,
);
}
#[test]
fn report_is_memoised_by_content() {
let (fx, repo, db, opts) = fresh();
let main_path = fx.dir.path().join("src/main.rs");
append(&main_path, MONSTER_FN);
let cache_root = tempfile::tempdir().expect("cache root");
let sidecar_dir = repo_cache_dir(cache_root.path(), fx.dir.path()).join("change-set");
let first = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("first");
assert_eq!(
sidecar_count(&sidecar_dir),
1,
"first build writes a sidecar"
);
let second = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("second");
assert_eq!(first, second, "a warm rebuild must return the same report");
assert_eq!(
sidecar_count(&sidecar_dir),
1,
"unchanged content must hit the same sidecar entry",
);
append(&main_path, "\n// content flip\n");
let _third = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("third");
assert_eq!(
sidecar_count(&sidecar_dir),
2,
"flipped content must produce a different cache key",
);
}
#[test]
fn report_key_folds_in_defect_calibration() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let head_sha = repo.head_sha().expect("head sha");
let root = fx.dir.path();
let changes = [modified("src/main.rs")];
let calib_a = root.join("a.calib.json");
std::fs::write(&calib_a, br#"{"weights":"a"}"#).expect("write calib a");
let calib_b = root.join("b.calib.json");
std::fs::write(&calib_b, br#"{"weights":"b"}"#).expect("write calib b");
let key = |cal: Option<std::path::PathBuf>| {
let opts = Options {
repo_path: root.to_path_buf(),
defect_calibration: cal,
..Options::default()
};
codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts).expect("report_key")
};
let uncalibrated = key(None);
let calibrated_a = key(Some(calib_a.clone()));
let calibrated_a_again = key(Some(calib_a));
let calibrated_b = key(Some(calib_b));
assert_ne!(
uncalibrated, calibrated_a,
"calibrated and uncalibrated runs must not share a cache key",
);
assert_eq!(
calibrated_a, calibrated_a_again,
"identical artifact content must yield a stable key",
);
assert_ne!(
calibrated_a, calibrated_b,
"different artifact content must yield different keys",
);
}
#[test]
fn report_key_folds_in_min_revs() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let head_sha = repo.head_sha().expect("head sha");
let changes = [modified("src/main.rs")];
let opts_a = Options {
repo_path: fx.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
let opts_b = Options {
min_revs: 7,
..opts_a.clone()
};
let opts_a_again = opts_a.clone();
let key_a = codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts_a)
.expect("report_key a");
let key_b = codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts_b)
.expect("report_key b");
let key_a_again =
codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts_a_again)
.expect("report_key a again");
assert_ne!(
key_a, key_b,
"differing only in min_revs must yield different cache keys",
);
assert_eq!(
key_a, key_a_again,
"identical Options must yield an identical cache key",
);
}
#[test]
fn report_key_folds_in_rows_limit() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let head_sha = repo.head_sha().expect("head sha");
let changes = [modified("src/main.rs")];
let opts_a = Options {
repo_path: fx.dir.path().to_path_buf(),
rows_limit: None,
..Options::default()
};
let opts_b = Options {
rows_limit: Some(5),
..opts_a.clone()
};
let key_a = codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts_a)
.expect("report_key a");
let key_b = codelore_lib::change_set::cache::report_key(&head_sha, &changes, &opts_b)
.expect("report_key b");
assert_ne!(
key_a, key_b,
"differing only in rows_limit must yield different cache keys",
);
}
#[test]
fn finding_ids_stable_across_runs() {
let (fx, repo, db, opts) = fresh();
append(&fx.dir.path().join("src/main.rs"), MONSTER_FN);
let cache_a = tempfile::tempdir().expect("cache a");
let cache_b = tempfile::tempdir().expect("cache b");
let first = build_change_set_report(&db, &repo, &opts, cache_a.path()).expect("first");
let second = build_change_set_report(&db, &repo, &opts, cache_b.path()).expect("second");
assert!(
!first.findings.is_empty(),
"the monster append must produce at least a health-drop finding",
);
let ids_a: Vec<&str> = first.findings.iter().map(|f| f.id.as_str()).collect();
let ids_b: Vec<&str> = second.findings.iter().map(|f| f.id.as_str()).collect();
assert_eq!(ids_a, ids_b, "finding ids must be stable across runs");
for f in &first.findings {
assert_eq!(
f.id.len(),
12,
"finding id is a 12-hex digest prefix: {f:?}"
);
assert!(
f.id.chars().all(|c| c.is_ascii_hexdigit()),
"finding id must be hex: {f:?}",
);
}
}
#[test]
fn report_renders_byte_identical_across_two_builds() {
let fx = differential_repo::build();
let repo = GixRepo::open(fx.dir.path()).expect("open repo");
let opts = Options {
repo_path: fx.dir.path().to_path_buf(),
..Options::default()
};
append(&fx.dir.path().join("src/main.rs"), MONSTER_FN);
let mut jsons = Vec::new();
for label in ["a", "b"] {
let db = FactsDb::new_in_memory().expect("open fact store");
db.ingest(&repo, &opts).expect("ingest");
let cache_root = tempfile::tempdir().expect("cache root");
let report = build_change_set_report(&db, &repo, &opts, cache_root.path()).expect("report");
jsons.push(serde_json::to_string(&report).unwrap_or_else(|e| panic!("json {label}: {e}")));
}
assert_eq!(jsons[0], jsons[1], "rendered report must be byte-identical");
}
fn complexity_metrics_count(db: &FactsDb) -> i64 {
db.query_row("SELECT COUNT(*) FROM complexity_metrics", [], |r| r.get(0))
.expect("count complexity_metrics")
}
fn permanent_table_count(db: &FactsDb) -> i64 {
db.query_row(
"SELECT COUNT(*) FROM duckdb_tables() WHERE temporary = false",
[],
|r| r.get(0),
)
.expect("count permanent tables")
}