#![cfg(feature = "geometric-backend")]
use std::path::PathBuf;
fn create_test_fixture(temp_dir: &std::path::Path) -> PathBuf {
let src_dir = temp_dir.join("src");
std::fs::create_dir(&src_dir).unwrap();
std::fs::write(
src_dir.join("lib.rs"),
r#"
pub fn add(a: i32, b: i32) -> i32 {
internal_add(a, b)
}
fn internal_add(x: i32, y: i32) -> i32 {
x + y
}
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
pub fn add(&self, other: &Point) -> Point {
Point::new(add(self.x, other.x), add(self.y, other.y))
}
}
"#,
)
.unwrap();
std::fs::write(
src_dir.join("main.rs"),
r#"
use test_fixture::add;
fn main() {
let result = compute(1, 2);
println!("{}", result);
helper_a();
helper_b();
}
fn compute(a: i32, b: i32) -> i32 {
let intermediate = step1(a);
step2(intermediate, b)
}
fn step1(x: i32) -> i32 {
x * 2
}
fn step2(x: i32, y: i32) -> i32 {
add(x, y)
}
fn helper_a() {
println!("A");
nested();
}
fn helper_b() {
println!("B");
}
fn nested() {
println!("nested");
}
// Mutual recursion cycle
fn is_even(n: u32) -> bool {
if n == 0 { true } else { is_odd(n - 1) }
}
fn is_odd(n: u32) -> bool {
if n == 0 { false } else { is_even(n - 1) }
}
"#,
)
.unwrap();
src_dir
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_condense_command_works() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_condense.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
let indexed =
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
assert!(indexed > 0, "Should have indexed at least one file");
backend.save_to_disk().unwrap();
let result = backend.condense_call_graph();
assert!(!result.supernodes.is_empty(), "Should have supernodes");
let total_members: usize = result.supernodes.iter().map(|s| s.len()).sum();
assert!(total_members > 0, "Should have symbols in supernodes");
assert_eq!(
result.node_to_supernode.len(),
total_members,
"Each symbol should be mapped to exactly one supernode"
);
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_condense_returns_meaningful_supernodes() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_condense.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
let result = backend.condense_call_graph();
let cycle_supernode = result.supernodes.iter().find(|s| s.len() > 1);
if let Some(cycle) = cycle_supernode {
assert!(
cycle.len() >= 2,
"Cycle supernode should have at least 2 members"
);
}
for (from, to) in &result.edges {
assert!(
*from < result.supernodes.len(),
"Edge from should reference valid supernode"
);
assert!(
*to < result.supernodes.len(),
"Edge to should reference valid supernode"
);
}
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_paths_command_works() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_paths.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
let all_symbols = backend.get_all_symbols().unwrap();
let main_sym = all_symbols
.iter()
.find(|s| s.name == "main" && s.file_path.ends_with("main.rs"))
.expect("Should find main function");
let result = backend.enumerate_paths(main_sym.id, None, 10, 100);
assert!(
result.total_enumerated > 0 || !result.paths.is_empty(),
"Should enumerate at least some paths or edges from main"
);
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_paths_finds_real_path() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_paths.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
let all_symbols = backend.get_all_symbols().unwrap();
let main_sym = all_symbols
.iter()
.find(|s| s.name == "main" && s.file_path.ends_with("main.rs"))
.expect("Should find main");
let compute_sym = all_symbols
.iter()
.find(|s| s.name == "compute")
.expect("Should find compute");
let result = backend.enumerate_paths(main_sym.id, Some(compute_sym.id), 5, 10);
assert!(
!result.paths.is_empty(),
"Should find path from main to compute. Symbols: {:?}",
all_symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
);
let first_path = &result.paths[0];
assert_eq!(first_path[0], main_sym.id, "Path should start with main");
assert_eq!(
first_path[first_path.len() - 1],
compute_sym.id,
"Path should end with compute"
);
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_paths_respects_bounds() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_paths.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
let all_symbols = backend.get_all_symbols().unwrap();
let main_sym = all_symbols
.iter()
.find(|s| s.name == "main" && s.file_path.ends_with("main.rs"))
.expect("Should find main");
let step2_sym = all_symbols
.iter()
.find(|s| s.name == "step2")
.expect("Should find step2");
let result_shallow = backend.enumerate_paths(main_sym.id, Some(step2_sym.id), 1, 10);
assert!(
result_shallow.paths.is_empty() || result_shallow.bounded_hit,
"Should not find path with insufficient depth"
);
let result_deep = backend.enumerate_paths(main_sym.id, Some(step2_sym.id), 5, 10);
assert!(
!result_deep.paths.is_empty() || result_deep.total_enumerated > 0,
"Should find or attempt path with sufficient depth"
);
let result_limited = backend.enumerate_paths(main_sym.id, None, 10, 2);
assert!(
result_limited.paths.len() <= 2,
"Should respect max_paths limit"
);
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_condense_and_paths_survive_reopen() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_reopen.geo");
let src_dir = create_test_fixture(temp_dir.path());
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
{
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
}
{
let backend = GeometricBackend::open(&db_path).unwrap();
let result = backend.condense_call_graph();
assert!(
!result.supernodes.is_empty(),
"Condense should work after reopen"
);
let all_symbols = backend.get_all_symbols().unwrap();
let main_sym = all_symbols
.iter()
.find(|s| s.name == "main" && s.file_path.ends_with("main.rs"))
.expect("Should find main after reopen");
let path_result = backend.enumerate_paths(main_sym.id, None, 5, 10);
let _ = path_result.total_enumerated; }
}
#[test]
#[cfg(feature = "geometric-backend")]
fn geometric_paths_handles_cycles() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test_cycles.geo");
let src_dir = temp_dir.path().join("src");
std::fs::create_dir(&src_dir).unwrap();
std::fs::write(
src_dir.join("main.rs"),
r#"
fn main() {
is_even(5);
}
fn is_even(n: u32) -> bool {
if n == 0 { true } else { is_odd(n - 1) }
}
fn is_odd(n: u32) -> bool {
if n == 0 { false } else { is_even(n - 1) }
}
"#,
)
.unwrap();
use magellan::graph::geo_index::{scan_directory_with_progress, IndexingMode};
use magellan::graph::geometric_backend::GeometricBackend;
let mut backend = GeometricBackend::create(&db_path).unwrap();
scan_directory_with_progress(&mut backend, &src_dir, None, IndexingMode::CfgFirst).unwrap();
backend.save_to_disk().unwrap();
let all_symbols = backend.get_all_symbols().unwrap();
let is_even_sym = all_symbols
.iter()
.find(|s| s.name == "is_even")
.expect("Should find is_even");
let result = backend.enumerate_paths(is_even_sym.id, None, 20, 50);
assert!(
result.total_enumerated < 1000,
"Should not enumerate excessively due to cycle detection. Got: {}",
result.total_enumerated
);
}