#[cfg(test)]
mod stress_tests {
use debtmap::builders::parallel_unified_analysis::{
ParallelUnifiedAnalysisBuilder, ParallelUnifiedAnalysisOptions,
};
use debtmap::core::FunctionMetrics;
use debtmap::priority::call_graph::{CallGraph, CallType, FunctionId};
use std::path::PathBuf;
use std::time::{Duration, Instant};
fn create_large_codebase_metrics(
num_files: usize,
funcs_per_file: usize,
) -> Vec<FunctionMetrics> {
let mut metrics = Vec::new();
for file_idx in 0..num_files {
let file_path =
PathBuf::from(format!("src/module_{}/file_{}.rs", file_idx / 10, file_idx));
for func_idx in 0..funcs_per_file {
let is_test = func_idx == 0 && file_idx % 10 == 0; let name = if is_test {
format!("test_function_{}", func_idx)
} else {
format!("function_{}_{}", file_idx, func_idx)
};
metrics.push(FunctionMetrics {
file: file_path.clone(),
name,
line: func_idx * 50 + 10,
length: 10 + (func_idx % 40), cyclomatic: 1 + (func_idx % 15) as u32, cognitive: (func_idx % 10) as u32,
nesting: (func_idx % 4) as u32,
is_test,
in_test_module: is_test,
visibility: Some("pub".to_string()),
is_trait_method: func_idx % 20 == 0,
entropy_score: Some(debtmap::complexity::entropy_core::EntropyScore {
token_entropy: 0.5 + (func_idx as f64 * 0.01) % 0.5,
pattern_repetition: 0.2,
branch_similarity: 0.3,
effective_complexity: func_idx as f64 * 0.7,
unique_variables: 5,
max_nesting: 2,
dampening_applied: 0.9,
}),
is_pure: Some(func_idx % 3 != 0),
purity_confidence: Some(if func_idx % 3 != 0 { 0.8 } else { 0.2 }),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_reason: None,
call_dependencies: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
});
}
}
metrics
}
fn create_complex_call_graph(metrics: &[FunctionMetrics]) -> CallGraph {
let mut call_graph = CallGraph::new();
for metric in metrics {
let func_id = FunctionId::new(metric.file.clone(), metric.name.clone(), metric.line);
call_graph.add_function(
func_id,
metric
.visibility
.as_ref()
.map(|v| v == "pub")
.unwrap_or(false),
metric.is_test,
metric.cyclomatic,
metric.length,
);
}
for i in 0..metrics.len() {
let caller = FunctionId::new(
metrics[i].file.clone(),
metrics[i].name.clone(),
metrics[i].line,
);
let num_calls = i % 6; for j in 0..num_calls {
let callee_idx = (i + j + 1) % metrics.len();
let callee = FunctionId::new(
metrics[callee_idx].file.clone(),
metrics[callee_idx].name.clone(),
metrics[callee_idx].line,
);
call_graph.add_call_parts(
caller.clone(),
callee,
if j % 2 == 0 {
CallType::Direct
} else {
CallType::Delegate },
);
}
}
call_graph
}
#[test]
#[ignore] fn stress_test_1000_files() {
let metrics = create_large_codebase_metrics(1000, 10); let call_graph = create_complex_call_graph(&metrics);
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: None, batch_size: 100,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let start = Instant::now();
let (data_flow, _purity, test_funcs, debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
let phase1_time = start.elapsed();
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None,
&Default::default(),
None,
);
let phase2_time = start.elapsed() - phase1_time;
let file_items = builder.execute_phase3_parallel(&metrics, None, false);
let phase3_time = start.elapsed() - phase1_time - phase2_time;
let (unified, _timings) = builder.build(data_flow, _purity, items, file_items, None);
let total_time = start.elapsed();
assert_eq!(
unified.items.len(),
10000,
"Should analyze all 10,000 functions"
);
assert_eq!(
unified.file_items.len(),
1000,
"Should have results for all 1000 files"
);
assert!(
total_time < Duration::from_secs(10),
"1000-file analysis took too long: {:?}",
total_time
);
println!("1000-file stress test results:");
println!(" Total time: {:?}", total_time);
println!(" Phase 1 (initialization): {:?}", phase1_time);
println!(" Phase 2 (function analysis): {:?}", phase2_time);
println!(" Phase 3 (file analysis): {:?}", phase3_time);
println!(" Items analyzed: {}", unified.items.len());
println!(" Files analyzed: {}", unified.file_items.len());
}
#[test]
#[ignore] fn stress_test_5000_files() {
let metrics = create_large_codebase_metrics(5000, 10); let call_graph = create_complex_call_graph(&metrics);
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: None, batch_size: 200, progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let start = Instant::now();
let (data_flow, _purity, test_funcs, debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None,
&Default::default(),
None,
);
let file_items = builder.execute_phase3_parallel(&metrics, None, false);
let (unified, _timings) = builder.build(data_flow, _purity, items, file_items, None);
let total_time = start.elapsed();
assert_eq!(
unified.items.len(),
50000,
"Should analyze all 50,000 functions"
);
assert_eq!(
unified.file_items.len(),
5000,
"Should have results for all 5000 files"
);
assert!(
total_time < Duration::from_secs(60),
"5000-file analysis took too long: {:?}",
total_time
);
println!("5000-file stress test completed in {:?}", total_time);
}
#[test]
#[ignore] fn stress_test_memory_pressure() {
let metrics = create_large_codebase_metrics(2000, 20); let call_graph = create_complex_call_graph(&metrics);
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(2), batch_size: 50, progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let (data_flow, _purity, test_funcs, debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None,
&Default::default(),
None,
);
let file_items = builder.execute_phase3_parallel(&metrics, None, false);
let (unified, _) = builder.build(data_flow, _purity, items, file_items, None);
assert_eq!(unified.items.len(), 40000);
println!("Memory pressure test passed with 40,000 functions");
}
#[test]
#[ignore] fn stress_test_highly_connected_graph() {
let metrics = create_large_codebase_metrics(100, 100); let mut call_graph = CallGraph::new();
for metric in &metrics {
let func_id = FunctionId::new(metric.file.clone(), metric.name.clone(), metric.line);
call_graph.add_function(
func_id,
true,
metric.is_test,
metric.cyclomatic,
metric.length,
);
}
for i in 0..metrics.len() {
let caller = FunctionId::new(
metrics[i].file.clone(),
metrics[i].name.clone(),
metrics[i].line,
);
for j in 1..=20 {
let callee_idx = (i + j * 97) % metrics.len(); let callee = FunctionId::new(
metrics[callee_idx].file.clone(),
metrics[callee_idx].name.clone(),
metrics[callee_idx].line,
);
call_graph.add_call_parts(caller.clone(), callee, CallType::Direct);
}
}
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 100,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let start = Instant::now();
let (data_flow, _purity, test_funcs, debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None,
&Default::default(),
None,
);
let duration = start.elapsed();
assert_eq!(items.len(), 10000);
assert!(
duration < Duration::from_secs(30),
"Highly connected graph analysis took too long: {:?}",
duration
);
println!(
"Highly connected graph (10k nodes, 200k edges) analyzed in {:?}",
duration
);
}
#[test]
#[ignore] fn stress_test_performance_scaling() {
let metrics = create_large_codebase_metrics(500, 20); let call_graph = create_complex_call_graph(&metrics);
let mut results = Vec::new();
for jobs in [1, 2, 4, 8, 16] {
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 100,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph.clone(), options);
let start = Instant::now();
let (data_flow, _purity, test_funcs, debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
let _items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None,
&Default::default(),
None,
);
builder.execute_phase3_parallel(&metrics, None, false);
let duration = start.elapsed();
results.push((jobs, duration));
println!("Jobs: {}, Time: {:?}", jobs, duration);
}
let single_thread_time = results[0].1;
let multi_thread_time = results[2].1;
assert!(
multi_thread_time < single_thread_time,
"Parallel execution should be faster than sequential"
);
let speedup = single_thread_time.as_secs_f64() / multi_thread_time.as_secs_f64();
assert!(
speedup > 2.0,
"Expected at least 2x speedup with 4 threads, got {:.2}x",
speedup
);
}
}