minerva 0.2.0

Causal ordering for distributed systems
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() {
    // Origin-anchored type/delete churn leaves sterile tombstone leaves, so an
    // honest retirement claim can collapse the skeleton completely.
    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() {
    // A dead span with a live tail is not sterile: the live successor anchors
    // through the tombstones, so leaf-first condense must spare them.
    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);
}

/// Gathers an honest retirement claim through the tracker meet: every claimed
/// dot is invisible at every replica in the simulated deployment.
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()
}