use magellan::CodeGraph;
use std::collections::BTreeSet;
use std::fs;
use std::path::PathBuf;
use std::sync::mpsc::sync_channel;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use tempfile::TempDir;
fn with_deadlock_timeout<F, T>(duration: Duration, f: F) -> Result<T, &'static str>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let handle = thread::spawn(f);
let start = Instant::now();
while !handle.is_finished() {
if start.elapsed() >= duration {
return Err("DEADLOCK");
}
std::thread::sleep(Duration::from_millis(100));
}
handle.join().map_err(|_| "PANIC")
}
#[test]
fn stress_concurrent_creates() {
with_deadlock_timeout(Duration::from_secs(30), || {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let start = Instant::now();
let handles: Vec<_> = (0..100)
.map(|i| {
let temp_dir = temp_dir.path().to_path_buf();
thread::spawn(move || {
let file_path = temp_dir.join(format!("file_{}.rs", i));
let content = format!("fn function_{}() {{}}", i);
fs::write(&file_path, content).unwrap();
file_path
})
})
.collect();
let mut file_paths = Vec::new();
for handle in handles {
file_paths.push(handle.join().unwrap());
}
let create_duration = start.elapsed();
println!(
"stress_concurrent_creates: file creation completed in {:?}",
create_duration
);
let mut graph = CodeGraph::open(&db_path).unwrap();
for file_path in &file_paths {
let path_key = magellan::validation::normalize_path(file_path)
.unwrap_or_else(|_| file_path.to_string_lossy().to_string());
let _ = graph.reconcile_file_path(file_path, &path_key);
}
let total_duration = start.elapsed();
println!(
"stress_concurrent_creates: total completed in {:?}",
total_duration
);
let file_count = graph.count_files().unwrap();
assert_eq!(
file_count, 100,
"Expected 100 files after concurrent creates, got {}",
file_count
);
let file_nodes = graph.all_file_nodes().unwrap();
assert_eq!(
file_nodes.len(),
100,
"Expected 100 file nodes, got {}",
file_nodes.len()
);
let mut paths: Vec<_> = file_nodes.keys().collect();
paths.sort();
assert_eq!(
paths.len(),
100,
"Expected 100 unique paths, got {} (possible duplicates)",
paths.len()
);
})
.expect("Test should complete without deadlock");
}
#[test]
fn stress_concurrent_modifies() {
with_deadlock_timeout(Duration::from_secs(30), || {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let file_path = temp_dir.path().join("shared.rs");
fs::write(&file_path, "fn initial() {}").unwrap();
let start = Instant::now();
let handles: Vec<_> = (0..50)
.map(|i| {
let file_path = file_path.clone();
thread::spawn(move || {
let content = format!("fn function_{}() {{}}", i);
fs::write(&file_path, content).unwrap();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let modify_duration = start.elapsed();
println!(
"stress_concurrent_modifies: modifications completed in {:?}",
modify_duration
);
let mut graph = CodeGraph::open(&db_path).unwrap();
let path_key = magellan::validation::normalize_path(&file_path)
.unwrap_or_else(|_| file_path.to_string_lossy().to_string());
let _ = graph.reconcile_file_path(&file_path, &path_key);
let total_duration = start.elapsed();
println!(
"stress_concurrent_modifies: total completed in {:?}",
total_duration
);
let file_count = graph.count_files().unwrap();
assert_eq!(
file_count, 1,
"Expected 1 file after concurrent modifies, got {}",
file_count
);
let file_nodes = graph.all_file_nodes().unwrap();
assert_eq!(
file_nodes.len(),
1,
"Expected 1 file node, got {} (possible corruption)",
file_nodes.len()
);
let symbols = graph.symbols_in_file(&path_key).unwrap();
assert_eq!(
symbols.len(),
1,
"Expected 1 symbol, got {} (possible corruption)",
symbols.len()
);
})
.expect("Test should complete without deadlock");
}
#[test]
fn stress_mixed_operations() {
with_deadlock_timeout(Duration::from_secs(30), || {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let start = Instant::now();
let create_handles: Vec<_> = (0..10)
.map(|thread_id| {
let temp_dir = temp_dir.path().to_path_buf();
thread::spawn(move || {
for i in 0..10 {
let file_id = thread_id * 10 + i;
let file_path = temp_dir.join(format!("file_{}.rs", file_id));
let content = format!("fn function_{}() {{}}", file_id);
fs::write(&file_path, content).unwrap();
}
})
})
.collect();
for handle in create_handles {
handle.join().unwrap();
}
let create_duration = start.elapsed();
println!(
"stress_mixed_operations: file creation completed in {:?}",
create_duration
);
let mixed_handles: Vec<_> = (0..10)
.map(|thread_id| {
let temp_dir = temp_dir.path().to_path_buf();
thread::spawn(move || {
for op_id in 0..10 {
let file_id = thread_id * 10 + op_id;
let file_path = temp_dir.join(format!("file_{}.rs", file_id));
match op_id % 4 {
0 | 1 | 3 => {
if file_path.exists() {
let content =
format!("fn modified_{}_{}() {{}}", thread_id, op_id);
fs::write(&file_path, content).unwrap();
}
}
2 => {
if file_path.exists() {
fs::remove_file(&file_path).unwrap();
}
}
_ => unreachable!(),
}
}
})
})
.collect();
for handle in mixed_handles {
handle.join().unwrap();
}
let op_duration = start.elapsed();
println!(
"stress_mixed_operations: mixed operations completed in {:?}",
op_duration
);
let mut graph = CodeGraph::open(&db_path).unwrap();
for entry in fs::read_dir(temp_dir.path()).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rs") {
let path_key = magellan::validation::normalize_path(&path)
.unwrap_or_else(|_| path.to_string_lossy().to_string());
let _ = graph.reconcile_file_path(&path, &path_key);
}
}
let total_duration = start.elapsed();
println!(
"stress_mixed_operations: total completed in {:?}",
total_duration
);
let graph_file_count = graph.count_files().unwrap();
assert!(
(70..=80).contains(&graph_file_count),
"Expected 70-80 files after mixed operations, got {}",
graph_file_count
);
let file_nodes = graph.all_file_nodes().unwrap();
assert_eq!(
file_nodes.len(),
graph_file_count,
"File count mismatch: count_files()={}, all_file_nodes()={}",
graph_file_count,
file_nodes.len()
);
})
.expect("Test should complete without deadlock");
}
#[test]
fn stress_pipeline_shared_state() {
with_deadlock_timeout(Duration::from_secs(30), || {
let dirty_paths: Arc<Mutex<BTreeSet<PathBuf>>> = Arc::new(Mutex::new(BTreeSet::new()));
let (wakeup_tx, _wakeup_rx) = sync_channel(1);
let start = Instant::now();
let handles: Vec<_> = (0..10)
.map(|thread_id| {
let dirty_paths = dirty_paths.clone();
let wakeup_tx = wakeup_tx.clone();
thread::spawn(move || {
for i in 0..100 {
let path = PathBuf::from(format!("/tmp/file_{}_{}.rs", thread_id, i));
{
let mut paths = dirty_paths.lock().unwrap();
paths.insert(path);
}
let _ = wakeup_tx.try_send(());
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let duration = start.elapsed();
println!("stress_pipeline_shared_state: completed in {:?}", duration);
let mut paths = dirty_paths.lock().unwrap();
let drained: Vec<_> = paths.iter().cloned().collect();
paths.clear();
assert_eq!(
drained.len(),
1000,
"Expected 1000 paths after concurrent inserts, got {}",
drained.len()
);
let unique: std::collections::HashSet<_> = drained.into_iter().collect();
assert_eq!(
unique.len(),
1000,
"Expected 1000 unique paths, got {}",
unique.len()
);
})
.expect("Test should complete without deadlock");
}
#[test]
#[ignore = "pre-existing: exceeds 60s timeout in CI, needs performance investigation"]
fn stress_database_integrity() {
with_deadlock_timeout(Duration::from_secs(120), || {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let start = Instant::now();
let handles: Vec<_> = (0..25)
.map(|thread_id| {
let temp_dir = temp_dir.path().to_path_buf();
thread::spawn(move || {
for i in 0..20 {
let file_id = thread_id * 20 + i;
let file_path = temp_dir.join(format!("file_{:04}.rs", file_id));
let content = format!("fn function_{}() {{}}", file_id);
fs::write(&file_path, content).unwrap();
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let create_duration = start.elapsed();
println!(
"stress_database_integrity: created 500 files in {:?}",
create_duration
);
let mut graph = CodeGraph::open(&db_path).unwrap();
let mut indexed_count = 0;
for entry in fs::read_dir(temp_dir.path()).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rs") {
let path_key = magellan::validation::normalize_path(&path)
.unwrap_or_else(|_| path.to_string_lossy().to_string());
let _ = graph.reconcile_file_path(&path, &path_key);
indexed_count += 1;
}
}
let total_duration = start.elapsed();
println!(
"stress_database_integrity: indexed {} files in {:?}",
indexed_count, total_duration
);
let file_count = graph.count_files().unwrap();
assert_eq!(file_count, 500, "Expected 500 files, got {}", file_count);
let file_nodes = graph.all_file_nodes().unwrap();
let mut total_symbols = 0;
for _file_node in file_nodes.values() {
total_symbols += 1; }
assert!(
total_symbols >= file_count,
"Symbol count {} < file count {} (data corruption)",
total_symbols,
file_count
);
assert_eq!(
file_nodes.len(),
file_count,
"File count mismatch: count_files()={}, all_file_nodes()={}",
file_count,
file_nodes.len()
);
let mut paths: Vec<_> = file_nodes.keys().collect();
paths.sort();
let unique_paths: std::collections::HashSet<_> = paths.into_iter().collect();
assert_eq!(
unique_paths.len(),
file_count,
"Expected {} unique paths, got {} (duplicates detected)",
file_count,
unique_paths.len()
);
println!(
"stress_database_integrity: verified integrity for {} files, {} symbols",
file_count, total_symbols
);
})
.expect("Test should complete without deadlock");
}
#[test]
fn stress_symbol_consistency() {
with_deadlock_timeout(Duration::from_secs(30), || {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let start = Instant::now();
let file_count = 100;
for i in 0..file_count {
let file_path = temp_dir.path().join(format!("test_{:03}.rs", i));
let content = format!("fn unique_function_{}() {{}}", i);
fs::write(&file_path, content).unwrap();
}
let create_duration = start.elapsed();
println!(
"stress_symbol_consistency: created {} files in {:?}",
file_count, create_duration
);
let handles: Vec<_> = (0..20)
.map(|thread_id| {
let temp_dir = temp_dir.path().to_path_buf();
thread::spawn(move || {
for i in (0..file_count).skip(5).step_by(5) {
let file_path = temp_dir.join(format!("test_{:03}.rs", i));
if file_path.exists() {
let content = format!("fn modified_by_thread_{}() {{}}", thread_id);
fs::write(&file_path, content).unwrap();
}
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let modify_duration = start.elapsed();
println!(
"stress_symbol_consistency: modifications completed in {:?}",
modify_duration
);
let mut graph = CodeGraph::open(&db_path).unwrap();
for i in 0..file_count {
let file_path = temp_dir.path().join(format!("test_{:03}.rs", i));
let path_key = magellan::validation::normalize_path(&file_path)
.unwrap_or_else(|_| file_path.to_string_lossy().to_string());
let _ = graph.reconcile_file_path(&file_path, &path_key);
}
let total_duration = start.elapsed();
println!(
"stress_symbol_consistency: total completed in {:?}",
total_duration
);
let graph_file_count = graph.count_files().unwrap();
assert_eq!(
graph_file_count, file_count,
"Expected {} files, got {}",
file_count, graph_file_count
);
let sample_files: Vec<_> = (0..file_count).step_by(10).collect();
for i in &sample_files {
let file_path = temp_dir.path().join(format!("test_{:03}.rs", i));
let path_key = magellan::validation::normalize_path(&file_path)
.unwrap_or_else(|_| file_path.to_string_lossy().to_string());
let content = fs::read_to_string(&file_path).unwrap();
let symbols = graph.symbols_in_file(&path_key).unwrap();
assert!(
!symbols.is_empty(),
"File {} has no symbols (data corruption)",
i
);
for symbol in &symbols {
if let Some(ref symbol_name) = symbol.name {
assert!(
content.contains(symbol_name),
"Symbol '{}' not found in file {} content (cross-file contamination?)",
symbol_name,
i
);
}
}
}
println!(
"stress_symbol_consistency: verified {} files with no cross-file contamination",
sample_files.len()
);
})
.expect("Test should complete without deadlock");
}