use splice::graph::CodeGraph;
use splice::relationships::{Relationship, RelationshipCache};
use splice::symbol::Language;
use sqlitegraph::SnapshotId;
use std::io::Write;
use std::path::Path;
use std::time::Instant;
use tempfile::TempDir;
struct TestGraphBuilder {
graph: CodeGraph,
temp_dir: TempDir,
symbols_per_file: usize,
}
impl TestGraphBuilder {
fn new() -> std::io::Result<Self> {
let temp_dir = TempDir::new()?;
let db_path = temp_dir.path().join("test_graph.db");
let graph = CodeGraph::open(&db_path).expect("Failed to open graph");
Ok(Self {
graph,
temp_dir,
symbols_per_file: 10,
})
}
fn graph_mut(&mut self) -> &mut CodeGraph {
&mut self.graph
}
fn graph(&self) -> &CodeGraph {
&self.graph
}
fn create_file_with_symbols(
&mut self,
file_index: usize,
num_symbols: usize,
) -> std::io::Result<std::path::PathBuf> {
let file_path = self
.temp_dir
.path()
.join(format!("test_file_{}.rs", file_index));
let mut file = std::fs::File::create(&file_path)?;
for i in 0..num_symbols {
let symbol_name = format!("function_{}_{}", file_index, i);
let source_code = format!(
r#"
/// Documentation for {}
pub fn {}() {{
// Implementation
}}
"#,
symbol_name, symbol_name
);
file.write_all(source_code.as_bytes())?;
let byte_start = source_code.len() * i;
let byte_end = byte_start + source_code.len();
let line_start = i * 5 + 1;
let line_end = line_start + 4;
self.graph
.store_symbol_with_file_and_language(
&file_path,
&symbol_name,
"function",
Language::Rust,
byte_start,
byte_end,
line_start,
line_end,
0,
0,
)
.expect("Failed to store symbol");
}
Ok(file_path)
}
fn create_call_chain(
&mut self,
file_path: &Path,
chain_name: &str,
depth: usize,
) -> sqlitegraph::NodeId {
let mut node_ids = Vec::new();
for i in 0..depth {
let symbol_name = format!("{}_link_{}", chain_name, i);
let byte_start = i * 100;
let byte_end = byte_start + 80;
let line_start = i * 5 + 1;
let line_end = line_start + 4;
let node_id = self
.graph
.store_symbol_with_file_and_language(
file_path,
&symbol_name,
"function",
Language::Rust,
byte_start,
byte_end,
line_start,
line_end,
0,
0,
)
.expect("Failed to store chain symbol");
node_ids.push(node_id);
}
node_ids[0]
}
fn create_many_callers(&mut self, file_path: &Path, num_callers: usize) -> sqlitegraph::NodeId {
let target_name = "target_function";
let target_id = self
.graph
.store_symbol_with_file_and_language(
file_path,
target_name,
"function",
Language::Rust,
0,
100,
1,
5,
0,
0,
)
.expect("Failed to store target symbol");
for i in 0..num_callers {
let caller_name = format!("caller_{}", i);
let byte_start = 100 + i * 80;
let byte_end = byte_start + 80;
let line_start = 6 + i * 5;
let line_end = line_start + 4;
self.graph
.store_symbol_with_file_and_language(
file_path,
&caller_name,
"function",
Language::Rust,
byte_start,
byte_end,
line_start,
line_end,
0,
0,
)
.expect("Failed to store caller symbol");
}
target_id
}
fn create_cycle(&mut self, file_path: &Path) -> Vec<sqlitegraph::NodeId> {
let mut node_ids = Vec::new();
for (i, name) in ["cycle_a", "cycle_b", "cycle_c"].iter().enumerate() {
let byte_start = i * 100;
let byte_end = byte_start + 80;
let line_start = i * 5 + 1;
let line_end = line_start + 4;
let node_id = self
.graph
.store_symbol_with_file_and_language(
file_path,
name,
"function",
Language::Rust,
byte_start,
byte_end,
line_start,
line_end,
0,
0,
)
.expect("Failed to store cycle symbol");
node_ids.push(node_id);
}
node_ids
}
}
fn small_graph() -> (CodeGraph, TempDir) {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
for file_index in 0..5 {
builder
.create_file_with_symbols(file_index, 10)
.expect("Failed to create file");
}
(builder.graph, builder.temp_dir)
}
fn medium_graph() -> (CodeGraph, TempDir) {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
for file_index in 0..20 {
builder
.create_file_with_symbols(file_index, 10)
.expect("Failed to create file");
}
(builder.graph, builder.temp_dir)
}
fn large_graph() -> (CodeGraph, TempDir) {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
for file_index in 0..100 {
builder
.create_file_with_symbols(file_index, 10)
.expect("Failed to create file");
}
(builder.graph, builder.temp_dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_small_graph_creation() {
let (_graph, _temp_dir) = small_graph();
assert!(true, "Small graph created without errors");
}
#[test]
fn test_medium_graph_creation() {
let (_graph, _temp_dir) = medium_graph();
assert!(true, "Medium graph created without errors");
}
#[test]
fn test_large_graph_creation() {
let (_graph, _temp_dir) = large_graph();
assert!(true, "Large graph created without errors");
}
#[test]
fn test_call_chain_creation() {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 3)
.expect("Failed to create file");
let first_node = builder.create_call_chain(&file_path, "test_chain", 3);
let snapshot_id = SnapshotId::current();
let backend = builder.graph().inner().expect("Failed to get backend");
let node = backend
.get_node(snapshot_id, first_node.as_i64())
.expect("Failed to retrieve node");
assert!(node.name.contains("test_chain"));
}
#[test]
fn test_many_callers_creation() {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 1)
.expect("Failed to create file");
let target_id = builder.create_many_callers(&file_path, 150);
let snapshot_id = SnapshotId::current();
let backend = builder.graph().inner().expect("Failed to get backend");
let node = backend
.get_node(snapshot_id, target_id.as_i64())
.expect("Failed to retrieve node");
assert_eq!(node.name, "target_function");
}
#[test]
fn test_cycle_creation() {
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 3)
.expect("Failed to create file");
let cycle_nodes = builder.create_cycle(&file_path);
assert_eq!(cycle_nodes.len(), 3);
let snapshot_id = SnapshotId::current();
let backend = builder.graph().inner().expect("Failed to get backend");
for node_id in cycle_nodes {
let node = backend
.get_node(snapshot_id, node_id.as_i64())
.expect("Failed to retrieve node");
assert!(node.name.contains("cycle_"));
}
}
#[test]
fn test_get_callers_small_graph() {
use splice::relationships::get_callers;
let (graph, temp_dir) = small_graph();
let file_path = temp_dir.path().join("test_file_0.rs");
let file_path_str = file_path.to_str().expect("Invalid path");
let node_id = graph
.find_symbol_in_file(file_path_str, "function_0_0")
.expect("Symbol not found");
let mut cache = RelationshipCache::new();
let start = Instant::now();
let result = get_callers(&graph, node_id, &mut cache);
let duration = start.elapsed();
assert!(result.is_ok(), "get_callers failed: {:?}", result.err());
assert!(
duration.as_millis() < 10,
"get_callers on small graph took {}ms, expected < 10ms",
duration.as_millis()
);
}
#[test]
fn test_get_callers_large_graph() {
use splice::relationships::get_callers;
let (graph, temp_dir) = large_graph();
let file_path = temp_dir.path().join("test_file_0.rs");
let file_path_str = file_path.to_str().expect("Invalid path");
let node_id = graph
.find_symbol_in_file(file_path_str, "function_0_0")
.expect("Symbol not found");
let mut cache = RelationshipCache::new();
let start = Instant::now();
let result = get_callers(&graph, node_id, &mut cache);
let duration = start.elapsed();
assert!(result.is_ok(), "get_callers failed: {:?}", result.err());
assert!(
duration.as_millis() < 100,
"get_callers on large graph took {}ms, expected < 100ms",
duration.as_millis()
);
}
#[test]
fn test_get_callees_large_graph() {
use splice::relationships::get_callees;
let (graph, temp_dir) = large_graph();
let file_path = temp_dir.path().join("test_file_0.rs");
let file_path_str = file_path.to_str().expect("Invalid path");
let node_id = graph
.find_symbol_in_file(file_path_str, "function_0_0")
.expect("Symbol not found");
let mut cache = RelationshipCache::new();
let start = Instant::now();
let result = get_callees(&graph, node_id, &mut cache);
let duration = start.elapsed();
assert!(result.is_ok(), "get_callees failed: {:?}", result.err());
assert!(
duration.as_millis() < 100,
"get_callees on large graph took {}ms, expected < 100ms",
duration.as_millis()
);
}
#[test]
fn test_threshold_enforcement() {
use splice::relationships::get_callers;
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 1)
.expect("Failed to create file");
let target_id = builder.create_many_callers(&file_path, 150);
let mut cache = RelationshipCache::new();
let result = get_callers(builder.graph(), target_id, &mut cache);
assert!(result.is_ok(), "get_callers should succeed");
}
#[test]
fn test_imports_exports_performance() {
use splice::relationships::{get_exports, get_imports};
let (graph, temp_dir) = large_graph();
let file_path = temp_dir.path().join("test_file_0.rs");
let mut cache = RelationshipCache::new();
let start = Instant::now();
let result_imports = get_imports(&graph, &file_path, &mut cache);
let result_exports = get_exports(&graph, &file_path, &mut cache);
let duration = start.elapsed();
assert!(
result_imports.is_ok(),
"get_imports failed: {:?}",
result_imports.err()
);
assert!(
result_exports.is_ok(),
"get_exports failed: {:?}",
result_exports.err()
);
assert!(
duration.as_millis() < 50,
"imports/exports query took {}ms, expected < 50ms",
duration.as_millis()
);
}
#[test]
fn test_session_caching() {
use splice::relationships::get_callers;
let (graph, temp_dir) = small_graph();
let file_path = temp_dir.path().join("test_file_0.rs");
let file_path_str = file_path.to_str().expect("Invalid path");
let node_id = graph
.find_symbol_in_file(file_path_str, "function_0_0")
.expect("Symbol not found");
let mut cache = RelationshipCache::new();
let start1 = Instant::now();
let result1 = get_callers(&graph, node_id, &mut cache);
let _duration1 = start1.elapsed();
assert!(result1.is_ok(), "First query failed");
let start2 = Instant::now();
let result2 = get_callers(&graph, node_id, &mut cache);
let _duration2 = start2.elapsed();
assert!(result2.is_ok(), "Second query failed");
let cache_key = format!("caller:{}", node_id.as_i64());
assert!(
cache.contains_key(&cache_key),
"Cache should contain the query result"
);
cache.clear();
assert!(
!cache.contains_key(&cache_key),
"Cache should be empty after clear"
);
}
#[test]
fn test_relationship_cache_methods() {
let mut cache = RelationshipCache::new();
let rel = Relationship {
rel_type: "caller".to_string(),
name: "test_function".to_string(),
kind: "function".to_string(),
file_path: "/test/path.rs".to_string(),
line_start: 10,
byte_start: 100,
byte_end: 200,
};
cache.set("test:key".to_string(), vec![rel.clone()]);
let retrieved = cache.get("test:key");
assert_eq!(retrieved, Some(&vec![rel]));
assert!(cache.contains_key("test:key"));
assert!(!cache.contains_key("nonexistent:key"));
cache.clear();
assert!(!cache.contains_key("test:key"));
assert_eq!(cache.get("test:key"), None);
}
#[test]
fn test_circular_dependency_detection() {
use splice::relationships::get_callers;
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 3)
.expect("Failed to create file");
let cycle_nodes = builder.create_cycle(&file_path);
let node_id = cycle_nodes[0];
let mut cache = RelationshipCache::new();
let result = get_callers(builder.graph(), node_id, &mut cache);
assert!(result.is_ok(), "get_callers should succeed");
}
#[test]
fn test_deep_chain_handling() {
use splice::relationships::get_callers;
let mut builder = TestGraphBuilder::new().expect("Failed to create builder");
let file_path = builder
.create_file_with_symbols(0, 26)
.expect("Failed to create file");
let first_node = builder.create_call_chain(&file_path, "deep_chain", 26);
let mut cache = RelationshipCache::new();
let start = Instant::now();
let result = get_callers(builder.graph(), first_node, &mut cache);
let duration = start.elapsed();
assert!(result.is_ok(), "get_callers should succeed on deep chain");
assert!(
duration.as_millis() < 100,
"Deep chain query took {}ms, expected < 100ms",
duration.as_millis()
);
}
}