use debtmap::analyzers::rust_call_graph::extract_call_graph;
use std::path::PathBuf;
#[test]
fn test_basic_function_calls_with_resolution() {
let code = r#"
fn main() {
helper();
process_data();
}
fn helper() {
println!("Helper");
}
fn process_data() {
validate();
}
fn validate() {
// Some validation
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
println!("\n=== Functions and their relationships ===");
let functions = call_graph.find_all_functions();
for func in &functions {
let callers = call_graph.get_callers(func);
let callees = call_graph.get_callees(func);
println!("Function: {} (line {})", func.name, func.line);
println!(
" Callers: {:?}",
callers.iter().map(|f| &f.name).collect::<Vec<_>>()
);
println!(
" Callees: {:?}",
callees.iter().map(|f| &f.name).collect::<Vec<_>>()
);
}
assert_eq!(functions.len(), 4, "Should find 4 functions");
let helper = functions
.iter()
.find(|f| f.name == "helper")
.expect("Should find helper function");
let helper_callers = call_graph.get_callers(helper);
assert_eq!(helper_callers.len(), 1, "helper should have 1 caller");
assert_eq!(
helper_callers[0].name, "main",
"helper should be called by main"
);
let main_func = functions
.iter()
.find(|f| f.name == "main")
.expect("Should find main function");
let main_callees = call_graph.get_callees(main_func);
assert_eq!(main_callees.len(), 2, "main should call 2 functions");
assert!(
main_callees.iter().any(|f| f.name == "helper"),
"main should call helper"
);
assert!(
main_callees.iter().any(|f| f.name == "process_data"),
"main should call process_data"
);
}
#[test]
fn test_method_calls_on_self() {
let code = r#"
struct Processor;
impl Processor {
fn process(&self) {
self.validate();
self.transform();
}
fn validate(&self) {
// validation
}
fn transform(&self) {
// transformation
}
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let functions = call_graph.find_all_functions();
println!("\n=== Methods found ===");
for func in &functions {
println!(" {}", func.name);
}
assert!(
functions.iter().any(|f| f.name == "Processor::process"),
"Should find Processor::process"
);
assert!(
functions.iter().any(|f| f.name == "Processor::validate"),
"Should find Processor::validate"
);
assert!(
functions.iter().any(|f| f.name == "Processor::transform"),
"Should find Processor::transform"
);
let process_fn = functions
.iter()
.find(|f| f.name == "Processor::process")
.expect("Should find process method");
let process_callees = call_graph.get_callees(process_fn);
assert_eq!(process_callees.len(), 2, "process should call 2 methods");
assert!(
process_callees
.iter()
.any(|f| f.name == "Processor::validate"),
"Should call validate"
);
assert!(
process_callees
.iter()
.any(|f| f.name == "Processor::transform"),
"Should call transform"
);
}
#[test]
fn test_closure_calls() {
let code = r#"
fn process_items(items: Vec<i32>) -> Vec<i32> {
items.iter()
.map(|x| transform(x))
.filter(|x| validate(x))
.collect()
}
fn transform(value: &i32) -> i32 {
*value * 2
}
fn validate(value: &i32) -> bool {
*value > 0
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let process_fn = call_graph
.find_all_functions()
.into_iter()
.find(|f| f.name == "process_items")
.expect("Should find process_items");
let process_callees = call_graph.get_callees(&process_fn);
println!("\n=== Calls from process_items ===");
for callee in &process_callees {
println!(" {}", callee.name);
}
assert!(
process_callees.iter().any(|f| f.name == "transform"),
"Should detect transform call in closure"
);
assert!(
process_callees.iter().any(|f| f.name == "validate"),
"Should detect validate call in closure"
);
}
#[test]
fn test_async_function_calls() {
let code = r#"
async fn main() {
fetch_data().await;
process_async().await;
}
async fn fetch_data() -> String {
// fetch data
"data".to_string()
}
async fn process_async() {
validate_async().await;
}
async fn validate_async() {
// async validation
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let main_func = call_graph
.find_all_functions()
.into_iter()
.find(|f| f.name == "main")
.expect("Should find main");
let main_callees = call_graph.get_callees(&main_func);
assert_eq!(main_callees.len(), 2, "main should call 2 async functions");
assert!(
main_callees.iter().any(|f| f.name == "fetch_data"),
"Should call fetch_data"
);
assert!(
main_callees.iter().any(|f| f.name == "process_async"),
"Should call process_async"
);
}
#[test]
fn test_associated_function_calls() {
let code = r#"
struct Calculator;
impl Calculator {
fn new() -> Self {
Calculator
}
fn calculate(value: i32) -> i32 {
Self::validate_input(value);
Self::process(value)
}
fn validate_input(value: i32) {
// validation
}
fn process(value: i32) -> i32 {
value * 2
}
}
fn main() {
let calc = Calculator::new();
let result = Calculator::calculate(42);
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let calculate_fn = call_graph
.find_all_functions()
.into_iter()
.find(|f| f.name == "Calculator::calculate")
.expect("Should find calculate function");
let calculate_callees = call_graph.get_callees(&calculate_fn);
println!("\n=== Calls from Calculator::calculate ===");
for callee in &calculate_callees {
println!(" {}", callee.name);
}
assert!(
calculate_callees.len() >= 2,
"calculate should call at least 2 functions"
);
}
#[test]
fn test_trait_impl_calls() {
let code = r#"
trait Handler {
fn handle(&self);
}
struct MyHandler;
impl Handler for MyHandler {
fn handle(&self) {
self.pre_process();
process_internal();
self.post_process();
}
}
impl MyHandler {
fn pre_process(&self) {
// pre-processing
}
fn post_process(&self) {
// post-processing
}
}
fn process_internal() {
// internal processing
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let functions = call_graph.find_all_functions();
let handle_fn = functions
.iter()
.find(|f| f.name.contains("handle"))
.expect("Should find handle method");
let handle_callees = call_graph.get_callees(handle_fn);
println!("\n=== Calls from handle ===");
for callee in &handle_callees {
println!(" {}", callee.name);
}
assert!(
handle_callees.len() >= 2,
"handle should call at least 2 functions"
);
assert!(
handle_callees.iter().any(|f| f.name == "process_internal"),
"Should call process_internal"
);
}
#[test]
fn test_cross_file_resolution_simulation() {
let code = r#"
mod utils {
pub fn helper() {
println!("Helper");
}
}
fn main() {
utils::helper();
process();
}
fn process() {
// processing
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let main_func = call_graph
.find_all_functions()
.into_iter()
.find(|f| f.name == "main")
.expect("Should find main");
let main_callees = call_graph.get_callees(&main_func);
println!("\n=== Calls from main ===");
for callee in &main_callees {
println!(" {}", callee.name);
}
assert!(
main_callees.iter().any(|f| f.name == "process"),
"Should call process"
);
}
#[test]
fn test_generic_function_calls() {
let code = r#"
fn process<T: Clone>(value: T) -> T {
validate(&value);
transform(value)
}
fn validate<T>(value: &T) {
// validation
}
fn transform<T: Clone>(value: T) -> T {
value.clone()
}
fn main() {
process(42);
process("hello");
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let process_fn = call_graph
.find_all_functions()
.into_iter()
.find(|f| f.name == "process")
.expect("Should find process");
let process_callees = call_graph.get_callees(&process_fn);
assert_eq!(
process_callees.len(),
2,
"process should call validate and transform"
);
assert!(
process_callees.iter().any(|f| f.name == "validate"),
"Should call validate"
);
assert!(
process_callees.iter().any(|f| f.name == "transform"),
"Should call transform"
);
}