use magellan::CodeGraph;
use std::fs;
use std::path::Path;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
fn write_and_sync(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
use std::fs::OpenOptions;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
std::io::Write::write_all(&mut file, bytes)?;
file.sync_all()?;
Ok(())
}
fn touch_and_sync(path: &Path) -> std::io::Result<()> {
use std::fs::OpenOptions;
let mut file = OpenOptions::new().append(true).open(path)?;
std::io::Write::write_all(&mut file, b"\n")?;
file.sync_all()?;
Ok(())
}
#[test]
fn test_create_event_indexes_file() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("test.db");
let file_path = root_path.join("test.rs");
thread::sleep(Duration::from_millis(10));
let file_path_clone = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path_clone, b"fn foo() {}\nfn bar() { foo(); }").unwrap();
touch_and_sync(&file_path_clone).unwrap();
});
magellan::run_indexer_n(root_path.clone(), db_path.clone(), 3).unwrap();
let mut graph = CodeGraph::open(&db_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(
symbols.len(),
2,
"Should index 2 symbols after MODIFY: foo, bar"
);
let foo_id = graph.symbol_id_by_name(&path_str, "foo").unwrap();
let foo_id = foo_id.expect("foo symbol should exist");
let references = graph.references_to_symbol(foo_id).unwrap();
assert_eq!(references.len(), 1, "Should index 1 reference to foo");
}
#[test]
fn test_modify_event_reindexes_file() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("test.db");
let file_path = root_path.join("test.rs");
let path_str = file_path.to_string_lossy().to_string();
let file_path_init = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path_init, b"fn foo() {}").unwrap();
touch_and_sync(&file_path_init).unwrap();
});
magellan::run_indexer_n(root_path.clone(), db_path.clone(), 3).unwrap();
thread::sleep(Duration::from_millis(50));
{
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 1, "Initial state: 1 symbol");
}
let file_path_clone = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
let modified_source = b"fn foo() {}\nfn bar() { foo(); }";
fs::write(&file_path_clone, modified_source).unwrap();
});
magellan::run_indexer_n(root_path.clone(), db_path.clone(), 1).unwrap();
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 2, "After modify: should have 2 symbols");
let foo_id = graph.symbol_id_by_name(&path_str, "foo").unwrap();
let foo_id = foo_id.expect("foo symbol should exist");
let references = graph.references_to_symbol(foo_id).unwrap();
assert_eq!(
references.len(),
1,
"Should have 1 reference to foo after re-index"
);
}
#[test]
fn test_delete_event_removes_file_data() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("test.db");
let file_path = root_path.join("test.rs");
let path_str = file_path.to_string_lossy().to_string();
let source = b"fn foo() {}";
let file_path_init = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path_init, source).unwrap();
touch_and_sync(&file_path_init).unwrap();
});
magellan::run_indexer_n(root_path.clone(), db_path.clone(), 3).unwrap();
thread::sleep(Duration::from_millis(50));
{
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 1, "Should have 1 symbol before delete");
}
fs::remove_file(&file_path).unwrap();
magellan::run_indexer_n(root_path.clone(), db_path.clone(), 1).unwrap();
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(
symbols.len(),
0,
"Should have 0 symbols after file deletion"
);
}
#[test]
fn test_multiple_sequential_events_produce_correct_final_state() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("test.db");
let file_path = root_path.join("test.rs");
let path_str = file_path.to_string_lossy().to_string();
thread::sleep(Duration::from_millis(10));
let root_path1 = root_path.clone();
let db_path1 = db_path.clone();
let file_path1 = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path1, b"fn foo() {}").unwrap();
touch_and_sync(&file_path1).unwrap();
});
magellan::run_indexer_n(root_path1, db_path1, 3).unwrap();
thread::sleep(Duration::from_millis(50));
{
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 1, "After initial write: 1 symbol");
}
let root_path2 = root_path.clone();
let db_path2 = db_path.clone();
let file_path2 = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path2, b"fn foo() {}\nfn bar() {}").unwrap();
touch_and_sync(&file_path2).unwrap();
});
magellan::run_indexer_n(root_path2, db_path2, 3).unwrap();
thread::sleep(Duration::from_millis(50));
{
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 2, "After adding bar: 2 symbols");
}
let root_path3 = root_path.clone();
let db_path3 = db_path.clone();
let file_path3 = file_path.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
write_and_sync(&file_path3, b"fn foo() {}\nfn bar() { foo(); }").unwrap();
touch_and_sync(&file_path3).unwrap();
});
magellan::run_indexer_n(root_path3, db_path3, 2).unwrap();
let mut graph = CodeGraph::open(&db_path).unwrap();
let symbols = graph.symbols_in_file(&path_str).unwrap();
assert_eq!(symbols.len(), 2, "After adding reference: still 2 symbols");
let foo_id = graph.symbol_id_by_name(&path_str, "foo").unwrap();
let foo_id = foo_id.expect("foo symbol should exist");
let references = graph.references_to_symbol(foo_id).unwrap();
assert_eq!(references.len(), 1, "Final state: 1 reference to foo");
}