Files
wifi-densepose/crates/ruvector-dag/tests/integration/dag_tests.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

248 lines
5.9 KiB
Rust

//! DAG integration tests
use ruvector_dag::dag::{OperatorNode, OperatorType, QueryDag};
#[test]
fn test_complex_query_dag() {
// Build a realistic query DAG
let mut dag = QueryDag::new();
// Add scan nodes
let scan1 = dag.add_node(OperatorNode::seq_scan(0, "users"));
let scan2 = dag.add_node(OperatorNode::hnsw_scan(1, "vectors_idx", 64));
// Add join
let join = dag.add_node(OperatorNode::hash_join(2, "user_id"));
dag.add_edge(scan1, join).unwrap();
dag.add_edge(scan2, join).unwrap();
// Add filter and result
let filter = dag.add_node(OperatorNode::filter(3, "score > 0.5"));
dag.add_edge(join, filter).unwrap();
let result = dag.add_node(OperatorNode::new(4, OperatorType::Result));
dag.add_edge(filter, result).unwrap();
// Verify structure
assert_eq!(dag.node_count(), 5);
assert_eq!(dag.edge_count(), 4);
// Verify topological order
let order = dag.topological_sort().unwrap();
assert_eq!(order.len(), 5);
// Scans should come before join
let scan1_pos = order.iter().position(|&x| x == scan1).unwrap();
let scan2_pos = order.iter().position(|&x| x == scan2).unwrap();
let join_pos = order.iter().position(|&x| x == join).unwrap();
assert!(scan1_pos < join_pos);
assert!(scan2_pos < join_pos);
}
#[test]
fn test_dag_depths() {
let mut dag = QueryDag::new();
// Create tree structure
// Edges: 3→1, 4→1, 1→0, 2→0
// Leaves (no outgoing edges): only node 0
// Depth is computed FROM LEAVES, so node 0 = depth 0
//
// 0 (leaf, depth 0)
// / \
// 1 2 (depth 1)
// / \
// 3 4 (depth 2)
for i in 0..5 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
dag.add_edge(3, 1).unwrap();
dag.add_edge(4, 1).unwrap();
dag.add_edge(1, 0).unwrap();
dag.add_edge(2, 0).unwrap();
let depths = dag.compute_depths();
// All nodes should have a depth
assert!(depths.contains_key(&0));
assert!(depths.contains_key(&1));
assert!(depths.contains_key(&2));
assert!(depths.contains_key(&3));
assert!(depths.contains_key(&4));
// Leaf node 0 (no outgoing edges) has depth 0
assert_eq!(depths[&0], 0);
// Nodes 1 and 2 are parents of leaf 0, so depth 1
assert_eq!(depths[&1], 1);
assert_eq!(depths[&2], 1);
// Nodes 3 and 4 are parents of 1, so depth 2
assert_eq!(depths[&3], 2);
assert_eq!(depths[&4], 2);
}
#[test]
fn test_dag_cycle_detection() {
let mut dag = QueryDag::new();
for i in 0..3 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
// Create valid edges
dag.add_edge(0, 1).unwrap();
dag.add_edge(1, 2).unwrap();
// Attempt to create cycle should fail
let result = dag.add_edge(2, 0);
assert!(result.is_err());
}
#[test]
fn test_dag_node_removal() {
let mut dag = QueryDag::new();
for i in 0..5 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
dag.add_edge(0, 1).unwrap();
dag.add_edge(1, 2).unwrap();
dag.add_edge(2, 3).unwrap();
dag.add_edge(3, 4).unwrap();
// Remove middle node
dag.remove_node(2);
assert_eq!(dag.node_count(), 4);
// Verify DAG is still valid after removal
let topo = dag.topological_sort();
assert!(topo.is_ok());
}
#[test]
fn test_dag_clone() {
let mut dag = QueryDag::new();
for i in 0..5 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
for i in 0..4 {
dag.add_edge(i, i + 1).unwrap();
}
let cloned = dag.clone();
assert_eq!(dag.node_count(), cloned.node_count());
assert_eq!(dag.edge_count(), cloned.edge_count());
}
#[test]
fn test_dag_topological_order() {
let mut dag = QueryDag::new();
// Create diamond pattern
// 0
// / \
// 1 2
// \ /
// 3
for i in 0..4 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
dag.add_edge(0, 1).unwrap();
dag.add_edge(0, 2).unwrap();
dag.add_edge(1, 3).unwrap();
dag.add_edge(2, 3).unwrap();
let order = dag.topological_sort().unwrap();
// Node 0 must come first
assert_eq!(order[0], 0);
// Node 3 must come last
assert_eq!(order[3], 3);
// Nodes 1 and 2 must be in the middle
assert!(order.contains(&1));
assert!(order.contains(&2));
}
#[test]
fn test_dag_parents_children() {
let mut dag = QueryDag::new();
for i in 0..4 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
// 0 -> 1 -> 3
// 2 ->
dag.add_edge(0, 1).unwrap();
dag.add_edge(1, 3).unwrap();
dag.add_edge(2, 3).unwrap();
// Parents of node 3
let preds = dag.parents(3);
assert_eq!(preds.len(), 2);
assert!(preds.contains(&1));
assert!(preds.contains(&2));
// Children of node 0
let succs = dag.children(0);
assert_eq!(succs.len(), 1);
assert!(succs.contains(&1));
}
#[test]
fn test_dag_leaves() {
let mut dag = QueryDag::new();
for i in 0..5 {
dag.add_node(OperatorNode::new(i, OperatorType::Result));
}
// 0 -> 2, 1 -> 2, 2 -> 3, 2 -> 4
dag.add_edge(0, 2).unwrap();
dag.add_edge(1, 2).unwrap();
dag.add_edge(2, 3).unwrap();
dag.add_edge(2, 4).unwrap();
// Get leaves using the API
let leaves = dag.leaves();
assert_eq!(leaves.len(), 2);
assert!(leaves.contains(&3));
assert!(leaves.contains(&4));
}
#[test]
fn test_dag_empty() {
let dag = QueryDag::new();
assert_eq!(dag.node_count(), 0);
assert_eq!(dag.edge_count(), 0);
let order = dag.topological_sort().unwrap();
assert!(order.is_empty());
}
#[test]
fn test_dag_single_node() {
let mut dag = QueryDag::new();
dag.add_node(OperatorNode::new(0, OperatorType::Result));
assert_eq!(dag.node_count(), 1);
assert_eq!(dag.edge_count(), 0);
let order = dag.topological_sort().unwrap();
assert_eq!(order.len(), 1);
assert_eq!(order[0], 0);
}