use context_creator::cli::Config;
use context_creator::core::cache::FileCache;
use context_creator::core::semantic::graph_builder::GraphBuilder;
use context_creator::core::semantic::graph_traverser::{GraphTraverser, TraversalOptions};
use context_creator::core::semantic::parallel_analyzer::{AnalysisOptions, ParallelAnalyzer};
use context_creator::core::semantic_graph::perform_semantic_analysis_graph;
use context_creator::core::walker::FileInfo;
use context_creator::utils::file_ext::FileType;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
fn create_test_project() -> (TempDir, Vec<FileInfo>) {
let temp_dir = TempDir::new().unwrap();
let dir = temp_dir.path();
fs::create_dir_all(dir.join("src")).unwrap();
fs::create_dir_all(dir.join("src/utils")).unwrap();
fs::write(
dir.join("src/main.rs"),
r#"
mod lib;
mod utils;
use lib::Config;
use utils::helper::process_data;
fn main() {
let config = Config::new();
process_data(&config);
}
"#,
)
.unwrap();
fs::write(
dir.join("src/lib.rs"),
r#"
pub struct Config {
pub debug: bool,
pub threads: usize,
}
impl Config {
pub fn new() -> Self {
Config {
debug: false,
threads: 4,
}
}
}
"#,
)
.unwrap();
fs::write(
dir.join("src/utils/mod.rs"),
r#"
pub mod helper;
"#,
)
.unwrap();
fs::write(
dir.join("src/utils/helper.rs"),
r#"
use crate::lib::Config;
pub fn process_data(config: &Config) {
if config.debug {
println!("Processing with {} threads", config.threads);
}
}
"#,
)
.unwrap();
let files = vec![
FileInfo {
path: dir.join("src/main.rs"),
relative_path: PathBuf::from("src/main.rs"),
size: 150,
file_type: FileType::Rust,
priority: 2.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
FileInfo {
path: dir.join("src/lib.rs"),
relative_path: PathBuf::from("src/lib.rs"),
size: 200,
file_type: FileType::Rust,
priority: 1.5,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
FileInfo {
path: dir.join("src/utils/mod.rs"),
relative_path: PathBuf::from("src/utils/mod.rs"),
size: 50,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
FileInfo {
path: dir.join("src/utils/helper.rs"),
relative_path: PathBuf::from("src/utils/helper.rs"),
size: 180,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
];
(temp_dir, files)
}
#[test]
fn test_new_modular_architecture_works_together() {
let (_temp_dir, files) = create_test_project();
let cache = Arc::new(FileCache::new());
let project_root = files[0]
.path
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf();
let analyzer = ParallelAnalyzer::new(&cache);
let analysis_options = AnalysisOptions {
semantic_depth: 3,
trace_imports: true,
include_types: true,
include_functions: true,
};
let file_paths: Vec<_> = files.iter().map(|f| f.path.clone()).collect();
let valid_files: HashSet<PathBuf> = files.iter().map(|f| f.path.clone()).collect();
let analysis_results = analyzer
.analyze_files(&file_paths, &project_root, &analysis_options, &valid_files)
.unwrap();
assert_eq!(analysis_results.len(), 4);
let builder = GraphBuilder::new();
let (mut graph, node_map) = builder.build(&files).unwrap();
for result in &analysis_results {
if let Some(&from_idx) = node_map.get(&files[result.file_index].path) {
for (import_path, edge_type) in &result.imports {
if let Some(target_file) = files.iter().find(|f| {
f.path
.to_str()
.unwrap()
.contains(&import_path.to_string_lossy().to_string())
}) {
if let Some(&to_idx) = node_map.get(&target_file.path) {
builder.add_edge(&mut graph, from_idx, to_idx, edge_type.clone());
}
}
}
}
}
let traverser = GraphTraverser::new();
let topo_result = traverser.topological_sort(&graph);
assert!(topo_result.is_ok(), "Should be able to topologically sort");
let main_idx = node_map[&files[0].path];
let reachable = traverser.find_reachable_nodes(&graph, main_idx);
assert!(!reachable.is_empty(), "Main should at least reach itself");
}
#[test]
fn test_backward_compatibility_with_original_api() {
let (_temp_dir, mut files) = create_test_project();
let cache = FileCache::new();
let config = Config {
trace_imports: true,
include_types: true,
include_callers: true,
semantic_depth: 3,
..Default::default()
};
let result = perform_semantic_analysis_graph(&mut files, &config, &cache);
assert!(result.is_ok());
}
#[test]
fn test_error_propagation_between_modules() {
let temp_dir = TempDir::new().unwrap();
let dir = temp_dir.path();
fs::write(
dir.join("invalid.rs"),
"This is not valid Rust code { ] } [",
)
.unwrap();
let files = [FileInfo {
path: dir.join("invalid.rs"),
relative_path: PathBuf::from("invalid.rs"),
size: 30,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
}];
let cache = Arc::new(FileCache::new());
let analyzer = ParallelAnalyzer::new(&cache);
let file_paths = vec![files[0].path.clone()];
let valid_files: HashSet<PathBuf> = [files[0].path.clone()].iter().cloned().collect();
let analysis_results = analyzer
.analyze_files(&file_paths, dir, &AnalysisOptions::default(), &valid_files)
.unwrap();
assert_eq!(analysis_results.len(), 1);
assert!(analysis_results[0].content_hash.is_some());
}
#[test]
fn test_performance_no_regression() {
let (_temp_dir, mut files_original) = create_test_project();
let cache = FileCache::new();
let mut files_modular = files_original.clone();
let config = Config {
trace_imports: true,
include_types: true,
include_callers: true,
semantic_depth: 3,
..Default::default()
};
let start_original = std::time::Instant::now();
perform_semantic_analysis_graph(&mut files_original, &config, &cache).unwrap();
let duration_original = start_original.elapsed();
let start_modular = std::time::Instant::now();
perform_semantic_analysis_graph(&mut files_modular, &config, &cache).unwrap();
let duration_modular = start_modular.elapsed();
assert!(
duration_modular.as_millis() <= duration_original.as_millis() * 3,
"Second run is too slow: {duration_modular:?} vs {duration_original:?} (may indicate system load)"
);
}
#[test]
fn test_cycle_detection_works_with_new_architecture() {
let temp_dir = TempDir::new().unwrap();
let dir = temp_dir.path();
fs::write(
dir.join("a.rs"),
r#"
use crate::b::TypeB;
pub struct TypeA {
b: TypeB,
}
"#,
)
.unwrap();
fs::write(
dir.join("b.rs"),
r#"
use crate::c::TypeC;
pub struct TypeB {
c: TypeC,
}
"#,
)
.unwrap();
fs::write(
dir.join("c.rs"),
r#"
use crate::a::TypeA;
pub struct TypeC {
a: TypeA,
}
"#,
)
.unwrap();
let files = vec![
FileInfo {
path: dir.join("a.rs"),
relative_path: PathBuf::from("a.rs"),
size: 80,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
FileInfo {
path: dir.join("b.rs"),
relative_path: PathBuf::from("b.rs"),
size: 80,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
FileInfo {
path: dir.join("c.rs"),
relative_path: PathBuf::from("c.rs"),
size: 80,
file_type: FileType::Rust,
priority: 1.0,
imports: Vec::new(),
imported_by: Vec::new(),
function_calls: Vec::new(),
type_references: Vec::new(),
exported_functions: Vec::new(),
},
];
let cache = Arc::new(FileCache::new());
let analyzer = ParallelAnalyzer::new(&cache);
let file_paths: Vec<_> = files.iter().map(|f| f.path.clone()).collect();
let valid_files: HashSet<PathBuf> = files.iter().map(|f| f.path.clone()).collect();
let _analysis_results = analyzer
.analyze_files(&file_paths, dir, &AnalysisOptions::default(), &valid_files)
.unwrap();
let builder = GraphBuilder::new();
let (graph, _) = builder.build(&files).unwrap();
let traverser = GraphTraverser::new();
let _topo_result = traverser.topological_sort(&graph);
}
#[test]
fn test_modules_work_together_seamlessly() {
let (_temp_dir, files) = create_test_project();
let cache = Arc::new(FileCache::new());
let project_root = files[0]
.path
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf();
let analyzer = ParallelAnalyzer::new(&cache);
let file_paths: Vec<_> = files.iter().map(|f| f.path.clone()).collect();
let valid_files: HashSet<PathBuf> = files.iter().map(|f| f.path.clone()).collect();
let analysis_results = analyzer
.analyze_files(
&file_paths,
&project_root,
&AnalysisOptions {
semantic_depth: 3,
trace_imports: true,
include_types: true,
include_functions: true,
},
&valid_files,
)
.unwrap();
let builder = GraphBuilder::new();
let (mut graph, node_map) = builder.build(&files).unwrap();
let path_to_index: HashMap<PathBuf, usize> = files
.iter()
.enumerate()
.map(|(i, f)| (f.path.clone(), i))
.collect();
builder.build_edges_from_analysis(&mut graph, &analysis_results, &path_to_index, &node_map);
let traverser = GraphTraverser::new();
let main_idx = node_map[&files[0].path];
let visited = traverser.traverse_bfs(
&graph,
main_idx,
&TraversalOptions {
max_depth: 5,
include_types: true,
include_functions: true,
},
);
assert!(
!visited.is_empty(),
"BFS should visit at least the start node"
);
}