#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::services::ast_rust::RustComplexityVisitor;
use syn::{parse_str, Visit};
use proptest::prelude::*;
#[test]
fn test_base_complexity_is_correct() {
let code = r#"
pub fn simple() -> i32 {
42
}
"#;
let ast = syn::parse_file(code).unwrap();
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
assert_eq!(visitor.functions.len(), 1);
let func = &visitor.functions[0];
assert_eq!(func.name, "simple");
assert_eq!(func.metrics.cyclomatic, 1);
assert_eq!(func.metrics.cognitive, 0);
}
#[test]
fn test_single_if_complexity() {
let code = r#"
pub fn with_if(x: i32) -> i32 {
if x > 0 {
x
} else {
0
}
}
"#;
let ast = syn::parse_file(code).unwrap();
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
assert_eq!(visitor.functions.len(), 1);
let func = &visitor.functions[0];
assert_eq!(func.name, "with_if");
assert!(func.metrics.cyclomatic >= 2, "Cyclomatic should be at least 2, got {}", func.metrics.cyclomatic);
assert!(func.metrics.cognitive >= 1, "Cognitive should be at least 1, got {}", func.metrics.cognitive);
}
#[test]
fn test_match_complexity() {
let code = r#"
pub fn with_match(x: i32) -> &'static str {
match x {
1 => "one",
2 => "two",
_ => "other",
}
}
"#;
let ast = syn::parse_file(code).unwrap();
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
assert_eq!(visitor.functions.len(), 1);
let func = &visitor.functions[0];
assert_eq!(func.name, "with_match");
assert!(func.metrics.cyclomatic >= 4, "Cyclomatic should be at least 4 for match with 3 arms, got {}", func.metrics.cyclomatic);
}
#[test]
fn test_no_cross_function_contamination() {
let code = r#"
pub fn first() -> i32 { 42 }
pub fn second(x: i32) -> i32 {
if x > 0 { x } else { 0 }
}
pub fn third() -> i32 { 84 }
"#;
let ast = syn::parse_file(code).unwrap();
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
assert_eq!(visitor.functions.len(), 3);
let first = &visitor.functions[0];
assert_eq!(first.name, "first");
assert_eq!(first.metrics.cyclomatic, 1);
assert_eq!(first.metrics.cognitive, 0);
let third = &visitor.functions[2];
assert_eq!(third.name, "third");
assert_eq!(third.metrics.cyclomatic, 1);
assert_eq!(third.metrics.cognitive, 0);
let second = &visitor.functions[1];
assert_eq!(second.name, "second");
assert!(second.metrics.cyclomatic >= 2);
assert!(second.metrics.cognitive >= 1);
}
proptest! {
#[test]
fn complexity_never_panics(
func_name in "[a-z][a-z0-9_]{0,20}",
return_type in "i32|String|bool",
) {
let code = format!(r#"
pub fn {}() -> {} {{
Default::default()
}}
"#, func_name, return_type);
if let Ok(ast) = syn::parse_file(&code) {
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
prop_assert!(visitor.functions.len() <= 1);
if !visitor.functions.is_empty() {
let func = &visitor.functions[0];
prop_assert!(func.metrics.cyclomatic >= 1);
prop_assert!(func.metrics.cognitive < 100); }
}
}
#[test]
fn cyclomatic_complexity_is_never_zero(
code in generate_valid_rust_function(),
) {
if let Ok(ast) = syn::parse_file(&code) {
let mut visitor = RustComplexityVisitor::new();
visitor.visit_file(&ast);
for func in &visitor.functions {
prop_assert!(func.metrics.cyclomatic >= 1,
"Cyclomatic complexity should never be zero for function {}", func.name);
}
}
}
}
fn generate_valid_rust_function() -> impl Strategy<Value = String> {
prop::collection::vec("[a-z][a-z0-9_]{0,10}", 1..=3)
.prop_map(|names| {
names.into_iter()
.map(|name| format!("pub fn {}() -> i32 {{ 42 }}", name))
.collect::<Vec<_>>()
.join("\n")
})
}
}
pub fn example_usage() {}