1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! End-to-end coverage for the Maintainability-Index band rollup.
//!
//! `analyses/mi.rs` has no `run_*` entry point — its computational surface
//! is `MiRollup::from_hotspots` (band rollup) plus `MiBand::from_rank`
//! (percentile→band classifier), both consuming the `mi` / `mi_rank`
//! columns the hotspots SQL produces. The inline unit tests feed
//! hand-built `HotspotRow`s; this binary drives the real
//! `ingest → run_hotspots → MiRollup` path so a break in the
//! complexity-scan → MI → percentile-rank chain surfaces here.
use codelore_lib::Options;
use codelore_lib::analyses::hotspots::run_hotspots;
use codelore_lib::analyses::mi::{MiBand, MiRollup};
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::GixRepo;
#[test]
fn mi_rollup_over_ingested_hotspots_is_consistent() {
let tiny = codelore_lib::test_support::tiny_repo::build();
let repo = GixRepo::open(tiny.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: tiny.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let rows = run_hotspots(&db, &opts).expect("run hotspots");
assert!(!rows.is_empty(), "tiny fixture should yield ≥1 hotspot row");
let rollup = MiRollup::from_hotspots(&rows);
// Invariant 1: every hotspot row is accounted for exactly once across
// the four buckets — banded files plus the `unknown` bucket cover the
// whole set with no double-counting.
assert_eq!(
rollup.known() + rollup.unknown,
rows.len(),
"rollup must partition all hotspot rows: known={} unknown={} total={}",
rollup.known(),
rollup.unknown,
rows.len()
);
// Invariant 2: the tiny fixture's two Rust files produce a finite MI
// and a finite percentile rank, so at least one file is banded (the
// complexity → MI → PERCENT_RANK chain actually ran).
assert!(
rollup.known() >= 1,
"expected ≥1 banded file from the Rust fixture; got {rollup:?}"
);
// Invariant 3: the rollup's per-band counts equal an independent tally
// computed by re-classifying each row through `MiBand::from_rank` —
// catches any drift between the rollup's bucketing and the public
// classifier.
let mut low = 0usize;
let mut moderate = 0usize;
let mut high = 0usize;
let mut unknown = 0usize;
for row in &rows {
match (row.mi, row.mi_rank) {
(Some(mi), Some(rank)) if mi.is_finite() && rank.is_finite() => {
match MiBand::from_rank(rank) {
MiBand::Low => low += 1,
MiBand::Moderate => moderate += 1,
MiBand::High => high += 1,
}
}
_ => unknown += 1,
}
}
assert_eq!(rollup.low, low, "low band count drift");
assert_eq!(rollup.moderate, moderate, "moderate band count drift");
assert_eq!(rollup.high, high, "high band count drift");
assert_eq!(rollup.unknown, unknown, "unknown band count drift");
// Invariant 4: every banded percentile rank lies in the [0, 1] domain
// that `PERCENT_RANK()` is contracted to emit.
for row in &rows {
if let Some(rank) = row.mi_rank
&& rank.is_finite()
{
assert!(
(0.0..=1.0).contains(&rank),
"mi_rank out of [0,1] for {}: {rank}",
row.path
);
}
}
}