extern crate alloc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use crate::metis::{Dot, Extent, Locus, Rhapsody, Verge};
use proptest::prelude::*;
use super::super::{Record, arb_ops, reference_order, run};
use super::walk::enrich_with_sided_edits;
use crate::metis::dot::RawDot;
fn reference_extent(record: &Record, start: Verge, end: Verge) -> Extent {
let full = reference_order(&Record {
woven: record.woven.clone(),
expelled: BTreeSet::new(),
});
let slot_of: BTreeMap<Dot, usize> = full
.iter()
.enumerate()
.map(|(index, &dot)| (dot, index))
.collect();
let position = |verge: Verge| -> Option<usize> {
match verge {
Verge::Origin => Some(0),
Verge::Terminus => Some(2 * full.len() + 3),
Verge::Before(dot) => Dot::try_from(dot)
.ok()
.and_then(|dot| slot_of.get(&dot))
.map(|&i| 2 * i + 1),
Verge::After(dot) => Dot::try_from(dot)
.ok()
.and_then(|dot| slot_of.get(&dot))
.map(|&i| 2 * i + 3),
}
};
let (start_pos, end_pos) = (position(start), position(end));
let (Some(start_pos), Some(end_pos)) = (start_pos, end_pos) else {
return Extent::Dangling {
start: start_pos.is_none(),
end: end_pos.is_none(),
};
};
if start == end {
return Extent::Covered(Vec::new());
}
let forward = start_pos < end_pos
|| (start_pos == end_pos
&& matches!(start, Verge::After(_))
&& matches!(end, Verge::Before(_)));
if !forward {
return Extent::Inverted;
}
let covered = full
.iter()
.enumerate()
.filter(|&(index, dot)| {
let slot_pos = 2 * index + 2;
start_pos < slot_pos && slot_pos < end_pos && !record.expelled.contains(dot)
})
.map(|(_, &dot)| dot)
.collect();
Extent::Covered(covered)
}
fn all_verges(record: &Record) -> Vec<Verge> {
let mut verges = alloc::vec![Verge::Origin, Verge::Terminus];
for &dot in record.woven.keys() {
verges.push(Verge::Before(dot.into()));
verges.push(Verge::After(dot.into()));
}
verges.push(Verge::Before(RawDot {
station: 9,
counter: 1,
}));
verges.push(Verge::After(RawDot {
station: 9,
counter: 1,
}));
verges
}
proptest! {
#[test]
fn prop_extent_agrees_with_the_positional_oracle(
ops in arb_ops(),
edits in prop::collection::vec((0usize..16, any::<bool>()), 0..6),
) {
let (live, mut record) = run(&ops);
let seq = enrich_with_sided_edits(&live, &mut record, &edits);
let store = seq.store();
let order = store.order();
let verges = all_verges(&record);
for &start in &verges {
for &end in &verges {
let got = store.extent(start, end);
prop_assert_eq!(
&got,
&reference_extent(&record, start, end),
"extent({:?}, {:?}) disagrees with the oracle",
start,
end,
);
if let Extent::Covered(covered) = &got
&& let Some(first) = covered.first()
{
let at = order.iter().position(|dot| dot == first);
prop_assert!(at.is_some_and(
|i| order[i..i + covered.len()] == covered[..],
));
}
}
}
}
#[test]
fn prop_edge_inserts_respect_the_sides(
ops in arb_ops(),
caret_pick in 0usize..16,
) {
let (live, mut record) = run(&ops);
let mut seq = enrich_with_sided_edits(&live, &mut record, &[]);
let visible = seq.store().order();
prop_assume!(!visible.is_empty());
let caret = visible[caret_pick % visible.len()];
let station = 9;
let clk = crate::kairos::Clock::with_default_config(
crate::kairos::TickCounter::new(),
station,
).unwrap();
let anchor = seq.store().anchor_for_visual_insert(Some(caret.into()));
if let Some(top) = seq.store().children_of(anchor).next()
&& let Some(locus) = seq.store().locus(top)
{
clk.observe(locus.rank);
}
let fresh = seq.next_dot(station);
let locus = Locus {
anchor,
rank: clk.now(0u16),
};
let mut delta = Rhapsody::new();
prop_assert!(delta.weave(fresh, locus));
seq = seq.merge(&crate::metis::Dotted::from_store(delta));
let store = seq.store();
let inside = |extent: &Extent| matches!(
extent, Extent::Covered(covered) if covered.contains(&fresh),
);
prop_assert!(inside(&store.extent(Verge::After(caret.into()), Verge::Terminus)));
prop_assert!(!inside(&store.extent(Verge::Origin, Verge::After(caret.into()))));
let order = store.order();
let caret_at = order.iter().position(|&dot| dot == caret).unwrap();
if let Some(&successor) = order.get(caret_at + 2) {
prop_assert!(inside(
&store.extent(Verge::After(caret.into()), Verge::Before(successor.into()))
));
prop_assert!(!inside(
&store.extent(Verge::Before(successor.into()), Verge::Terminus)
));
}
}
}