#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod accurate_complexity_tests {
use crate::services::accurate_complexity_analyzer::AccurateComplexityAnalyzer;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_cyclomatic_complexity_calculation() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn complex_function(x: i32) -> i32 {
if x > 0 { // +1
if x > 10 { // +1
20
} else {
10
}
} else if x < -10 { // +1
match x { // +1 for match
-20 => 1,
-30 => 2,
_ => 3,
}
} else {
for i in 0..5 { // +1
println!("{}", i);
}
0
}
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(result.functions.len(), 1);
assert_eq!(
result.functions[0].cyclomatic_complexity, 6,
"Expected cyclomatic complexity of 6 (1 base + 5 decision points: if, nested if, else if, match, for loop)"
);
}
#[tokio::test]
async fn test_cognitive_complexity_calculation() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn nested_function(x: i32) -> i32 {
if x > 0 { // +1 (nesting 0)
for i in 0..x { // +2 (nesting 1)
if i % 2 == 0 { // +3 (nesting 2)
if i > 5 { // +4 (nesting 3)
return i;
}
}
}
}
0
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(result.functions.len(), 1);
assert!(
result.functions[0].cognitive_complexity >= 4,
"Cognitive complexity should be ≥4 for nested function with 4 control structures, got: {}",
result.functions[0].cognitive_complexity
);
}
#[tokio::test]
async fn test_simple_function_low_complexity() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn simple_add(a: i32, b: i32) -> i32 {
a + b
}
fn simple_multiply(x: i32) -> i32 {
x * 2
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(result.functions.len(), 2);
for func in &result.functions {
assert_eq!(
func.cyclomatic_complexity, 1,
"Simple functions should have complexity of 1"
);
assert_eq!(
func.cognitive_complexity, 0,
"Simple functions should have cognitive complexity of 0"
);
}
}
#[tokio::test]
async fn test_exclude_test_files() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("lib_test.rs");
fs::write(
&test_file,
r#"
#[test]
fn test_something() {
// Complex test code
for i in 0..10 {
if i > 5 {
assert!(true);
}
}
}
"#,
)
.unwrap();
let src_file = temp_dir.path().join("lib.rs");
fs::write(
&src_file,
r#"
/// Actual function.
pub fn actual_function() -> i32 {
42
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new().exclude_tests(true);
let project_result = analyzer.analyze_project(temp_dir.path()).await.unwrap();
assert_eq!(project_result.files_analyzed, 1, "Should exclude test file");
assert!(project_result
.file_metrics
.iter()
.any(|f| f.file_path.ends_with("lib.rs")));
assert!(!project_result
.file_metrics
.iter()
.any(|f| f.file_path.ends_with("lib_test.rs")));
}
#[tokio::test]
async fn test_annotation_support() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
#[allow(complex_function)]
fn intentionally_complex(x: i32) -> i32 {
// Very complex function that should be ignored
let mut result = 0;
for i in 0..x {
if i % 2 == 0 {
for j in 0..i {
if j % 3 == 0 {
result += j;
}
}
}
}
result
}
fn normal_complex(x: i32) -> i32 {
// This one should be reported
let mut result = 0;
for i in 0..x {
if i % 2 == 0 {
result += i;
}
}
result
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new().respect_annotations(true);
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(result.functions.len(), 2);
let annotated = result
.functions
.iter()
.find(|f| f.name.contains("intentionally_complex"))
.unwrap();
assert!(
annotated.suppressed,
"Annotated function should be marked as suppressed"
);
let normal = result
.functions
.iter()
.find(|f| f.name.contains("normal_complex"))
.unwrap();
assert!(
!normal.suppressed,
"Normal function should not be suppressed"
);
}
#[tokio::test]
async fn test_match_complexity() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn match_function(x: Option<i32>) -> i32 {
match x { // +1 for match
Some(n) if n > 0 => n * 2, // +1 for guard
Some(n) if n < 0 => -n, // +1 for guard
Some(0) => 0,
None => -1,
}
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(
result.functions[0].cyclomatic_complexity, 4,
"Match with 2 guards should have complexity 4: base(1) + match(1) + 2 guards(2) = 4"
);
}
#[tokio::test]
async fn test_boolean_operator_complexity() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn boolean_logic(a: bool, b: bool, c: bool) -> bool {
if a && b || c { // +3 (if + && + ||)
true
} else {
false
}
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(
result.functions[0].cyclomatic_complexity, 4,
"Boolean operators should add to complexity: base(1) + if(1) + &&(1) + ||(1) = 4"
);
}
#[tokio::test]
async fn test_question_mark_operator() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn try_function() -> Result<i32, String> {
let x = some_operation()?; // +1
let y = another_op()?; // +1
Ok(x + y)
}
fn some_operation() -> Result<i32, String> {
Ok(42)
}
fn another_op() -> Result<i32, String> {
Ok(10)
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
let try_fn = result
.functions
.iter()
.find(|f| f.name.contains("try_function"))
.unwrap();
assert_eq!(
try_fn.cyclomatic_complexity, 3,
"? operator should add 1 to complexity per use"
);
}
#[tokio::test]
async fn test_loop_complexity() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn loop_function(mut x: i32) -> i32 {
while x > 0 { // +1
x -= 1;
}
loop { // +1
if x < -10 { // +1
break;
}
x -= 1;
}
x
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert_eq!(
result.functions[0].cyclomatic_complexity, 4,
"while and loop should each add 1 to complexity"
);
}
#[tokio::test]
async fn test_recursion_cognitive_complexity() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn factorial(n: u32) -> u32 {
if n <= 1 {
1
} else {
n * factorial(n - 1) // Recursive call adds cognitive weight
}
}
"#,
)
.unwrap();
let analyzer = AccurateComplexityAnalyzer::new();
let result = analyzer.analyze_file(&test_file).await.unwrap();
assert!(
result.functions[0].cognitive_complexity >= 2,
"Recursive functions should have cognitive complexity ≥2 (if + recursion), got: {}",
result.functions[0].cognitive_complexity
);
}
}