Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
//! Comprehensive tests for E-value accumulator
//!
//! Tests cover:
//! - E-value bounds (E[e] <= 1 under null)
//! - Overflow/underflow protection
//! - Update rules (Product, Average, ExponentialMoving, Maximum)
//! - Stopping rules
use cognitum_gate_kernel::evidence::{
EValueAccumulator, EValueError, StoppingDecision, StoppingRule, UpdateRule,
E_VALUE_MAX, E_VALUE_MIN,
};
#[cfg(test)]
mod basic_operations {
use super::*;
#[test]
fn test_accumulator_creation() {
let acc = EValueAccumulator::new();
assert_eq!(acc.current_value(), 1.0);
assert_eq!(acc.observation_count(), 0);
}
#[test]
fn test_observe_updates_count() {
let mut acc = EValueAccumulator::new();
acc.observe(0.5);
assert_eq!(acc.observation_count(), 1);
acc.observe(0.7);
assert_eq!(acc.observation_count(), 2);
}
#[test]
fn test_reset() {
let mut acc = EValueAccumulator::new();
acc.observe(0.5);
acc.reset();
assert_eq!(acc.current_value(), 1.0);
assert_eq!(acc.observation_count(), 0);
}
}
#[cfg(test)]
mod update_rules {
use super::*;
#[test]
fn test_product_rule() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
acc.observe_evalue(2.0);
assert!((acc.current_value() - 2.0).abs() < 0.001);
acc.observe_evalue(3.0);
assert!((acc.current_value() - 6.0).abs() < 0.001);
}
#[test]
fn test_average_rule() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Average);
acc.observe_evalue(2.0);
acc.observe_evalue(4.0);
assert!((acc.current_value() - 3.0).abs() < 0.001);
}
#[test]
fn test_exponential_moving() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::ExponentialMoving { lambda: 0.5 });
acc.observe_evalue(2.0);
acc.observe_evalue(4.0);
assert!((acc.current_value() - 3.0).abs() < 0.001);
}
#[test]
fn test_maximum_rule() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Maximum);
acc.observe_evalue(2.0);
acc.observe_evalue(5.0);
acc.observe_evalue(3.0);
assert_eq!(acc.current_value(), 5.0);
}
}
#[cfg(test)]
mod bounds_and_overflow {
use super::*;
#[test]
fn test_e_value_clamping_high() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
acc.observe_evalue(1e20);
assert!(acc.current_value() <= E_VALUE_MAX);
}
#[test]
fn test_e_value_clamping_low() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
acc.observe_evalue(1e-20);
assert!(acc.current_value() >= E_VALUE_MIN);
}
#[test]
fn test_product_overflow_protection() {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
for _ in 0..100 {
acc.observe_evalue(100.0);
}
assert!(acc.current_value() <= E_VALUE_MAX);
assert!(acc.current_value().is_finite());
}
}
#[cfg(test)]
mod likelihood_ratio {
use super::*;
#[test]
fn test_valid_likelihood_ratio() {
let result = EValueAccumulator::from_likelihood_ratio(0.9, 0.1);
assert!(result.is_ok());
assert!((result.unwrap() - 9.0).abs() < 0.001);
}
#[test]
fn test_zero_denominator() {
let result = EValueAccumulator::from_likelihood_ratio(0.5, 0.0);
assert_eq!(result, Err(EValueError::DivisionByZero));
}
#[test]
fn test_nan_input() {
let result = EValueAccumulator::from_likelihood_ratio(f64::NAN, 0.5);
assert_eq!(result, Err(EValueError::InvalidInput));
}
}
#[cfg(test)]
mod mixture_evalue {
use super::*;
#[test]
fn test_uniform_mixture() {
let components = [2.0, 4.0, 6.0];
let weights = [1.0, 1.0, 1.0];
let result = EValueAccumulator::mixture(&components, &weights);
assert!(result.is_ok());
assert!((result.unwrap() - 4.0).abs() < 0.001);
}
#[test]
fn test_empty_mixture() {
let result = EValueAccumulator::mixture(&[], &[]);
assert_eq!(result, Err(EValueError::InvalidInput));
}
}
#[cfg(test)]
mod stopping_rules {
use super::*;
#[test]
fn test_continue_decision() {
let rule = StoppingRule::new(100.0);
let acc = EValueAccumulator::new();
assert_eq!(rule.check(&acc), StoppingDecision::Continue);
}
#[test]
fn test_accept_decision() {
let rule = StoppingRule::new(100.0);
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
for _ in 0..10 {
acc.observe_evalue(2.0);
}
assert!(acc.current_value() > 100.0);
assert_eq!(rule.check(&acc), StoppingDecision::Accept);
}
#[test]
fn test_reject_decision() {
let rule = StoppingRule::with_accept(100.0, 0.01);
let mut acc = EValueAccumulator::with_rule(UpdateRule::Product);
for _ in 0..10 {
acc.observe_evalue(0.1);
}
assert!(acc.current_value() < 0.01);
assert_eq!(rule.check(&acc), StoppingDecision::Reject);
}
#[test]
fn test_confidence_calculation() {
let rule = StoppingRule::default();
let mut acc = EValueAccumulator::new();
assert_eq!(rule.confidence(&acc), 0.0);
acc.observe_evalue(2.0);
assert!((rule.confidence(&acc) - 0.5).abs() < 0.001);
}
}
#[cfg(test)]
mod combine_evalues {
use super::*;
#[test]
fn test_combine_basic() {
let combined = EValueAccumulator::combine(2.0, 3.0);
assert_eq!(combined, 6.0);
}
#[test]
fn test_combine_overflow_clamped() {
let combined = EValueAccumulator::combine(1e10, 1e10);
assert!(combined <= E_VALUE_MAX);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_e_value_always_positive(score in 0.0f64..1.0) {
let acc = EValueAccumulator::new();
let e = acc.compute_e_value(score);
assert!(e > 0.0);
}
#[test]
fn prop_e_value_bounded(score in 0.0f64..1.0) {
let acc = EValueAccumulator::new();
let e = acc.compute_e_value(score);
assert!(e >= E_VALUE_MIN);
assert!(e <= E_VALUE_MAX);
}
#[test]
fn prop_maximum_never_decreases(observations in proptest::collection::vec(0.1f64..10.0, 1..20)) {
let mut acc = EValueAccumulator::with_rule(UpdateRule::Maximum);
let mut max_seen = 0.0f64;
for o in observations {
acc.observe_evalue(o);
let current = acc.current_value();
assert!(current >= max_seen);
max_seen = current;
}
}
}
}

View File

@@ -0,0 +1,302 @@
//! Integration tests for full tick cycle
//!
//! Tests cover:
//! - Complete WorkerTileState lifecycle
//! - Delta processing sequences
//! - Tick report generation
//! - Multiple tile coordination scenarios
use cognitum_gate_kernel::{
Delta, DeltaError, WorkerTileState,
shard::{Edge, EdgeId, VertexId, Weight},
report::{TileReport, TileStatus},
};
#[cfg(test)]
mod worker_tile_lifecycle {
use super::*;
#[test]
fn test_tile_creation() {
let tile = WorkerTileState::new(42);
assert_eq!(tile.tile_id, 42);
assert_eq!(tile.coherence, 0);
assert_eq!(tile.tick, 0);
}
#[test]
fn test_initial_report() {
let mut tile = WorkerTileState::new(5);
let report = tile.tick(1000);
assert_eq!(report.tile_id, 5);
assert_eq!(report.status, TileStatus::Active);
}
}
#[cfg(test)]
mod delta_processing {
use super::*;
#[test]
fn test_edge_add_delta() {
let mut tile = WorkerTileState::new(0);
let edge = Edge::new(VertexId(0), VertexId(1));
let delta = Delta::EdgeAdd { edge, weight: Weight(100) };
assert!(tile.ingest_delta(&delta).is_ok());
assert_eq!(tile.graph_shard.edge_count(), 1);
}
#[test]
fn test_edge_remove_delta() {
let mut tile = WorkerTileState::new(0);
let edge = Edge::new(VertexId(0), VertexId(1));
tile.ingest_delta(&Delta::EdgeAdd { edge, weight: Weight(100) }).unwrap();
tile.ingest_delta(&Delta::EdgeRemove { edge: EdgeId(0) }).unwrap();
assert_eq!(tile.graph_shard.edge_count(), 0);
}
#[test]
fn test_weight_update_delta() {
let mut tile = WorkerTileState::new(0);
let edge = Edge::new(VertexId(0), VertexId(1));
tile.ingest_delta(&Delta::EdgeAdd { edge, weight: Weight(100) }).unwrap();
tile.ingest_delta(&Delta::WeightUpdate { edge: EdgeId(0), weight: Weight(200) }).unwrap();
assert_eq!(tile.graph_shard.get_weight(EdgeId(0)), Some(Weight(200)));
}
#[test]
fn test_observation_delta() {
let mut tile = WorkerTileState::new(0);
tile.ingest_delta(&Delta::Observation { score: 0.8 }).unwrap();
assert_eq!(tile.e_accumulator.observation_count(), 1);
}
#[test]
fn test_self_loop_rejected() {
let mut tile = WorkerTileState::new(0);
let edge = Edge::new(VertexId(5), VertexId(5));
let delta = Delta::EdgeAdd { edge, weight: Weight(100) };
assert_eq!(tile.ingest_delta(&delta), Err(DeltaError::InvalidEdge));
}
}
#[cfg(test)]
mod tick_cycle {
use super::*;
#[test]
fn test_single_tick() {
let mut tile = WorkerTileState::new(10);
let report = tile.tick(1000);
assert_eq!(report.tile_id, 10);
assert_eq!(tile.tick, 1000);
}
#[test]
fn test_tick_updates_timestamp() {
let mut tile = WorkerTileState::new(0);
tile.tick(1000);
assert_eq!(tile.tick, 1000);
tile.tick(2000);
assert_eq!(tile.tick, 2000);
}
#[test]
fn test_tick_after_deltas() {
let mut tile = WorkerTileState::new(0);
tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(0), VertexId(1)),
weight: Weight(100),
}).unwrap();
tile.ingest_delta(&Delta::Observation { score: 0.9 }).unwrap();
let report = tile.tick(1000);
assert!(report.is_healthy());
}
#[test]
fn test_multiple_tick_cycles() {
let mut tile = WorkerTileState::new(0);
for i in 0..10 {
tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(i as u8), VertexId((i + 1) as u8)),
weight: Weight(100),
}).unwrap();
tile.ingest_delta(&Delta::Observation { score: 0.8 }).unwrap();
let report = tile.tick((i + 1) * 1000);
assert!(report.is_healthy());
}
assert_eq!(tile.graph_shard.edge_count(), 10);
}
}
#[cfg(test)]
mod e_value_accumulation {
use super::*;
#[test]
fn test_e_value_in_report() {
let mut tile = WorkerTileState::new(0);
for _ in 0..5 {
tile.ingest_delta(&Delta::Observation { score: 0.9 }).unwrap();
}
let report = tile.tick(1000);
assert!(report.e_value > 0.0);
}
}
#[cfg(test)]
mod multi_tile_scenario {
use super::*;
#[test]
fn test_deterministic_across_tiles() {
let deltas = [
Delta::EdgeAdd { edge: Edge::new(VertexId(0), VertexId(1)), weight: Weight(100) },
Delta::EdgeAdd { edge: Edge::new(VertexId(1), VertexId(2)), weight: Weight(150) },
Delta::Observation { score: 0.9 },
];
let mut tile1 = WorkerTileState::new(0);
let mut tile2 = WorkerTileState::new(0);
for delta in &deltas {
tile1.ingest_delta(delta).unwrap();
tile2.ingest_delta(delta).unwrap();
}
let report1 = tile1.tick(1000);
let report2 = tile2.tick(1000);
assert_eq!(report1.coherence, report2.coherence);
assert!((report1.e_value - report2.e_value).abs() < 0.001);
}
#[test]
fn test_tile_network() {
let mut tiles: Vec<WorkerTileState> = (0..10)
.map(|id| WorkerTileState::new(id))
.collect();
for (tile_idx, tile) in tiles.iter_mut().enumerate() {
let base = (tile_idx * 10) as u8;
for i in 0..5u8 {
let _ = tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(base + i), VertexId(base + i + 1)),
weight: Weight(100),
});
}
}
let reports: Vec<TileReport> = tiles
.iter_mut()
.enumerate()
.map(|(idx, tile)| tile.tick((idx as u64) * 100))
.collect();
for report in &reports {
assert!(report.is_healthy());
}
}
}
#[cfg(test)]
mod edge_cases {
use super::*;
#[test]
fn test_empty_tile_tick() {
let mut tile = WorkerTileState::new(0);
let report = tile.tick(1000);
assert!(report.is_healthy());
}
#[test]
fn test_tile_with_only_observations() {
let mut tile = WorkerTileState::new(0);
for _ in 0..100 {
tile.ingest_delta(&Delta::Observation { score: 0.5 }).unwrap();
}
let report = tile.tick(1000);
assert!(report.is_healthy());
assert_eq!(tile.graph_shard.edge_count(), 0);
}
#[test]
fn test_tick_at_max() {
let mut tile = WorkerTileState::new(0);
let report = tile.tick(u64::MAX);
assert_eq!(tile.tick, u64::MAX);
assert!(report.is_healthy());
}
#[test]
fn test_alternating_add_remove() {
let mut tile = WorkerTileState::new(0);
for _ in 0..100 {
tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(0), VertexId(1)),
weight: Weight(100),
}).unwrap();
tile.ingest_delta(&Delta::EdgeRemove { edge: EdgeId(0) }).unwrap();
}
assert!(tile.tick(1000).is_healthy());
assert_eq!(tile.graph_shard.edge_count(), 0);
}
}
#[cfg(test)]
mod stress_tests {
use super::*;
#[test]
fn test_high_volume_deltas() {
let mut tile = WorkerTileState::new(0);
for i in 0..1000 {
let src = (i % 200) as u8;
let dst = ((i + 1) % 200) as u8;
if src != dst {
let _ = tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(src), VertexId(dst)),
weight: Weight(100),
});
}
if i % 10 == 0 {
let _ = tile.ingest_delta(&Delta::Observation { score: 0.8 });
}
}
assert!(tile.tick(10000).is_healthy());
}
#[test]
fn test_rapid_tick_cycles() {
let mut tile = WorkerTileState::new(0);
tile.ingest_delta(&Delta::EdgeAdd {
edge: Edge::new(VertexId(0), VertexId(1)),
weight: Weight(100),
}).unwrap();
for i in 0..1000u64 {
assert!(tile.tick(i).is_healthy());
}
}
}

View File

@@ -0,0 +1,249 @@
//! Comprehensive tests for TileReport generation and serialization
//!
//! Tests cover:
//! - Report creation and initialization
//! - Serialization/deserialization roundtrips
//! - Checksum verification
//! - WitnessFragment operations
use cognitum_gate_kernel::report::{TileReport, TileStatus, WitnessFragment};
use cognitum_gate_kernel::shard::EdgeId;
#[cfg(test)]
mod tile_status {
use super::*;
#[test]
fn test_status_values() {
assert_eq!(TileStatus::Active as u8, 0);
assert_eq!(TileStatus::Idle as u8, 1);
assert_eq!(TileStatus::Recovery as u8, 2);
assert_eq!(TileStatus::Error as u8, 3);
}
#[test]
fn test_status_from_u8() {
assert_eq!(TileStatus::from_u8(0), Some(TileStatus::Active));
assert_eq!(TileStatus::from_u8(1), Some(TileStatus::Idle));
assert_eq!(TileStatus::from_u8(255), None);
}
#[test]
fn test_is_healthy() {
assert!(TileStatus::Active.is_healthy());
assert!(TileStatus::Idle.is_healthy());
assert!(!TileStatus::Error.is_healthy());
}
}
#[cfg(test)]
mod witness_fragment {
use super::*;
#[test]
fn test_fragment_creation() {
let frag = WitnessFragment::new(42);
assert_eq!(frag.tile_id, 42);
assert_eq!(frag.min_cut_value, 0);
}
#[test]
fn test_is_fragile() {
let mut frag = WitnessFragment::new(0);
frag.min_cut_value = 5;
assert!(frag.is_fragile(10));
assert!(!frag.is_fragile(5));
}
#[test]
fn test_fragment_hash_deterministic() {
let frag = WitnessFragment::new(5);
assert_eq!(frag.compute_hash(), frag.compute_hash());
}
#[test]
fn test_fragment_hash_unique() {
let frag1 = WitnessFragment::new(1);
let frag2 = WitnessFragment::new(2);
assert_ne!(frag1.compute_hash(), frag2.compute_hash());
}
}
#[cfg(test)]
mod tile_report_creation {
use super::*;
#[test]
fn test_new_report() {
let report = TileReport::new(5);
assert_eq!(report.tile_id, 5);
assert_eq!(report.status, TileStatus::Active);
assert!(report.is_healthy());
}
#[test]
fn test_error_report() {
let report = TileReport::error(10);
assert_eq!(report.status, TileStatus::Error);
assert!(!report.is_healthy());
}
#[test]
fn test_idle_report() {
let report = TileReport::idle(15);
assert_eq!(report.status, TileStatus::Idle);
assert!(report.is_healthy());
}
}
#[cfg(test)]
mod report_health_checks {
use super::*;
#[test]
fn test_needs_attention_boundary_moved() {
let mut report = TileReport::new(0);
assert!(!report.needs_attention());
report.boundary_moved = true;
assert!(report.needs_attention());
}
#[test]
fn test_needs_attention_negative_coherence() {
let mut report = TileReport::new(0);
report.coherence = -100;
assert!(report.needs_attention());
}
}
#[cfg(test)]
mod coherence_conversion {
use super::*;
#[test]
fn test_coherence_f32_values() {
let mut report = TileReport::new(0);
report.coherence = 0;
assert!((report.coherence_f32() - 0.0).abs() < 0.001);
report.coherence = 256;
assert!((report.coherence_f32() - 1.0).abs() < 0.01);
report.coherence = -128;
assert!((report.coherence_f32() - (-0.5)).abs() < 0.01);
}
}
#[cfg(test)]
mod serialization {
use super::*;
#[test]
fn test_to_bytes_size() {
let report = TileReport::new(0);
let bytes = report.to_bytes();
assert_eq!(bytes.len(), 64);
}
#[test]
fn test_roundtrip_basic() {
let report = TileReport::new(42);
let bytes = report.to_bytes();
let restored = TileReport::from_bytes(&bytes).unwrap();
assert_eq!(report.tile_id, restored.tile_id);
assert_eq!(report.status, restored.status);
}
#[test]
fn test_roundtrip_with_data() {
let mut report = TileReport::new(100);
report.coherence = 512;
report.e_value = 2.5;
report.boundary_moved = true;
report.suspicious_edges[0] = EdgeId(100);
let bytes = report.to_bytes();
let restored = TileReport::from_bytes(&bytes).unwrap();
assert_eq!(restored.coherence, 512);
assert!((restored.e_value - 2.5).abs() < 0.001);
assert!(restored.boundary_moved);
assert_eq!(restored.suspicious_edges[0], EdgeId(100));
}
}
#[cfg(test)]
mod checksum {
use super::*;
#[test]
fn test_checksum_deterministic() {
let report = TileReport::new(42);
assert_eq!(report.checksum(), report.checksum());
}
#[test]
fn test_checksum_different_reports() {
let r1 = TileReport::new(1);
let r2 = TileReport::new(2);
assert_ne!(r1.checksum(), r2.checksum());
}
#[test]
fn test_verify_checksum() {
let report = TileReport::new(42);
let cs = report.checksum();
assert!(report.verify_checksum(cs));
assert!(!report.verify_checksum(0));
}
}
#[cfg(test)]
mod report_size {
use super::*;
use std::mem::size_of;
#[test]
fn test_report_fits_cache_line() {
assert!(size_of::<TileReport>() <= 64);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_serialization_roundtrip(
tile_id in 0u8..255,
coherence in i16::MIN..i16::MAX,
e_value in 0.0f32..100.0,
boundary_moved: bool
) {
let mut report = TileReport::new(tile_id);
report.coherence = coherence;
report.e_value = e_value;
report.boundary_moved = boundary_moved;
let bytes = report.to_bytes();
let restored = TileReport::from_bytes(&bytes).unwrap();
assert_eq!(report.tile_id, restored.tile_id);
assert_eq!(report.coherence, restored.coherence);
assert_eq!(report.boundary_moved, restored.boundary_moved);
}
#[test]
fn prop_checksum_changes_with_data(a: i16, b: i16) {
prop_assume!(a != b);
let mut r1 = TileReport::new(0);
let mut r2 = TileReport::new(0);
r1.coherence = a;
r2.coherence = b;
assert_ne!(r1.checksum(), r2.checksum());
}
}
}

View File

@@ -0,0 +1,299 @@
//! 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);
}
}
}