use crate::maintainability::compute_maintainability_index;
fn expected_simplified(cc: f64, loc: f64) -> f64 {
let raw = 171.0 - 0.23 * cc - 16.2 * loc.ln();
round2(raw.max(0.0))
}
fn expected_full(cc: f64, loc: f64, vol: f64) -> f64 {
let raw = 171.0 - 5.2 * vol.ln() - 0.23 * cc - 16.2 * loc.ln();
round2(raw.max(0.0))
}
fn round2(val: f64) -> f64 {
(val * 100.0).round() / 100.0
}
#[test]
fn identity_loc_1_cc_0_no_halstead() {
let mi = compute_maintainability_index(0.0, 1.0, None).unwrap();
assert_eq!(mi.score, 171.0);
assert_eq!(mi.grade, "A");
}
#[test]
fn identity_volume_1_equals_simplified() {
let simplified = compute_maintainability_index(10.0, 100.0, None).unwrap();
let full = compute_maintainability_index(10.0, 100.0, Some(1.0)).unwrap();
assert_eq!(simplified.score, full.score);
}
#[test]
fn identity_cc_0_removes_complexity_penalty() {
let mi = compute_maintainability_index(0.0, 50.0, None).unwrap();
let expected = round2(171.0 - 16.2 * 50.0_f64.ln());
assert!((mi.score - expected).abs() < f64::EPSILON);
}
#[test]
fn reference_small_well_maintained_module() {
let mi = compute_maintainability_index(3.0, 30.0, None).unwrap();
let expected = expected_simplified(3.0, 30.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "A");
}
#[test]
fn reference_small_module_with_halstead() {
let mi = compute_maintainability_index(3.0, 30.0, Some(80.0)).unwrap();
let expected = expected_full(3.0, 30.0, 80.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "A");
}
#[test]
fn reference_moderate_module_simplified() {
let mi = compute_maintainability_index(15.0, 300.0, None).unwrap();
let expected = expected_simplified(15.0, 300.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "B");
}
#[test]
fn reference_moderate_module_with_halstead() {
let mi = compute_maintainability_index(15.0, 300.0, Some(1000.0)).unwrap();
let expected = expected_full(15.0, 300.0, 1000.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "C");
}
#[test]
fn reference_legacy_high_complexity() {
let mi = compute_maintainability_index(50.0, 2000.0, Some(8000.0)).unwrap();
let expected = expected_full(50.0, 2000.0, 8000.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "C");
}
#[test]
fn reference_legacy_simplified_still_low() {
let mi = compute_maintainability_index(80.0, 5000.0, None).unwrap();
let expected = expected_simplified(80.0, 5000.0);
assert!((mi.score - expected).abs() < f64::EPSILON);
assert_eq!(mi.grade, "C");
}
#[test]
fn reference_loc_is_e() {
let e = std::f64::consts::E;
let mi = compute_maintainability_index(10.0, e, None).unwrap();
let loc_rounded = round2(e);
let expected = expected_simplified(10.0, loc_rounded);
assert!((mi.score - expected).abs() < f64::EPSILON);
}
#[test]
fn reference_volume_is_e() {
let e = std::f64::consts::E;
let simplified = compute_maintainability_index(10.0, 100.0, None).unwrap();
let full = compute_maintainability_index(10.0, 100.0, Some(e)).unwrap();
let diff = simplified.score - full.score;
assert!((diff - 5.2).abs() < 0.01);
}
#[test]
fn reference_table_simplified() {
let cases: Vec<(f64, f64, f64, &str)> = vec![
(1.0, 10.0, expected_simplified(1.0, 10.0), "A"),
(5.0, 50.0, expected_simplified(5.0, 50.0), "A"),
(10.0, 100.0, expected_simplified(10.0, 100.0), "A"),
(20.0, 500.0, expected_simplified(20.0, 500.0), "B"),
(50.0, 1000.0, expected_simplified(50.0, 1000.0), "C"),
(100.0, 5000.0, expected_simplified(100.0, 5000.0), "C"),
];
for (cc, loc, expected_score, expected_grade) in cases {
let mi = compute_maintainability_index(cc, loc, None)
.unwrap_or_else(|| panic!("None for CC={cc}, LOC={loc}"));
assert!(
(mi.score - expected_score).abs() < f64::EPSILON,
"CC={cc}, LOC={loc}: expected {expected_score}, got {}",
mi.score
);
assert_eq!(
mi.grade, expected_grade,
"CC={cc}, LOC={loc}: expected grade {expected_grade}, got {}",
mi.grade
);
}
}
#[test]
fn reference_table_full() {
let cases: Vec<(f64, f64, f64, f64, &str)> = vec![
(1.0, 10.0, 20.0, expected_full(1.0, 10.0, 20.0), "A"),
(5.0, 50.0, 100.0, expected_full(5.0, 50.0, 100.0), "B"),
(10.0, 100.0, 200.0, expected_full(10.0, 100.0, 200.0), "B"),
(20.0, 500.0, 2000.0, expected_full(20.0, 500.0, 2000.0), "C"),
(
50.0,
2000.0,
10000.0,
expected_full(50.0, 2000.0, 10000.0),
"C",
),
];
for (cc, loc, vol, expected_score, expected_grade) in cases {
let mi = compute_maintainability_index(cc, loc, Some(vol))
.unwrap_or_else(|| panic!("None for CC={cc}, LOC={loc}, V={vol}"));
assert!(
(mi.score - expected_score).abs() < f64::EPSILON,
"CC={cc}, LOC={loc}, V={vol}: expected {expected_score}, got {}",
mi.score
);
assert_eq!(
mi.grade, expected_grade,
"CC={cc}, LOC={loc}, V={vol}: expected grade {expected_grade}, got {}",
mi.grade
);
}
}
#[test]
fn cc_coefficient_is_0_23() {
let mi1 = compute_maintainability_index(10.0, 100.0, None).unwrap();
let mi2 = compute_maintainability_index(11.0, 100.0, None).unwrap();
let diff = mi1.score - mi2.score;
assert!(
(diff - 0.23).abs() < 0.01,
"CC coefficient: expected ~0.23 drop, got {diff}"
);
}
#[test]
fn halstead_coefficient_is_5_2() {
let e = std::f64::consts::E;
let mi1 = compute_maintainability_index(10.0, 100.0, Some(e)).unwrap();
let mi2 = compute_maintainability_index(10.0, 100.0, Some(e * e)).unwrap();
let diff = mi1.score - mi2.score;
assert!(
(diff - 5.2).abs() < 0.01,
"Halstead coefficient: expected ~5.2 drop, got {diff}"
);
}
#[test]
fn loc_coefficient_is_16_2() {
let e = std::f64::consts::E;
let e_rounded = round2(e);
let e2 = e * e;
let e2_rounded = round2(e2);
let mi1 = compute_maintainability_index(10.0, e, None).unwrap();
let mi2 = compute_maintainability_index(10.0, e2, None).unwrap();
let diff = mi1.score - mi2.score;
let expected_diff = 16.2 * (e2_rounded.ln() - e_rounded.ln());
assert!(
(diff - expected_diff).abs() < 0.1,
"LOC coefficient: expected ~{expected_diff} drop, got {diff}"
);
}