use exocortex_cluster::change_log::{ChangeLog, Replay, RingChangeLog};
use exocortex_storage::Invalidation as StorageInv;
use exocortex_wire::cluster::v1::InvalidationEnvelope;
fn envelope(lsn: u64) -> InvalidationEnvelope {
let inv = StorageInv::MemoryUpserted {
id: exocortex_kernel::MemoryId([lsn as u8; 16]),
lsn,
};
let inv_pb = exocortex_cluster::sse::invalidation_to_pb(&inv);
let mut env = InvalidationEnvelope {
wire_version: 1,
ontology_fingerprint: vec![0; 32],
emitter_node_id: "conformance".into(),
inv: Some(inv_pb),
hmac: vec![],
};
exocortex_wire::signing::sign_invalidation_envelope(&[7; 32], &mut env);
env
}
#[test]
fn replay_returns_strictly_newer_envelopes_oldest_first() {
let log = RingChangeLog::new();
for lsn in 1..=5 {
log.append(envelope(lsn));
}
match log.replay_since(2) {
Replay::Fresh(envs) => {
let lsns: Vec<u64> = envs
.iter()
.map(|e| e.inv.as_ref().unwrap().backend_lsn)
.collect();
assert_eq!(lsns, vec![3, 4, 5]);
}
Replay::TooOld => panic!("mid-buffer replay must be fresh"),
}
assert!(matches!(log.replay_since(5), Replay::Fresh(envs) if envs.is_empty()));
}
#[test]
fn floor_is_the_oldest_buffered_lsn_not_the_frontier() {
let log = RingChangeLog::with_capacity(3);
for lsn in 11..=15 {
log.append(envelope(lsn));
}
assert_eq!(log.replay_floor(), 13, "oldest of the last 3");
assert_eq!(log.frontier(), Some(15));
assert!(matches!(log.replay_since(12), Replay::Fresh(_)));
assert!(matches!(log.replay_since(11), Replay::TooOld));
}
#[test]
fn empty_log_is_only_fresh_before_the_first_append() {
let log = RingChangeLog::with_capacity(2);
assert!(matches!(log.replay_since(0), Replay::Fresh(envs) if envs.is_empty()));
for lsn in 1..=4 {
log.append(envelope(lsn));
}
assert!(matches!(log.replay_since(1), Replay::TooOld));
assert!(matches!(log.replay_since(4), Replay::Fresh(envs) if envs.is_empty()));
}
#[test]
fn u64_max_since_answers_without_overflow() {
let log = RingChangeLog::new();
log.append(envelope(1));
assert!(matches!(log.replay_since(u64::MAX), Replay::Fresh(envs) if envs.is_empty()));
}
#[test]
fn frontier_tracks_the_high_water_mark() {
let log = RingChangeLog::with_capacity(1);
assert_eq!(log.frontier(), None);
log.append(envelope(9));
log.append(envelope(10));
assert_eq!(log.replay_floor(), 10, "capacity 1 evicted 9");
assert_eq!(log.frontier(), Some(10));
}
#[test]
fn the_seam_is_object_safe() {
fn takes_dyn(log: &dyn ChangeLog) -> u64 {
log.append(envelope(1));
log.replay_floor()
}
let log = RingChangeLog::new();
assert_eq!(takes_dyn(&log), 1);
}