extern crate alloc;
use alloc::vec::Vec;
use crate::kairos::Kairos;
use crate::metis::{Anchor, Dot, Locus, Rhapsody};
use proptest::prelude::*;
use super::d;
mod refusals;
#[derive(Clone, Debug)]
struct Stroke {
station_pick: usize,
seek: bool,
before_side: bool,
tick: u64,
}
fn arb_strokes() -> impl Strategy<Value = Vec<Stroke>> {
prop::collection::vec(
(
0usize..3,
prop::bool::weighted(0.15),
any::<bool>(),
0u64..5000,
)
.prop_map(|(station_pick, seek, before_side, tick)| Stroke {
station_pick,
seek,
before_side,
tick,
}),
0..96,
)
}
fn build_typed(strokes: &[Stroke]) -> Rhapsody {
let mut rhapsody = Rhapsody::new();
let mut next_index = [1u64; 3];
let mut last_dot: [Option<Dot>; 3] = [None; 3];
let mut clocks = [(0u64, 0u16); 3];
for stroke in strokes {
let pick = stroke.station_pick % 3;
let station = u32::try_from(pick).expect("small pick") + 1;
let dot = d(station, next_index[pick]);
next_index[pick] += 1;
let anchor = match (stroke.seek, last_dot[pick]) {
(false, Some(previous)) => Anchor::After(previous.into()),
(true, Some(previous)) if stroke.before_side => Anchor::Before(previous.into()),
_ => Anchor::Origin,
};
let (physical, logical) = clocks[pick];
let minted = if stroke.tick == 0 {
let bumped = logical.saturating_add(1);
if bumped == u16::MAX {
(physical.saturating_add(1), 0)
} else {
(physical, bumped)
}
} else {
(physical.saturating_add(stroke.tick), 0)
};
clocks[pick] = minted;
let rank = Kairos::new(minted.0, minted.1, station, 0u16);
assert!(rhapsody.weave(dot, Locus { anchor, rank }));
last_dot[pick] = Some(dot);
}
rhapsody
}
proptest! {
#[test]
fn prop_snapshot_round_trips_and_agrees_with_v2(strokes in arb_strokes()) {
let rhapsody = build_typed(&strokes);
let by_snapshot = Rhapsody::from_snapshot_bytes(&rhapsody.to_snapshot_bytes());
prop_assert_eq!(by_snapshot, Ok(rhapsody.clone()));
let by_v2 = Rhapsody::from_bytes(&rhapsody.to_bytes()).expect("v2 round-trips");
prop_assert_eq!(by_v2, rhapsody);
}
#[test]
fn prop_snapshot_decode_then_encode_is_identity(strokes in arb_strokes()) {
let bytes = build_typed(&strokes).to_snapshot_bytes();
let (decoded, tail) = Rhapsody::from_snapshot_prefix(&bytes).expect("own frame decodes");
prop_assert!(tail.is_empty());
prop_assert_eq!(decoded.to_snapshot_bytes(), bytes);
}
#[test]
fn prop_snapshot_round_trips_adversarial_histories(
weaves in super::properties::arb_weaves(),
) {
let rhapsody = super::properties::build(&weaves);
let bytes = rhapsody.to_snapshot_bytes();
prop_assert_eq!(rhapsody.snapshot_encoded_len(), bytes.len());
prop_assert_eq!(Rhapsody::from_snapshot_bytes(&bytes), Ok(rhapsody));
}
#[test]
fn prop_snapshot_never_pays_more_than_the_header(strokes in arb_strokes()) {
let rhapsody = build_typed(&strokes);
let snapshot = rhapsody.to_snapshot_bytes();
prop_assert_eq!(rhapsody.snapshot_encoded_len(), snapshot.len());
prop_assert!(snapshot.len() <= rhapsody.to_bytes().len() + 4);
}
#[test]
fn prop_rank_successor_is_the_clock_frozen_mint(physical in 0u64..1_000_000, mints in 1usize..24) {
use crate::kairos::{Clock, ClockConfig, VirtualTimeSource};
let source = VirtualTimeSource::new(physical);
let clock = Clock::new(source, 7, ClockConfig::default())
.expect("virtual clock builds");
let mut last = clock.now(0u16);
for _ in 0..mints {
let next = clock.now(0u16);
let expected = crate::metis::rank_successor(last.physical(), last.logical());
prop_assert_eq!((next.physical(), next.logical()), expected);
last = next;
}
}
}
#[test]
fn test_rank_successor_rolls_over_at_the_sentinel() {
assert_eq!(
crate::metis::rank_successor(41, u16::MAX - 2),
(41, u16::MAX - 1)
);
assert_eq!(crate::metis::rank_successor(41, u16::MAX - 1), (42, 0));
assert_eq!(crate::metis::rank_successor(41, u16::MAX), (42, 0));
assert_eq!(
crate::metis::rank_successor(u64::MAX, u16::MAX),
(u64::MAX, 0)
);
}
#[test]
fn test_a_burst_compresses_toward_the_step_width() {
let strokes: Vec<Stroke> = (0..64)
.map(|k| Stroke {
station_pick: 0,
seek: false,
before_side: false,
tick: if k % 4 == 0 { 0 } else { 50_000_000 },
})
.collect();
let rhapsody = build_typed(&strokes);
let snapshot = rhapsody.to_snapshot_bytes();
let v2 = rhapsody.to_bytes();
assert_eq!(v2.len(), 5 + 30 + 63 * 42 + 21);
assert_eq!(snapshot.len(), 9 + 46 + 63 * 6 + 21);
assert!(snapshot.len() * 5 < v2.len());
}
#[test]
fn test_tombstones_ride_inside_runs() {
use crate::metis::{DotSet, DotStore};
let strokes: Vec<Stroke> = (0..8)
.map(|_| Stroke {
station_pick: 0,
seek: false,
before_side: false,
tick: 1000,
})
.collect();
let rhapsody = build_typed(&strokes);
let mut removal_context = DotSet::new();
for index in [3u64, 4, 5] {
assert!(removal_context.insert(d(1, index)));
}
let self_context = rhapsody.woven().clone();
let churned = rhapsody.causal_merge(&self_context, &Rhapsody::new(), &removal_context);
assert_eq!(churned.skeleton_len(), 8);
assert_eq!(churned.visible_len(), 5);
let frame = churned.to_snapshot_bytes();
assert_eq!(&frame[1..5], &1u32.to_be_bytes());
assert_eq!(&frame[5..9], &0u32.to_be_bytes());
assert_eq!(Rhapsody::from_snapshot_bytes(&frame), Ok(churned));
}
#[test]
fn test_a_deleted_low_dot_survives_the_suffix_budget() {
use crate::metis::{DotSet, DotStore};
let strokes: Vec<Stroke> = (0..40)
.map(|_| Stroke {
station_pick: 0,
seek: false,
before_side: false,
tick: 1000,
})
.collect();
let rhapsody = build_typed(&strokes);
let mut removal_context = DotSet::new();
assert!(removal_context.insert(d(1, 1)));
let self_context = rhapsody.woven().clone();
let churned = rhapsody.causal_merge(&self_context, &Rhapsody::new(), &removal_context);
assert_eq!(churned.skeleton_len(), 40);
assert_eq!(churned.visible_len(), 39);
let suffix = {
let mut visible = DotSet::new();
for index in 2..=40u64 {
assert!(visible.insert(d(1, index)));
}
visible.to_bytes()
};
assert!(DotSet::from_bytes(&suffix).is_err());
assert_eq!(
Rhapsody::from_snapshot_bytes(&churned.to_snapshot_bytes()).as_ref(),
Ok(&churned)
);
assert_eq!(
Rhapsody::from_bytes(&churned.to_bytes()).as_ref(),
Ok(&churned)
);
}
#[test]
fn test_prefix_acceptance_is_independent_of_the_tail() {
use crate::metis::{DotSet, DotStore};
let strokes: Vec<Stroke> = (0..40)
.map(|_| Stroke {
station_pick: 0,
seek: false,
before_side: false,
tick: 1000,
})
.collect();
let rhapsody = build_typed(&strokes);
let mut removal_context = DotSet::new();
assert!(removal_context.insert(d(1, 1)));
let self_context = rhapsody.woven().clone();
let churned = rhapsody.causal_merge(&self_context, &Rhapsody::new(), &removal_context);
let frame = churned.to_snapshot_bytes();
let (bare, bare_tail) = Rhapsody::from_snapshot_prefix(&frame).expect("bare frame decodes");
assert!(bare_tail.is_empty());
let mut framed = frame.clone();
framed.extend_from_slice(&[0xAA; 64]);
let (tailed, tail) = Rhapsody::from_snapshot_prefix(&framed).expect("tailed frame decodes");
assert_eq!(tail, &[0xAA; 64]);
assert_eq!(bare, tailed);
let v2 = churned.to_bytes();
let (v2_bare, _) = Rhapsody::from_prefix(&v2).expect("bare v2 decodes");
let mut v2_framed = v2;
v2_framed.extend_from_slice(&[0xAA; 64]);
let (v2_tailed, v2_tail) = Rhapsody::from_prefix(&v2_framed).expect("tailed v2 decodes");
assert_eq!(v2_tail, &[0xAA; 64]);
assert_eq!(v2_bare, v2_tailed);
}
proptest! {
#[test]
fn prop_round_trips_survive_deletion(strokes in arb_strokes(), mask in any::<u64>()) {
use crate::metis::{DotSet, DotStore};
let rhapsody = build_typed(&strokes);
let mut removal_context = DotSet::new();
for (ordinal, dot) in rhapsody.woven().dots().enumerate() {
if mask & (1 << (ordinal % 64)) != 0 {
let _ = removal_context.insert(dot);
}
}
let self_context = rhapsody.woven().clone();
let churned = rhapsody.causal_merge(&self_context, &Rhapsody::new(), &removal_context);
let by_snapshot = Rhapsody::from_snapshot_bytes(&churned.to_snapshot_bytes());
prop_assert_eq!(by_snapshot, Ok(churned.clone()));
let by_v2 = Rhapsody::from_bytes(&churned.to_bytes());
prop_assert_eq!(by_v2, Ok(churned));
}
#[test]
fn prop_wire_bytes_are_spelling_independent(strokes in arb_strokes(), mask in any::<u64>()) {
use crate::metis::{DotSet, DotStore, Retired};
let rhapsody = build_typed(&strokes);
let mut removal_context = DotSet::new();
for (ordinal, dot) in rhapsody.woven().dots().enumerate() {
if mask & (1 << (ordinal % 64)) != 0 {
let _ = removal_context.insert(dot);
}
}
let self_context = rhapsody.woven().clone();
let mut churned = rhapsody.causal_merge(&self_context, &Rhapsody::new(), &removal_context);
let _ = churned.condense(&Retired::trust(removal_context));
let v2 = churned.to_bytes();
let snapshot = churned.to_snapshot_bytes();
let rebuilt = Rhapsody::from_bytes(&v2).expect("the canonical frame decodes");
prop_assert_eq!(&rebuilt, &churned);
prop_assert_eq!(rebuilt.to_bytes(), v2, "v2 bytes are layout-free");
prop_assert_eq!(
rebuilt.to_snapshot_bytes(),
snapshot,
"snapshot bytes are layout-free"
);
}
}