swactor/crates/distribution/tests/swim_dissemination.rs

152 lines
5.2 KiB
Rust
Raw Normal View History

use distribution::swim::dissemination::{DisseminationQueue, membership_update};
use distribution::types::{MemberState, NodeId};
fn node(byte: u8) -> NodeId {
NodeId([byte; 32])
}
// ─── Basic queue operations ─────────────────────────────────────────────────
#[test]
fn enqueue_and_take_single_update() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 5);
assert_eq!(q.len(), 1);
let updates = q.take(10);
assert_eq!(updates.len(), 1);
assert_eq!(updates[0].node_id, node(1));
}
#[test]
fn take_respects_max_count() {
let mut q = DisseminationQueue::new(3);
for i in 1..=5 {
q.enqueue(membership_update(node(i), MemberState::Alive, 0), 10);
}
let updates = q.take(2);
assert_eq!(updates.len(), 2);
}
// ─── Priority ordering ─────────────────────────────────────────────────────
#[test]
fn dead_updates_are_prioritized_over_suspect_and_alive() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 10);
q.enqueue(membership_update(node(2), MemberState::Dead, 0), 10);
q.enqueue(membership_update(node(3), MemberState::Suspect, 0), 10);
let updates = q.take(3);
assert_eq!(updates[0].state, MemberState::Dead, "Dead should be first");
assert_eq!(
updates[1].state,
MemberState::Suspect,
"Suspect should be second"
);
assert_eq!(updates[2].state, MemberState::Alive, "Alive should be last");
}
// ─── Transmit budget and eviction ───────────────────────────────────────────
#[test]
fn entries_evicted_after_transmit_budget_exhausted() {
// lambda=1, cluster_size=2 → budget = 1 * ceil(log2(2)) = 1
let mut q = DisseminationQueue::new(1);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 2);
// First take: remaining goes from 1 to 0
let updates = q.take(10);
assert_eq!(updates.len(), 1);
// Entry should be evicted now
assert_eq!(q.len(), 0);
let updates = q.take(10);
assert_eq!(updates.len(), 0);
}
#[test]
fn larger_cluster_gives_higher_transmit_budget() {
// lambda=2, cluster_size=16 → budget = 2 * ceil(log2(16)) = 2 * 4 = 8
let mut q = DisseminationQueue::new(2);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 16);
// Take 8 times — entry should survive all of them
for i in 0..8 {
let updates = q.take(10);
assert_eq!(updates.len(), 1, "take #{i} should still have the entry");
}
// 9th take: entry should be evicted
assert_eq!(q.len(), 0);
}
// ─── Dedup: newer update for same node replaces older ───────────────────────
#[test]
fn newer_update_for_same_node_replaces_older() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 10);
q.enqueue(membership_update(node(1), MemberState::Suspect, 0), 10);
assert_eq!(q.len(), 1, "should replace, not duplicate");
let updates = q.take(10);
assert_eq!(updates[0].state, MemberState::Suspect);
}
#[test]
fn higher_incarnation_replaces_lower() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Dead, 5), 10);
// Same node, higher incarnation, Alive (incarnation wins over state)
q.enqueue(membership_update(node(1), MemberState::Alive, 6), 10);
assert_eq!(q.len(), 1);
let updates = q.take(10);
assert_eq!(updates[0].incarnation, 6);
assert_eq!(updates[0].state, MemberState::Alive);
}
#[test]
fn lower_incarnation_is_ignored() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Alive, 5), 10);
q.enqueue(membership_update(node(1), MemberState::Dead, 3), 10);
let updates = q.take(10);
assert_eq!(
updates[0].incarnation, 5,
"older incarnation should be ignored"
);
assert_eq!(updates[0].state, MemberState::Alive);
}
// ─── Piggyback serialization ────────────────────────────────────────────────
#[test]
fn pack_and_unpack_piggyback_roundtrip() {
let mut q = DisseminationQueue::new(3);
q.enqueue(membership_update(node(1), MemberState::Alive, 0), 10);
q.enqueue(membership_update(node(2), MemberState::Dead, 3), 10);
let bytes = q.pack_piggyback(10);
assert!(!bytes.is_empty());
let unpacked = DisseminationQueue::unpack_piggyback(&bytes);
assert_eq!(unpacked.len(), 2);
// Dead should be first (priority ordering from take())
assert_eq!(unpacked[0].state, MemberState::Dead);
}
#[test]
fn unpack_empty_piggyback_returns_empty() {
let unpacked = DisseminationQueue::unpack_piggyback(&[]);
assert!(unpacked.is_empty());
}
#[test]
fn unpack_garbage_returns_empty() {
let unpacked = DisseminationQueue::unpack_piggyback(b"not valid json");
assert!(unpacked.is_empty());
}