minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use super::super::support::{arb_history, perm_of};
use crate::metis::{BoundedInsert, CausalIdeal, Event};
use alloc::vec::Vec;
use core::num::NonZeroUsize;
use proptest::prelude::*;

proptest! {
    /// After every `try_insert`, the live backlog never exceeds `capacity`.
    #[test]
    fn prop_try_insert_never_exceeds_capacity(
        (history, perm) in arb_history()
            .prop_flat_map(|history| {
                let perm = perm_of(history.len());
                (Just(history), perm)
            }),
        cap in 1usize..6,
    ) {
        let cap = NonZeroUsize::new(cap).unwrap();
        let mut buf = CausalIdeal::new();
        for &index in &perm {
            if let Err(at) = buf.try_insert(history[index].clone(), cap) {
                prop_assert_eq!(at.event.payload, history[index].payload);
            }
            prop_assert!(buf.pending_len() <= cap.get());
        }
    }
}

proptest! {
    /// Atomic replacement preserves the bound, progress, and resident causal closure.
    #[test]
    fn prop_try_insert_evicting_where_preserves_causal_closure(
        (history, perm) in arb_history()
            .prop_flat_map(|history| {
                let perm = perm_of(history.len());
                (Just(history), perm)
            }),
        cap in 1usize..6,
    ) {
        let cap = NonZeroUsize::new(cap).unwrap();
        let mut buf = CausalIdeal::new();
        let mut pending = Vec::new();
        for &index in &perm {
            let arrival = history[index].clone();
            let before = buf.delivered().clone();
            let outcome = buf.try_insert_evicting_where(
                arrival.clone(),
                cap,
                |_| true,
            );
            prop_assert!(buf.pending_len() <= cap.get());
            prop_assert_eq!(buf.delivered(), &before);
            match outcome {
                Ok(BoundedInsert::Buffered) => pending.push(arrival),
                Ok(BoundedInsert::DroppedStale) => {
                    prop_assert!(false, "a valid unreleased history event is not stale");
                }
                Ok(BoundedInsert::Evicted { evicted }) => {
                    let victim = pending
                        .iter()
                        .position(|resident| resident.stamp == evicted.stamp)
                        .expect("the returned victim was pending");
                    prop_assert!(is_causally_maximal(victim, &pending, &arrival));
                    let _ = pending.swap_remove(victim);
                    pending.push(arrival);
                }
                Err(refused) => {
                    prop_assert_eq!(refused.event.stamp, arrival.stamp);
                    prop_assert!(pending
                        .iter()
                        .enumerate()
                        .all(|(candidate, _)| !is_causally_maximal(candidate, &pending, &arrival)));
                }
            }
            prop_assert_eq!(buf.pending_len(), pending.len());
        }
    }
}

fn is_causally_maximal(candidate: usize, pending: &[Event<u32>], arrival: &Event<u32>) -> bool {
    let victim = &pending[candidate];
    !depends_on(arrival, victim)
        && pending
            .iter()
            .enumerate()
            .all(|(index, event)| index == candidate || !depends_on(event, victim))
}

fn depends_on(event: &Event<u32>, predecessor: &Event<u32>) -> bool {
    let station = predecessor.stamp.station_id();
    let sequence = predecessor.deps.get(station);
    let event_dot = (
        event.stamp.station_id(),
        event.deps.get(event.stamp.station_id()),
    );

    event_dot != (station, sequence) && event.deps.get(station) >= sequence
}