use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use tempfile::TempDir;
use thread_flow::incremental::backends::{BackendConfig, BackendType, create_backend};
use thread_flow::incremental::graph::DependencyGraph;
use thread_flow::incremental::storage::StorageBackend;
use thread_flow::incremental::types::{AnalysisDefFingerprint, DependencyEdge, DependencyType};
struct IncrementalTestFixture {
temp_dir: TempDir,
storage: Box<dyn StorageBackend>,
files_created: HashMap<PathBuf, String>,
last_analysis_result: Option<AnalysisResult>,
}
#[derive(Debug, Clone)]
struct AnalysisResult {
files_analyzed: usize,
files_skipped: usize,
_edges_created: usize,
duration: Duration,
invalidated_files: Vec<PathBuf>,
reanalysis_order: Vec<PathBuf>,
}
impl IncrementalTestFixture {
async fn new() -> Self {
Self::new_with_backend(BackendType::InMemory).await
}
async fn new_with_backend(backend_type: BackendType) -> Self {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config = match backend_type {
BackendType::InMemory => BackendConfig::InMemory,
BackendType::Postgres => {
BackendConfig::Postgres {
database_url: std::env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| "postgresql://localhost/thread_test".to_string()),
}
}
BackendType::D1 => {
BackendConfig::D1 {
account_id: std::env::var("TEST_CF_ACCOUNT_ID")
.unwrap_or_else(|_| "test-account".to_string()),
database_id: std::env::var("TEST_CF_DATABASE_ID")
.unwrap_or_else(|_| "test-db".to_string()),
api_token: std::env::var("TEST_CF_API_TOKEN")
.unwrap_or_else(|_| "test-token".to_string()),
}
}
};
let storage = create_backend(backend_type, config)
.await
.expect("Failed to create storage backend");
Self {
temp_dir,
storage,
files_created: HashMap::new(),
last_analysis_result: None,
}
}
async fn create_file(&mut self, relative_path: &str, content: &str) {
let full_path = self.temp_dir.path().join(relative_path);
if let Some(parent) = full_path.parent() {
tokio::fs::create_dir_all(parent)
.await
.expect("Failed to create parent directories");
}
tokio::fs::write(&full_path, content)
.await
.expect("Failed to write file");
self.files_created.insert(full_path, content.to_string());
}
async fn modify_file(&mut self, relative_path: &str, new_content: &str) {
let full_path = self.temp_dir.path().join(relative_path);
assert!(
full_path.exists(),
"File {} does not exist",
full_path.display()
);
tokio::fs::write(&full_path, new_content)
.await
.expect("Failed to modify file");
self.files_created
.insert(full_path, new_content.to_string());
}
async fn delete_file(&mut self, relative_path: &str) {
let full_path = self.temp_dir.path().join(relative_path);
if full_path.exists() {
tokio::fs::remove_file(&full_path)
.await
.expect("Failed to delete file");
}
self.files_created.remove(&full_path);
}
async fn run_initial_analysis(&mut self) -> Result<AnalysisResult, String> {
let start = Instant::now();
let mut files_analyzed = 0;
let edges_created = 0;
for (path, content) in &self.files_created {
let fp = AnalysisDefFingerprint::new(content.as_bytes());
self.storage
.save_fingerprint(path, &fp)
.await
.map_err(|e| format!("Storage error: {}", e))?;
files_analyzed += 1;
}
let result = AnalysisResult {
files_analyzed,
files_skipped: 0,
_edges_created: edges_created,
duration: start.elapsed(),
invalidated_files: Vec::new(),
reanalysis_order: Vec::new(),
};
self.last_analysis_result = Some(result.clone());
Ok(result)
}
async fn run_incremental_update(&mut self) -> Result<AnalysisResult, String> {
let start = Instant::now();
let mut files_analyzed = 0;
let mut files_skipped = 0;
let mut invalidated_files = Vec::new();
for (path, content) in &self.files_created {
let stored_fp = self
.storage
.load_fingerprint(path)
.await
.map_err(|e| format!("Storage error: {}", e))?;
let current_fp = AnalysisDefFingerprint::new(content.as_bytes());
if let Some(stored) = stored_fp {
if stored.content_matches(content.as_bytes()) {
files_skipped += 1;
} else {
self.storage
.save_fingerprint(path, ¤t_fp)
.await
.map_err(|e| format!("Storage error: {}", e))?;
files_analyzed += 1;
invalidated_files.push(path.clone());
}
} else {
self.storage
.save_fingerprint(path, ¤t_fp)
.await
.map_err(|e| format!("Storage error: {}", e))?;
files_analyzed += 1;
invalidated_files.push(path.clone());
}
}
let result = AnalysisResult {
files_analyzed,
files_skipped,
_edges_created: 0,
duration: start.elapsed(),
invalidated_files,
reanalysis_order: Vec::new(),
};
self.last_analysis_result = Some(result.clone());
Ok(result)
}
async fn verify_fingerprint_exists(&self, relative_path: &str) -> bool {
let full_path = self.temp_dir.path().join(relative_path);
self.storage
.load_fingerprint(&full_path)
.await
.ok()
.flatten()
.is_some()
}
fn test_dir(&self) -> &Path {
self.temp_dir.path()
}
}
fn create_test_rust_file(name: &str, imports: &[&str]) -> String {
let mut content = String::new();
for import in imports {
content.push_str(&format!("use {};\n", import));
}
content.push('\n');
content.push_str(&format!("pub fn {}() {{\n", name));
content.push_str(" println!(\"Hello from {}\");\n");
content.push_str("}\n");
content
}
fn create_test_graph(edges: &[(&str, &str)]) -> DependencyGraph {
let mut graph = DependencyGraph::new();
for (from, to) in edges {
let edge = DependencyEdge::new(
PathBuf::from(from),
PathBuf::from(to),
DependencyType::Import,
);
graph.add_edge(edge);
}
graph
}
#[tokio::test]
async fn test_initial_analysis_creates_baseline() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file(
"src/main.rs",
&create_test_rust_file("main", &["crate::utils", "crate::config"]),
)
.await;
fixture
.create_file(
"src/utils.rs",
&create_test_rust_file("utils", &["std::collections::HashMap"]),
)
.await;
fixture
.create_file("src/config.rs", &create_test_rust_file("config", &[]))
.await;
let result = fixture.run_initial_analysis().await.unwrap();
assert_eq!(result.files_analyzed, 3);
assert_eq!(result.files_skipped, 0);
assert!(fixture.verify_fingerprint_exists("src/main.rs").await);
assert!(fixture.verify_fingerprint_exists("src/utils.rs").await);
assert!(fixture.verify_fingerprint_exists("src/config.rs").await);
}
#[tokio::test]
async fn test_no_changes_skips_reanalysis() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/lib.rs", &create_test_rust_file("lib", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 0);
assert_eq!(result.files_skipped, 1);
assert!(result.invalidated_files.is_empty());
}
#[tokio::test]
async fn test_single_file_change_triggers_reanalysis() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/b.rs", &create_test_rust_file("b", &["std::fmt"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed > 0);
assert!(
result
.invalidated_files
.contains(&fixture.test_dir().join("src/b.rs"))
);
}
#[tokio::test]
async fn test_multiple_file_changes_batched() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &[]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/a.rs", &create_test_rust_file("a", &["std::io"]))
.await;
fixture
.modify_file("src/b.rs", &create_test_rust_file("b", &["std::fs"]))
.await;
fixture
.modify_file("src/c.rs", &create_test_rust_file("c", &["std::env"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 3);
assert_eq!(result.invalidated_files.len(), 3);
}
#[tokio::test]
async fn test_storage_persistence_across_sessions() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/main.rs", &create_test_rust_file("main", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
let graph = DependencyGraph::new();
fixture.storage.save_full_graph(&graph).await.unwrap();
let loaded_graph = fixture.storage.load_full_graph().await.unwrap();
assert_eq!(loaded_graph.node_count(), graph.node_count());
assert_eq!(loaded_graph.edge_count(), graph.edge_count());
}
#[tokio::test]
async fn test_incremental_update_updates_storage() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/lib.rs", &create_test_rust_file("lib", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
let old_fp = fixture
.storage
.load_fingerprint(&fixture.test_dir().join("src/lib.rs"))
.await
.unwrap()
.unwrap();
fixture
.modify_file("src/lib.rs", &create_test_rust_file("lib", &["std::io"]))
.await;
fixture.run_incremental_update().await.unwrap();
let new_fp = fixture
.storage
.load_fingerprint(&fixture.test_dir().join("src/lib.rs"))
.await
.unwrap()
.unwrap();
assert_ne!(
old_fp.fingerprint().as_slice(),
new_fp.fingerprint().as_slice()
);
}
#[tokio::test]
async fn test_deleted_file_handled_gracefully() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file(
"src/main.rs",
&create_test_rust_file("main", &["crate::utils"]),
)
.await;
fixture
.create_file("src/utils.rs", &create_test_rust_file("utils", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture.delete_file("src/utils.rs").await;
let result = fixture.run_incremental_update().await;
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_detect_file_addition() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &[]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed > 0);
assert!(
result
.invalidated_files
.contains(&fixture.test_dir().join("src/c.rs"))
);
}
#[tokio::test]
async fn test_detect_file_modification() {
let mut fixture = IncrementalTestFixture::new().await;
fixture.create_file("src/lib.rs", "fn old() {}").await;
fixture.run_initial_analysis().await.unwrap();
fixture.modify_file("src/lib.rs", "fn new() {}").await;
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 1);
assert!(
result
.invalidated_files
.contains(&fixture.test_dir().join("src/lib.rs"))
);
}
#[tokio::test]
async fn test_detect_file_deletion() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/temp.rs", &create_test_rust_file("temp", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture.delete_file("src/temp.rs").await;
let result = fixture.run_incremental_update().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_no_change_detection_identical_content() {
let mut fixture = IncrementalTestFixture::new().await;
let content = create_test_rust_file("test", &[]);
fixture.create_file("src/test.rs", &content).await;
fixture.run_initial_analysis().await.unwrap();
fixture.modify_file("src/test.rs", &content).await;
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 0);
assert_eq!(result.files_skipped, 1);
}
#[tokio::test]
async fn test_whitespace_changes_detected() {
let mut fixture = IncrementalTestFixture::new().await;
fixture.create_file("src/lib.rs", "fn test() {}").await;
fixture.run_initial_analysis().await.unwrap();
fixture.modify_file("src/lib.rs", "fn test() { }").await;
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 1);
}
#[tokio::test]
async fn test_multiple_changes_same_file() {
let mut fixture = IncrementalTestFixture::new().await;
fixture.create_file("src/lib.rs", "// v1").await;
fixture.run_initial_analysis().await.unwrap();
fixture.modify_file("src/lib.rs", "// v2").await;
let result1 = fixture.run_incremental_update().await.unwrap();
assert_eq!(result1.files_analyzed, 1);
fixture.modify_file("src/lib.rs", "// v3").await;
let result2 = fixture.run_incremental_update().await.unwrap();
assert_eq!(result2.files_analyzed, 1);
}
#[tokio::test]
async fn test_change_leaf_file_no_propagation() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &["crate::c"]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/c.rs", &create_test_rust_file("c", &["std::io"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
let invalidated = result.invalidated_files;
assert!(invalidated.iter().any(|p| p.ends_with("c.rs")));
}
#[tokio::test]
async fn test_change_root_file_invalidates_tree() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &["crate::c"]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/a.rs",
&create_test_rust_file("a", &["crate::b", "std::env"]),
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.invalidated_files.iter().any(|p| p.ends_with("a.rs")));
}
#[tokio::test]
async fn test_change_middle_file_partial_invalidation() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &["crate::c"]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture
.create_file("src/d.rs", &create_test_rust_file("d", &["crate::b"]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/b.rs",
&create_test_rust_file("b", &["crate::c", "std::io"]),
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.invalidated_files.iter().any(|p| p.ends_with("b.rs")));
}
#[tokio::test]
async fn test_diamond_dependency_invalidation() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file(
"src/a.rs",
&create_test_rust_file("a", &["crate::b", "crate::c"]),
)
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &["crate::d"]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &["crate::d"]))
.await;
fixture
.create_file("src/d.rs", &create_test_rust_file("d", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/a.rs",
&create_test_rust_file("a", &["crate::b", "crate::c", "std::env"]),
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.invalidated_files.iter().any(|p| p.ends_with("a.rs")));
}
#[tokio::test]
async fn test_multiple_simultaneous_changes() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &["crate::d"]))
.await;
fixture
.create_file("src/d.rs", &create_test_rust_file("d", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/a.rs",
&create_test_rust_file("a", &["crate::b", "std::io"]),
)
.await;
fixture
.modify_file(
"src/c.rs",
&create_test_rust_file("c", &["crate::d", "std::fs"]),
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed >= 2);
}
#[tokio::test]
async fn test_circular_dependency_handled() {
let _fixture = IncrementalTestFixture::new().await;
let graph = create_test_graph(&[("src/a.rs", "src/b.rs"), ("src/b.rs", "src/a.rs")]);
assert_eq!(graph.edge_count(), 2);
}
#[tokio::test]
async fn test_weak_dependency_not_propagated() {
let graph = create_test_graph(&[("src/main.rs", "src/lib.rs")]);
assert_eq!(graph.edge_count(), 1);
}
#[tokio::test]
async fn test_symbol_level_invalidation() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", "use crate::b::foo;\n\npub fn main() { foo(); }")
.await;
fixture
.create_file("src/b.rs", "pub fn foo() {}\npub fn bar() {}")
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/b.rs",
"pub fn foo() {}\npub fn bar() { println!(\"changed\"); }",
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.invalidated_files.iter().any(|p| p.ends_with("b.rs")));
}
#[tokio::test]
async fn test_topological_sort_basic() {
let graph = create_test_graph(&[("src/a.rs", "src/b.rs"), ("src/b.rs", "src/c.rs")]);
assert_eq!(graph.edge_count(), 2);
assert_eq!(graph.node_count(), 3);
}
#[tokio::test]
async fn test_topological_sort_parallel_branches() {
let graph = create_test_graph(&[
("src/a.rs", "src/b.rs"),
("src/a.rs", "src/c.rs"),
("src/b.rs", "src/d.rs"),
("src/c.rs", "src/d.rs"),
]);
assert_eq!(graph.edge_count(), 4);
assert_eq!(graph.node_count(), 4);
}
#[tokio::test]
async fn test_topological_sort_multiple_roots() {
let graph = create_test_graph(&[("src/a.rs", "src/c.rs"), ("src/b.rs", "src/c.rs")]);
assert_eq!(graph.edge_count(), 2);
assert_eq!(graph.node_count(), 3);
}
#[tokio::test]
async fn test_topological_sort_detects_cycles() {
let graph = create_test_graph(&[
("src/a.rs", "src/b.rs"),
("src/b.rs", "src/c.rs"),
("src/c.rs", "src/a.rs"),
]);
assert_eq!(graph.edge_count(), 3);
}
#[tokio::test]
async fn test_reanalysis_respects_dependencies() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &["crate::b"]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &["crate::c"]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/b.rs",
&create_test_rust_file("b", &["crate::c", "std::io"]),
)
.await;
let result = fixture.run_incremental_update().await.unwrap();
let _order = result.reanalysis_order;
assert!(result.files_analyzed > 0);
}
#[tokio::test]
async fn test_independent_files_analyzed_parallel() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &[]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture
.create_file("src/c.rs", &create_test_rust_file("c", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/a.rs", &create_test_rust_file("a", &["std::io"]))
.await;
fixture
.modify_file("src/b.rs", &create_test_rust_file("b", &["std::fs"]))
.await;
fixture
.modify_file("src/c.rs", &create_test_rust_file("c", &["std::env"]))
.await;
let start = Instant::now();
let result = fixture.run_incremental_update().await.unwrap();
let _duration = start.elapsed();
assert_eq!(result.files_analyzed, 3);
}
#[cfg(feature = "parallel")]
#[tokio::test]
async fn test_rayon_parallel_execution() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..10 {
fixture
.create_file(
&format!("src/file{}.rs", i),
&create_test_rust_file(&format!("file{}", i), &[]),
)
.await;
}
fixture.run_initial_analysis().await.unwrap();
for i in 0..10 {
fixture
.modify_file(
&format!("src/file{}.rs", i),
&create_test_rust_file(&format!("file{}", i), &["std::io"]),
)
.await;
}
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 10);
}
#[tokio::test]
async fn test_tokio_async_execution() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..10 {
fixture
.create_file(
&format!("src/async{}.rs", i),
&create_test_rust_file(&format!("async{}", i), &[]),
)
.await;
}
fixture.run_initial_analysis().await.unwrap();
for i in 0..10 {
fixture
.modify_file(
&format!("src/async{}.rs", i),
&create_test_rust_file(&format!("async{}", i), &["std::fs"]),
)
.await;
}
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 10);
}
#[tokio::test]
async fn test_sequential_fallback() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/a.rs", &create_test_rust_file("a", &[]))
.await;
fixture
.create_file("src/b.rs", &create_test_rust_file("b", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/a.rs", &create_test_rust_file("a", &["std::io"]))
.await;
fixture
.modify_file("src/b.rs", &create_test_rust_file("b", &["std::fs"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert_eq!(result.files_analyzed, 2);
}
#[tokio::test]
async fn test_concurrency_limit_respected() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..100 {
fixture
.create_file(
&format!("src/f{}.rs", i),
&create_test_rust_file(&format!("f{}", i), &[]),
)
.await;
}
fixture.run_initial_analysis().await.unwrap();
}
#[tokio::test]
async fn test_concurrent_storage_access_safe() {
let fixture = IncrementalTestFixture::new().await;
let _storage_ref = &fixture.storage;
}
#[tokio::test]
async fn test_incremental_faster_than_full() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..1000 {
fixture
.create_file(
&format!("src/perf{}.rs", i),
&create_test_rust_file(&format!("perf{}", i), &[]),
)
.await;
}
let full_start = Instant::now();
fixture.run_initial_analysis().await.unwrap();
let full_duration = full_start.elapsed();
for i in 0..10 {
fixture
.modify_file(
&format!("src/perf{}.rs", i),
&create_test_rust_file(&format!("perf{}", i), &["std::io"]),
)
.await;
}
let inc_start = Instant::now();
fixture.run_incremental_update().await.unwrap();
let inc_duration = inc_start.elapsed();
println!("Full: {:?}, Incremental: {:?}", full_duration, inc_duration);
}
#[tokio::test]
async fn test_incremental_overhead_under_10ms() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/single.rs", &create_test_rust_file("single", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file(
"src/single.rs",
&create_test_rust_file("single", &["std::io"]),
)
.await;
let start = Instant::now();
fixture.run_incremental_update().await.unwrap();
let duration = start.elapsed();
println!("Incremental update duration: {:?}", duration);
}
#[tokio::test]
async fn test_cache_hit_rate_above_90_percent() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..100 {
fixture
.create_file(
&format!("src/cache{}.rs", i),
&create_test_rust_file(&format!("cache{}", i), &[]),
)
.await;
}
fixture.run_initial_analysis().await.unwrap();
for i in 0..5 {
fixture
.modify_file(
&format!("src/cache{}.rs", i),
&create_test_rust_file(&format!("cache{}", i), &["std::io"]),
)
.await;
}
let result = fixture.run_incremental_update().await.unwrap();
let total = result.files_analyzed + result.files_skipped;
let hit_rate = if total > 0 {
(result.files_skipped as f64 / total as f64) * 100.0
} else {
0.0
};
println!("Cache hit rate: {:.2}%", hit_rate);
}
#[cfg(feature = "parallel")]
#[tokio::test]
async fn test_parallel_speedup_with_rayon() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..100 {
fixture
.create_file(
&format!("src/par{}.rs", i),
&create_test_rust_file(&format!("par{}", i), &[]),
)
.await;
}
fixture.run_initial_analysis().await.unwrap();
for i in 0..100 {
fixture
.modify_file(
&format!("src/par{}.rs", i),
&create_test_rust_file(&format!("par{}", i), &["std::io"]),
)
.await;
}
let result = fixture.run_incremental_update().await.unwrap();
println!("Parallel duration: {:?}", result.duration);
}
#[tokio::test]
async fn test_large_graph_performance() {
let mut fixture = IncrementalTestFixture::new().await;
for i in 0..100 {
fixture
.create_file(
&format!("src/large{}.rs", i),
&create_test_rust_file(&format!("large{}", i), &[]),
)
.await;
}
let start = Instant::now();
fixture.run_initial_analysis().await.unwrap();
let duration = start.elapsed();
println!("Large graph analysis duration: {:?}", duration);
}
#[tokio::test]
async fn test_inmemory_backend_integration() {
let mut fixture = IncrementalTestFixture::new_with_backend(BackendType::InMemory).await;
fixture
.create_file("src/mem.rs", &create_test_rust_file("mem", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/mem.rs", &create_test_rust_file("mem", &["std::io"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed > 0);
}
#[cfg(feature = "postgres-backend")]
#[tokio::test]
async fn test_postgres_backend_integration() {
if std::env::var("TEST_DATABASE_URL").is_err() {
eprintln!("Skipping Postgres test: TEST_DATABASE_URL not set");
return;
}
let mut fixture = IncrementalTestFixture::new_with_backend(BackendType::Postgres).await;
fixture
.create_file("src/pg.rs", &create_test_rust_file("pg", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/pg.rs", &create_test_rust_file("pg", &["std::fs"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed > 0);
}
#[cfg(feature = "d1-backend")]
#[tokio::test]
async fn test_d1_backend_integration() {
if std::env::var("TEST_CF_ACCOUNT_ID").is_err() {
eprintln!("Skipping D1 test: TEST_CF_ACCOUNT_ID not set");
return;
}
let mut fixture = IncrementalTestFixture::new_with_backend(BackendType::D1).await;
fixture
.create_file("src/d1.rs", &create_test_rust_file("d1", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture
.modify_file("src/d1.rs", &create_test_rust_file("d1", &["std::env"]))
.await;
let result = fixture.run_incremental_update().await.unwrap();
assert!(result.files_analyzed > 0);
}
#[tokio::test]
async fn test_backend_error_handling() {
let fixture = IncrementalTestFixture::new().await;
let result = fixture
.storage
.load_fingerprint(Path::new("nonexistent"))
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_graph_cycle_detection() {
let graph = create_test_graph(&[
("src/a.rs", "src/b.rs"),
("src/b.rs", "src/c.rs"),
("src/c.rs", "src/a.rs"),
]);
assert_eq!(graph.edge_count(), 3);
}
#[tokio::test]
async fn test_extraction_error_during_reanalysis() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/good.rs", &create_test_rust_file("good", &[]))
.await;
fixture.create_file("src/bad.rs", "fn {{{").await;
let result = fixture.run_initial_analysis().await;
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_missing_file_during_reanalysis() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/temp.rs", &create_test_rust_file("temp", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
fixture.delete_file("src/temp.rs").await;
let result = fixture.run_incremental_update().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_invalid_fingerprint_in_storage() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/corrupt.rs", &create_test_rust_file("corrupt", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
}
#[tokio::test]
async fn test_concurrent_modification_conflict() {
let mut fixture = IncrementalTestFixture::new().await;
fixture
.create_file("src/conflict.rs", &create_test_rust_file("conflict", &[]))
.await;
fixture.run_initial_analysis().await.unwrap();
}
#[tokio::test]
async fn test_partial_graph_recovery() {
let fixture = IncrementalTestFixture::new().await;
let graph = DependencyGraph::new();
fixture.storage.save_full_graph(&graph).await.unwrap();
}