#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod unified_ast_integration_tests {
use super::super::{
AstStrategy, AstRegistry, UnifiedAstProcessor, UnifiedAstAnalyzer,
AstConfig, AstAnalysisResult,
languages::{rust::RustStrategy},
};
use crate::services::file_classifier::FileClassifier;
use tempfile::TempDir;
use std::fs;
#[test]
fn test_ast_strategies_creation() {
let _rust = RustStrategy::new();
let _registry = AstRegistry::new();
let _processor = UnifiedAstProcessor::new();
let _analyzer = UnifiedAstAnalyzer::new();
assert!(true, "All AST components created successfully");
}
#[test]
fn test_ast_registry_operations() {
let registry = AstRegistry::new();
let extensions = registry.list_supported_extensions();
assert!(extensions.contains(&"rs"), "Should support Rust files");
let rust_strategy = registry.get_strategy("rs");
assert!(rust_strategy.is_some(), "Should have Rust strategy");
if let Some(strategy) = rust_strategy {
assert_eq!(strategy.primary_extension(), "rs", "Primary extension should be 'rs'");
assert_eq!(strategy.language_name(), "Rust", "Language name should be 'Rust'");
assert!(strategy.can_handle("rs"), "Should handle .rs files");
}
}
#[tokio::test]
async fn test_ast_processor_with_rust_code() {
let temp_dir = TempDir::new().unwrap();
let rust_file = temp_dir.path().join("test.rs");
fs::write(&rust_file, r#"
use std::collections::HashMap;
/// A test struct with documentation
pub struct TestStruct {
pub field1: i32,
field2: String,
}
impl TestStruct {
/// Creates a new TestStruct
pub fn new(value: i32) -> Self {
Self {
field1: value,
field2: format!("Value: {}", value),
}
}
/// A method with moderate complexity
pub fn calculate(&self, data: &[i32]) -> HashMap<i32, i32> {
let mut result = HashMap::new();
for (index, &item) in data.iter().enumerate() {
if item > 0 {
let processed = if item > 10 {
item * 2
} else {
item + self.field1
};
result.insert(index as i32, processed);
}
}
result
}
}
/// A standalone function
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_struct_creation() {
let test_struct = TestStruct::new(42);
assert_eq!(test_struct.field1, 42);
}
}
"#).unwrap();
let processor = UnifiedAstProcessor::new();
let result = processor.process_file(&rust_file).await;
match result {
Ok(analysis) => {
assert_eq!(analysis.language, "Rust", "Should identify as Rust");
assert_eq!(analysis.file_path, rust_file, "File path should match");
assert!(analysis.analysis_duration_ms > 0, "Should have positive analysis time");
assert!(!analysis.context.path.is_empty(), "Should have path");
assert_eq!(analysis.context.language, "Rust", "Context language should be Rust");
println!("✅ AST analysis successful: {} items found", analysis.context.items.len());
},
Err(e) => {
println!("⚠️ AST analysis failed gracefully: {}", e);
}
}
}
#[tokio::test]
async fn test_multiple_file_processing() {
let temp_dir = TempDir::new().unwrap();
let files = vec![
temp_dir.path().join("file1.rs"),
temp_dir.path().join("file2.rs"),
temp_dir.path().join("file3.rs"),
];
for (i, file) in files.iter().enumerate() {
fs::write(file, format!(r#"
/// Function .
pub fn function_{}() -> i32 {{
{}
}}
"#, i, i * 10)).unwrap();
}
let processor = UnifiedAstProcessor::new();
let file_paths: Vec<&std::path::Path> = files.iter().map(|f| f.as_path()).collect();
let results = processor.process_files(&file_paths).await;
assert_eq!(results.len(), 3, "Should have 3 results");
let mut successful = 0;
for (i, result) in results.iter().enumerate() {
match result {
Ok(analysis) => {
successful += 1;
assert_eq!(analysis.language, "Rust", "File {} should be Rust", i);
println!("✅ File {} analyzed successfully", i);
},
Err(e) => {
println!("⚠️ File {} failed gracefully: {}", i, e);
}
}
}
println!("Multiple file processing: {}/3 files successful", successful);
}
#[tokio::test]
async fn test_unified_ast_analyzer() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("analyzer_test.rs");
fs::write(&test_file, r#"
fn simple_function() -> String {
"Hello, AST!".to_string()
}
fn complex_function(input: &str) -> Vec<String> {
let mut words = Vec::new();
for word in input.split_whitespace() {
if word.len() > 3 {
words.push(word.to_uppercase());
} else {
words.push(word.to_lowercase());
}
}
words
}
"#).unwrap();
let analyzer = UnifiedAstAnalyzer::new();
let languages = analyzer.supported_languages();
assert!(languages.contains(&"rs"), "Should support Rust");
let result = analyzer.analyze_file(&test_file).await;
match result {
Ok(analysis) => {
assert_eq!(analysis.language, "Rust", "Should identify as Rust");
assert!(analysis.analysis_duration_ms > 0, "Should have analysis time");
println!("✅ Unified analyzer successful");
},
Err(e) => {
println!("⚠️ Unified analyzer failed gracefully: {}", e);
}
}
}
#[test]
fn test_ast_configuration() {
let default_config = AstConfig::default();
assert!(default_config.include_complexity, "Should include complexity by default");
assert!(default_config.include_functions, "Should include functions by default");
assert!(default_config.include_types, "Should include types by default");
assert!(default_config.include_imports, "Should include imports by default");
assert!(default_config.max_depth.is_none(), "Max depth should be None by default");
let custom_config = AstConfig {
include_complexity: false,
include_functions: true,
include_types: false,
include_imports: true,
max_depth: Some(5),
};
let processor = UnifiedAstProcessor::with_config(custom_config.clone());
assert!(true, "Custom configuration works");
}
#[tokio::test]
async fn test_error_handling() {
let processor = UnifiedAstProcessor::new();
let non_existent = std::path::PathBuf::from("/this/does/not/exist.rs");
let result = processor.process_file(&non_existent).await;
assert!(result.is_err(), "Should fail for non-existent file");
if let Err(e) = result {
assert!(!e.to_string().is_empty(), "Error should have description");
println!("Graceful error handling: {}", e);
}
let temp_dir = TempDir::new().unwrap();
let unsupported_file = temp_dir.path().join("test.unknown");
fs::write(&unsupported_file, "some content").unwrap();
let result = processor.process_file(&unsupported_file).await;
assert!(result.is_err(), "Should fail for unsupported extension");
if let Err(e) = result {
println!("Graceful handling of unsupported extension: {}", e);
}
}
#[tokio::test]
async fn test_ast_determinism() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("determinism_test.rs");
fs::write(&test_file, r#"
fn deterministic_function() -> i32 {
let mut sum = 0;
for i in 1..=10 {
sum += i;
}
sum
}
"#).unwrap();
let processor = UnifiedAstProcessor::new();
let result1 = processor.process_file(&test_file).await;
let result2 = processor.process_file(&test_file).await;
match (result1, result2) {
(Ok(r1), Ok(r2)) => {
assert_eq!(r1.language, r2.language, "Language should be consistent");
assert_eq!(r1.file_path, r2.file_path, "File path should be consistent");
assert!(r1.analysis_duration_ms > 0 && r2.analysis_duration_ms > 0,
"Analysis times should be positive");
println!("✅ AST analysis is deterministic");
},
(Err(_), Err(_)) => {
println!("✅ Consistent error behavior");
},
_ => panic!("Inconsistent AST analysis results"),
}
}
#[tokio::test]
async fn test_ast_with_file_classifier() {
let temp_dir = TempDir::new().unwrap();
let rust_file = temp_dir.path().join("classified.rs");
fs::write(&rust_file, r#"
// This is a Rust file for classification testing
pub mod test_module {
/// Test function.
pub fn test_function() -> &'static str {
"Hello from classified code!"
}
}
"#).unwrap();
let registry = AstRegistry::new();
let classifier = FileClassifier::default();
let result = registry.analyze_file(&rust_file, &classifier).await;
match result {
Ok(context) => {
assert_eq!(context.language, "Rust", "Should classify as Rust");
assert!(!context.path.is_empty(), "Should have file path");
println!("✅ AST analysis with classifier successful");
},
Err(e) => {
println!("⚠️ AST analysis with classifier failed gracefully: {}", e);
}
}
}
#[tokio::test]
async fn test_ast_performance() {
let temp_dir = TempDir::new().unwrap();
let large_file = temp_dir.path().join("performance_test.rs");
let mut content = String::new();
content.push_str("use std::collections::HashMap;\n\n");
for i in 0..50 {
content.push_str(&format!(r#"
/// Function .
pub fn function_{}(input: &[i32]) -> HashMap<i32, String> {{
let mut result = HashMap::new();
for (index, &value) in input.iter().enumerate() {{
if value > {} {{
result.insert(index as i32, format!("Value: {{}}", value));
}}
}}
result
}}
"#, i, i * 10));
}
fs::write(&large_file, content).unwrap();
let processor = UnifiedAstProcessor::new();
let start_time = std::time::Instant::now();
let result = processor.process_file(&large_file).await;
let elapsed = start_time.elapsed();
assert!(elapsed.as_secs() < 120, "Analysis should complete within 120 seconds");
match result {
Ok(analysis) => {
println!("✅ Performance test passed: {} items in {:?}",
analysis.context.items.len(), elapsed);
assert!(analysis.analysis_duration_ms < 10000,
"Analysis duration should be under 10 seconds");
},
Err(e) => {
println!("⚠️ Performance test failed gracefully in {:?}: {}", elapsed, e);
}
}
}
#[tokio::test]
async fn test_complete_ast_workflow() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).unwrap();
let main_rs = src_dir.join("main.rs");
let lib_rs = src_dir.join("lib.rs");
let mod_rs = src_dir.join("module.rs");
fs::write(&main_rs, r#"
use crate::module::helper_function;
fn main() {
println!("Hello, world!");
let result = helper_function(42);
println!("Result: {}", result);
}
"#).unwrap();
fs::write(&lib_rs, r#"
pub mod module;
/// Library function.
pub fn library_function(input: i32) -> String {
format!("Processed: {}", input)
}
"#).unwrap();
fs::write(&mod_rs, r#"
/// Helper function.
pub fn helper_function(value: i32) -> i32 {
if value > 0 {
value * 2
} else {
0
}
}
/// Another helper.
pub fn another_helper(data: &[String]) -> Vec<String> {
data.iter().map(|s| s.to_uppercase()).collect()
}
"#).unwrap();
let processor = UnifiedAstProcessor::new();
let files = vec![main_rs.as_path(), lib_rs.as_path(), mod_rs.as_path()];
let results = processor.process_files(&files).await;
assert_eq!(results.len(), 3, "Should process all 3 files");
let mut successful_analyses = 0;
for (i, result) in results.iter().enumerate() {
match result {
Ok(analysis) => {
successful_analyses += 1;
assert_eq!(analysis.language, "Rust", "File {} should be Rust", i);
assert!(analysis.analysis_duration_ms > 0, "Should have analysis time");
println!("✅ File {} AST analysis completed", i);
},
Err(e) => {
println!("⚠️ File {} AST analysis failed gracefully: {}", i, e);
}
}
}
println!("Complete AST workflow: {}/3 files analyzed successfully", successful_analyses);
assert!(successful_analyses > 0, "At least one file should be analyzed successfully");
}
}