extern crate alloc;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use crate::metis::{Dot, DotSet, Retired, Retirement, Stability};
use super::super::editor::{Replica, gossip_text};
use super::split_two;
#[test]
fn test_gc_bounds_the_session() {
let roster = [1u32, 2u32, 3u32];
let mut replicas: Vec<Replica> = roster.iter().map(|&s| Replica::new(s, &roster)).collect();
let pairs_per_replica = 300usize;
for round in 0..pairs_per_replica {
for idx in 0..replicas.len() {
let ch = char::from(b'a' + u8::try_from(round % 26).unwrap_or(0));
let dot = replicas[idx].insert(ch, None);
replicas[idx].delete(dot);
let next = (idx + 1) % replicas.len();
let (from, into) = split_two(&mut replicas, idx, next);
gossip_text(from, into);
}
}
for _ in 0..2 {
for i in 0..replicas.len() {
for j in 0..replicas.len() {
if i != j {
let (from, into) = split_two(&mut replicas, i, j);
gossip_text(from, into);
}
}
}
}
let total_writes = pairs_per_replica * replicas.len();
for r in &replicas {
assert_eq!(r.visible_len(), 0);
}
let skeleton_before = replicas[0].skeleton_len();
assert_eq!(
skeleton_before, total_writes,
"every write is a retained tombstone before GC"
);
let retired = honest_retirement(&replicas);
let mut stability = Stability::new(roster.iter().copied());
for r in &replicas {
r.report_into(&mut stability);
}
let _watermark = stability.watermark();
let mut total_excised = 0usize;
for r in &mut replicas {
total_excised += r.condense(&retired);
}
assert!(total_excised > 0);
for r in &replicas {
assert_eq!(
r.skeleton_len(),
0,
"origin-anchored churn leaves NO uncollectable residue: every tombstone is a sterile leaf",
);
}
}
#[test]
fn test_gc_residue_is_anchored_tombstone_chains() {
let roster = [1u32];
let mut r = Replica::new(1, &roster);
let mut chain = Vec::new();
let mut anchor = None;
for i in 0..10 {
let ch = char::from(b'a' + u8::try_from(i).unwrap_or(0));
let dot = r.insert(ch, anchor);
chain.push(dot);
anchor = Some(dot);
}
for &dot in &chain[1..9] {
r.delete(dot);
}
assert_eq!(r.visible_len(), 2);
assert_eq!(r.skeleton_len(), 10);
let mut retired = DotSet::new();
for &dot in &chain[1..9] {
let _ = retired.insert(dot);
}
let excised = r.condense(&Retired::trust(retired));
assert_eq!(
excised, 0,
"an internally-dead span with a live tail is uncollectable by leaf-first condense"
);
assert_eq!(
r.skeleton_len(),
10,
"the 8 dead middles are retained as anchored tombstones"
);
assert_eq!(r.visible_len(), 2);
}
fn honest_retirement(replicas: &[Replica]) -> Retired {
let mut all_dots: BTreeSet<Dot> = BTreeSet::new();
for replica in replicas {
for dot in replica.text_context().dots() {
let _ = all_dots.insert(dot);
}
}
let mut retirement = Retirement::new(replicas.iter().map(Replica::station));
for replica in replicas {
let mut applied = DotSet::new();
for &dot in &all_dots {
if !replica.order().contains(&dot) {
let _ = applied.insert(dot);
}
}
retirement
.acknowledge(replica.station(), &applied)
.expect("a replica's own station is on the roster");
}
retirement.retired()
}