use super::super::super::{EdgeId, Timestamp, VertexId};
use super::super::MutableCsr;
#[test]
fn test_compact_with_ts_merges_overflow() {
let mut csr = MutableCsr::with_capacity(10, 100);
for i in 1..=6 {
let dst = VertexId::edge_endpoint_key(i as u32, 0);
csr.insert_edge(0u32, dst, EdgeId(i as u64), 1).unwrap();
}
csr.delete_edge(0u32, EdgeId(3), 5).unwrap();
csr.delete_edge(0u32, EdgeId(5), 5).unwrap();
csr.delete_edge(0u32, EdgeId(6), 5).unwrap();
let removed = csr.compact_with_ts_reporting(6, 0.25, &mut |_, _| {});
assert_eq!(removed, 3);
assert!(csr.overflow_chunks.get(0).is_none_or(Vec::is_empty));
let edges = csr.edges_of(0u32, 3);
assert_eq!(edges.len(), 3);
}
#[test]
fn test_compact_with_ts_keeps_deleted_entries_without_cutoff() {
let mut csr = MutableCsr::with_capacity(10, 100);
for i in 1..=3 {
let dst = VertexId::edge_endpoint_key(i as u32, 0);
csr.insert_edge(0u32, dst, EdgeId(i as u64), 1).unwrap();
}
csr.delete_edge(0u32, EdgeId(2), 5).unwrap();
let removed = csr.compact_with_ts_reporting(Timestamp::MAX, 0.25, &mut |_, _| {});
assert_eq!(removed, 0);
assert_eq!(csr.edges_of(0u32, 3).len(), 3);
assert_eq!(csr.edges_of(0u32, 6).len(), 2);
let removed = csr.compact_with_ts_reporting(6, 0.25, &mut |_, _| {});
assert_eq!(removed, 1);
assert_eq!(csr.edges_of(0u32, 3).len(), 2);
}
#[test]
fn test_compact_with_ts_reporting_reports_removed_edges() {
let mut csr = MutableCsr::with_capacity(10, 100);
for i in 1..=3 {
let dst = VertexId::edge_endpoint_key(i as u32, 0);
csr.insert_edge(0u32, dst, EdgeId(i as u64), 1).unwrap();
}
csr.delete_edge(0u32, EdgeId(2), 5).unwrap();
let mut reported = Vec::new();
let removed = csr.compact_with_ts_reporting(6, 0.25, &mut |edge_id, delete_ts| {
reported.push((edge_id, delete_ts));
});
assert_eq!(removed, 1);
assert_eq!(reported, vec![(EdgeId(2), 5)]);
}
#[test]
fn test_compact_with_ts_guards_reserve_ratio_ge_one() {
let mut csr = MutableCsr::with_capacity(4, 100);
for i in 1..=6i64 {
csr.insert_edge(
0u32,
VertexId::edge_endpoint_key((i) as u32, 0),
EdgeId(i as u64),
1,
)
.unwrap();
}
csr.insert_edge(1u32, VertexId::edge_endpoint_key(1, 0), EdgeId(7), 1)
.unwrap();
let removed = csr.compact_with_ts_reporting(3, 1.0, &mut |_, _| {});
assert_eq!(removed, 0);
let capacity = csr.total_edge_capacity;
assert!(
capacity <= 7 + 4,
"capacity must stay bounded, got {}",
capacity
);
assert_eq!(csr.edges_of(0u32, 3).len(), 6);
assert_eq!(csr.edges_of(1u32, 3).len(), 1);
}
#[test]
fn test_compact_with_ts_zero_ratio_keeps_exact_degree() {
let mut csr = MutableCsr::with_capacity(4, 100);
for i in 1..=3i64 {
csr.insert_edge(
0u32,
VertexId::edge_endpoint_key((i) as u32, 0),
EdgeId(i as u64),
1,
)
.unwrap();
}
let removed = csr.compact_with_ts_reporting(3, 0.0, &mut |_, _| {});
assert_eq!(removed, 0);
assert_eq!(csr.total_edge_capacity, 3);
assert_eq!(csr.edges_of(0u32, 3).len(), 3);
}
#[test]
fn test_overflow_cleared_after_compact() {
let mut csr = MutableCsr::with_overflow_chunk_edges(10, 100, 2);
for vid in 0..20u32 {
for i in 0..8 {
let dst = VertexId::edge_endpoint_key((vid + 1) * 100 + i as u32, 0);
csr.insert_edge(vid, dst, EdgeId(vid as u64 * 10 + i as u64), 1)
.unwrap();
}
}
assert!(!csr.overflow_chunks.is_empty());
let mut removed = Vec::new();
csr.compact_with_ts_reporting(2, 0.0, &mut |id, ts| removed.push((id, ts)));
assert!(csr.overflow_chunks.is_empty());
}
#[test]
fn test_compact_vertex_is_row_scoped() {
let mut csr = MutableCsr::with_capacity(10, 100);
csr.insert_edge(0u32, VertexId::edge_endpoint_key(1, 0), EdgeId(100), 1)
.unwrap();
csr.insert_edge(0u32, VertexId::edge_endpoint_key(2, 0), EdgeId(101), 1)
.unwrap();
csr.insert_edge(5u32, VertexId::edge_endpoint_key(6, 0), EdgeId(102), 1)
.unwrap();
assert!(csr.delete_edge(0u32, EdgeId(100), 2).unwrap());
assert_eq!(csr.reclaimable_count(0, 3), 1);
assert_eq!(csr.reclaimable_count(5, 3), 0);
assert!(csr.vertex_needs_compact(0, 3));
assert!(!csr.vertex_needs_compact(5, 3));
let mut reported = Vec::new();
let removed = csr.compact_vertex_with_reporting(0, 3, &mut |id, ts| reported.push((id, ts)));
assert_eq!(removed, 1);
assert_eq!(reported, vec![(EdgeId(100), 2)]);
assert_eq!(csr.reclaimable_count(0, 3), 0);
assert_eq!(csr.edges_of(5, 3).len(), 1);
assert_eq!(csr.edges_of(0, 3).len(), 1);
let (live, dead, _) = csr.vertex_census(0);
assert_eq!((live, dead), (1, 0));
}
#[test]
fn test_compact_vertex_keeps_pinned_tombstones() {
let mut csr = MutableCsr::with_capacity(10, 100);
csr.insert_edge(0u32, VertexId::edge_endpoint_key(1, 0), EdgeId(100), 1)
.unwrap();
assert!(csr.delete_edge(0u32, EdgeId(100), 10).unwrap());
assert_eq!(csr.reclaimable_count(0, 5), 0);
assert!(!csr.vertex_needs_compact(0, 5));
let removed = csr.compact_vertex_with_reporting(0, 5, &mut |_, _| {});
assert_eq!(removed, 0);
assert_eq!(csr.edges_of(0, 9).len(), 1);
assert_eq!(csr.edges_of(0, 10).len(), 0);
}
#[test]
fn test_compact_vertex_repacks_overflow() {
let mut csr = MutableCsr::with_overflow_chunk_edges(10, 100, 2);
for i in 0..6u64 {
let dst = VertexId::edge_endpoint_key(100 + i as u32, 0);
csr.insert_edge(0u32, dst, EdgeId(i), 1).unwrap();
}
assert!(csr.get_overflow_chunks(0).is_some());
assert!(csr.delete_edge(0u32, EdgeId(0), 2).unwrap());
assert!(csr.delete_edge(0u32, EdgeId(5), 2).unwrap());
let removed = csr.compact_vertex_with_reporting(0, 3, &mut |_, _| {});
assert_eq!(removed, 2);
assert_eq!(csr.edges_of(0, 3).len(), 4);
assert_eq!(csr.reclaimable_count(0, 3), 0);
}
#[test]
fn test_compact_reduces_fragmentation() {
let mut csr = MutableCsr::with_capacity(10, 100);
for i in 1..=6 {
let dst = VertexId::edge_endpoint_key(i as u32, 0);
csr.insert_edge(0u32, dst, EdgeId(i as u64), 1).unwrap();
}
for i in 1..=3 {
csr.delete_edge(0u32, EdgeId(i as u64), 2).unwrap();
}
let ratio_before = csr.fragmentation_ratio();
assert!(
ratio_before > 0.5,
"Setup failed: insufficient fragmentation"
);
csr.compact_with_ts_reporting(100, 0.25, &mut |_, _| {});
let ratio_after = csr.fragmentation_ratio();
assert!(
ratio_after < ratio_before,
"Compact did not reduce fragmentation: before={}, after={}",
ratio_before,
ratio_after
);
}
#[test]
fn compact_tighten_leaves_no_live_tail_slots() {
let mut csr = MutableCsr::with_capacity(10, 100);
for i in 0..6u32 {
csr.insert_edge(
0u32,
VertexId::edge_endpoint_key(i + 1, 0),
EdgeId(i as u64),
1,
)
.unwrap();
}
csr.delete_edge(0u32, EdgeId(1), 5).unwrap();
csr.delete_edge(0u32, EdgeId(3), 5).unwrap();
let removed = csr.compact_vertex_with_reporting(0u32, 6, &mut |_, _| {});
assert_eq!(removed, 2);
let data = csr.dump();
let mut loaded = MutableCsr::new();
loaded
.load(&data)
.expect("post-compact dump must round-trip");
assert_eq!(loaded.edge_count(), csr.edge_count());
}