use debtmap::analysis::call_graph::{
CrossModuleTracker, FrameworkPatternDetector, FunctionPointerTracker, RustCallGraph,
TraitRegistry,
};
use debtmap::analysis::purity_analysis::PurityAnalyzer;
use debtmap::analysis::purity_propagation::{PurityCallGraphAdapter, PurityPropagator};
use debtmap::core::FunctionMetrics;
use debtmap::priority::call_graph::{CallGraph, CallType, FunctionId};
use std::path::PathBuf;
fn create_test_metric(
name: &str,
file: &str,
line: usize,
is_pure: Option<bool>,
confidence: Option<f32>,
) -> FunctionMetrics {
FunctionMetrics {
name: name.to_string(),
file: PathBuf::from(file),
line,
cyclomatic: 1,
cognitive: 0,
nesting: 0,
length: 5,
is_test: false,
visibility: None,
is_trait_method: false,
in_test_module: false,
entropy_score: None,
is_pure,
purity_confidence: confidence,
purity_reason: None,
call_dependencies: None,
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,
}
}
#[test]
fn test_pure_function_calling_pure_function() {
let mut call_graph = CallGraph::new();
let helper_id = FunctionId::new(PathBuf::from("test.rs"), "pure_helper".to_string(), 10);
let caller_id = FunctionId::new(PathBuf::from("test.rs"), "caller".to_string(), 20);
call_graph.add_call_parts(caller_id.clone(), helper_id.clone(), CallType::Direct);
let metrics = vec![
create_test_metric("pure_helper", "test.rs", 10, Some(true), Some(0.95)),
create_test_metric("caller", "test.rs", 20, Some(true), Some(0.95)),
];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let caller_result = propagator.get_result(&caller_id).unwrap();
assert!(
caller_result.level == debtmap::analysis::purity_analysis::PurityLevel::StrictlyPure,
"Pure function calling pure function should be pure"
);
assert!(
caller_result.confidence > 0.85,
"Confidence should remain high, got {}",
caller_result.confidence
);
}
#[test]
fn test_pure_recursive_function() {
let mut call_graph = CallGraph::new();
let factorial_id = FunctionId::new(PathBuf::from("test.rs"), "factorial".to_string(), 10);
call_graph.add_call_parts(factorial_id.clone(), factorial_id.clone(), CallType::Direct);
let metrics = vec![create_test_metric(
"factorial",
"test.rs",
10,
Some(true),
Some(0.95),
)];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let result = propagator.get_result(&factorial_id).unwrap();
assert!(
result.level == debtmap::analysis::purity_analysis::PurityLevel::StrictlyPure,
"Pure recursive function should remain pure"
);
assert!(
result.confidence < 0.95,
"Confidence should be reduced due to recursion, got {}",
result.confidence
);
}
#[test]
fn test_impure_recursive_function() {
let mut call_graph = CallGraph::new();
let recursive_id = FunctionId::new(
PathBuf::from("test.rs"),
"recursive_with_io".to_string(),
10,
);
call_graph.add_call_parts(recursive_id.clone(), recursive_id.clone(), CallType::Direct);
let metrics = vec![create_test_metric(
"recursive_with_io",
"test.rs",
10,
Some(false), Some(0.95),
)];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let result = propagator.get_result(&recursive_id).unwrap();
assert!(
result.level == debtmap::analysis::purity_analysis::PurityLevel::Impure,
"Recursive function with side effects should be impure"
);
}
#[test]
fn test_confidence_decreases_with_depth() {
let mut call_graph = CallGraph::new();
let a_id = FunctionId::new(PathBuf::from("test.rs"), "func_a".to_string(), 10);
let b_id = FunctionId::new(PathBuf::from("test.rs"), "func_b".to_string(), 20);
let c_id = FunctionId::new(PathBuf::from("test.rs"), "func_c".to_string(), 30);
call_graph.add_call_parts(a_id.clone(), b_id.clone(), CallType::Direct);
call_graph.add_call_parts(b_id.clone(), c_id.clone(), CallType::Direct);
let metrics = vec![
create_test_metric("func_a", "test.rs", 10, Some(true), Some(0.95)),
create_test_metric("func_b", "test.rs", 20, Some(true), Some(0.95)),
create_test_metric("func_c", "test.rs", 30, Some(true), Some(0.95)),
];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let a_result = propagator.get_result(&a_id).unwrap();
let c_result = propagator.get_result(&c_id).unwrap();
assert!(
a_result.confidence <= c_result.confidence,
"Root function should have equal or lower confidence than leaf, got {} vs {}",
a_result.confidence,
c_result.confidence
);
}
#[test]
fn test_cross_file_purity_propagation() {
let mut call_graph = CallGraph::new();
let helper_id = FunctionId::new(PathBuf::from("file1.rs"), "helper".to_string(), 10);
let caller_id = FunctionId::new(PathBuf::from("file2.rs"), "caller".to_string(), 20);
call_graph.add_call_parts(caller_id.clone(), helper_id.clone(), CallType::Direct);
let metrics = vec![
create_test_metric("helper", "file1.rs", 10, Some(true), Some(0.95)),
create_test_metric("caller", "file2.rs", 20, Some(true), Some(0.95)),
];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let caller_result = propagator.get_result(&caller_id).unwrap();
assert!(
caller_result.level == debtmap::analysis::purity_analysis::PurityLevel::StrictlyPure,
"Purity should propagate across file boundaries"
);
}
#[test]
#[ignore] fn test_impure_caller_propagates_impurity() {
let mut call_graph = CallGraph::new();
let impure_id = FunctionId::new(PathBuf::from("test.rs"), "impure_func".to_string(), 10);
let caller_id = FunctionId::new(PathBuf::from("test.rs"), "caller".to_string(), 20);
call_graph.add_call_parts(caller_id.clone(), impure_id.clone(), CallType::Direct);
let metrics = vec![
create_test_metric("impure_func", "test.rs", 10, Some(false), Some(0.95)),
create_test_metric("caller", "test.rs", 20, Some(true), Some(0.95)), ];
let rust_graph = RustCallGraph {
base_graph: call_graph,
trait_registry: TraitRegistry::new(),
function_pointer_tracker: FunctionPointerTracker::new(),
framework_patterns: FrameworkPatternDetector::new(),
cross_module_tracker: CrossModuleTracker::new(),
};
let adapter = PurityCallGraphAdapter::from_rust_graph(rust_graph);
let purity_analyzer = PurityAnalyzer::new();
let mut propagator = PurityPropagator::new(adapter, purity_analyzer);
propagator.propagate(&metrics).unwrap();
let caller_result = propagator.get_result(&caller_id).unwrap();
assert!(
caller_result.level == debtmap::analysis::purity_analysis::PurityLevel::Impure,
"Calling impure function should make caller impure"
);
}