use std::path::PathBuf;
#[test]
#[allow(deprecated)]
fn test_extract_calls_detects_function_calls() {
let source = r#"
fn caller() {
helper();
}
fn helper() {}
"#;
let mut parser = magellan::Parser::new().unwrap();
let symbols = parser.extract_symbols(PathBuf::from("test.rs"), source.as_bytes());
assert_eq!(symbols.len(), 2, "Should have 2 functions");
let functions: Vec<_> = symbols
.iter()
.filter(|s| s.kind == magellan::SymbolKind::Function)
.collect();
assert_eq!(functions.len(), 2);
let calls = parser.extract_calls(PathBuf::from("test.rs"), source.as_bytes(), &symbols);
assert_eq!(calls.len(), 1, "Should have 1 call: caller → helper");
assert_eq!(calls[0].caller, "caller");
assert_eq!(calls[0].callee, "helper");
}
#[test]
#[allow(deprecated)]
fn test_extract_calls_ignores_type_references() {
let source = r#"
fn process(data: Data) {
data.method();
}
struct Data;
impl Data {
fn method(&self) {}
}
"#;
let mut parser = magellan::Parser::new().unwrap();
let symbols = parser.extract_symbols(PathBuf::from("test.rs"), source.as_bytes());
assert!(symbols.len() >= 2);
let calls = parser.extract_calls(PathBuf::from("test.rs"), source.as_bytes(), &symbols);
assert!(
calls.iter().any(|c| c.callee == "method"),
"Should call method"
);
assert!(
!calls.iter().any(|c| c.callee == "Data"),
"Should not call struct name"
);
}
#[test]
#[allow(deprecated)]
fn test_code_graph_stores_and_queries_calls_edges() {
use magellan::CodeGraph;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = r#"
fn main() {
parse();
execute();
}
fn parse() {}
fn execute() {
parse();
}
"#;
graph.index_file("test.rs", source.as_bytes()).unwrap();
let calls_from_main = graph.calls_from_symbol("test.rs", "main").unwrap();
assert_eq!(calls_from_main.len(), 2, "main should call 2 functions");
let called_names: Vec<_> = calls_from_main.iter().map(|c| c.callee.as_str()).collect();
assert!(called_names.contains(&"parse"));
assert!(called_names.contains(&"execute"));
let calls_from_execute = graph.calls_from_symbol("test.rs", "execute").unwrap();
assert_eq!(
calls_from_execute.len(),
1,
"execute should call 1 function"
);
assert_eq!(calls_from_execute[0].callee, "parse");
let calls_to_parse = graph.callers_of_symbol("test.rs", "parse").unwrap();
assert_eq!(
calls_to_parse.len(),
2,
"parse should be called by 2 functions"
);
let caller_names: Vec<_> = calls_to_parse.iter().map(|c| c.caller.as_str()).collect();
assert!(caller_names.contains(&"main"));
assert!(caller_names.contains(&"execute"));
}
#[test]
#[allow(deprecated)]
fn test_cross_file_method_calls_are_indexed() {
use magellan::CodeGraph;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let library_source = r#"
struct Widget;
impl Widget {
fn render(&self) {}
}
"#;
let caller_source = r#"
fn invoke(widget: &Widget) {
widget.render();
}
"#;
graph
.index_file("lib.rs", library_source.as_bytes())
.unwrap();
graph
.index_file("main.rs", caller_source.as_bytes())
.unwrap();
let calls_to_render = graph.callers_of_symbol("lib.rs", "render").unwrap();
assert_eq!(
calls_to_render.len(),
1,
"render should be called from another file"
);
assert_eq!(calls_to_render[0].caller, "invoke");
assert_eq!(calls_to_render[0].callee, "render");
assert_eq!(calls_to_render[0].file_path, PathBuf::from("main.rs"));
}
#[test]
#[allow(deprecated)]
fn test_extract_calls_handles_nested_calls() {
let source = r#"
fn outer() {
inner1();
inner2();
inner1(); // duplicate call
}
fn inner1() {}
fn inner2() {}
"#;
let mut parser = magellan::Parser::new().unwrap();
let symbols = parser.extract_symbols(PathBuf::from("test.rs"), source.as_bytes());
let calls = parser.extract_calls(PathBuf::from("test.rs"), source.as_bytes(), &symbols);
assert_eq!(
calls.len(),
3,
"Should have 3 calls (inner1 twice, inner2 once)"
);
let unique_calls: std::collections::HashSet<_> =
calls.iter().map(|c| (&c.caller, &c.callee)).collect();
assert_eq!(
unique_calls.len(),
2,
"Should have 2 unique caller-callee pairs"
);
}
#[test]
#[allow(deprecated)]
fn test_cross_file_call_resolution() {
use magellan::CodeGraph;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let caller_path = "caller.rs";
let caller_source = r#"
pub fn caller() {
callee();
}
pub fn another_caller() {
callee();
}
"#;
let callee_path = "callee.rs";
let callee_source = r#"
pub fn callee() {
println!("Called from another file");
}
"#;
graph
.index_file(caller_path, caller_source.as_bytes())
.unwrap();
graph
.index_file(callee_path, callee_source.as_bytes())
.unwrap();
graph
.index_calls(caller_path, caller_source.as_bytes())
.unwrap();
graph
.index_calls(callee_path, callee_source.as_bytes())
.unwrap();
let caller_symbols = graph.symbols_in_file(caller_path).unwrap();
assert_eq!(
caller_symbols.len(),
2,
"Should have 2 symbols in caller.rs"
);
let callee_symbols = graph.symbols_in_file(callee_path).unwrap();
assert_eq!(callee_symbols.len(), 1, "Should have 1 symbol in callee.rs");
let _caller_id = graph
.symbol_id_by_name(caller_path, "caller")
.unwrap()
.expect("caller symbol should exist");
let _another_caller_id = graph
.symbol_id_by_name(caller_path, "another_caller")
.unwrap()
.expect("another_caller symbol should exist");
let _callee_id = graph
.symbol_id_by_name(callee_path, "callee")
.unwrap()
.expect("callee symbol should exist");
let calls_from_caller = graph.calls_from_symbol(caller_path, "caller").unwrap();
assert_eq!(
calls_from_caller.len(),
1,
"caller should have 1 outgoing call"
);
assert_eq!(
calls_from_caller[0].callee, "callee",
"caller should call callee"
);
assert_eq!(
calls_from_caller[0].file_path,
std::path::PathBuf::from(caller_path),
"Call should be in caller.rs"
);
let calls_from_another = graph
.calls_from_symbol(caller_path, "another_caller")
.unwrap();
assert_eq!(
calls_from_another.len(),
1,
"another_caller should have 1 outgoing call"
);
assert_eq!(
calls_from_another[0].callee, "callee",
"another_caller should call callee"
);
let calls_to_callee = graph.callers_of_symbol(callee_path, "callee").unwrap();
assert_eq!(
calls_to_callee.len(),
2,
"callee should have 2 incoming calls from both callers"
);
let callers: Vec<_> = calls_to_callee.iter().map(|c| &c.caller).collect();
assert!(
callers.contains(&&"caller".to_string()),
"callee should be called by caller"
);
assert!(
callers.contains(&&"another_caller".to_string()),
"callee should be called by another_caller"
);
for call in &calls_to_callee {
assert_eq!(
call.file_path,
std::path::PathBuf::from(caller_path),
"All calls to callee should originate from caller.rs"
);
}
}
#[test]
#[allow(deprecated)]
fn test_extract_calls_from_tree_matches_extract_calls() {
let source = r#"
fn caller() {
helper();
}
fn helper() {}
"#;
let mut parser = magellan::Parser::new().unwrap();
let symbols = parser.extract_symbols(PathBuf::from("test.rs"), source.as_bytes());
let mut ts_parser = tree_sitter::Parser::new();
ts_parser
.set_language(&tree_sitter_rust::language())
.unwrap();
let tree = ts_parser.parse(source.as_bytes(), None).unwrap();
let calls_parse = parser.extract_calls(PathBuf::from("test.rs"), source.as_bytes(), &symbols);
let calls_tree = magellan::Parser::extract_calls_from_tree(
&tree,
PathBuf::from("test.rs"),
source.as_bytes(),
&symbols,
);
assert_eq!(
calls_parse.len(),
calls_tree.len(),
"Tree-based extraction should produce same number of calls"
);
assert_eq!(calls_parse, calls_tree, "Call facts should be identical");
}