use sqlitegraph::{GraphEdgeCreate, GraphEntityCreate, SqliteGraph, SqliteGraphError};
use std::time::{Duration, Instant};
fn node_count(graph: &SqliteGraph) -> Result<i64, SqliteGraphError> {
let ids = graph.list_entity_ids()?;
Ok(ids.len() as i64)
}
fn edge_count(graph: &SqliteGraph) -> Result<i64, SqliteGraphError> {
let entity_ids = graph.list_entity_ids()?;
let mut total_edges = 0;
for &id in &entity_ids {
let outgoing = graph.query().outgoing(id)?;
total_edges += outgoing.len();
}
Ok(total_edges as i64)
}
fn warm_cache(graph: &SqliteGraph) -> Result<(), SqliteGraphError> {
let entity_ids = graph.list_entity_ids()?;
for &id in &entity_ids {
let _ = graph.query().outgoing(id);
let _ = graph.query().incoming(id);
}
Ok(())
}
fn insert_entity(graph: &SqliteGraph, create: GraphEntityCreate) -> Result<i64, SqliteGraphError> {
let entity = sqlitegraph::GraphEntity {
id: 0, kind: create.kind,
name: create.name,
file_path: create.file_path,
data: create.data,
};
graph.insert_entity(&entity)
}
fn insert_edge(graph: &SqliteGraph, create: GraphEdgeCreate) -> Result<i64, SqliteGraphError> {
let edge = sqlitegraph::GraphEdge {
id: 0, from_id: create.from_id,
to_id: create.to_id,
edge_type: create.edge_type,
data: create.data,
};
graph.insert_edge(&edge)
}
fn create_test_graph() -> Result<SqliteGraph, SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let entity1 = GraphEntityCreate {
kind: "function".to_string(),
name: "main".to_string(),
file_path: Some("src/main.rs".to_string()),
data: serde_json::json!({"line": 10}),
};
let entity2 = GraphEntityCreate {
kind: "function".to_string(),
name: "helper".to_string(),
file_path: Some("src/helper.rs".to_string()),
data: serde_json::json!({"line": 5}),
};
let entity3 = GraphEntityCreate {
kind: "variable".to_string(),
name: "config".to_string(),
file_path: Some("src/config.rs".to_string()),
data: serde_json::json!({"type": "String"}),
};
let id1 = insert_entity(&graph, entity1)?;
let id2 = insert_entity(&graph, entity2)?;
let id3 = insert_entity(&graph, entity3)?;
let edge1 = GraphEdgeCreate {
from_id: id1,
to_id: id2,
edge_type: "calls".to_string(),
data: serde_json::json!({"line": 15}),
};
let edge2 = GraphEdgeCreate {
from_id: id1,
to_id: id3,
edge_type: "reads".to_string(),
data: serde_json::json!({"line": 12}),
};
insert_edge(&graph, edge1)?;
insert_edge(&graph, edge2)?;
Ok(graph)
}
#[test]
fn test_empty_graph_snapshot() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count(), 0);
assert_eq!(snapshot.edge_count(), 0);
assert!(!snapshot.contains_node(1));
assert!(!snapshot.contains_node(999));
assert_eq!(snapshot.get_outgoing(1), None);
assert_eq!(snapshot.get_incoming(1), None);
Ok(())
}
#[test]
fn test_empty_graph_snapshot_after_writes() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
warm_cache(&graph)?;
let snapshot_empty = graph.acquire_snapshot()?;
assert_eq!(snapshot_empty.node_count(), 0);
for i in 0..10 {
insert_entity(
&graph,
GraphEntityCreate {
kind: "test".to_string(),
name: format!("test_{}", i),
file_path: Some(format!("test_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
}
assert_eq!(snapshot_empty.node_count(), 0);
assert_eq!(snapshot_empty.edge_count(), 0);
warm_cache(&graph)?;
let snapshot_populated = graph.acquire_snapshot()?;
assert!(snapshot_populated.node_count() > 0);
Ok(())
}
#[test]
fn test_multiple_empty_snapshots() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
warm_cache(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let snapshot2 = graph.acquire_snapshot()?;
let snapshot3 = graph.acquire_snapshot()?;
assert_eq!(snapshot1.node_count(), 0);
assert_eq!(snapshot2.node_count(), 0);
assert_eq!(snapshot3.node_count(), 0);
Ok(())
}
#[test]
fn test_large_graph_snapshot_memory() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let num_nodes = 10_000;
println!("Creating {} nodes...", num_nodes);
for i in 0..num_nodes {
insert_entity(
&graph,
GraphEntityCreate {
kind: "large".to_string(),
name: format!("large_node_{}", i),
file_path: Some(format!("large_{}.rs", i)),
data: serde_json::json!({"index": i}),
},
)?;
}
println!("Warming cache...");
warm_cache(&graph)?;
let total_nodes = node_count(&graph)?;
println!("Total nodes: {}", total_nodes);
println!("Acquiring snapshots...");
let snapshot1 = graph.acquire_snapshot()?;
let snapshot2 = graph.acquire_snapshot()?;
let snapshot3 = graph.acquire_snapshot()?;
assert_eq!(snapshot1.node_count() as i64, total_nodes);
assert_eq!(snapshot2.node_count() as i64, total_nodes);
assert_eq!(snapshot3.node_count() as i64, total_nodes);
println!("All snapshots consistent with {} nodes", total_nodes);
Ok(())
}
#[test]
fn test_large_graph_snapshot_performance() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let num_nodes = 5_000;
for i in 0..num_nodes {
insert_entity(
&graph,
GraphEntityCreate {
kind: "perf".to_string(),
name: format!("perf_node_{}", i),
file_path: Some(format!("perf_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
}
warm_cache(&graph)?;
let start = Instant::now();
let snapshot = graph.acquire_snapshot()?;
let duration = start.elapsed();
println!(
"Snapshot acquisition for {} nodes: {:?}",
num_nodes, duration
);
assert!(snapshot.node_count() > 0);
assert!(duration < Duration::from_secs(5));
Ok(())
}
#[test]
fn test_rapid_snapshot_lifecycle() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let iterations = 10_000;
println!("Creating {} snapshots...", iterations);
let start = Instant::now();
for i in 0..iterations {
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.node_count() > 0, "Snapshot {} invalid", i);
}
let duration = start.elapsed();
println!(
"Created and dropped {} snapshots in {:?}",
iterations, duration
);
let final_snapshot = graph.acquire_snapshot()?;
assert!(final_snapshot.node_count() > 0);
Ok(())
}
#[test]
fn test_rapid_snapshot_creation() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let mut snapshots = Vec::new();
let count = 1_000;
let start = Instant::now();
for _ in 0..count {
snapshots.push(graph.acquire_snapshot()?);
}
let duration = start.elapsed();
println!("Created {} snapshots in {:?}", count, duration);
for (i, snapshot) in snapshots.iter().enumerate() {
assert!(snapshot.node_count() > 0, "Snapshot {} invalid", i);
}
assert!(duration < Duration::from_secs(1));
Ok(())
}
#[test]
fn test_snapshot_clone_stress() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = Arc::new(graph.acquire_snapshot()?);
let start = Instant::now();
for _ in 0..10_000 {
let _clone = Arc::clone(&snapshot);
}
let duration = start.elapsed();
println!("10K Arc clones in {:?}", duration);
assert!(duration < Duration::from_millis(100));
Ok(())
}
#[test]
fn test_snapshot_during_transaction_commit() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let count1 = snapshot1.node_count();
for i in 0..5 {
insert_entity(
&graph,
GraphEntityCreate {
kind: "commit_test".to_string(),
name: format!("commit_func_{}", i),
file_path: Some(format!("commit_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
}
assert_eq!(snapshot1.node_count(), count1);
warm_cache(&graph)?;
let snapshot2 = graph.acquire_snapshot()?;
assert!(snapshot2.node_count() > count1);
Ok(())
}
#[test]
fn test_snapshot_isolation_with_deletes() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
let original_count = snapshot.node_count();
assert!(original_count > 0);
let entity_ids = graph.list_entity_ids()?;
if !entity_ids.is_empty() {
graph.delete_entity(entity_ids[0])?;
}
let new_count = node_count(&graph)?;
assert!(new_count < original_count as i64);
assert_eq!(snapshot.node_count(), original_count);
Ok(())
}
#[test]
fn test_snapshot_with_deleted_node_visibility() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let entity_ids = graph.list_entity_ids()?;
if entity_ids.len() < 2 {
return Ok(()); }
let test_node = entity_ids[0];
let neighbors_before = graph.query().outgoing(test_node)?;
let snapshot = graph.acquire_snapshot()?;
let snapshot_neighbors = snapshot.get_outgoing(test_node);
assert_eq!(snapshot_neighbors, Some(&neighbors_before));
graph.delete_entity(test_node)?;
assert!(snapshot.contains_node(test_node));
assert_eq!(snapshot.get_outgoing(test_node), Some(&neighbors_before));
Ok(())
}
#[test]
fn test_snapshot_with_single_node() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let entity_id = insert_entity(
&graph,
GraphEntityCreate {
kind: "single".to_string(),
name: "single_node".to_string(),
file_path: Some("single.rs".to_string()),
data: serde_json::json!({}),
},
)?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count(), 1);
assert_eq!(snapshot.edge_count(), 0);
assert!(snapshot.contains_node(entity_id));
assert_eq!(snapshot.get_outgoing(entity_id), Some(&vec![]));
assert_eq!(snapshot.get_incoming(entity_id), Some(&vec![]));
Ok(())
}
#[test]
fn test_snapshot_with_disconnected_components() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let id1 = insert_entity(
&graph,
GraphEntityCreate {
kind: "comp1".to_string(),
name: "comp1_node1".to_string(),
file_path: Some("comp1.rs".to_string()),
data: serde_json::json!({}),
},
)?;
let id2 = insert_entity(
&graph,
GraphEntityCreate {
kind: "comp1".to_string(),
name: "comp1_node2".to_string(),
file_path: Some("comp1.rs".to_string()),
data: serde_json::json!({}),
},
)?;
let id3 = insert_entity(
&graph,
GraphEntityCreate {
kind: "comp2".to_string(),
name: "comp2_node1".to_string(),
file_path: Some("comp2.rs".to_string()),
data: serde_json::json!({}),
},
)?;
let id4 = insert_entity(
&graph,
GraphEntityCreate {
kind: "comp2".to_string(),
name: "comp2_node2".to_string(),
file_path: Some("comp2.rs".to_string()),
data: serde_json::json!({}),
},
)?;
insert_edge(
&graph,
GraphEdgeCreate {
from_id: id1,
to_id: id2,
edge_type: "connects".to_string(),
data: serde_json::json!({}),
},
)?;
insert_edge(
&graph,
GraphEdgeCreate {
from_id: id3,
to_id: id4,
edge_type: "connects".to_string(),
data: serde_json::json!({}),
},
)?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count(), 4);
assert_eq!(snapshot.edge_count(), 2);
let neighbors1 = snapshot.get_outgoing(id1);
assert_eq!(neighbors1, Some(&vec![id2]));
let neighbors3 = snapshot.get_outgoing(id3);
assert_eq!(neighbors3, Some(&vec![id4]));
Ok(())
}
#[test]
fn test_snapshot_consistency_under_modifications() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let entity_ids = graph.list_entity_ids()?;
let snapshot = graph.acquire_snapshot()?;
let original_count = snapshot.node_count();
let original_edges = snapshot.edge_count();
for i in 0..10 {
insert_entity(
&graph,
GraphEntityCreate {
kind: "mod".to_string(),
name: format!("mod_func_{}", i),
file_path: Some(format!("mod_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
if !entity_ids.is_empty() {
let new_id = insert_entity(
&graph,
GraphEntityCreate {
kind: "edge_mod".to_string(),
name: format!("edge_mod_{}", i),
file_path: Some(format!("edge_mod_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
insert_edge(
&graph,
GraphEdgeCreate {
from_id: entity_ids[0],
to_id: new_id,
edge_type: "mod_edge".to_string(),
data: serde_json::json!({}),
},
)?;
}
}
assert_eq!(snapshot.node_count(), original_count);
assert_eq!(snapshot.edge_count(), original_edges);
Ok(())
}
#[test]
fn test_multiple_snapshots_different_states() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let count1 = snapshot1.node_count();
for i in 0..5 {
insert_entity(
&graph,
GraphEntityCreate {
kind: "phase1".to_string(),
name: format!("phase1_{}", i),
file_path: Some(format!("phase1_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
}
warm_cache(&graph)?;
let snapshot2 = graph.acquire_snapshot()?;
let count2 = snapshot2.node_count();
for i in 0..5 {
insert_entity(
&graph,
GraphEntityCreate {
kind: "phase2".to_string(),
name: format!("phase2_{}", i),
file_path: Some(format!("phase2_{}.rs", i)),
data: serde_json::json!({}),
},
)?;
}
warm_cache(&graph)?;
let snapshot3 = graph.acquire_snapshot()?;
let count3 = snapshot3.node_count();
assert!(count1 < count2);
assert!(count2 < count3);
assert_eq!(snapshot1.node_count(), count1);
assert_eq!(snapshot2.node_count(), count2);
assert_eq!(snapshot3.node_count(), count3);
Ok(())
}
#[test]
fn test_snapshot_outlives_graph() -> Result<(), SqliteGraphError> {
let snapshot = {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snap = graph.acquire_snapshot()?;
snap
};
let count = snapshot.node_count();
assert!(count > 0, "Snapshot should remain valid");
let _ = snapshot.edge_count();
Ok(())
}
#[test]
fn test_snapshot_clone_independence() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot1 = Arc::new(graph.acquire_snapshot()?);
let snapshot2 = Arc::clone(&snapshot1);
assert_eq!(snapshot1.node_count(), snapshot2.node_count());
assert_eq!(snapshot1.edge_count(), snapshot2.edge_count());
let _ = insert_entity(
&graph,
GraphEntityCreate {
kind: "clone_test".to_string(),
name: "clone_node".to_string(),
file_path: Some("clone.rs".to_string()),
data: serde_json::json!({}),
},
);
warm_cache(&graph)?;
let original_count = snapshot1.node_count();
assert_eq!(snapshot2.node_count(), original_count);
let new_snapshot = graph.acquire_snapshot()?;
assert!(new_snapshot.node_count() > original_count);
Ok(())
}
#[test]
fn test_nested_snapshots() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let count1 = snapshot1.node_count();
let snapshot2 = graph.acquire_snapshot()?;
let count2 = snapshot2.node_count();
assert_eq!(count1, count2);
assert_eq!(snapshot1.node_count(), snapshot2.node_count());
let _ = insert_entity(
&graph,
GraphEntityCreate {
kind: "nested".to_string(),
name: "nested_node".to_string(),
file_path: Some("nested.rs".to_string()),
data: serde_json::json!({}),
},
);
warm_cache(&graph)?;
let snapshot3 = graph.acquire_snapshot()?;
assert_eq!(snapshot1.node_count(), count1);
assert_eq!(snapshot2.node_count(), count2);
assert!(snapshot3.node_count() > count2);
Ok(())
}
#[test]
fn test_snapshot_consistency_with_writes() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
let original_count = snapshot.node_count();
let original_edges = snapshot.edge_count();
for i in 0..20 {
let _ = insert_entity(
&graph,
GraphEntityCreate {
kind: "write_test".to_string(),
name: format!("write_node_{}", i),
file_path: Some(format!("write_{}.rs", i)),
data: serde_json::json!({}),
},
);
}
warm_cache(&graph)?;
assert_eq!(snapshot.node_count(), original_count);
assert_eq!(snapshot.edge_count(), original_edges);
let final_count = node_count(&graph)?;
assert!(final_count > original_count as i64);
Ok(())
}
#[test]
fn test_empty_transaction() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let ids = graph.list_entity_ids()?;
assert!(ids.is_empty(), "Graph should be empty");
let count_after = node_count(&graph)?;
assert_eq!(count_after, 0, "Count should still be 0");
Ok(())
}
#[test]
fn test_transaction_with_failed_operations() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let id1 = insert_entity(
&graph,
GraphEntityCreate {
kind: "test".to_string(),
name: "success".to_string(),
file_path: Some("success.rs".to_string()),
data: serde_json::json!({}),
},
)?;
let result = insert_edge(
&graph,
GraphEdgeCreate {
from_id: id1,
to_id: 99999, edge_type: "fails".to_string(),
data: serde_json::json!({}),
},
);
assert!(result.is_err(), "Edge to non-existent node should fail");
let ids = graph.list_entity_ids()?;
assert_eq!(ids.len(), 1, "Entity should exist");
Ok(())
}
#[test]
fn test_partial_modification_state() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let initial_count = node_count(&graph)?;
for i in 0..5 {
let _ = insert_entity(
&graph,
GraphEntityCreate {
kind: "partial".to_string(),
name: format!("partial_{}", i),
file_path: Some(format!("partial_{}.rs", i)),
data: serde_json::json!({}),
},
);
}
let entity_ids = graph.list_entity_ids()?;
if !entity_ids.is_empty() {
let _ = insert_edge(
&graph,
GraphEdgeCreate {
from_id: entity_ids[0],
to_id: 99998,
edge_type: "bad_edge".to_string(),
data: serde_json::json!({}),
},
);
}
let final_count = node_count(&graph)?;
assert!(
final_count > initial_count,
"Successful inserts should persist"
);
let _ = graph.acquire_snapshot()?;
Ok(())
}
use std::sync::Arc;