use crate::complexity::generate_complexity_histogram;
use tokmd_analysis_types::{ComplexityRisk, FileComplexity};
fn make_file_complexity(
path: &str,
function_count: usize,
max_fn_len: usize,
cyclomatic: usize,
cognitive: Option<usize>,
max_nesting: Option<usize>,
risk: ComplexityRisk,
) -> FileComplexity {
FileComplexity {
path: path.to_string(),
module: "src".to_string(),
function_count,
max_function_length: max_fn_len,
cyclomatic_complexity: cyclomatic,
cognitive_complexity: cognitive,
max_nesting,
risk_level: risk,
functions: None,
}
}
#[test]
fn histogram_empty_files() {
let hist = generate_complexity_histogram(&[], 5);
assert_eq!(hist.total, 0);
assert_eq!(hist.counts.len(), 7);
assert!(hist.counts.iter().all(|c| *c == 0));
}
#[test]
fn histogram_single_low_complexity_file() {
let files = vec![make_file_complexity(
"a.rs",
1,
10,
2,
None,
None,
ComplexityRisk::Low,
)];
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.total, 1);
assert_eq!(hist.counts[0], 1); assert_eq!(hist.counts[1], 0);
}
#[test]
fn histogram_single_high_complexity_file() {
let files = vec![make_file_complexity(
"a.rs",
10,
100,
35,
Some(50),
Some(6),
ComplexityRisk::High,
)];
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.total, 1);
assert_eq!(hist.counts[6], 1);
}
#[test]
fn histogram_distributes_across_buckets() {
let files = vec![
make_file_complexity("a.rs", 1, 5, 2, None, None, ComplexityRisk::Low),
make_file_complexity("b.rs", 3, 15, 7, None, None, ComplexityRisk::Low),
make_file_complexity("c.rs", 5, 30, 12, None, None, ComplexityRisk::Moderate),
make_file_complexity("d.rs", 8, 50, 18, None, None, ComplexityRisk::Moderate),
make_file_complexity("e.rs", 10, 80, 25, None, None, ComplexityRisk::High),
make_file_complexity(
"f.rs",
15,
100,
45,
Some(80),
Some(7),
ComplexityRisk::Critical,
),
];
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.total, 6);
assert_eq!(hist.counts[0], 1); assert_eq!(hist.counts[1], 1); assert_eq!(hist.counts[2], 1); assert_eq!(hist.counts[3], 1); assert_eq!(hist.counts[5], 1); assert_eq!(hist.counts[6], 1); }
#[test]
fn histogram_bucket_boundaries() {
let hist = generate_complexity_histogram(&[], 5);
assert_eq!(hist.buckets, vec![0, 5, 10, 15, 20, 25, 30]);
}
#[test]
fn histogram_all_in_first_bucket() {
let files: Vec<FileComplexity> = (0..5)
.map(|i| make_file_complexity(&format!("{i}.rs"), 1, 5, i, None, None, ComplexityRisk::Low))
.collect();
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.counts[0], 5);
assert_eq!(hist.counts[1..].iter().sum::<u32>(), 0);
}
#[test]
fn histogram_all_in_last_bucket() {
let files: Vec<FileComplexity> = (0..3)
.map(|i| {
make_file_complexity(
&format!("{i}.rs"),
20,
100,
50 + i,
None,
None,
ComplexityRisk::Critical,
)
})
.collect();
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.counts[6], 3);
assert_eq!(hist.counts[..6].iter().sum::<u32>(), 0);
}
#[test]
fn histogram_deterministic() {
let files = vec![
make_file_complexity("a.rs", 2, 10, 3, None, None, ComplexityRisk::Low),
make_file_complexity("b.rs", 5, 30, 15, None, None, ComplexityRisk::Moderate),
];
let a = generate_complexity_histogram(&files, 5);
let b = generate_complexity_histogram(&files, 5);
assert_eq!(a.buckets, b.buckets);
assert_eq!(a.counts, b.counts);
assert_eq!(a.total, b.total);
}
#[test]
fn file_complexity_low_risk() {
let fc = make_file_complexity("a.rs", 2, 10, 3, Some(5), Some(2), ComplexityRisk::Low);
assert_eq!(fc.risk_level, ComplexityRisk::Low);
assert_eq!(fc.function_count, 2);
assert_eq!(fc.cyclomatic_complexity, 3);
}
#[test]
fn file_complexity_moderate_risk() {
let fc = make_file_complexity(
"b.rs",
15,
40,
15,
Some(30),
Some(4),
ComplexityRisk::Moderate,
);
assert_eq!(fc.risk_level, ComplexityRisk::Moderate);
}
#[test]
fn file_complexity_high_risk() {
let fc = make_file_complexity("c.rs", 30, 80, 35, Some(60), Some(6), ComplexityRisk::High);
assert_eq!(fc.risk_level, ComplexityRisk::High);
assert_eq!(fc.cognitive_complexity, Some(60));
assert_eq!(fc.max_nesting, Some(6));
}
#[test]
fn file_complexity_critical_risk() {
let fc = make_file_complexity(
"d.rs",
60,
150,
80,
Some(120),
Some(10),
ComplexityRisk::Critical,
);
assert_eq!(fc.risk_level, ComplexityRisk::Critical);
}
#[test]
fn file_complexity_without_cognitive() {
let fc = make_file_complexity("e.rs", 5, 20, 10, None, None, ComplexityRisk::Low);
assert!(fc.cognitive_complexity.is_none());
assert!(fc.max_nesting.is_none());
}
#[test]
fn zero_function_file() {
let fc = make_file_complexity("empty.rs", 0, 0, 1, None, None, ComplexityRisk::Low);
assert_eq!(fc.function_count, 0);
assert_eq!(fc.max_function_length, 0);
assert_eq!(fc.cyclomatic_complexity, 1); }
#[test]
fn histogram_with_zero_complexity_files() {
let files = vec![
make_file_complexity("a.rs", 0, 0, 0, None, None, ComplexityRisk::Low),
make_file_complexity("b.rs", 0, 0, 0, None, None, ComplexityRisk::Low),
];
let hist = generate_complexity_histogram(&files, 5);
assert_eq!(hist.counts[0], 2); assert_eq!(hist.total, 2);
}
#[test]
fn file_complexities_sort_by_cyclomatic_desc() {
let mut files = [
make_file_complexity("a.rs", 1, 5, 3, None, None, ComplexityRisk::Low),
make_file_complexity("c.rs", 5, 30, 20, None, None, ComplexityRisk::Moderate),
make_file_complexity("b.rs", 3, 15, 10, None, None, ComplexityRisk::Low),
];
files.sort_by(|a, b| {
b.cyclomatic_complexity
.cmp(&a.cyclomatic_complexity)
.then_with(|| a.path.cmp(&b.path))
});
assert_eq!(files[0].path, "c.rs");
assert_eq!(files[1].path, "b.rs");
assert_eq!(files[2].path, "a.rs");
}
#[test]
fn file_complexities_sort_stable_on_tie() {
let mut files = [
make_file_complexity("b.rs", 2, 10, 5, None, None, ComplexityRisk::Low),
make_file_complexity("a.rs", 2, 10, 5, None, None, ComplexityRisk::Low),
];
files.sort_by(|a, b| {
b.cyclomatic_complexity
.cmp(&a.cyclomatic_complexity)
.then_with(|| a.path.cmp(&b.path))
});
assert_eq!(files[0].path, "a.rs");
assert_eq!(files[1].path, "b.rs");
}