use super::*;
fn rust_markers() -> &'static CognitiveMarkers {
super::super::markers::cognitive_markers_for("Rust").unwrap()
}
fn python_markers() -> &'static CognitiveMarkers {
super::super::markers::cognitive_markers_for("Python").unwrap()
}
fn c_markers() -> &'static CognitiveMarkers {
super::super::markers::cognitive_markers_for("C").unwrap()
}
fn make_lines(code: &str) -> (Vec<String>, Vec<LineKind>) {
let lines: Vec<String> = code.lines().map(String::from).collect();
let kinds = vec![LineKind::Code; lines.len()];
(lines, kinds)
}
#[test]
fn simple_function_no_branches() {
let (lines, kinds) = make_lines("fn main() {\n let x = 1;\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions.len(), 1);
assert_eq!(result.functions[0].complexity, 0);
assert_eq!(result.functions[0].level, CognitiveLevel::Simple);
}
#[test]
fn simple_if() {
let (lines, kinds) = make_lines("fn foo() {\n if x > 0 {\n bar();\n }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 1);
}
#[test]
fn nested_if_in_if() {
let (lines, kinds) = make_lines(
"fn foo() {\n if x > 0 {\n if y > 0 {\n bar();\n }\n }\n}\n",
);
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn if_else_if_else() {
let (lines, kinds) = make_lines(
"fn foo() {\n if x > 0 {\n a();\n } else if y > 0 {\n b();\n } else {\n c();\n }\n}\n",
);
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn boolean_sequence_same_operator() {
let (lines, kinds) = make_lines("fn foo() {\n if a && b && c {\n bar();\n }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 2);
}
#[test]
fn boolean_sequence_mixed_operators() {
let (lines, kinds) = make_lines("fn foo() {\n if a && b || c {\n bar();\n }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn triple_nesting_if_for_if() {
let (lines, kinds) = make_lines(
"fn foo() {\n if x > 0 {\n for i in items {\n if y > 0 {\n bar();\n }\n }\n }\n}\n",
);
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 6);
}
#[test]
fn keywords_in_strings_not_counted() {
let (lines, kinds) = make_lines(
"fn foo() {\n let kw = [\"if\", \"for\", \"while\", \"match\"];\n let s = \"if x && y || z\";\n}\n",
);
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 0);
}
#[test]
fn python_indent_scoped() {
let code = "def foo():\n if x > 0:\n for i in items:\n bar()\n";
let (lines, kinds) = make_lines(code);
let result = analyze(&lines, &kinds, python_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn python_elif() {
let code = "def foo():\n if x > 0:\n a()\n elif y > 0:\n b()\n else:\n c()\n";
let (lines, kinds) = make_lines(code);
let result = analyze(&lines, &kinds, python_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn two_functions() {
let (lines, kinds) =
make_lines("fn foo() {\n if x > 0 {\n a();\n }\n}\nfn bar() {\n b();\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions.len(), 2);
assert_eq!(result.functions[0].name, "foo");
assert_eq!(result.functions[0].complexity, 1);
assert_eq!(result.functions[1].name, "bar");
assert_eq!(result.functions[1].complexity, 0);
}
#[test]
fn empty_input_returns_none() {
assert!(analyze(&[], &[], rust_markers()).is_none());
}
#[test]
fn all_comments_returns_none() {
let lines = vec!["// comment".to_string()];
let kinds = vec![LineKind::Comment];
assert!(analyze(&lines, &kinds, rust_markers()).is_none());
}
#[test]
fn threshold_boundaries() {
assert_eq!(CognitiveLevel::from_complexity(0), CognitiveLevel::Simple);
assert_eq!(CognitiveLevel::from_complexity(4), CognitiveLevel::Simple);
assert_eq!(CognitiveLevel::from_complexity(5), CognitiveLevel::Moderate);
assert_eq!(CognitiveLevel::from_complexity(9), CognitiveLevel::Moderate);
assert_eq!(CognitiveLevel::from_complexity(10), CognitiveLevel::Complex);
assert_eq!(CognitiveLevel::from_complexity(14), CognitiveLevel::Complex);
assert_eq!(
CognitiveLevel::from_complexity(15),
CognitiveLevel::VeryComplex
);
assert_eq!(
CognitiveLevel::from_complexity(24),
CognitiveLevel::VeryComplex
);
assert_eq!(CognitiveLevel::from_complexity(25), CognitiveLevel::Extreme);
}
#[test]
fn level_display() {
assert_eq!(CognitiveLevel::Simple.as_str(), "simple");
assert_eq!(CognitiveLevel::Moderate.as_str(), "moderate");
assert_eq!(CognitiveLevel::Complex.as_str(), "complex");
assert_eq!(CognitiveLevel::VeryComplex.as_str(), "very complex");
assert_eq!(CognitiveLevel::Extreme.as_str(), "extreme");
}
#[test]
fn level_serde() {
assert_eq!(
serde_json::to_string(&CognitiveLevel::VeryComplex).unwrap(),
"\"very_complex\""
);
}
#[test]
fn file_with_no_functions_uses_implicit() {
let (lines, kinds) = make_lines("let x = 1;\nif true { foo(); }\n");
let markers = super::super::markers::cognitive_markers_for("Haskell").unwrap();
let result = analyze(&lines, &kinds, markers).unwrap();
assert_eq!(result.functions.len(), 1);
assert_eq!(result.functions[0].name, "<file>");
}
#[test]
fn aggregation_stats() {
let (lines, kinds) =
make_lines("fn foo() {\n if x > 0 {\n a();\n }\n}\nfn bar() {\n b();\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.total_complexity, 1); assert_eq!(result.max_complexity, 1);
assert!((result.avg_complexity - 0.5).abs() < 0.01);
}
#[test]
fn c_family_function_detection() {
let (lines, kinds) = make_lines(
"int main(int argc, char *argv[]) {\n if (argc > 1) {\n printf(\"hi\");\n }\n return 0;\n}\n",
);
let result = analyze(&lines, &kinds, c_markers()).unwrap();
assert_eq!(result.functions.len(), 1);
assert_eq!(result.functions[0].name, "main");
assert_eq!(result.functions[0].complexity, 1);
}
#[test]
fn match_with_nested_if() {
let (lines, kinds) = make_lines(
"fn foo() {\n match x {\n 1 => {\n if y > 0 {\n bar();\n }\n }\n _ => {}\n }\n}\n",
);
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 3);
}
#[test]
fn while_loop() {
let (lines, kinds) = make_lines("fn foo() {\n while x > 0 {\n x -= 1;\n }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 1);
}
#[test]
fn loop_keyword() {
let (lines, kinds) = make_lines("fn foo() {\n loop {\n break;\n }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 1);
}
#[test]
fn word_boundary_notify_not_if() {
let (lines, kinds) = make_lines("fn foo() {\n notify();\n ifdef();\n life();\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 0);
}
#[test]
fn boolean_sequence_reset_at_semicolon() {
let (lines, kinds) =
make_lines("fn foo() {\n if a && b { x(); }\n if c || d { y(); }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(result.functions[0].complexity, 4);
}
#[test]
fn closure_inline_not_counted_as_flow_nesting() {
let (lines, kinds) =
make_lines("fn foo() {\n for item in items { process(item, |x| { x + 1 }) }\n}\n");
let result = analyze(&lines, &kinds, rust_markers()).unwrap();
assert_eq!(
result.functions[0].complexity, 1,
"closure brace on same line as for should not add nesting penalty"
);
}