git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
340 lines
9.2 KiB
Rust
340 lines
9.2 KiB
Rust
//! Comprehensive coverage tests for ruvector-mincut
|
|
//!
|
|
//! Ensures 100% test coverage across all modules.
|
|
|
|
use ruvector_mincut::certificate::{AuditData, AuditEntryType, AuditLogger, CutCertificate};
|
|
use ruvector_mincut::connectivity::DynamicConnectivity;
|
|
use ruvector_mincut::instance::{InstanceResult, StubInstance, WitnessHandle};
|
|
use ruvector_mincut::prelude::*;
|
|
use ruvector_mincut::wrapper::MinCutWrapper;
|
|
use std::sync::Arc;
|
|
|
|
// ============================================================================
|
|
// Connectivity Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_connectivity_edge_cases() {
|
|
let mut dc = DynamicConnectivity::new();
|
|
|
|
// Empty graph
|
|
assert!(!dc.is_connected());
|
|
assert_eq!(dc.component_count(), 0);
|
|
|
|
// Single vertex
|
|
dc.add_vertex(0);
|
|
assert!(dc.is_connected()); // Single vertex is connected
|
|
assert_eq!(dc.component_count(), 1);
|
|
|
|
// Two isolated vertices
|
|
dc.add_vertex(1);
|
|
assert!(!dc.is_connected());
|
|
assert_eq!(dc.component_count(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_connectivity_path_compression() {
|
|
let mut dc = DynamicConnectivity::new();
|
|
|
|
// Build long chain: 0-1-2-3-4-5
|
|
for i in 0..5 {
|
|
dc.insert_edge(i, i + 1);
|
|
}
|
|
|
|
// Query should trigger path compression
|
|
assert!(dc.connected(0, 5));
|
|
assert!(dc.connected(0, 3));
|
|
assert!(dc.connected(2, 4));
|
|
}
|
|
|
|
#[test]
|
|
fn test_connectivity_multiple_components() {
|
|
let mut dc = DynamicConnectivity::new();
|
|
|
|
// Component 1: 0-1-2
|
|
dc.insert_edge(0, 1);
|
|
dc.insert_edge(1, 2);
|
|
|
|
// Component 2: 3-4
|
|
dc.insert_edge(3, 4);
|
|
|
|
// Component 3: 5 alone
|
|
dc.add_vertex(5);
|
|
|
|
assert_eq!(dc.component_count(), 3);
|
|
assert!(dc.connected(0, 2));
|
|
assert!(dc.connected(3, 4));
|
|
assert!(!dc.connected(0, 3));
|
|
assert!(!dc.connected(0, 5));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Witness Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_witness_large_vertex_ids() {
|
|
use roaring::RoaringBitmap;
|
|
|
|
// Test with vertex IDs near u32::MAX limit
|
|
let mut membership = RoaringBitmap::new();
|
|
membership.insert(u32::MAX - 1);
|
|
membership.insert(u32::MAX);
|
|
|
|
let witness = WitnessHandle::new((u32::MAX - 1) as u64, membership, 5);
|
|
|
|
assert!(witness.contains((u32::MAX - 1) as u64));
|
|
assert!(witness.contains(u32::MAX as u64));
|
|
assert!(!witness.contains(0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_witness_equality() {
|
|
use roaring::RoaringBitmap;
|
|
|
|
let mut m1 = RoaringBitmap::new();
|
|
m1.insert(1);
|
|
m1.insert(2);
|
|
|
|
let mut m2 = RoaringBitmap::new();
|
|
m2.insert(1);
|
|
m2.insert(2);
|
|
|
|
let w1 = WitnessHandle::new(1, m1, 5);
|
|
let w2 = WitnessHandle::new(1, m2, 5);
|
|
|
|
assert_eq!(w1, w2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_witness_materialize() {
|
|
use roaring::RoaringBitmap;
|
|
|
|
let mut membership = RoaringBitmap::new();
|
|
membership.insert(1);
|
|
membership.insert(3);
|
|
membership.insert(5);
|
|
|
|
let witness = WitnessHandle::new(1, membership, 3);
|
|
let (u, v_minus_u) = witness.materialize_partition();
|
|
|
|
assert!(u.contains(&1));
|
|
assert!(u.contains(&3));
|
|
assert!(u.contains(&5));
|
|
assert_eq!(u.len(), 3);
|
|
|
|
// v_minus_u contains 0, 2, 4 (up to max which is 5)
|
|
assert!(v_minus_u.contains(&0));
|
|
assert!(v_minus_u.contains(&2));
|
|
assert!(v_minus_u.contains(&4));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Instance Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_stub_instance_range_behavior() {
|
|
let graph = DynamicGraph::new();
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(1, 2, 1.0).unwrap();
|
|
|
|
// Range [0, 0] - min cut 1 is above range
|
|
let mut instance = StubInstance::new(&graph, 0, 0);
|
|
assert!(matches!(instance.query(), InstanceResult::AboveRange));
|
|
|
|
// Range [0, 1] - min cut 1 is in range
|
|
let mut instance = StubInstance::new(&graph, 0, 1);
|
|
match instance.query() {
|
|
InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
|
|
_ => panic!("Expected ValueInRange"),
|
|
}
|
|
|
|
// Range [0, 10] - min cut 1 is in range
|
|
let mut instance = StubInstance::new(&graph, 0, 10);
|
|
match instance.query() {
|
|
InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
|
|
_ => panic!("Expected ValueInRange"),
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Wrapper Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_wrapper_time_tracking() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(graph);
|
|
|
|
assert_eq!(wrapper.current_time(), 0);
|
|
|
|
wrapper.insert_edge(0, 1, 2);
|
|
assert_eq!(wrapper.current_time(), 1);
|
|
|
|
wrapper.insert_edge(1, 3, 4);
|
|
assert_eq!(wrapper.current_time(), 2);
|
|
|
|
wrapper.delete_edge(0, 1, 2);
|
|
assert_eq!(wrapper.current_time(), 3);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Certificate Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_certificate_json_roundtrip() {
|
|
let cert = CutCertificate::new();
|
|
let json = cert.to_json().expect("Failed to serialize certificate");
|
|
|
|
// Verify JSON is valid and contains expected fields
|
|
assert!(json.contains("\"witness_summaries\""));
|
|
assert!(json.contains("\"version\""));
|
|
assert!(json.contains("\"localkcut_responses\""));
|
|
assert!(json.contains("\"timestamp\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_audit_logger_capacity() {
|
|
let logger = AuditLogger::new(5);
|
|
|
|
// Add more than capacity
|
|
for i in 0..10 {
|
|
logger.log(
|
|
AuditEntryType::WitnessCreated,
|
|
AuditData::Witness {
|
|
hash: i,
|
|
boundary: i,
|
|
seed: i,
|
|
},
|
|
);
|
|
}
|
|
|
|
// Should only have last 5
|
|
let entries = logger.export();
|
|
assert_eq!(entries.len(), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_audit_logger_filtering() {
|
|
let logger = AuditLogger::new(100);
|
|
|
|
logger.log(
|
|
AuditEntryType::WitnessCreated,
|
|
AuditData::Witness {
|
|
hash: 1,
|
|
boundary: 1,
|
|
seed: 1,
|
|
},
|
|
);
|
|
logger.log(
|
|
AuditEntryType::LocalKCutQuery,
|
|
AuditData::Query {
|
|
budget: 5,
|
|
radius: 10,
|
|
seeds: vec![1],
|
|
},
|
|
);
|
|
logger.log(
|
|
AuditEntryType::WitnessCreated,
|
|
AuditData::Witness {
|
|
hash: 2,
|
|
boundary: 2,
|
|
seed: 2,
|
|
},
|
|
);
|
|
|
|
let recent = logger.recent(2);
|
|
assert_eq!(recent.len(), 2);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Integration Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_full_workflow_path_to_cycle() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Build path: 0-1-2-3
|
|
for i in 0..3 {
|
|
let edge_id = graph.insert_edge(i, i + 1, 1.0).unwrap();
|
|
wrapper.insert_edge(edge_id, i, i + 1);
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert_eq!(result.value(), 1); // Path has min cut 1
|
|
|
|
// Add edge to form cycle
|
|
let edge_id = graph.insert_edge(3, 0, 1.0).unwrap();
|
|
wrapper.insert_edge(edge_id, 3, 0);
|
|
|
|
let result = wrapper.query();
|
|
assert_eq!(result.value(), 2); // Cycle has min cut 2
|
|
}
|
|
|
|
#[test]
|
|
fn test_full_workflow_split_and_merge() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Build triangle
|
|
let e1 = graph.insert_edge(0, 1, 1.0).unwrap();
|
|
let e2 = graph.insert_edge(1, 2, 1.0).unwrap();
|
|
let e3 = graph.insert_edge(2, 0, 1.0).unwrap();
|
|
|
|
wrapper.insert_edge(e1, 0, 1);
|
|
wrapper.insert_edge(e2, 1, 2);
|
|
wrapper.insert_edge(e3, 2, 0);
|
|
|
|
assert_eq!(wrapper.query().value(), 2);
|
|
|
|
// Delete edge to break cycle
|
|
graph.delete_edge(2, 0).unwrap();
|
|
wrapper.delete_edge(e3, 2, 0);
|
|
|
|
assert_eq!(wrapper.query().value(), 1); // Now a path
|
|
}
|
|
|
|
// ============================================================================
|
|
// Edge Cases
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_self_loop_ignored() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Self-loops should be ignored or handled gracefully
|
|
let _result = graph.insert_edge(0, 0, 1.0);
|
|
// Implementation may accept or reject - just ensure no panic
|
|
}
|
|
|
|
#[test]
|
|
fn test_parallel_edges() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
|
|
// Second edge between same vertices
|
|
let _result = graph.insert_edge(0, 1, 1.0);
|
|
|
|
// Should either fail (EdgeExists) or succeed
|
|
// Just ensure consistent behavior
|
|
}
|
|
|
|
#[test]
|
|
fn test_large_vertex_ids() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
let large_id = 1_000_000u64;
|
|
graph.insert_edge(large_id, large_id + 1, 1.0).unwrap();
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
wrapper.insert_edge(0, large_id, large_id + 1);
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
}
|