liminal-server 0.23.1

Standalone server for the liminal messaging bus
Documentation
//! The explicit keys that replaced `BindingEpoch`'s derived order keep the
//! order the derive gave, wherever an ordered collection used it. Each golden
//! is written by hand from the derived-order rule: the variant in declaration
//! order, then the fields in declaration order, with a binding epoch as its
//! connection, then its generation. Each line says why it follows the one
//! before.

use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;

use liminal_protocol::wire::{BindingEpoch, ConnectionIncarnation, Generation};

use super::super::fate_occurrence::FateOccurrenceKey;
use super::ObserverProgressOccurrence;
use crate::server::participant::dispatch_impact::DispatchTarget;

fn epoch(server: u64, ordinal: u64, generation: u64) -> Result<BindingEpoch, Box<dyn Error>> {
    Ok(BindingEpoch::new(
        ConnectionIncarnation::new(server, ordinal),
        Generation::new(generation).ok_or("a generation is never zero")?,
    ))
}

#[test]
fn observer_progress_occurrences_keep_the_derived_order() -> Result<(), Box<dyn Error>> {
    use ObserverProgressOccurrence::{
        AttachedTerminal, DetachedTerminal, DiedTerminal, Leave, MarkerAck, ParticipantAck,
        RecoveredFate,
    };
    let golden = vec![
        // the first variant
        AttachedTerminal {
            conversation_id: 1,
            participant_id: 1,
            terminal_delivery_seq: 9,
        },
        // the same variant and conversation, a higher participant
        AttachedTerminal {
            conversation_id: 1,
            participant_id: 2,
            terminal_delivery_seq: 1,
        },
        // the second variant comes after every first
        DetachedTerminal {
            conversation_id: 1,
            participant_id: 1,
            terminal_delivery_seq: 5,
        },
        // the third variant
        DiedTerminal {
            conversation_id: 1,
            participant_id: 1,
            terminal_delivery_seq: 5,
        },
        // the fourth variant
        ParticipantAck {
            conversation_id: 1,
            participant_id: 1,
            through_seq: 4,
            source_sequence: 9,
        },
        // through_seq is declared before source_sequence, so it decides first
        ParticipantAck {
            conversation_id: 1,
            participant_id: 1,
            through_seq: 5,
            source_sequence: 1,
        },
        // the fifth variant
        MarkerAck {
            conversation_id: 1,
            participant_id: 1,
            marker_delivery_seq: 3,
            resulting_cursor: 9,
            base_log_head: 9,
            extension_sequence: 9,
        },
        // marker_delivery_seq is declared first, so it decides first
        MarkerAck {
            conversation_id: 1,
            participant_id: 1,
            marker_delivery_seq: 4,
            resulting_cursor: 1,
            base_log_head: 1,
            extension_sequence: 1,
        },
        // the sixth variant
        Leave {
            conversation_id: 1,
            participant_id: 1,
            left_delivery_seq: 7,
        },
        // the seventh variant
        RecoveredFate {
            conversation_id: 1,
            participant_id: 1,
            binding_epoch: epoch(1, 1, 5)?,
            resulting_floor: 9,
        },
        // the same epoch at a higher floor
        RecoveredFate {
            conversation_id: 1,
            participant_id: 1,
            binding_epoch: epoch(1, 1, 5)?,
            resulting_floor: 10,
        },
        // binding_epoch is declared before resulting_floor: the same connection
        // at a higher generation comes after, though its floor is lower
        RecoveredFate {
            conversation_id: 1,
            participant_id: 1,
            binding_epoch: epoch(1, 1, 6)?,
            resulting_floor: 1,
        },
        // a later connection comes after, though its generation and floor are lower
        RecoveredFate {
            conversation_id: 1,
            participant_id: 1,
            binding_epoch: epoch(1, 2, 2)?,
            resulting_floor: 0,
        },
        // a higher conversation comes after every lower one in the same variant
        RecoveredFate {
            conversation_id: 2,
            participant_id: 0,
            binding_epoch: epoch(0, 0, 1)?,
            resulting_floor: 0,
        },
    ];
    let held: BTreeSet<ObserverProgressOccurrence> = golden.iter().rev().copied().collect();
    assert_eq!(held.into_iter().collect::<Vec<_>>(), golden);
    Ok(())
}

#[test]
fn dispatch_targets_keep_the_derived_order() -> Result<(), Box<dyn Error>> {
    let golden = vec![
        // the lowest participant, connection and generation
        DispatchTarget::new(1, epoch(1, 1, 5)?),
        // the same connection at a higher generation
        DispatchTarget::new(1, epoch(1, 1, 6)?),
        // a later connection ordinal comes after, though its generation is lower
        DispatchTarget::new(1, epoch(1, 2, 2)?),
        // a later server incarnation comes after any ordinal of an earlier one
        DispatchTarget::new(1, epoch(2, 0, 1)?),
        // a higher participant comes after every epoch of a lower one
        DispatchTarget::new(2, epoch(0, 0, 1)?),
    ];
    let held: BTreeSet<DispatchTarget> = golden.iter().rev().copied().collect();
    assert_eq!(held.into_iter().collect::<Vec<_>>(), golden);
    Ok(())
}

#[test]
fn fate_occurrence_keys_keep_the_derived_order() -> Result<(), Box<dyn Error>> {
    let key = |conversation_id: u64,
               participant_id: u64,
               binding_epoch: BindingEpoch|
     -> FateOccurrenceKey {
        FateOccurrenceKey {
            conversation_id,
            participant_id,
            binding_epoch,
        }
    };
    let golden = vec![
        // the lowest conversation, participant, connection and generation
        key(1, 1, epoch(1, 1, 5)?),
        // the same connection at a higher generation
        key(1, 1, epoch(1, 1, 6)?),
        // a later connection comes after, though its generation is lower
        key(1, 1, epoch(1, 2, 2)?),
        // a higher participant comes after every epoch of a lower one
        key(1, 2, epoch(0, 0, 1)?),
        // a higher conversation comes after every participant of a lower one
        key(2, 0, epoch(0, 0, 1)?),
    ];
    let held: BTreeMap<FateOccurrenceKey, usize> = golden
        .iter()
        .rev()
        .copied()
        .enumerate()
        .map(|(at, key)| (key, at))
        .collect();
    assert_eq!(held.into_keys().collect::<Vec<_>>(), golden);
    Ok(())
}