extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::num::NonZeroUsize;
use crate::metis::Dot;
use proptest::prelude::*;
use super::fabric::Fabric;
use super::replica::Replica;
use super::{act, assert_converged, crash, fleet_of, resync};
const ROSTER: [u32; 3] = [1, 2, 3];
type Op = (usize, usize, usize, usize);
fn station_of(op: Op) -> u32 {
ROSTER[op.0 % ROSTER.len()]
}
fn play(fabric: &mut Fabric, fleet: &mut BTreeMap<u32, Replica>, op: Op) {
let (_, kind, at, to) = op;
let station = station_of(op);
match kind % 3 {
0 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.insert_visible(at, out)
});
}
1 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.delete_visible(at, out)
});
}
_ => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.move_visible(at, Some(to), out)
});
}
}
let steps = fabric.schedule().below(4);
for _ in 0..steps {
let _ = fabric.step(fleet);
}
}
fn play_window(fabric: &mut Fabric, fleet: &mut BTreeMap<u32, Replica>, op: Op) {
let (_, kind, at, to) = op;
let station = station_of(op);
if fleet[&station].adopted() {
if kind % 2 == 0 {
let _ = act(fabric, fleet, station, |replica, out| {
replica.insert_visible(at, out)
});
} else {
let _ = act(fabric, fleet, station, |replica, out| {
replica.delete_visible(at, out)
});
}
} else {
match kind % 3 {
0 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.insert_visible(at, out)
});
}
1 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.delete_visible(at, out)
});
}
_ => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.move_visible(at, Some(to), out)
});
}
}
}
let steps = fabric.schedule().below(4);
for _ in 0..steps {
let _ = fabric.step(fleet);
}
}
fn cross_boundary(
fabric: &mut Fabric,
fleet: &mut BTreeMap<u32, Replica>,
declarers: &[u32],
window: &[Op],
expected_generation: u64,
) {
fabric.drain(fleet);
for &declarer in declarers {
let _ = act(fabric, fleet, declarer, |replica, out| {
replica.try_declare(out)
})
.expect("a settled watermark self-supports the declaration");
}
let severed = if fabric.schedule().one_in(2) {
let station = ROSTER[fabric.schedule().below(ROSTER.len())];
fabric.sever(&[station]);
Some(station)
} else {
None
};
for &op in window {
play_window(fabric, fleet, op);
}
if severed.is_some() {
fabric.drain(fleet);
assert!(
fleet.values().all(|replica| replica.seals.len()
< usize::try_from(expected_generation).expect("small") - 1),
"no seal may fire while a member is silent"
);
fabric.heal();
}
fabric.drain(fleet);
for replica in fleet.values() {
assert_eq!(
replica.generation(),
expected_generation,
"replica {} did not seal generation {}",
replica.id(),
expected_generation - 1
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn any_schedule_seals_two_epochs_and_converges(
seed in any::<u64>(),
first_tape in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 1..16),
first_window in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 0..6),
second_tape in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 1..10),
second_window in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 0..4),
declarers in prop::sample::subsequence(ROSTER.to_vec(), 1..=2),
) {
let mut fleet = fleet_of(&ROSTER, NonZeroUsize::new(2).expect("positive"));
let mut fabric = Fabric::new(seed, &ROSTER, 7);
for &op in &first_tape {
play(&mut fabric, &mut fleet, op);
}
cross_boundary(&mut fabric, &mut fleet, &declarers, &first_window, 2);
assert_converged(&fleet);
for &op in &second_tape {
play(&mut fabric, &mut fleet, op);
}
let second_declarer = [ROSTER[usize::try_from(seed % 3).expect("small")]];
cross_boundary(&mut fabric, &mut fleet, &second_declarer, &second_window, 3);
assert_converged(&fleet);
for replica in fleet.values() {
prop_assert_eq!(replica.seals.len(), 2);
}
}
}
fn play_hygiene(fabric: &mut Fabric, fleet: &mut BTreeMap<u32, Replica>, op: Op) {
let (_, kind, at, to) = op;
let station = station_of(op);
match kind % 4 {
0 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.insert_visible(at, out)
});
}
1 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.delete_visible(at, out)
});
}
2 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.move_visible(at, Some(to), out)
});
}
_ => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.retire_orphans(out)
});
}
}
let steps = fabric.schedule().below(4);
for _ in 0..steps {
let _ = fabric.step(fleet);
}
}
fn condense_point(fabric: &mut Fabric, fleet: &mut BTreeMap<u32, Replica>) {
fabric.drain(fleet);
let mut counts = Vec::new();
for &station in &ROSTER {
counts.push(fleet.get_mut(&station).expect("roster member").condense());
}
assert!(
counts.windows(2).all(|pair| pair[0] == pair[1]),
"a settled fleet condenses identically everywhere: {counts:?}"
);
assert_converged(fleet);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn any_schedule_with_condense_and_hygiene_converges(
seed in any::<u64>(),
first_tape in prop::collection::vec((0usize..3, 0usize..4, 0usize..8, 0usize..8), 1..12),
second_tape in prop::collection::vec((0usize..3, 0usize..4, 0usize..8, 0usize..8), 1..10),
third_tape in prop::collection::vec((0usize..3, 0usize..4, 0usize..8, 0usize..8), 1..8),
declarer in 0usize..3,
) {
let mut fleet = fleet_of(&ROSTER, NonZeroUsize::new(2).expect("positive"));
let mut fabric = Fabric::new(seed, &ROSTER, 7);
for &op in &first_tape {
play_hygiene(&mut fabric, &mut fleet, op);
}
condense_point(&mut fabric, &mut fleet);
for &op in &second_tape {
play_hygiene(&mut fabric, &mut fleet, op);
}
condense_point(&mut fabric, &mut fleet);
cross_boundary(&mut fabric, &mut fleet, &[ROSTER[declarer]], &[], 2);
assert_converged(&fleet);
for &op in &third_tape {
play_hygiene(&mut fabric, &mut fleet, op);
}
condense_point(&mut fabric, &mut fleet);
for replica in fleet.values() {
prop_assert_eq!(replica.seals.len(), 1);
}
}
}
fn play_disciplined(
fabric: &mut Fabric,
fleet: &mut BTreeMap<u32, Replica>,
op: Op,
mints: &mut Vec<Dot>,
) {
let (_, kind, at, to) = op;
let station = station_of(op);
match kind % 5 {
0 => {
let minted = act(fabric, fleet, station, |replica, out| {
replica.insert_disciplined(at, out)
});
mints.push(minted);
}
1 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.delete_visible(at, out)
});
}
2 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.move_visible(at, Some(to), out)
});
}
3 => {
let _ = act(fabric, fleet, station, |replica, out| {
replica.retire_orphans(out)
});
}
_ => {
let _ = fleet.get_mut(&station).expect("roster member").condense();
}
}
let steps = fabric.schedule().below(4);
for _ in 0..steps {
let _ = fabric.step(fleet);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn a_disciplined_fleet_condenses_on_local_evidence_and_converges(
seed in any::<u64>(),
tape in prop::collection::vec((0usize..3, 0usize..5, 0usize..8, 0usize..8), 1..24),
) {
let mut fleet = fleet_of(&ROSTER, NonZeroUsize::new(2).expect("positive"));
let mut fabric = Fabric::new(seed, &ROSTER, 7);
let mut mints = Vec::new();
for &op in &tape {
play_disciplined(&mut fabric, &mut fleet, op, &mut mints);
}
fabric.drain(&mut fleet);
for replica in fleet.values() {
let store = replica.text().store();
for &dot in &mints {
if store.locus(dot).is_some() {
prop_assert!(
store.is_reachable(dot),
"a disciplined weave never strands: {dot:?} \
is woven but unreachable at replica {}",
replica.id()
);
}
}
}
for replica in fleet.values_mut() {
let _ = replica.condense();
}
assert_converged(&fleet);
}
}
fn crash_point(fabric: &mut Fabric, fleet: &mut BTreeMap<u32, Replica>, station: u32) {
crash(
fleet,
&ROSTER,
NonZeroUsize::new(2).expect("positive"),
station,
);
resync(fabric, fleet, station);
let steps = fabric.schedule().below(6);
for _ in 0..steps {
let _ = fabric.step(fleet);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn any_schedule_with_crashes_rehydrates_and_converges(
seed in any::<u64>(),
tape in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 1..12),
crashes in prop::collection::vec((0usize..3, 0usize..12), 1..4),
window in prop::collection::vec((0usize..3, 0usize..3, 0usize..8, 0usize..8), 0..4),
window_crash in 0usize..3,
declarer in 0usize..3,
) {
let mut fleet = fleet_of(&ROSTER, NonZeroUsize::new(2).expect("positive"));
let mut fabric = Fabric::new(seed, &ROSTER, 7);
for (index, &op) in tape.iter().enumerate() {
for &(station, position) in &crashes {
if position == index {
crash_point(&mut fabric, &mut fleet, ROSTER[station]);
}
}
play(&mut fabric, &mut fleet, op);
}
fabric.drain(&mut fleet);
let _ = act(&mut fabric, &mut fleet, ROSTER[declarer], |replica, out| {
replica.try_declare(out)
})
.expect("a settled watermark self-supports the declaration");
for &op in &window {
play_window(&mut fabric, &mut fleet, op);
}
crash_point(&mut fabric, &mut fleet, ROSTER[window_crash]);
fabric.drain(&mut fleet);
assert_converged(&fleet);
for replica in fleet.values() {
prop_assert_eq!(replica.generation(), 2);
prop_assert_eq!(replica.seals.len(), 1);
}
}
}