use crate::maintainability::{attach_halstead_metrics, compute_maintainability_index};
use tokmd_analysis_types::{
ComplexityReport, ComplexityRisk, FileComplexity, HalsteadMetrics, TechnicalDebtLevel,
TechnicalDebtRatio,
};
#[test]
fn nan_loc_returns_none() {
let result = compute_maintainability_index(10.0, f64::NAN, None);
if let Some(mi) = result {
assert!(mi.score.is_nan() || mi.score >= 0.0);
}
}
#[test]
fn nan_cc_produces_result() {
let result = compute_maintainability_index(f64::NAN, 100.0, None);
if let Some(mi) = result {
assert!(mi.score.is_nan() || mi.score >= 0.0);
}
}
#[test]
fn nan_halstead_volume_falls_back_to_simplified() {
let simplified = compute_maintainability_index(10.0, 100.0, None).unwrap();
let with_nan = compute_maintainability_index(10.0, 100.0, Some(f64::NAN)).unwrap();
assert_eq!(simplified.score, with_nan.score);
assert_eq!(with_nan.avg_halstead_volume, None);
}
#[test]
fn positive_infinity_loc_returns_score_zero() {
let mi = compute_maintainability_index(10.0, f64::INFINITY, None).unwrap();
assert_eq!(mi.score, 0.0);
assert_eq!(mi.grade, "C");
}
#[test]
fn negative_infinity_loc_returns_none() {
assert!(compute_maintainability_index(10.0, f64::NEG_INFINITY, None).is_none());
}
#[test]
fn positive_infinity_cc_returns_score_zero() {
let mi = compute_maintainability_index(f64::INFINITY, 100.0, None).unwrap();
assert_eq!(mi.score, 0.0);
assert_eq!(mi.grade, "C");
}
#[test]
fn positive_infinity_halstead_volume_returns_score_zero() {
let mi = compute_maintainability_index(10.0, 100.0, Some(f64::INFINITY)).unwrap();
assert_eq!(mi.score, 0.0);
assert_eq!(mi.grade, "C");
}
#[test]
fn negative_infinity_halstead_volume_falls_back_to_simplified() {
let simplified = compute_maintainability_index(10.0, 100.0, None).unwrap();
let with_neg_inf = compute_maintainability_index(10.0, 100.0, Some(f64::NEG_INFINITY)).unwrap();
assert_eq!(simplified.score, with_neg_inf.score);
}
#[test]
fn subnormal_loc_produces_result() {
let tiny = f64::MIN_POSITIVE; let mi = compute_maintainability_index(0.0, tiny, None).unwrap();
assert!(mi.score >= 0.0);
}
#[test]
fn subnormal_halstead_volume_used_when_positive() {
let tiny = f64::MIN_POSITIVE;
let mi = compute_maintainability_index(10.0, 100.0, Some(tiny)).unwrap();
let simplified = compute_maintainability_index(10.0, 100.0, None).unwrap();
assert!(mi.score > simplified.score);
assert_eq!(mi.avg_halstead_volume, Some(tiny));
}
#[test]
fn loc_epsilon_above_zero() {
let mi = compute_maintainability_index(0.0, f64::EPSILON, None).unwrap();
assert!(mi.score >= 0.0);
}
#[test]
fn loc_one_simplified() {
let mi = compute_maintainability_index(0.0, 1.0, None).unwrap();
assert_eq!(mi.score, 171.0);
}
#[test]
fn loc_just_below_zero_returns_none() {
assert!(compute_maintainability_index(0.0, -f64::EPSILON, None).is_none());
}
#[test]
fn score_never_negative_with_extreme_cc_and_loc() {
let mi = compute_maintainability_index(1_000_000.0, 1e15, None).unwrap();
assert_eq!(mi.score, 0.0);
}
#[test]
fn score_never_negative_with_extreme_halstead() {
let mi = compute_maintainability_index(0.0, 1.0, Some(1e300)).unwrap();
assert_eq!(mi.score, 0.0);
}
#[test]
fn score_clamped_at_zero_gets_grade_c() {
let mi = compute_maintainability_index(10000.0, 1e10, Some(1e10)).unwrap();
assert_eq!(mi.score, 0.0);
assert_eq!(mi.grade, "C");
}
#[test]
fn loc_rounding_exact_half_rounds_up() {
let mi = compute_maintainability_index(10.0, 50.005, None).unwrap();
assert_eq!(mi.avg_loc, 50.01);
}
#[test]
fn loc_rounding_preserves_integer() {
let mi = compute_maintainability_index(10.0, 100.0, None).unwrap();
assert_eq!(mi.avg_loc, 100.0);
}
#[test]
fn score_rounding_preserves_two_decimals() {
for loc in [1.0, 7.3, 42.7, 99.99, 500.123] {
let mi = compute_maintainability_index(5.0, loc, None).unwrap();
let scaled = mi.score * 100.0;
let diff = (scaled - scaled.round()).abs();
assert!(
diff < 1e-8,
"LOC={loc}: score {} not rounded to 2 decimals",
mi.score
);
}
}
fn make_halstead(volume: f64) -> HalsteadMetrics {
HalsteadMetrics {
distinct_operators: 10,
distinct_operands: 20,
total_operators: 60,
total_operands: 120,
vocabulary: 30,
length: 180,
volume,
difficulty: 5.0,
effort: 500.0,
time_seconds: 27.78,
estimated_bugs: 0.05,
}
}
fn sample_complexity() -> ComplexityReport {
ComplexityReport {
total_functions: 3,
avg_function_length: 10.0,
max_function_length: 20,
avg_cyclomatic: 10.0,
max_cyclomatic: 18,
avg_cognitive: None,
max_cognitive: None,
avg_nesting_depth: None,
max_nesting_depth: None,
high_risk_files: 0,
histogram: None,
halstead: None,
maintainability_index: compute_maintainability_index(10.0, 100.0, None),
technical_debt: Some(TechnicalDebtRatio {
ratio: 10.0,
complexity_points: 10,
code_kloc: 1.0,
level: TechnicalDebtLevel::Low,
}),
files: vec![FileComplexity {
path: "src/lib.rs".to_string(),
module: "src".to_string(),
function_count: 3,
max_function_length: 20,
cyclomatic_complexity: 18,
cognitive_complexity: None,
max_nesting: None,
risk_level: ComplexityRisk::Low,
functions: None,
}],
}
}
#[test]
fn attach_halstead_nan_volume_preserves_mi() {
let mut report = sample_complexity();
let before = report.maintainability_index.as_ref().unwrap().score;
attach_halstead_metrics(&mut report, make_halstead(f64::NAN));
let after = report.maintainability_index.as_ref().unwrap().score;
assert_eq!(before, after);
assert!(report.halstead.is_some());
}
#[test]
fn attach_halstead_infinity_volume_recomputes_to_zero() {
let mut report = sample_complexity();
attach_halstead_metrics(&mut report, make_halstead(f64::INFINITY));
let mi = report.maintainability_index.as_ref().unwrap();
assert_eq!(mi.score, 0.0);
assert_eq!(mi.grade, "C");
}
#[test]
fn attach_halstead_multiple_times_idempotent_with_same_volume() {
let mut report = sample_complexity();
attach_halstead_metrics(&mut report, make_halstead(200.0));
let first_score = report.maintainability_index.as_ref().unwrap().score;
attach_halstead_metrics(&mut report, make_halstead(200.0));
let second_score = report.maintainability_index.as_ref().unwrap().score;
assert_eq!(first_score, second_score);
}
#[test]
fn attach_halstead_then_detach_mi_and_reattach() {
let mut report = sample_complexity();
attach_halstead_metrics(&mut report, make_halstead(200.0));
assert!(report.halstead.is_some());
report.maintainability_index = None;
attach_halstead_metrics(&mut report, make_halstead(500.0));
assert!(report.maintainability_index.is_none());
assert_eq!(report.halstead.as_ref().unwrap().volume, 500.0);
}
#[test]
fn attach_halstead_decreasing_volumes_increase_mi() {
let mut report1 = sample_complexity();
attach_halstead_metrics(&mut report1, make_halstead(1000.0));
let score_high_vol = report1.maintainability_index.as_ref().unwrap().score;
let mut report2 = sample_complexity();
attach_halstead_metrics(&mut report2, make_halstead(100.0));
let score_low_vol = report2.maintainability_index.as_ref().unwrap().score;
assert!(
score_low_vol > score_high_vol,
"lower volume should yield higher MI"
);
}