use debtmap::analyzers::rust::RustAnalyzer;
use debtmap::analyzers::Analyzer;
use debtmap::core::{ast::Ast, Language};
use std::path::PathBuf;
#[cfg(test)]
mod rust_analyzer_tests {
use super::*;
#[test]
fn test_rust_analyzer_default() {
let analyzer1 = RustAnalyzer::default();
let analyzer2 = RustAnalyzer::new();
let simple_code = r#"
fn hello() {
println!("Hello, world!");
}
"#;
let ast1 = analyzer1
.parse(simple_code, PathBuf::from("test1.rs"))
.unwrap();
let ast2 = analyzer2
.parse(simple_code, PathBuf::from("test2.rs"))
.unwrap();
assert!(matches!(ast1, Ast::Rust(_)));
assert!(matches!(ast2, Ast::Rust(_)));
}
#[test]
fn test_rust_analyzer_default_complexity_threshold() {
let analyzer = RustAnalyzer::default();
let complex_code = r#"
fn complex_function(x: i32) -> i32 {
if x > 10 {
if x > 20 {
return x * 2;
} else {
return x + 10;
}
} else if x > 5 {
return x - 5;
} else {
return x;
}
}
"#;
let ast = analyzer
.parse(complex_code, PathBuf::from("complex.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
assert_eq!(metrics.language, Language::Rust);
assert!(!metrics.complexity.functions.is_empty());
}
#[test]
fn test_rust_analyzer_default_empty_file() {
let analyzer = RustAnalyzer::default();
let empty_code = "";
let ast = analyzer
.parse(empty_code, PathBuf::from("empty.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
assert_eq!(metrics.language, Language::Rust);
assert!(metrics.complexity.functions.is_empty());
assert_eq!(metrics.complexity.cyclomatic_complexity, 0);
assert_eq!(metrics.complexity.cognitive_complexity, 0);
}
#[test]
fn test_rust_analyzer_default_with_structs() {
let analyzer = RustAnalyzer::default();
let struct_code = r#"
struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: String, age: u32) -> Self {
Self { name, age }
}
fn get_age(&self) -> u32 {
self.age
}
}
"#;
let ast = analyzer
.parse(struct_code, PathBuf::from("struct.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
assert_eq!(metrics.language, Language::Rust);
assert_eq!(metrics.complexity.functions.len(), 2);
assert_eq!(metrics.complexity.functions[0].name, "Person::new");
assert_eq!(metrics.complexity.functions[1].name, "Person::get_age");
}
#[test]
fn test_rust_analyzer_default_with_tests() {
let analyzer = RustAnalyzer::default();
let test_code = r#"
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
}
"#;
let ast = analyzer
.parse(test_code, PathBuf::from("with_tests.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
assert_eq!(metrics.language, Language::Rust);
assert!(metrics.complexity.functions.iter().any(|f| f.name == "add"));
assert!(metrics
.complexity
.functions
.iter()
.any(|f| f.name == "test_add"));
let test_fn = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "test_add")
.unwrap();
assert!(test_fn.is_test, "test_add should be marked as a test");
let add_fn = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "add")
.unwrap();
assert!(!add_fn.is_test, "add should not be marked as a test");
}
#[test]
fn test_rust_analyzer_tokio_test_detection() {
let analyzer = RustAnalyzer::default();
let test_code = r#"
#[tokio::test]
async fn test_async_function() {
assert!(true);
}
#[test]
fn test_regular_test() {
assert!(true);
}
fn test_prefixed_function() {
// Should be detected as test by name
}
fn it_should_work() {
// Should be detected as test by name pattern
}
fn regular_function() {
// Should NOT be detected as test
}
"#;
let ast = analyzer
.parse(test_code, PathBuf::from("tokio_tests.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
let async_test = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "test_async_function")
.unwrap();
assert!(
async_test.is_test,
"tokio::test function should be marked as test"
);
let regular_test = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "test_regular_test")
.unwrap();
assert!(
regular_test.is_test,
"regular test function should be marked as test"
);
let prefixed = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "test_prefixed_function")
.unwrap();
assert!(
prefixed.is_test,
"test_ prefixed function should be marked as test"
);
let it_fn = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "it_should_work")
.unwrap();
assert!(
it_fn.is_test,
"it_ prefixed function should be marked as test"
);
let regular = metrics
.complexity
.functions
.iter()
.find(|f| f.name == "regular_function")
.unwrap();
assert!(
!regular.is_test,
"regular function should not be marked as test"
);
}
#[test]
fn test_rust_analyzer_test_file_detection() {
let analyzer = RustAnalyzer::default();
let code = r#"
fn setup_test_data() {
// Helper function in a test file
}
fn validate_state() {
// Another helper in test file
}
fn test_something() {
setup_test_data();
validate_state();
}
"#;
let test_paths = vec![
"src/tests/mod.rs",
"src/test/helpers.rs",
"src/module_test.rs",
"tests/integration_tests.rs",
"src/test_utils.rs",
];
for path in test_paths {
let ast = analyzer.parse(code, PathBuf::from(path)).unwrap();
let metrics = analyzer.analyze(&ast);
for func in &metrics.complexity.functions {
if func.name.starts_with("test_") {
assert!(
func.is_test,
"test_ prefixed function '{}' in test file '{}' should be marked as test",
func.name, path
);
} else {
assert!(
!func.is_test,
"Helper function '{}' in test file '{}' should not be marked as test",
func.name, path
);
}
}
}
let ast = analyzer.parse(code, PathBuf::from("src/lib.rs")).unwrap();
let metrics = analyzer.analyze(&ast);
for func in &metrics.complexity.functions {
if func.name.starts_with("test_") {
assert!(
func.is_test,
"test_ prefixed function should be marked as test"
);
} else {
assert!(
!func.is_test,
"Function '{}' should not be marked as test",
func.name
);
}
}
}
#[test]
fn test_rust_analyzer_default_with_macros() {
let analyzer = RustAnalyzer::default();
let macro_code = r#"
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
fn use_macro() {
say_hello!();
}
"#;
let ast = analyzer
.parse(macro_code, PathBuf::from("macros.rs"))
.unwrap();
let metrics = analyzer.analyze(&ast);
assert_eq!(metrics.language, Language::Rust);
assert_eq!(metrics.complexity.functions.len(), 1);
assert_eq!(metrics.complexity.functions[0].name, "use_macro");
}
#[test]
fn test_rust_analyzer_default_error_handling() {
let analyzer = RustAnalyzer::default();
let invalid_code = "fn broken(";
let result = analyzer.parse(invalid_code, PathBuf::from("invalid.rs"));
assert!(result.is_err());
}
#[test]
fn test_rust_analyzer_default_multiple_instances() {
let analyzer1 = RustAnalyzer::default();
let analyzer2 = RustAnalyzer::default();
let code1 = "fn func1() { }";
let code2 = "fn func2() { let x = 5; }";
let ast1 = analyzer1.parse(code1, PathBuf::from("file1.rs")).unwrap();
let ast2 = analyzer2.parse(code2, PathBuf::from("file2.rs")).unwrap();
let metrics1 = analyzer1.analyze(&ast1);
let metrics2 = analyzer2.analyze(&ast2);
assert_eq!(metrics1.complexity.functions[0].name, "func1");
assert_eq!(metrics2.complexity.functions[0].name, "func2");
}
}