git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
300 lines
8.4 KiB
Rust
300 lines
8.4 KiB
Rust
//! Comprehensive tests for CompactGraph operations
|
|
//!
|
|
//! Tests cover:
|
|
//! - Edge add/remove operations
|
|
//! - Weight updates
|
|
//! - Boundary edge management
|
|
//! - Edge cases (empty graph, max capacity, boundary conditions)
|
|
//! - Property-based tests for invariant verification
|
|
|
|
use cognitum_gate_kernel::shard::{CompactGraph, Edge, EdgeId, VertexId, Weight};
|
|
use cognitum_gate_kernel::{DeltaError, MAX_EDGES, MAX_VERTICES};
|
|
|
|
#[cfg(test)]
|
|
mod basic_operations {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_empty_graph() {
|
|
let graph = CompactGraph::new();
|
|
assert!(graph.is_empty());
|
|
assert_eq!(graph.edge_count(), 0);
|
|
assert_eq!(graph.vertex_count(), 0);
|
|
assert!(!graph.is_full());
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_single_edge() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let weight = Weight(100);
|
|
|
|
let result = graph.add_edge(edge, weight);
|
|
assert!(result.is_ok());
|
|
|
|
let edge_id = result.unwrap();
|
|
assert_eq!(graph.edge_count(), 1);
|
|
assert_eq!(graph.vertex_count(), 2);
|
|
assert_eq!(graph.get_weight(edge_id), Some(weight));
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_multiple_edges() {
|
|
let mut graph = CompactGraph::new();
|
|
|
|
let edges = [
|
|
(Edge::new(VertexId(0), VertexId(1)), Weight(100)),
|
|
(Edge::new(VertexId(1), VertexId(2)), Weight(200)),
|
|
(Edge::new(VertexId(2), VertexId(3)), Weight(300)),
|
|
];
|
|
|
|
for (edge, weight) in edges {
|
|
let result = graph.add_edge(edge, weight);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
assert_eq!(graph.edge_count(), 3);
|
|
assert_eq!(graph.vertex_count(), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_edge() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let edge_id = graph.add_edge(edge, Weight(100)).unwrap();
|
|
|
|
let result = graph.remove_edge(edge_id);
|
|
assert!(result.is_ok());
|
|
assert_eq!(graph.edge_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_nonexistent_edge() {
|
|
let mut graph = CompactGraph::new();
|
|
let result = graph.remove_edge(EdgeId(999));
|
|
assert_eq!(result, Err(DeltaError::EdgeNotFound));
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_weight() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let edge_id = graph.add_edge(edge, Weight(100)).unwrap();
|
|
|
|
let result = graph.update_weight(edge_id, Weight(500));
|
|
assert!(result.is_ok());
|
|
assert_eq!(graph.get_weight(edge_id), Some(Weight(500)));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod edge_canonicalization {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_canonical_ordering() {
|
|
let e1 = Edge::new(VertexId(5), VertexId(3));
|
|
let e2 = Edge::new(VertexId(3), VertexId(5));
|
|
|
|
assert_eq!(e1.canonical(), e2.canonical());
|
|
}
|
|
|
|
#[test]
|
|
fn test_self_loop_rejected() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(5), VertexId(5));
|
|
|
|
let result = graph.add_edge(edge, Weight(100));
|
|
assert_eq!(result, Err(DeltaError::InvalidEdge));
|
|
}
|
|
|
|
#[test]
|
|
fn test_duplicate_edge_updates_weight() {
|
|
let mut graph = CompactGraph::new();
|
|
let e1 = Edge::new(VertexId(0), VertexId(1));
|
|
let e2 = Edge::new(VertexId(1), VertexId(0));
|
|
|
|
let id1 = graph.add_edge(e1, Weight(100)).unwrap();
|
|
let id2 = graph.add_edge(e2, Weight(200)).unwrap();
|
|
|
|
assert_eq!(id1, id2);
|
|
assert_eq!(graph.edge_count(), 1);
|
|
assert_eq!(graph.get_weight(id1), Some(Weight(200)));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod boundary_edges {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_mark_boundary() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let edge_id = graph.add_edge(edge, Weight(100)).unwrap();
|
|
|
|
assert_eq!(graph.total_internal_weight(), 100);
|
|
assert_eq!(graph.total_boundary_weight(), 0);
|
|
|
|
graph.mark_boundary(edge_id).unwrap();
|
|
|
|
assert_eq!(graph.total_internal_weight(), 0);
|
|
assert_eq!(graph.total_boundary_weight(), 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_unmark_boundary() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let edge_id = graph.add_edge(edge, Weight(100)).unwrap();
|
|
|
|
graph.mark_boundary(edge_id).unwrap();
|
|
graph.unmark_boundary(edge_id).unwrap();
|
|
|
|
assert_eq!(graph.total_boundary_weight(), 0);
|
|
assert_eq!(graph.total_internal_weight(), 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_boundary_changed_flag() {
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(0), VertexId(1));
|
|
let edge_id = graph.add_edge(edge, Weight(100)).unwrap();
|
|
|
|
graph.clear_boundary_changed();
|
|
assert!(!graph.boundary_changed_since_last_update());
|
|
|
|
graph.mark_boundary(edge_id).unwrap();
|
|
assert!(graph.boundary_changed_since_last_update());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod weight_operations {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_weight_from_f32() {
|
|
let w = Weight::from_f32(1.0);
|
|
assert_eq!(w.0, 256);
|
|
|
|
let w2 = Weight::from_f32(2.0);
|
|
assert_eq!(w2.0, 512);
|
|
}
|
|
|
|
#[test]
|
|
fn test_weight_to_f32() {
|
|
let w = Weight(256);
|
|
assert!((w.to_f32() - 1.0).abs() < 0.01);
|
|
}
|
|
|
|
#[test]
|
|
fn test_weight_saturating_operations() {
|
|
let w1 = Weight(u16::MAX - 10);
|
|
let w2 = Weight(100);
|
|
let sum = w1.saturating_add(w2);
|
|
assert_eq!(sum, Weight::MAX);
|
|
|
|
let w3 = Weight(10);
|
|
let diff = w3.saturating_sub(w2);
|
|
assert_eq!(diff, Weight::ZERO);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod vertex_degree {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_vertex_degree_after_add() {
|
|
let mut graph = CompactGraph::new();
|
|
|
|
graph.add_edge(Edge::new(VertexId(0), VertexId(1)), Weight(100)).unwrap();
|
|
graph.add_edge(Edge::new(VertexId(0), VertexId(2)), Weight(100)).unwrap();
|
|
graph.add_edge(Edge::new(VertexId(0), VertexId(3)), Weight(100)).unwrap();
|
|
|
|
assert_eq!(graph.vertex_degree(VertexId(0)), 3);
|
|
assert_eq!(graph.vertex_degree(VertexId(1)), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vertex_degree_after_remove() {
|
|
let mut graph = CompactGraph::new();
|
|
|
|
let id1 = graph.add_edge(Edge::new(VertexId(0), VertexId(1)), Weight(100)).unwrap();
|
|
graph.add_edge(Edge::new(VertexId(0), VertexId(2)), Weight(100)).unwrap();
|
|
|
|
graph.remove_edge(id1).unwrap();
|
|
assert_eq!(graph.vertex_degree(VertexId(0)), 1);
|
|
assert_eq!(graph.vertex_degree(VertexId(1)), 0);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod min_cut_estimation {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_min_cut_empty_graph() {
|
|
let graph = CompactGraph::new();
|
|
assert_eq!(graph.local_min_cut(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_min_cut_single_edge() {
|
|
let mut graph = CompactGraph::new();
|
|
graph.add_edge(Edge::new(VertexId(0), VertexId(1)), Weight(100)).unwrap();
|
|
assert_eq!(graph.local_min_cut(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_min_cut_clique() {
|
|
let mut graph = CompactGraph::new();
|
|
|
|
for i in 0..4u8 {
|
|
for j in (i + 1)..4 {
|
|
graph.add_edge(Edge::new(VertexId(i), VertexId(j)), Weight(100)).unwrap();
|
|
}
|
|
}
|
|
|
|
assert_eq!(graph.local_min_cut(), 3);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod property_tests {
|
|
use super::*;
|
|
use proptest::prelude::*;
|
|
|
|
proptest! {
|
|
#[test]
|
|
fn prop_add_remove_invariant(src in 0u8..250, dst in 0u8..250, weight in 1u16..1000) {
|
|
prop_assume!(src != dst);
|
|
|
|
let mut graph = CompactGraph::new();
|
|
let edge = Edge::new(VertexId(src), VertexId(dst));
|
|
let id = graph.add_edge(edge, Weight(weight)).unwrap();
|
|
|
|
assert_eq!(graph.edge_count(), 1);
|
|
graph.remove_edge(id).unwrap();
|
|
assert_eq!(graph.edge_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn prop_canonical_symmetry(a in 0u8..250, b in 0u8..250) {
|
|
prop_assume!(a != b);
|
|
|
|
let e1 = Edge::new(VertexId(a), VertexId(b));
|
|
let e2 = Edge::new(VertexId(b), VertexId(a));
|
|
assert_eq!(e1.canonical(), e2.canonical());
|
|
}
|
|
|
|
#[test]
|
|
fn prop_weight_roundtrip(f in 0.0f32..200.0) {
|
|
let weight = Weight::from_f32(f);
|
|
let back = weight.to_f32();
|
|
assert!((f - back).abs() < 0.01 || back >= 255.0);
|
|
}
|
|
}
|
|
}
|