#![cfg_attr(coverage_nightly, coverage(off))]
use super::*;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[test]
fn test_grade_from_score() {
assert_eq!(Grade::from_score(95.0), Grade::APLus);
assert_eq!(Grade::from_score(90.0), Grade::A);
assert_eq!(Grade::from_score(85.0), Grade::AMinus);
assert_eq!(Grade::from_score(80.0), Grade::BPlus);
assert_eq!(Grade::from_score(75.0), Grade::B);
assert_eq!(Grade::from_score(70.0), Grade::BMinus);
assert_eq!(Grade::from_score(65.0), Grade::CPlus);
assert_eq!(Grade::from_score(60.0), Grade::C);
assert_eq!(Grade::from_score(55.0), Grade::CMinus);
assert_eq!(Grade::from_score(50.0), Grade::D);
assert_eq!(Grade::from_score(45.0), Grade::F);
}
#[test]
fn test_grade_from_score_boundaries() {
assert_eq!(Grade::from_score(100.0), Grade::APLus);
assert_eq!(Grade::from_score(94.9), Grade::A);
assert_eq!(Grade::from_score(89.9), Grade::AMinus);
assert_eq!(Grade::from_score(49.9), Grade::F);
assert_eq!(Grade::from_score(0.0), Grade::F);
assert_eq!(Grade::from_score(-10.0), Grade::F);
}
#[test]
fn test_grade_display_all() {
assert_eq!(format!("{}", Grade::APLus), "A+");
assert_eq!(format!("{}", Grade::A), "A");
assert_eq!(format!("{}", Grade::AMinus), "A-");
assert_eq!(format!("{}", Grade::BPlus), "B+");
assert_eq!(format!("{}", Grade::B), "B");
assert_eq!(format!("{}", Grade::BMinus), "B-");
assert_eq!(format!("{}", Grade::CPlus), "C+");
assert_eq!(format!("{}", Grade::C), "C");
assert_eq!(format!("{}", Grade::CMinus), "C-");
assert_eq!(format!("{}", Grade::D), "D");
assert_eq!(format!("{}", Grade::F), "F");
}
#[test]
fn test_grade_default() {
let grade = Grade::default();
assert_eq!(grade, Grade::C);
}
#[test]
fn test_grade_ordering() {
assert!(Grade::APLus < Grade::A);
assert!(Grade::A < Grade::AMinus);
assert!(Grade::AMinus < Grade::BPlus);
assert!(Grade::D < Grade::F);
}
#[test]
fn test_grade_clone_copy() {
let g1 = Grade::APLus;
let g2 = g1;
let g3 = g1;
assert_eq!(g1, g2);
assert_eq!(g1, g3);
}
#[test]
fn test_grade_serialization() {
let grade = Grade::BPlus;
let json = serde_json::to_string(&grade).unwrap();
let deserialized: Grade = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, Grade::BPlus);
}
#[test]
fn test_grade_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Grade::A);
set.insert(Grade::B);
assert!(set.contains(&Grade::A));
assert!(!set.contains(&Grade::F));
}
#[test]
fn test_tdg_score_default() {
let score = TdgScore::default();
assert_eq!(score.structural_complexity, 25.0);
assert_eq!(score.semantic_complexity, 20.0);
assert_eq!(score.duplication_ratio, 20.0);
assert_eq!(score.coupling_score, 15.0);
assert_eq!(score.doc_coverage, 10.0);
assert_eq!(score.consistency_score, 10.0);
assert_eq!(score.entropy_score, 0.0);
assert_eq!(score.total, 100.0);
assert_eq!(score.grade, Grade::APLus);
assert_eq!(score.confidence, 1.0);
assert_eq!(score.language, Language::Unknown);
assert!(score.file_path.is_none());
assert!(score.penalties_applied.is_empty());
assert_eq!(score.critical_defects_count, 0);
assert!(!score.has_critical_defects);
}
#[test]
fn test_tdg_score_calculate_total() {
let mut score = TdgScore {
structural_complexity: 20.0,
semantic_complexity: 18.0,
duplication_ratio: 19.0,
coupling_score: 14.0,
doc_coverage: 9.0,
consistency_score: 8.0,
entropy_score: 12.0, has_contract_coverage: true, ..TdgScore::default()
};
score.calculate_total();
assert_eq!(score.total, 98.0);
assert_eq!(score.grade, Grade::APLus); }
#[test]
fn test_tdg_score_calculate_total_clamping() {
let mut score = TdgScore {
structural_complexity: 50.0, semantic_complexity: 30.0, duplication_ratio: 40.0, coupling_score: 25.0, doc_coverage: 20.0, consistency_score: 15.0, entropy_score: 20.0, ..TdgScore::default()
};
score.calculate_total();
assert_eq!(score.total, 100.0);
}
#[test]
fn test_tdg_score_calculate_total_zero() {
let mut score = TdgScore {
structural_complexity: 0.0,
semantic_complexity: 0.0,
duplication_ratio: 0.0,
coupling_score: 0.0,
doc_coverage: 0.0,
consistency_score: 0.0,
entropy_score: 0.0,
..TdgScore::default()
};
score.calculate_total();
assert_eq!(score.total, 0.0);
assert_eq!(score.grade, Grade::F);
}
#[test]
fn test_tdg_score_critical_defects_autofail() {
let mut score = TdgScore {
structural_complexity: 25.0,
semantic_complexity: 20.0,
duplication_ratio: 20.0,
coupling_score: 15.0,
doc_coverage: 10.0,
consistency_score: 10.0,
has_critical_defects: true,
critical_defects_count: 1,
..TdgScore::default()
};
score.calculate_total();
assert_eq!(score.total, 0.0);
assert_eq!(score.grade, Grade::F);
}
#[test]
fn test_tdg_score_contract_coverage_caps_a_to_aminus() {
let mut score = TdgScore {
structural_complexity: 25.0,
semantic_complexity: 20.0,
duplication_ratio: 20.0,
coupling_score: 15.0,
doc_coverage: 10.0,
consistency_score: 10.0,
has_contract_coverage: false,
..TdgScore::default()
};
score.calculate_total();
assert!(score.total >= 90.0);
assert_eq!(score.grade, Grade::AMinus);
}
#[test]
fn test_tdg_score_contract_coverage_allows_a() {
let mut score = TdgScore {
structural_complexity: 25.0,
semantic_complexity: 20.0,
duplication_ratio: 20.0,
coupling_score: 15.0,
doc_coverage: 10.0,
consistency_score: 10.0,
has_contract_coverage: true,
..TdgScore::default()
};
score.calculate_total();
assert!(score.total >= 95.0);
assert_eq!(score.grade, Grade::APLus);
}
#[test]
fn test_tdg_score_contract_coverage_no_effect_below_aminus() {
let mut score = TdgScore {
structural_complexity: 20.0,
semantic_complexity: 16.0,
duplication_ratio: 16.0,
coupling_score: 12.0,
doc_coverage: 8.0,
consistency_score: 8.0,
has_contract_coverage: false,
..TdgScore::default()
};
score.calculate_total();
assert_eq!(score.grade, Grade::BPlus);
}
#[test]
fn test_tdg_score_set_metric() {
let mut score = TdgScore::default();
score.set_metric(MetricCategory::StructuralComplexity, 15.0);
assert_eq!(score.structural_complexity, 15.0);
score.set_metric(MetricCategory::SemanticComplexity, 12.0);
assert_eq!(score.semantic_complexity, 12.0);
score.set_metric(MetricCategory::Duplication, 18.0);
assert_eq!(score.duplication_ratio, 18.0);
score.set_metric(MetricCategory::Coupling, 10.0);
assert_eq!(score.coupling_score, 10.0);
score.set_metric(MetricCategory::Documentation, 8.0);
assert_eq!(score.doc_coverage, 8.0);
score.set_metric(MetricCategory::Consistency, 7.0);
assert_eq!(score.consistency_score, 7.0);
}
#[test]
fn test_tdg_score_clone() {
let score = TdgScore {
structural_complexity: 20.0,
file_path: Some(PathBuf::from("/test/file.rs")),
..TdgScore::default()
};
let cloned = score.clone();
assert_eq!(cloned.structural_complexity, 20.0);
assert_eq!(cloned.file_path, Some(PathBuf::from("/test/file.rs")));
}
#[test]
fn test_tdg_score_serialization() {
let score = TdgScore {
structural_complexity: 20.0,
total: 85.0,
grade: Grade::AMinus,
..TdgScore::default()
};
let json = serde_json::to_string(&score).unwrap();
let deserialized: TdgScore = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.structural_complexity, 20.0);
assert_eq!(deserialized.total, 85.0);
}
#[test]
fn test_metric_category_clone_copy() {
let cat = MetricCategory::StructuralComplexity;
let cat2 = cat;
assert_eq!(cat, cat2);
}
#[test]
fn test_metric_category_serialization() {
let cat = MetricCategory::Duplication;
let json = serde_json::to_string(&cat).unwrap();
let deserialized: MetricCategory = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, MetricCategory::Duplication);
}
#[test]
fn test_metric_category_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(MetricCategory::StructuralComplexity);
set.insert(MetricCategory::Coupling);
assert!(set.contains(&MetricCategory::StructuralComplexity));
assert!(!set.contains(&MetricCategory::Documentation));
}
#[test]
fn test_metric_category_debug() {
let cat = MetricCategory::SemanticComplexity;
let debug = format!("{:?}", cat);
assert!(debug.contains("SemanticComplexity"));
}
#[test]
fn test_penalty_attribution_creation() {
let penalty = PenaltyAttribution {
source_metric: MetricCategory::StructuralComplexity,
amount: 5.0,
applied_to: HashSet::from([MetricCategory::StructuralComplexity]),
issue: "High complexity".to_string(),
};
assert_eq!(penalty.amount, 5.0);
assert!(penalty
.applied_to
.contains(&MetricCategory::StructuralComplexity));
}
#[test]
fn test_penalty_attribution_clone() {
let penalty = PenaltyAttribution {
source_metric: MetricCategory::Duplication,
amount: 3.0,
applied_to: HashSet::from([MetricCategory::Duplication, MetricCategory::Consistency]),
issue: "Code duplication detected".to_string(),
};
let cloned = penalty.clone();
assert_eq!(cloned.amount, 3.0);
assert_eq!(cloned.applied_to.len(), 2);
}
#[test]
fn test_penalty_attribution_serialization() {
let penalty = PenaltyAttribution {
source_metric: MetricCategory::Documentation,
amount: 2.0,
applied_to: HashSet::from([MetricCategory::Documentation]),
issue: "Missing docs".to_string(),
};
let json = serde_json::to_string(&penalty).unwrap();
let deserialized: PenaltyAttribution = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.amount, 2.0);
}
#[test]
fn test_project_score_default() {
let score = ProjectScore::default();
assert!(score.files.is_empty());
assert_eq!(score.average_score, 0.0);
assert_eq!(score.total_files, 0);
assert!(score.language_distribution.is_empty());
}
#[test]
fn test_project_score_aggregate_empty() {
let score = ProjectScore::aggregate(vec![]);
assert_eq!(score.total_files, 0);
assert_eq!(score.average_score, 0.0);
assert_eq!(score.average_grade, Grade::F);
}
#[test]
fn test_project_score_aggregate_single() {
let tdg_score = TdgScore {
total: 85.0,
language: Language::Rust,
..TdgScore::default()
};
let project = ProjectScore::aggregate(vec![tdg_score]);
assert_eq!(project.total_files, 1);
assert_eq!(project.average_score, 85.0);
assert_eq!(project.average_grade, Grade::AMinus);
assert_eq!(
*project.language_distribution.get(&Language::Rust).unwrap(),
1
);
}
#[test]
fn test_project_score_aggregate_multiple() {
let scores = vec![
TdgScore {
total: 90.0,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 80.0,
language: Language::Python,
..TdgScore::default()
},
TdgScore {
total: 70.0,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(project.total_files, 3);
assert_eq!(project.average_score, 80.0);
assert_eq!(project.average_grade, Grade::BPlus);
assert_eq!(
*project.language_distribution.get(&Language::Rust).unwrap(),
2
);
assert_eq!(
*project
.language_distribution
.get(&Language::Python)
.unwrap(),
1
);
}
#[test]
fn test_project_score_average_empty() {
let project = ProjectScore::default();
let avg = project.average();
assert_eq!(avg.structural_complexity, 25.0); }
#[test]
fn test_project_score_average_single() {
let tdg_score = TdgScore {
structural_complexity: 20.0,
semantic_complexity: 15.0,
language: Language::TypeScript,
..TdgScore::default()
};
let project = ProjectScore {
files: vec![tdg_score],
language_distribution: HashMap::from([(Language::TypeScript, 1)]),
..ProjectScore::default()
};
let avg = project.average();
assert_eq!(avg.structural_complexity, 20.0);
assert_eq!(avg.semantic_complexity, 15.0);
assert_eq!(avg.language, Language::TypeScript);
}
#[test]
fn test_project_score_average_multiple() {
let scores = vec![
TdgScore {
structural_complexity: 20.0,
semantic_complexity: 10.0,
..TdgScore::default()
},
TdgScore {
structural_complexity: 10.0,
semantic_complexity: 20.0,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
let avg = project.average();
assert_eq!(avg.structural_complexity, 15.0);
assert_eq!(avg.semantic_complexity, 15.0);
}
#[test]
fn test_project_score_f_grade_capping() {
let scores = vec![
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 40.0, grade: Grade::F,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(project.f_grade_count, 1);
assert!(project.grade_capped);
assert_eq!(project.average_grade, Grade::B);
}
#[test]
fn test_project_score_no_f_grade_capping() {
let scores = vec![
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 90.0,
grade: Grade::A,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(project.f_grade_count, 0);
assert!(!project.grade_capped);
assert_eq!(project.average_grade, Grade::A);
}
#[test]
fn test_project_score_grade_distribution() {
let scores = vec![
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 85.0,
grade: Grade::AMinus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 75.0,
grade: Grade::B,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(*project.grade_distribution.get(&Grade::APLus).unwrap(), 1);
assert_eq!(*project.grade_distribution.get(&Grade::AMinus).unwrap(), 1);
assert_eq!(*project.grade_distribution.get(&Grade::B).unwrap(), 1);
}
#[test]
fn test_project_score_multiple_f_grades() {
let scores = vec![
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 95.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 30.0, grade: Grade::F,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 40.0, grade: Grade::F,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(project.f_grade_count, 2);
assert_eq!(*project.grade_distribution.get(&Grade::F).unwrap_or(&0), 2);
}
#[test]
fn test_project_score_f_grade_capping_from_a_plus() {
let scores = vec![
TdgScore {
total: 97.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 97.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 97.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 97.0,
grade: Grade::APLus,
language: Language::Rust,
..TdgScore::default()
},
TdgScore {
total: 45.0, grade: Grade::F,
language: Language::Rust,
..TdgScore::default()
},
];
let project = ProjectScore::aggregate(scores);
assert_eq!(project.f_grade_count, 1);
assert!(project.grade_capped);
assert_eq!(project.average_grade, Grade::B);
}
#[test]
fn test_comparison_new_improvement() {
let source1 = TdgScore {
total: 70.0,
structural_complexity: 15.0,
semantic_complexity: 10.0,
file_path: Some(PathBuf::from("source1.rs")),
..TdgScore::default()
};
let source2 = TdgScore {
total: 85.0,
structural_complexity: 20.0,
semantic_complexity: 15.0,
file_path: Some(PathBuf::from("source2.rs")),
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert_eq!(comparison.delta, 15.0);
assert!(comparison.improvement_percentage > 0.0);
assert_eq!(comparison.winner, "source2.rs");
assert!(!comparison.improvements.is_empty());
}
#[test]
fn test_comparison_new_regression() {
let source1 = TdgScore {
total: 85.0,
structural_complexity: 20.0,
doc_coverage: 10.0,
file_path: Some(PathBuf::from("before.rs")),
..TdgScore::default()
};
let source2 = TdgScore {
total: 70.0,
structural_complexity: 15.0,
doc_coverage: 5.0,
file_path: Some(PathBuf::from("after.rs")),
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert_eq!(comparison.delta, -15.0);
assert!(comparison.improvement_percentage < 0.0);
assert_eq!(comparison.winner, "before.rs");
assert!(!comparison.regressions.is_empty());
}
#[test]
fn test_comparison_new_no_path() {
let source1 = TdgScore {
total: 70.0,
..TdgScore::default()
};
let source2 = TdgScore {
total: 80.0,
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert_eq!(comparison.winner, "source2");
}
#[test]
fn test_comparison_zero_source() {
let source1 = TdgScore {
total: 0.0,
..TdgScore::default()
};
let source2 = TdgScore {
total: 50.0,
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert_eq!(comparison.improvement_percentage, 0.0); }
#[test]
fn test_comparison_duplication_improvement() {
let source1 = TdgScore {
duplication_ratio: 10.0,
..TdgScore::default()
};
let source2 = TdgScore {
duplication_ratio: 15.0,
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert!(comparison
.improvements
.iter()
.any(|s| s.contains("duplication")));
}
#[test]
fn test_comparison_serialization() {
let comparison = Comparison::new(TdgScore::default(), TdgScore::default());
let json = serde_json::to_string(&comparison).unwrap();
let deserialized: Comparison = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.delta, 0.0);
}
#[test]
fn test_comparison_missing_branches() {
let source1 = TdgScore {
semantic_complexity: 20.0,
duplication_ratio: 15.0,
doc_coverage: 5.0,
..TdgScore::default()
};
let source2 = TdgScore {
semantic_complexity: 10.0,
duplication_ratio: 8.0,
doc_coverage: 12.0,
..TdgScore::default()
};
let comparison = Comparison::new(source1, source2);
assert!(comparison
.regressions
.iter()
.any(|s| s.contains("Semantic")));
assert!(comparison
.regressions
.iter()
.any(|s| s.contains("duplication")));
assert!(comparison
.improvements
.iter()
.any(|s| s.contains("Documentation")));
}
#[test]
fn test_penalty_tracker() {
let mut tracker = PenaltyTracker::new();
let penalty1 = tracker.apply(
"issue1".to_string(),
MetricCategory::StructuralComplexity,
3.5,
"High cyclomatic complexity".to_string(),
);
assert_eq!(penalty1, Some(3.5));
let penalty2 = tracker.apply(
"issue1".to_string(),
MetricCategory::StructuralComplexity,
3.5,
"High cyclomatic complexity".to_string(),
);
assert_eq!(penalty2, None);
let attributions = tracker.get_attributions();
assert_eq!(attributions.len(), 1);
assert_eq!(attributions[0].amount, 3.5);
}
#[test]
fn test_penalty_tracker_default() {
let tracker = PenaltyTracker::default();
assert!(tracker.get_attributions().is_empty());
}
#[test]
fn test_penalty_tracker_multiple_issues() {
let mut tracker = PenaltyTracker::new();
tracker.apply(
"issue1".to_string(),
MetricCategory::StructuralComplexity,
3.0,
"High complexity".to_string(),
);
tracker.apply(
"issue2".to_string(),
MetricCategory::Duplication,
2.0,
"Code duplication".to_string(),
);
tracker.apply(
"issue3".to_string(),
MetricCategory::Documentation,
1.5,
"Missing docs".to_string(),
);
let attributions = tracker.get_attributions();
assert_eq!(attributions.len(), 3);
}
#[test]
fn test_penalty_tracker_same_category_different_ids() {
let mut tracker = PenaltyTracker::new();
let p1 = tracker.apply(
"complexity-func1".to_string(),
MetricCategory::StructuralComplexity,
2.0,
"func1 too complex".to_string(),
);
let p2 = tracker.apply(
"complexity-func2".to_string(),
MetricCategory::StructuralComplexity,
3.0,
"func2 too complex".to_string(),
);
assert_eq!(p1, Some(2.0));
assert_eq!(p2, Some(3.0));
assert_eq!(tracker.get_attributions().len(), 2);
}