use super::*;
#[test]
fn test_bounding_box_creation() {
let min = Vec3::new(-1.0, -2.0, -3.0);
let max = Vec3::new(1.0, 2.0, 3.0);
let bbox = BoundingBox::new(min, max);
assert_eq!(bbox.min, min);
assert_eq!(bbox.max, max);
}
#[test]
fn test_bounding_box_contains() {
let bbox = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
assert!(bbox.contains(Vec3::ZERO));
assert!(bbox.contains(Vec3::new(0.5, 0.5, 0.5)));
assert!(bbox.contains(Vec3::new(-1.0, -1.0, -1.0))); assert!(bbox.contains(Vec3::new(1.0, 1.0, 1.0)));
assert!(!bbox.contains(Vec3::new(2.0, 0.0, 0.0)));
assert!(!bbox.contains(Vec3::new(0.0, 2.0, 0.0)));
assert!(!bbox.contains(Vec3::new(0.0, 0.0, 2.0)));
}
#[test]
fn test_bounding_box_intersects() {
let bbox1 = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
let bbox2 = BoundingBox::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(2.0, 2.0, 2.0));
let bbox3 = BoundingBox::new(Vec3::new(2.0, 2.0, 2.0), Vec3::new(3.0, 3.0, 3.0));
assert!(bbox1.intersects(&bbox2));
assert!(!bbox1.intersects(&bbox3));
}
#[test]
fn test_bounding_box_center_and_size() {
let bbox = BoundingBox::new(Vec3::new(-2.0, -1.0, 0.0), Vec3::new(2.0, 3.0, 4.0));
assert_eq!(bbox.center(), Vec3::new(0.0, 1.0, 2.0));
assert_eq!(bbox.size(), Vec3::new(4.0, 4.0, 4.0));
}
#[test]
fn test_bounding_box_subdivide() {
let bbox = BoundingBox::new(Vec3::new(-2.0, -2.0, -2.0), Vec3::new(2.0, 2.0, 2.0));
let octants = bbox.subdivide();
assert_eq!(octants.len(), 8);
for octant in &octants {
assert!(octant.min.x >= bbox.min.x);
assert!(octant.min.y >= bbox.min.y);
assert!(octant.min.z >= bbox.min.z);
assert!(octant.max.x <= bbox.max.x);
assert!(octant.max.y <= bbox.max.y);
assert!(octant.max.z <= bbox.max.z);
}
for i in 0..8 {
for j in (i + 1)..8 {
if octants[i].intersects(&octants[j]) {
let intersection = BoundingBox::new(
octants[i].min.max(octants[j].min),
octants[i].max.min(octants[j].max),
);
let size = intersection.size();
assert!(size.x == 0.0 || size.y == 0.0 || size.z == 0.0);
}
}
}
}
#[test]
fn test_octree_node_creation() {
let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
let node = OctreeNode::new(bounds, 0);
assert_eq!(node.bounds, bounds);
assert_eq!(node.depth, 0);
assert!(node.is_leaf());
assert!(node.nodes.is_empty());
assert!(node.children.is_none());
}
#[test]
fn test_octree_node_capacity() {
let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
let node = OctreeNode::new(bounds, 0);
assert_eq!(node.capacity(), 8);
let deep_node = OctreeNode::new(bounds, 5);
assert_eq!(deep_node.capacity(), 18); }
#[test]
fn test_octree_creation() {
let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
let octree = Octree::new(bounds);
assert_eq!(octree.len(), 0);
assert!(octree.is_empty());
}
#[test]
fn test_octree_from_nodes() {
let nodes = vec![
NodePoint {
id: 1,
x: 0.0,
y: 0.0,
z: 0.0,
},
NodePoint {
id: 2,
x: 1.0,
y: 1.0,
z: 1.0,
},
NodePoint {
id: 3,
x: -1.0,
y: -1.0,
z: -1.0,
},
];
let octree = Octree::from_nodes(&nodes);
assert_eq!(octree.len(), 3);
assert!(!octree.is_empty());
}
#[test]
fn test_octree_insert_and_query() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-10.0, -10.0, -10.0),
Vec3::new(10.0, 10.0, 10.0),
));
let node1 = NodePoint {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
};
let node2 = NodePoint {
id: 2,
x: -2.0,
y: -2.0,
z: -2.0,
};
let node3 = NodePoint {
id: 3,
x: 5.0,
y: 5.0,
z: 5.0,
};
assert!(octree.insert(node1));
assert!(octree.insert(node2));
assert!(octree.insert(node3));
assert_eq!(octree.len(), 3);
let results = octree.query_sphere(Vec3::ZERO, 3.0);
println!("Results in radius 3.0: {:?}", results);
assert_eq!(results.len(), 1);
let results = octree.query_sphere(Vec3::ZERO, 10.0);
println!("Results in radius 10.0: {:?}", results);
assert_eq!(results.len(), 3); }
#[test]
fn test_octree_out_of_bounds() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-10.0, -10.0, -10.0),
Vec3::new(10.0, 10.0, 10.0),
));
let out_of_bounds_node = NodePoint {
id: 1,
x: 15.0,
y: 15.0,
z: 15.0,
};
assert!(!octree.insert(out_of_bounds_node));
assert_eq!(octree.len(), 0);
let in_bounds_node = NodePoint {
id: 2,
x: 5.0,
y: 5.0,
z: 5.0,
};
assert!(octree.insert(in_bounds_node));
assert_eq!(octree.len(), 1);
}
#[test]
fn test_octree_query_stats_collects_lookup_counts() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
for i in 0..32 {
let x = (i as f32 - 16.0) * 3.0;
assert!(octree.insert(NodePoint {
id: i as u64,
x,
y: 0.0,
z: 0.0,
}));
}
let (results, stats) = octree.query_sphere_with_stats(Vec3::ZERO, 12.0);
assert!(!results.is_empty());
assert!(stats.nodes_visited > 0);
assert!(stats.leaf_nodes_scanned > 0);
assert!(stats.points_tested >= results.len());
assert_eq!(stats.points_returned, results.len());
}
#[test]
fn test_octree_lookup_growth_is_sublinear_for_8x_more_points() {
fn populate_axis_grid(octree: &mut Octree, axis: usize) {
let mut id = 0u64;
let span = 400.0f32;
let step = span / axis as f32;
let offset = -span * 0.5;
for xi in 0..axis {
for yi in 0..axis {
for zi in 0..axis {
let x = offset + xi as f32 * step;
let y = offset + yi as f32 * step;
let z = offset + zi as f32 * step;
let inserted = octree.insert(NodePoint { id, x, y, z });
assert!(inserted);
id += 1;
}
}
}
}
let bounds = BoundingBox::new(
Vec3::new(-256.0, -256.0, -256.0),
Vec3::new(256.0, 256.0, 256.0),
);
let mut small = Octree::new(bounds);
populate_axis_grid(&mut small, 8); let (_, small_stats) = small.query_sphere_with_stats(Vec3::new(0.0, 0.0, 0.0), 20.0);
let mut large = Octree::new(bounds);
populate_axis_grid(&mut large, 16); let (_, large_stats) = large.query_sphere_with_stats(Vec3::new(0.0, 0.0, 0.0), 20.0);
assert!(small_stats.nodes_visited > 0);
assert!(large_stats.nodes_visited > 0);
assert!(
large_stats.nodes_visited <= small_stats.nodes_visited * 5,
"lookup growth too steep: small={} large={}",
small_stats.nodes_visited,
large_stats.nodes_visited
);
}
#[test]
fn test_octree_serialization_basic() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
let nodes = vec![
NodePoint {
id: 1,
x: 10.0,
y: 20.0,
z: 30.0,
},
NodePoint {
id: 2,
x: -15.0,
y: 25.0,
z: -35.0,
},
NodePoint {
id: 3,
x: 50.0,
y: 50.0,
z: 50.0,
},
];
for node in &nodes {
assert!(octree.insert(*node), "Should insert node {}", node.id);
}
assert_eq!(octree.len(), 3, "Should have 3 nodes");
let serialized = octree.to_bytes().expect("Should serialize octree");
assert!(
!serialized.is_empty(),
"Serialized data should not be empty"
);
let restored = Octree::from_bytes(&serialized).expect("Should deserialize octree");
assert_eq!(restored.len(), 3, "Restored octree should have 3 nodes");
let query_results = restored.query_sphere(Vec3::new(10.0, 20.0, 30.0), 5.0);
assert!(
query_results.iter().any(|n| n.id == 1),
"Restored octree should find node 1"
);
}
#[test]
fn test_octree_persistence_roundtrip() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-500.0, -500.0, -500.0),
Vec3::new(500.0, 500.0, 500.0),
));
for i in 0..100 {
let x = ((i % 10) as f32 - 5.0) * 50.0;
let y = ((i / 10) as f32 - 5.0) * 50.0;
let z = 0.0;
octree.insert(NodePoint {
id: i as u64,
x,
y,
z,
});
}
assert_eq!(octree.len(), 100, "Should have 100 nodes");
let original_results = octree.query_sphere(Vec3::ZERO, 100.0);
let original_count = original_results.len();
let bytes = octree.to_bytes().expect("Should serialize");
let restored = Octree::from_bytes(&bytes).expect("Should deserialize");
assert_eq!(restored.len(), 100, "Restored octree should have 100 nodes");
let restored_results = restored.query_sphere(Vec3::ZERO, 100.0);
assert_eq!(
restored_results.len(),
original_count,
"Queries should return same count after roundtrip"
);
}
#[test]
fn test_octree_cfg_store_integration() {
use crate::cfg_store::{CfgBlock, CfgStore};
use tempfile::tempdir;
let temp_dir = tempdir().unwrap();
let db_path = temp_dir.path().join("test_octree_integration.db");
let mut store = CfgStore::create(&db_path).unwrap();
let blocks = vec![
CfgBlock {
id: 1,
function_id: 42,
block_kind: "entry".to_string(),
terminator: "fallthrough".to_string(),
byte_start: 0,
byte_end: 50,
start_line: 1,
start_col: 0,
end_line: 5,
end_col: 10,
dominator_depth: 0,
loop_nesting: 0,
branch_count: 0,
},
CfgBlock {
id: 2,
function_id: 42,
block_kind: "if".to_string(),
terminator: "conditional".to_string(),
byte_start: 50,
byte_end: 100,
start_line: 6,
start_col: 4,
end_line: 10,
end_col: 15,
dominator_depth: 1,
loop_nesting: 0,
branch_count: 1,
},
];
for block in &blocks {
store.insert_block(block.clone()).unwrap();
}
let nearby_before = store.query_nearby(Vec3::new(0.0, 0.0, 42.0), 5.0);
assert!(
!nearby_before.is_empty(),
"Should find blocks before persistence"
);
let octree_bytes = store.octree.to_bytes().expect("Should serialize octree");
let restored_octree = Octree::from_bytes(&octree_bytes).expect("Should deserialize octree");
let nearby_after = restored_octree.query_sphere(Vec3::new(0.0, 0.0, 42.0), 5.0);
assert!(!nearby_after.is_empty(), "Should find blocks after restore");
}
#[test]
#[cfg(feature = "telemetry")]
fn test_octree_telemetry_loop_guard() {
use crate::telemetry::LoopGuard;
let guard = LoopGuard::new("octree_traversal", 100);
for i in 0..150 {
if i < 100 {
assert!(guard.check().is_ok(), "Should allow iteration {}", i);
} else {
assert!(guard.check().is_err(), "Should fail at iteration {}", i);
break;
}
}
}
#[test]
fn test_knn_basic() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
let nodes = vec![
NodePoint {
id: 1,
x: 0.0,
y: 0.0,
z: 0.0,
}, NodePoint {
id: 2,
x: 1.0,
y: 0.0,
z: 0.0,
}, NodePoint {
id: 3,
x: 3.0,
y: 0.0,
z: 0.0,
}, NodePoint {
id: 4,
x: 10.0,
y: 0.0,
z: 0.0,
}, ];
for node in &nodes {
octree.insert(*node);
}
let results = octree.query_knn(Vec3::new(0.0, 0.0, 0.0), 2);
assert_eq!(results.len(), 2);
assert_eq!(results[0].0.id, 1); assert_eq!(results[1].0.id, 2); assert!((results[0].1 - 0.0).abs() < 0.001);
assert!((results[1].1 - 1.0).abs() < 0.001);
}
#[test]
fn test_knn_empty_octree() {
let octree = Octree::new(BoundingBox::new(
Vec3::new(-10.0, -10.0, -10.0),
Vec3::new(10.0, 10.0, 10.0),
));
let results = octree.query_knn(Vec3::ZERO, 5);
assert!(results.is_empty());
}
#[test]
fn test_knn_k_larger_than_count() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
for i in 0..3 {
octree.insert(NodePoint {
id: i as u64,
x: i as f32 * 10.0,
y: 0.0,
z: 0.0,
});
}
let results = octree.query_knn(Vec3::ZERO, 10);
assert_eq!(results.len(), 3); }
#[test]
fn test_knn_k_zero() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-10.0, -10.0, -10.0),
Vec3::new(10.0, 10.0, 10.0),
));
octree.insert(NodePoint {
id: 1,
x: 0.0,
y: 0.0,
z: 0.0,
});
let results = octree.query_knn(Vec3::ZERO, 0);
assert!(results.is_empty());
}
#[test]
fn test_knn_sorted_by_distance() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
let positions = [
(5.0, 0.0, 0.0), (1.0, 0.0, 0.0), (10.0, 0.0, 0.0), (2.0, 0.0, 0.0), ];
for (i, (x, y, z)) in positions.iter().enumerate() {
octree.insert(NodePoint {
id: i as u64,
x: *x,
y: *y,
z: *z,
});
}
let results = octree.query_knn(Vec3::ZERO, 4);
for i in 1..results.len() {
assert!(
results[i].1 >= results[i - 1].1,
"Results should be sorted by distance"
);
}
assert_eq!(results[0].0.id, 1); assert_eq!(results[1].0.id, 3); assert_eq!(results[2].0.id, 0); assert_eq!(results[3].0.id, 2); }
#[test]
fn test_knn_3d_positions() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
let nodes = vec![
NodePoint {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
}, NodePoint {
id: 2,
x: 2.0,
y: 2.0,
z: 2.0,
}, NodePoint {
id: 3,
x: 0.0,
y: 0.0,
z: 0.0,
}, ];
for node in &nodes {
octree.insert(*node);
}
let results = octree.query_knn(Vec3::new(0.0, 0.0, 0.0), 3);
assert_eq!(results.len(), 3);
assert_eq!(results[0].0.id, 3); assert_eq!(results[1].0.id, 1); assert_eq!(results[2].0.id, 2);
assert!((results[0].1 - 0.0).abs() < 0.001);
assert!((results[1].1 - 3.0).abs() < 0.001); assert!((results[2].1 - 12.0).abs() < 0.001); }
#[test]
fn test_knn_with_stats() {
let mut octree = Octree::new(BoundingBox::new(
Vec3::new(-100.0, -100.0, -100.0),
Vec3::new(100.0, 100.0, 100.0),
));
for i in 0..100 {
octree.insert(NodePoint {
id: i as u64,
x: (i as f32 - 50.0) * 2.0,
y: 0.0,
z: 0.0,
});
}
let (results, stats) = octree.query_knn_with_stats(Vec3::ZERO, 10);
assert_eq!(results.len(), 10);
assert!(stats.nodes_visited > 0, "Should track nodes visited");
assert!(stats.points_tested > 0, "Should track points tested");
}
#[test]
fn test_knn_correctness_against_brute_force() {
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
let mut rng = StdRng::seed_from_u64(42);
let mut nodes = Vec::new();
for i in 0..500 {
nodes.push(NodePoint {
id: i as u64,
x: rng.random_range(-10.0..10.0),
y: rng.random_range(-10.0..10.0),
z: rng.random_range(-10.0..10.0),
});
}
let octree = Octree::from_nodes(&nodes);
assert_eq!(octree.len(), nodes.len());
let k = 5;
let mut mismatches = 0;
for query in &nodes {
let knn = octree.query_knn(Vec3::new(query.x, query.y, query.z), k + 1);
let mut octree_ids: Vec<u64> = knn
.iter()
.map(|(n, _)| n.id)
.filter(|&id| id != query.id)
.take(k)
.collect();
octree_ids.sort_unstable();
let mut brute: Vec<(u64, f32)> = nodes
.iter()
.filter(|n| n.id != query.id)
.map(|n| {
let d = (Vec3::new(n.x, n.y, n.z) - Vec3::new(query.x, query.y, query.z))
.length_squared();
(n.id, d)
})
.collect();
brute.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let mut brute_ids: Vec<u64> = brute.iter().take(k).map(|(id, _)| *id).collect();
brute_ids.sort_unstable();
if octree_ids != brute_ids {
mismatches += 1;
}
}
assert_eq!(
mismatches,
0,
"k-NN mismatched for {} / {} query points",
mismatches,
nodes.len()
);
}
#[test]
fn test_knn_correctness_on_unit_sphere() {
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
let mut rng = StdRng::seed_from_u64(123);
let mut nodes = Vec::new();
for i in 0..1200 {
let theta = rng.random_range(0.0..std::f32::consts::TAU);
let phi = rng.random_range(0.0..std::f32::consts::PI);
nodes.push(NodePoint {
id: i as u64,
x: phi.sin() * theta.cos(),
y: phi.sin() * theta.sin(),
z: phi.cos(),
});
}
let octree = Octree::from_nodes(&nodes);
assert_eq!(octree.len(), nodes.len());
let k = 4;
let mut mismatches = 0;
for query in &nodes {
let knn = octree.query_knn(Vec3::new(query.x, query.y, query.z), k + 1);
let mut octree_ids: Vec<u64> = knn
.iter()
.map(|(n, _)| n.id)
.filter(|&id| id != query.id)
.take(k)
.collect();
octree_ids.sort_unstable();
let mut brute: Vec<(u64, f32)> = nodes
.iter()
.filter(|n| n.id != query.id)
.map(|n| {
let d = (Vec3::new(n.x, n.y, n.z) - Vec3::new(query.x, query.y, query.z))
.length_squared();
(n.id, d)
})
.collect();
brute.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let mut brute_ids: Vec<u64> = brute.iter().take(k).map(|(id, _)| *id).collect();
brute_ids.sort_unstable();
if octree_ids != brute_ids {
mismatches += 1;
}
}
assert_eq!(
mismatches,
0,
"k-NN mismatched for {} / {} query points on unit sphere",
mismatches,
nodes.len()
);
}