use debtmap::builders::parallel_unified_analysis::{
ParallelUnifiedAnalysisBuilder, ParallelUnifiedAnalysisOptions,
};
use debtmap::core::FunctionMetrics;
use debtmap::priority::call_graph::CallGraph;
use std::path::PathBuf;
fn create_test_metrics(count: usize) -> Vec<FunctionMetrics> {
(0..count)
.map(|i| FunctionMetrics {
file: PathBuf::from(format!("test{}.rs", i / 10)),
name: format!("function_{}", i),
line: i * 10,
length: 20 + (i % 30), cyclomatic: ((i % 10) as u32 + 5), cognitive: ((i % 5) as u32 + 3), nesting: (i % 3) as u32 + 1, is_test: i % 20 == 0,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(i % 3 == 0),
purity_confidence: if i % 3 == 0 { Some(0.9) } else { Some(0.1) },
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
})
.collect()
}
#[test]
fn test_parallel_unified_analysis_execution() {
let metrics = create_test_metrics(100);
let call_graph = CallGraph::new();
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 25,
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);
assert!(!purity.is_empty());
assert_eq!(purity.len(), 100);
let pure_count = purity.values().filter(|&&v| v).count();
assert!(pure_count > 0);
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None, &Default::default(), None, );
assert!(!items.is_empty());
let file_items = builder.execute_phase3_parallel(&metrics, None, false);
let (_unified, timings) = builder.build(data_flow, purity, items, file_items, None);
assert!(timings.total >= std::time::Duration::from_secs(0));
assert!(timings.data_flow_creation >= std::time::Duration::from_secs(0));
assert!(timings.purity_analysis >= std::time::Duration::from_secs(0));
}
#[test]
fn test_parallel_analysis_determinism() {
let mut metrics = create_test_metrics(50);
let dup_file = PathBuf::from("test0.rs");
let dup_line = 10;
metrics.push(FunctionMetrics {
file: dup_file.clone(),
name: "function_dup_1".to_string(),
line: dup_line,
length: 20,
cyclomatic: 15,
cognitive: 10,
nesting: 2,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.9),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
});
metrics.push(FunctionMetrics {
file: dup_file.clone(),
name: "function_dup_2".to_string(),
line: dup_line,
length: 20,
cyclomatic: 15,
cognitive: 10,
nesting: 2,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.9),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
});
let call_graph = CallGraph::new();
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 10,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut results = Vec::new();
for _ in 0..5 {
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph.clone(), options.clone());
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 item_data: Vec<_> = unified
.items
.iter()
.map(|item| {
(
item.location.file.clone(),
item.location.function.clone(),
item.location.line,
item.unified_score.final_score,
)
})
.collect();
results.push(item_data);
}
let first_result = &results[0];
for (i, result) in results.iter().enumerate().skip(1) {
assert_eq!(
first_result.len(),
result.len(),
"Result {} has different item count",
i
);
for (j, (item1, item2)) in first_result.iter().zip(result.iter()).enumerate() {
assert_eq!(
item1, item2,
"Result {}, Item {} differs: {:?} vs {:?}",
i, j, item1, item2
);
}
}
let dup1_present = first_result
.iter()
.any(|(_, name, _, _)| name == "function_dup_1");
let dup2_present = first_result
.iter()
.any(|(_, name, _, _)| name == "function_dup_2");
assert!(dup1_present, "function_dup_1 should be present in results");
assert!(dup2_present, "function_dup_2 should be present in results");
}
#[test]
fn test_optimized_test_detector() {
use debtmap::builders::parallel_unified_analysis::OptimizedTestDetector;
use debtmap::priority::call_graph::FunctionId;
use std::sync::Arc;
let mut call_graph = CallGraph::new();
let test_func = FunctionId::new(
PathBuf::from("tests/test.rs"),
"test_something".to_string(),
10,
);
let helper_func = FunctionId::new(PathBuf::from("src/lib.rs"), "helper".to_string(), 20);
let main_func = FunctionId::new(PathBuf::from("src/main.rs"), "main".to_string(), 5);
call_graph.add_function(test_func.clone(), false, true, 5, 20);
call_graph.add_function(helper_func.clone(), false, false, 3, 15);
call_graph.add_function(main_func.clone(), true, false, 10, 50);
call_graph.add_call_parts(
test_func.clone(),
helper_func.clone(),
debtmap::priority::call_graph::CallType::Direct,
);
let detector = OptimizedTestDetector::new(Arc::new(call_graph));
assert!(detector.is_test_only(&test_func));
assert!(detector.is_test_only(&helper_func)); assert!(!detector.is_test_only(&main_func));
let all_test_only = detector.find_all_test_only_functions();
assert!(all_test_only.contains(&test_func));
assert!(all_test_only.contains(&helper_func));
assert!(!all_test_only.contains(&main_func));
}
#[test]
fn test_parallel_vs_sequential_consistency() {
use debtmap::builders::unified_analysis;
let metrics = create_test_metrics(50);
let call_graph = CallGraph::new();
let sequential_result = unified_analysis::create_unified_analysis_with_exclusions(
&metrics,
&call_graph,
None,
&Default::default(),
None,
None,
false,
None,
None,
false,
chrono::Utc::now(),
);
std::env::set_var("DEBTMAP_PARALLEL", "true");
let parallel_result = unified_analysis::create_unified_analysis_with_exclusions(
&metrics,
&call_graph,
None,
&Default::default(),
None,
None,
false,
None,
None,
false,
chrono::Utc::now(),
);
std::env::remove_var("DEBTMAP_PARALLEL");
assert_eq!(sequential_result.items.len(), parallel_result.items.len());
assert_eq!(
sequential_result.file_items.len(),
parallel_result.file_items.len()
);
}
#[test]
#[ignore] fn test_large_codebase_parallel_analysis() {
use std::time::{Duration, Instant};
let metrics = create_test_metrics(500);
let mut call_graph = CallGraph::new();
for metric in &metrics {
let func_id = debtmap::priority::call_graph::FunctionId::new(
metric.file.clone(),
metric.name.clone(),
metric.line,
);
call_graph.add_function(
func_id,
false,
metric.is_test,
metric.cyclomatic,
metric.length,
);
}
for i in 0..metrics.len() - 1 {
if i % 5 == 0 {
let caller = debtmap::priority::call_graph::FunctionId::new(
metrics[i].file.clone(),
metrics[i].name.clone(),
metrics[i].line,
);
let callee = debtmap::priority::call_graph::FunctionId::new(
metrics[i + 1].file.clone(),
metrics[i + 1].name.clone(),
metrics[i + 1].line,
);
call_graph.add_call_parts(
caller,
callee,
debtmap::priority::call_graph::CallType::Direct,
);
}
}
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 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 duration = start.elapsed();
assert!(timings.total > Duration::from_secs(0));
assert!(
duration.as_secs() < 25,
"Large codebase analysis took too long: {:?}",
duration
);
assert!(timings.total > std::time::Duration::from_secs(0));
assert_eq!(
timings.total,
timings.data_flow_creation
+ timings.purity_analysis
+ timings.test_detection
+ timings.debt_aggregation
+ timings.function_analysis
+ timings.file_analysis
+ timings.aggregation
+ timings.sorting
);
}
#[test]
fn test_parallel_analysis_different_batch_sizes() {
let metrics = create_test_metrics(200);
let call_graph = CallGraph::new();
for batch_size in [10, 50, 100, 200] {
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph.clone(), 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,
);
assert!(!items.is_empty(), "Should have processed some items");
}
}
#[test]
fn test_parallel_analysis_with_coverage_data() {
use debtmap::core::{DebtItem, DebtType, Priority};
let metrics = create_test_metrics(100);
let call_graph = CallGraph::new();
let debt_items: Vec<DebtItem> = metrics
.iter()
.filter(|m| m.cyclomatic > 5) .map(|m| DebtItem {
id: format!("debt_{}", m.name),
debt_type: DebtType::Complexity {
cyclomatic: m.cyclomatic,
cognitive: m.cognitive,
},
priority: Priority::Medium,
file: m.file.clone(),
line: m.line,
column: None,
message: format!("High complexity: {}", m.cyclomatic),
context: Some(m.name.clone()),
})
.collect();
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 25,
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, Some(&debt_items));
let items = builder.execute_phase2_parallel(
&metrics,
&test_funcs,
&debt_agg,
&data_flow,
None, &Default::default(),
None,
);
assert!(!items.is_empty());
let high_complexity_count = metrics.iter().filter(|m| m.cyclomatic > 5).count();
assert!(
high_complexity_count > 0,
"Should have some high complexity functions"
);
}
#[test]
fn test_parallel_analysis_memory_efficiency() {
let metrics = create_test_metrics(1000);
let call_graph = CallGraph::new();
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,
);
assert!(!items.is_empty(), "Should have processed some items");
}
#[test]
fn test_data_flow_graph_population_integration() {
let metrics = create_test_metrics(50);
let call_graph = CallGraph::new();
let options = ParallelUnifiedAnalysisOptions::default();
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let (data_flow, _purity, _test_funcs, _debt_agg) =
builder.execute_phase1_parallel(&metrics, None);
assert_eq!(
data_flow.call_graph().get_all_functions().count(),
0, "DataFlowGraph call graph should match initialized state"
);
}
#[test]
fn test_god_objects_created_in_parallel_analysis() {
use std::fs::write;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let god_file_path = temp_dir.path().join("god_object.rs");
let content = "pub struct GodStruct {\n".to_string()
+ &(0..100)
.map(|i| format!(" field_{}: i32,\n", i))
.collect::<String>()
+ "}\n\nimpl GodStruct {\n"
+ &(0..60)
.map(|i| format!(" pub fn method_{}(&self) {{ }}\n", i))
.collect::<String>()
+ "}";
write(&god_file_path, content).unwrap();
let metrics: Vec<FunctionMetrics> = (0..60)
.map(|i| FunctionMetrics {
file: god_file_path.clone(),
name: format!("method_{}", i),
line: i * 10 + 100,
length: 5,
cyclomatic: 2,
cognitive: 1,
nesting: 1,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.1),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
})
.collect();
let call_graph = CallGraph::new();
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 function_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, function_items, file_items, None);
let god_items: Vec<_> = unified
.items
.iter()
.filter(|item| item.god_object_indicators.is_some())
.collect();
assert!(
!god_items.is_empty(),
"God objects should be created as UnifiedDebtItems in parallel analysis"
);
for god_item in &god_items {
let indicators = god_item.god_object_indicators.as_ref().unwrap();
assert!(
indicators.is_god_object,
"God object indicator should be true"
);
assert!(
indicators.method_count > 0 || indicators.field_count > 0,
"God object should have methods or fields"
);
if let Some(tier) = god_item.tier {
assert!(
matches!(
tier,
debtmap::priority::RecommendationTier::T1CriticalArchitecture
| debtmap::priority::RecommendationTier::T2ComplexUntested
),
"God objects should be classified as T1 or T2, got {:?}",
tier
);
}
}
let file_god_objects: Vec<_> = unified
.file_items
.iter()
.filter(|item| {
item.metrics
.god_object_analysis
.as_ref()
.is_some_and(|a| a.is_god_object)
})
.collect();
assert!(
!file_god_objects.is_empty(),
"God objects should also be in file_items"
);
}
#[test]
fn test_god_objects_not_created_when_disabled() {
use std::fs::write;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
write(&file_path, "pub struct Test {}").unwrap();
let metrics: Vec<FunctionMetrics> = (0..60)
.map(|i| FunctionMetrics {
file: file_path.clone(),
name: format!("method_{}", i),
line: i * 10,
length: 5,
cyclomatic: 2,
cognitive: 1,
nesting: 1,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.1),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
})
.collect();
let call_graph = CallGraph::new();
let options = ParallelUnifiedAnalysisOptions::default();
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let (data_flow, purity, test_funcs, debt_agg) = builder.execute_phase1_parallel(&metrics, None);
let function_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, true);
let (unified, _) = builder.build(data_flow, purity, function_items, file_items, None);
let god_items: Vec<_> = unified
.items
.iter()
.filter(|item| item.god_object_indicators.is_some())
.collect();
assert!(
god_items.is_empty(),
"God objects should not be created when no_god_object flag is true"
);
}
#[test]
fn test_god_objects_visible_in_tui() {
use debtmap::tui::results::app::ResultsApp;
use std::fs::write;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let god_file_path = temp_dir.path().join("god.rs");
let content = "pub struct God { }\nimpl God {\n".to_string()
+ &(0..60)
.map(|i| format!(" pub fn method_{}(&self) {{ }}\n", i))
.collect::<String>()
+ "}";
write(&god_file_path, content).unwrap();
let metrics: Vec<FunctionMetrics> = (0..60)
.map(|i| FunctionMetrics {
file: god_file_path.clone(),
name: format!("method_{}", i),
line: i * 10 + 10,
length: 5,
cyclomatic: 8, cognitive: 5,
nesting: 2,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.1),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
})
.collect();
let call_graph = CallGraph::new();
let options = ParallelUnifiedAnalysisOptions::default();
let mut builder = ParallelUnifiedAnalysisBuilder::new(call_graph, options);
let (data_flow, purity, test_funcs, debt_agg) = builder.execute_phase1_parallel(&metrics, None);
let function_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, function_items, file_items, None);
let app = ResultsApp::new(unified);
let total_items = app.item_count();
assert!(total_items > 0, "TUI should have items from analysis");
let god_items_in_tui: Vec<_> = app
.filtered_items()
.filter(|item| {
item.god_object_indicators
.as_ref()
.map(|i| i.is_god_object)
.unwrap_or(false)
})
.collect();
assert!(
!god_items_in_tui.is_empty(),
"God objects should be visible in TUI (via ResultsApp.filtered_items())"
);
let all_items_with_god: Vec<_> = app
.analysis()
.items
.iter()
.filter(|item| {
item.god_object_indicators
.as_ref()
.map(|i| i.is_god_object)
.unwrap_or(false)
})
.collect();
assert!(
!all_items_with_god.is_empty(),
"God objects should be in analysis.items (accessible to TUI)"
);
}
#[test]
#[ignore] fn test_data_flow_population_performance_overhead() {
use std::time::Instant;
let metrics = create_test_metrics(200);
let call_graph = CallGraph::new();
let iterations = 5;
let mut baseline_times = Vec::new();
for _ in 0..iterations {
let options = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 50,
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);
baseline_times.push(start.elapsed());
}
let baseline_avg = baseline_times.iter().sum::<std::time::Duration>() / iterations as u32;
assert!(
baseline_avg.as_secs() < 5,
"Baseline analysis took too long: {:?}",
baseline_avg
);
eprintln!("Data flow population performance (200 functions):");
eprintln!(" Average time: {:?}", baseline_avg);
eprintln!(" Min time: {:?}", baseline_times.iter().min().unwrap());
eprintln!(" Max time: {:?}", baseline_times.iter().max().unwrap());
}
#[test]
fn test_extraction_pipeline_baseline_equivalence() {
use debtmap::extraction::{ExtractedFileData, UnifiedFileExtractor};
use std::collections::HashMap;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_module.rs");
let test_code = r#"
use std::collections::HashMap;
pub struct Calculator {
state: i32,
}
impl Calculator {
pub fn new() -> Self {
Self { state: 0 }
}
pub fn add(&mut self, value: i32) -> i32 {
self.state += value;
self.state
}
pub fn compute_complex(&self, items: &[i32]) -> i32 {
items.iter()
.filter(|&&x| x > 0)
.map(|&x| x * 2)
.fold(0, |acc, x| acc + x)
}
}
fn pure_function(x: i32, y: i32) -> i32 {
x + y
}
fn complex_function(data: &[String]) -> HashMap<String, usize> {
let mut result = HashMap::new();
for item in data {
if item.len() > 3 {
let count = result.entry(item.clone()).or_insert(0);
*count += 1;
}
}
result
}
"#;
std::fs::write(&test_file, test_code).unwrap();
let metrics = vec![
FunctionMetrics {
file: test_file.clone(),
name: "Calculator::new".to_string(),
line: 10,
length: 3,
cyclomatic: 1,
cognitive: 0,
nesting: 0,
is_test: false,
in_test_module: false,
visibility: Some("pub".to_string()),
is_trait_method: false,
entropy_score: None,
is_pure: Some(true),
purity_confidence: Some(0.95),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
},
FunctionMetrics {
file: test_file.clone(),
name: "Calculator::add".to_string(),
line: 14,
length: 4,
cyclomatic: 1,
cognitive: 0,
nesting: 0,
is_test: false,
in_test_module: false,
visibility: Some("pub".to_string()),
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.9),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
},
FunctionMetrics {
file: test_file.clone(),
name: "Calculator::compute_complex".to_string(),
line: 19,
length: 6,
cyclomatic: 2,
cognitive: 2,
nesting: 1,
is_test: false,
in_test_module: false,
visibility: Some("pub".to_string()),
is_trait_method: false,
entropy_score: None,
is_pure: Some(true),
purity_confidence: Some(0.95),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
},
FunctionMetrics {
file: test_file.clone(),
name: "pure_function".to_string(),
line: 27,
length: 3,
cyclomatic: 1,
cognitive: 0,
nesting: 0,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(true),
purity_confidence: Some(0.99),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
},
FunctionMetrics {
file: test_file.clone(),
name: "complex_function".to_string(),
line: 31,
length: 10,
cyclomatic: 3,
cognitive: 4,
nesting: 2,
is_test: false,
in_test_module: false,
visibility: None,
is_trait_method: false,
entropy_score: None,
is_pure: Some(false),
purity_confidence: Some(0.3),
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
purity_reason: None,
call_dependencies: None,
},
];
let content = std::fs::read_to_string(&test_file).unwrap();
let extracted_data = UnifiedFileExtractor::extract(&test_file, &content).unwrap();
let mut extracted_map: HashMap<PathBuf, ExtractedFileData> = HashMap::new();
extracted_map.insert(test_file.clone(), extracted_data);
let call_graph = CallGraph::new();
let options_with_extracted = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 25,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder_with_extracted =
ParallelUnifiedAnalysisBuilder::new(call_graph.clone(), options_with_extracted)
.with_extracted_data(extracted_map.clone());
let (data_flow_with, purity_with, test_funcs_with, _debt_agg_with) =
builder_with_extracted.execute_phase1_parallel(&metrics, None);
let options_without_extracted = ParallelUnifiedAnalysisOptions {
parallel: true,
jobs: Some(4),
batch_size: 25,
progress: false,
reference_time: chrono::Utc::now(),
};
let mut builder_without_extracted =
ParallelUnifiedAnalysisBuilder::new(call_graph.clone(), options_without_extracted);
let (data_flow_without, purity_without, test_funcs_without, _debt_agg_without) =
builder_without_extracted.execute_phase1_parallel(&metrics, None);
assert_eq!(
purity_with.len(),
purity_without.len(),
"Purity analysis count should match"
);
assert_eq!(
test_funcs_with.len(),
test_funcs_without.len(),
"Test function count should match"
);
let call_graph_with = data_flow_with.call_graph();
let call_graph_without = data_flow_without.call_graph();
assert_eq!(
call_graph_with.get_all_functions().count(),
call_graph_without.get_all_functions().count(),
"Call graph function count should match"
);
for (key, value_with) in &purity_with {
if let Some(value_without) = purity_without.get(key) {
assert_eq!(
value_with, value_without,
"Purity value for {} should match between extraction paths",
key
);
}
}
}
#[test]
#[ignore] fn test_extraction_pipeline_speedup() {
use std::time::Instant;
let src_path = std::path::Path::new("src");
if !src_path.exists() {
eprintln!("Skipping speedup test - src directory not found");
return;
}
let rust_files: Vec<PathBuf> = walkdir::WalkDir::new(src_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map(|ext| ext == "rs").unwrap_or(false))
.map(|e| e.path().to_path_buf())
.take(50) .collect();
if rust_files.is_empty() {
eprintln!("Skipping speedup test - no Rust files found");
return;
}
eprintln!("Testing with {} Rust files", rust_files.len());
let extraction_start = Instant::now();
let mut extracted_count = 0;
for path in &rust_files {
if let Ok(content) = std::fs::read_to_string(path) {
if debtmap::extraction::UnifiedFileExtractor::extract(path, &content).is_ok() {
extracted_count += 1;
}
}
}
let extraction_time = extraction_start.elapsed();
let simulated_start = Instant::now();
let simulated_parses = 3; for _ in 0..simulated_parses {
for path in &rust_files {
if let Ok(content) = std::fs::read_to_string(path) {
let _ = syn::parse_file(&content);
}
}
}
let simulated_time = simulated_start.elapsed();
let extraction_ms = extraction_time.as_millis() as f64;
let simulated_ms = simulated_time.as_millis() as f64;
let speedup = if extraction_ms > 0.0 {
simulated_ms / extraction_ms
} else {
f64::INFINITY
};
eprintln!("\nSpec 213 Speedup Test Results:");
eprintln!(" Files processed: {}", rust_files.len());
eprintln!(" Files successfully extracted: {}", extracted_count);
eprintln!(" Unified extraction time: {:?}", extraction_time);
eprintln!(
" Simulated per-function parsing time ({} passes): {:?}",
simulated_parses, simulated_time
);
eprintln!(" Speedup factor: {:.1}x", speedup);
assert!(
extracted_count > 0,
"Should successfully extract at least some files"
);
}