extern crate alloc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use crate::metis::{Dot, DotMap, DotSet, Dotted};
use proptest::prelude::*;
type Doc = Dotted<DotMap<u8, DotSet>>;
type Outcome = (Vec<Doc>, BTreeMap<Dot, u8>, BTreeSet<Dot>);
const REPLICAS: usize = 3;
const KEYS: u8 = 3;
const MAX_OPS: usize = 400;
proptest! {
#[test]
fn prop_op_tape_interpreter_matches_the_suite(
tape in prop::collection::vec(any::<u8>(), 0..512),
) {
let (live, minted, expelled) = interpret(&tape);
let forward = live.iter().fold(Doc::new(), |acc, doc| acc.merge(doc));
let backward = live.iter().rev().fold(Doc::new(), |acc, doc| acc.merge(doc));
prop_assert_eq!(&forward, &backward);
let mut survivors: DotMap<u8, DotSet> = DotMap::new();
for (&dot, &key) in &minted {
if expelled.contains(&dot) {
continue;
}
let mut held: DotSet = survivors.get(&key).cloned().unwrap_or_default();
prop_assert!(held.insert(dot));
let _ = survivors.insert(key, held);
}
prop_assert_eq!(forward.store(), &survivors);
let mut seen = DotSet::new();
for &dot in minted.keys() {
let _ = seen.insert(dot);
}
prop_assert_eq!(forward.context(), &seen);
for doc in &live {
prop_assert_eq!(&forward.merge(doc), &forward);
}
for a in &live {
for b in &live {
let via_delta = b.merge(&a.delta_for(b.context()));
let via_full = b.merge(a);
prop_assert_eq!(via_delta, via_full);
}
}
for doc in &live {
let rebuilt = Dotted::try_new(doc.store().clone(), doc.context().clone());
prop_assert!(rebuilt.is_ok());
}
}
}
fn interpret(tape: &[u8]) -> Outcome {
let mut live: Vec<Doc> = (0..REPLICAS).map(|_| Dotted::new()).collect();
let mut minted: BTreeMap<Dot, u8> = BTreeMap::new();
let mut expelled: BTreeSet<Dot> = BTreeSet::new();
for pair in tape.chunks_exact(2).take(MAX_OPS) {
let opcode = pair[0];
let argument = pair[1];
let replica = usize::from(argument % 3);
let other = usize::from((argument / 3) % 3);
let key = (argument / 3) % KEYS;
match opcode % 5 {
0 => {
let station = u32::try_from(replica).unwrap_or(u32::MAX);
let fresh = live[replica].next_dot(station);
let mut held = DotSet::new();
assert!(held.insert(fresh), "a fresh dot is always new");
let delta = Dotted::from_store(DotMap::singleton(key, held));
live[replica] = live[replica].merge(&delta);
let _ = minted.insert(fresh, key);
}
1 => {
let observed = live[replica].store().get(&key).cloned().unwrap_or_default();
for dot in observed.dots() {
let _ = expelled.insert(dot);
}
let delta: Doc = Dotted::from_context(observed);
live[replica] = live[replica].merge(&delta);
}
2 => {
let learned = live[replica].merge(&live[other]);
live[replica] = learned;
}
3 => {
let delta = live[other].delta_for(live[replica].context());
live[replica] = live[replica].merge(&delta);
}
_ => {
let context = live[replica].context();
let bytes = context.to_bytes();
let decoded = DotSet::from_bytes(&bytes).expect("own context frame decodes");
assert_eq!(
decoded.to_bytes(),
bytes,
"an accepted context frame must re-encode identically"
);
assert_eq!(
&decoded, context,
"the round trip must preserve the context"
);
}
}
}
(live, minted, expelled)
}