extern crate alloc;
use alloc::vec::Vec;
use core::num::NonZeroU32;
use crate::kairos::{Clock, TickCounter};
use crate::metis::{Anchor, Dot, DotSet, Locus, Rhapsody};
use proptest::prelude::*;
use super::{Seq, arb_ops_with_paste, run};
#[derive(Clone, Debug)]
enum Storm {
InsertInside { pick: usize },
DeleteSpan { pick: usize, span: usize },
UndoLastInsert,
}
fn arb_storm() -> impl Strategy<Value = Vec<Storm>> {
let op = prop_oneof![
3 => (0usize..64).prop_map(|pick| Storm::InsertInside { pick }),
2 => (0usize..64, 1usize..9).prop_map(|(pick, span)| Storm::DeleteSpan { pick, span }),
1 => Just(Storm::UndoLastInsert),
];
prop::collection::vec(op, 0..14)
}
proptest! {
#[test]
fn prop_chain_identity_survives_interior_ambiguity(
ops in arb_ops_with_paste(),
storm in arb_storm(),
fork_inserts in 0usize..4,
) {
let (live, _) = run(&ops);
let mut state = live.iter().fold(Seq::new(), |acc, r| acc.merge(r));
let fork = state.clone();
let before: Vec<(Dot, Locus)> = state
.store()
.woven()
.dots()
.map(|dot| {
let locus = state.store().locus(dot).expect("woven dots have loci");
(dot, locus)
})
.collect();
let before_order: Vec<Dot> = state.store().order();
let clock = Clock::with_default_config(TickCounter::new(), 9).unwrap();
let mut inserted: Vec<Dot> = Vec::new();
for action in &storm {
match *action {
Storm::InsertInside { pick } => {
let woven: Vec<Dot> = state.store().woven().dots().collect();
if woven.is_empty() {
continue;
}
let anchor = Anchor::After(woven[pick % woven.len()].into());
if let Some(top) = state.store().children_of(anchor).next()
&& let Some(locus) = state.store().locus(top)
{
clock.observe(locus.rank);
}
let dot = state.next_dot(9);
let rank = clock.now(0u16);
let mut delta = Rhapsody::new();
let woven_fresh = delta.weave(dot, Locus { anchor, rank });
prop_assert!(woven_fresh);
state.merge_from(&Seq::from_store(delta));
inserted.push(dot);
}
Storm::DeleteSpan { pick, span } => {
let visible = state.store().order();
if visible.is_empty() {
continue;
}
let start = pick % visible.len();
let mut superseded = DotSet::new();
for &dot in visible.iter().skip(start).take(span) {
let _ = superseded.insert(dot);
}
state.merge_from(&Seq::from_context(superseded));
}
Storm::UndoLastInsert => {
if let Some(dot) = inserted.pop() {
let mut superseded = DotSet::new();
let _ = superseded.insert(dot);
state.merge_from(&Seq::from_context(superseded));
}
}
}
}
let mut fork = fork;
if fork_inserts > 0 {
let fork_clock =
Clock::with_default_config(TickCounter::new(), 8).unwrap();
let len = u32::try_from(fork_inserts).expect("small");
let reservation = fork_clock.now_run(NonZeroU32::new(len).unwrap(), 0u16);
let first = fork.next_dot(8);
let anchor = fork
.store()
.order()
.first()
.map_or(Anchor::Origin, |&head| Anchor::After(head.into()));
let mut delta = Rhapsody::new();
prop_assert!(delta.weave_run(first, anchor, reservation));
fork.merge_from(&Seq::from_store(delta));
}
let pure = state.merge(&fork);
state.merge_from(&fork);
prop_assert_eq!(&state, &pure);
state.store().check_order_thread();
for &(dot, locus) in &before {
prop_assert_eq!(
state.store().locus(dot),
Some(locus),
"dot ({}, {}) lost or changed its locus under interior ambiguity",
dot.station(),
dot.counter()
);
}
let after_order = state.store().order();
let originals_after: Vec<Dot> = after_order
.iter()
.copied()
.filter(|dot| before.iter().any(|&(seen, _)| seen == *dot))
.collect();
let originals_expected: Vec<Dot> = before_order
.iter()
.copied()
.filter(|&dot| state.store().is_visible(dot))
.collect();
prop_assert_eq!(originals_after, originals_expected);
let store = state.store();
let v2 = store.to_bytes();
let decoded = Rhapsody::from_bytes(&v2).expect("own encoding decodes");
prop_assert_eq!(&decoded, store);
prop_assert_eq!(&decoded.to_bytes(), &v2);
let snapshot = store.to_snapshot_bytes();
let resnapped = Rhapsody::from_snapshot_bytes(&snapshot).expect("own snapshot decodes");
prop_assert_eq!(&resnapped, store);
prop_assert_eq!(resnapped.to_snapshot_bytes(), snapshot);
prop_assert_eq!(resnapped.to_bytes(), v2);
prop_assert_eq!(decoded.order(), store.order());
}
}