use std::collections::BTreeSet;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use liminal::durability::DurableStore;
use liminal::durability::bridge::block_on;
use super::registry::ConversationRegistry;
use super::registry_snapshot::SNAPSHOT_KEY;
use super::route_key_migration::MARKER_KEY;
use super::routed_stream::read_head;
use super::routed_stream_codec::{head_key, row_key};
use super::tests_outcome_unknown::{UnknownOutcomeStore, Write};
use super::tests_registry_snapshot_fixture::{
KEY, Recording, TestResult, ascending, logged, memory, snapshot_in, stored_snapshot,
};
#[test]
fn registry_registration_is_one_batch_and_the_crossing_carries_the_snapshot() -> TestResult {
let recording = Recording::new(memory());
let registry = ConversationRegistry::new(recording.shared());
assert!(registry.restore()?.is_empty());
recording.take_batches()?;
recording.other_writes.store(0, Ordering::SeqCst);
let head = head_key(KEY)?;
for id in 0..255 {
registry.register(id)?;
}
let batches = recording.take_batches()?;
assert_eq!(batches.len(), 255);
for (sequence, (route, items)) in (0_u64..).zip(&batches) {
assert_eq!(route, KEY);
let keys: Vec<Vec<u8>> = items.iter().map(|item| item.key.clone()).collect();
let expected = if sequence == 0 {
vec![row_key(KEY, 0)?, MARKER_KEY.to_vec(), head.clone()]
} else {
vec![row_key(KEY, sequence)?, head.clone()]
};
assert_eq!(keys, expected);
}
assert_eq!(recording.flushes.load(Ordering::SeqCst), 0);
assert_eq!(recording.other_writes.load(Ordering::SeqCst), 0);
assert!(stored_snapshot(recording.as_ref())?.is_none());
registry.register(255)?;
let batches = recording.take_batches()?;
let [(route, items)] = batches.as_slice() else {
return Err(format!("the crossing wrote {} batches", batches.len()).into());
};
assert_eq!(route, KEY);
let keys: Vec<Vec<u8>> = items.iter().map(|item| item.key.clone()).collect();
assert_eq!(keys, vec![row_key(KEY, 255)?, SNAPSHOT_KEY.to_vec(), head]);
let (at_256, expected) = snapshot_in(items)?;
assert_eq!(expected, None);
assert_eq!(at_256.covered_next_seq, 256);
assert_eq!(at_256.sets.live, ascending(0..256));
let (_, hash_256) = stored_snapshot(recording.as_ref())?.ok_or("no snapshot at 256")?;
for id in 256..511 {
registry.register(id)?;
}
recording.take_batches()?;
registry.register(511)?;
let batches = recording.take_batches()?;
let [(_, items)] = batches.as_slice() else {
return Err("the second crossing wrote no single batch".into());
};
let (at_512, expected) = snapshot_in(items)?;
assert_eq!(expected, Some(hash_256));
assert_eq!(at_512.covered_next_seq, 512);
assert_eq!(at_512.sets.live, ascending(0..512));
assert_eq!(recording.flushes.load(Ordering::SeqCst), 0);
assert_eq!(recording.other_writes.load(Ordering::SeqCst), 0);
Ok(())
}
fn struck_store(write: Write) -> (Arc<dyn DurableStore>, Arc<UnknownOutcomeStore>) {
let memory = memory();
let fault = Arc::new(UnknownOutcomeStore::new(&memory, write, KEY));
(memory, fault)
}
fn registered_255(store: &Arc<dyn DurableStore>) -> TestResult<ConversationRegistry> {
let registry = ConversationRegistry::new(Arc::clone(store));
registry.restore()?;
for id in 0..255 {
registry.register(id)?;
}
Ok(registry)
}
fn strike_unlanded_crossing() -> TestResult<(
Arc<dyn DurableStore>,
Arc<UnknownOutcomeStore>,
ConversationRegistry,
)> {
let (memory, fault) = struck_store(Write::AppendAbsentIo);
let store: Arc<dyn DurableStore> = fault.clone();
let registry = registered_255(&store)?;
fault.arm();
let (answer, logs) = logged(|| registry.register(255))?;
assert!(answer.is_err(), "the struck crossing answered {answer:?}");
assert!(fault.struck(), "the crossing batch was never struck");
assert!(
!logs.contains("WARN"),
"a warning stood in for the fault: {logs}"
);
fault.recover();
assert_eq!(
block_on(memory.get_routed(KEY, &row_key(KEY, 255)?))??,
None
);
assert!(stored_snapshot(memory.as_ref())?.is_none());
let routed = block_on(read_head(memory.as_ref(), KEY, KEY))??.ok_or("no routed head")?;
assert_eq!(routed.next_sequence, 255);
Ok((memory, fault, registry))
}
#[test]
fn registry_unlanded_crossing_is_recovered_at_its_original_head_after_restart() -> TestResult {
let (memory, fault, _) = strike_unlanded_crossing()?;
let store: Arc<dyn DurableStore> = fault;
let restarted = ConversationRegistry::new(Arc::clone(&store));
assert_eq!(restarted.restore()?, (0_u64..255).collect::<Vec<_>>());
restarted.register(255)?;
assert!(block_on(memory.get_routed(KEY, &row_key(KEY, 255)?))??.is_some());
let (snapshot, _) = stored_snapshot(memory.as_ref())?.ok_or("no snapshot at 256")?;
assert_eq!(snapshot.covered_next_seq, 256);
assert_eq!(snapshot.sets.live, ascending(0..256));
assert_eq!(
ConversationRegistry::new(store).restore()?,
(0_u64..256).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn registry_unlanded_crossing_is_recovered_on_the_same_handle() -> TestResult {
let (memory, fault, registry) = strike_unlanded_crossing()?;
registry.register(255)?;
let (snapshot, _) = stored_snapshot(memory.as_ref())?.ok_or("no snapshot at 256")?;
assert_eq!(snapshot.covered_next_seq, 256);
assert_eq!(snapshot.sets.live, ascending(0..256));
let store: Arc<dyn DurableStore> = fault;
assert_eq!(
ConversationRegistry::new(store).restore()?,
(0_u64..256).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn registry_landed_crossing_under_an_unknown_outcome_keeps_its_id_and_hash() -> TestResult {
let (memory, fault) = struck_store(Write::Append);
let recording = Recording::new(fault.clone() as Arc<dyn DurableStore>);
let store = recording.shared();
let registry = registered_255(&store)?;
fault.arm_unsettled();
assert!(registry.register(255).is_err());
assert!(fault.struck(), "the crossing batch was never struck");
let (landed, landed_hash) =
stored_snapshot(memory.as_ref())?.ok_or("the landed crossing left no snapshot")?;
assert_eq!(landed.covered_next_seq, 256);
registry.register(256)?;
assert!(block_on(memory.get_routed(KEY, &row_key(KEY, 256)?))??.is_some());
for id in 257..511 {
registry.register(id)?;
}
recording.take_batches()?;
registry.register(511)?;
let batches = recording.take_batches()?;
let [(_, items)] = batches.as_slice() else {
return Err("the crossing at 512 wrote no single batch".into());
};
let (at_512, expected) = snapshot_in(items)?;
assert_eq!(expected, Some(landed_hash));
assert!(
at_512.sets.live.contains(&255),
"the snapshot at 512 misses the id of the row that landed unanswered"
);
assert_eq!(at_512.sets.live, ascending(0..512));
Ok(())
}
#[test]
fn registry_duplicate_registrations_snapshot_one_id() -> TestResult {
let memory = memory();
let registry = ConversationRegistry::new(Arc::clone(&memory));
registry.restore()?;
for _ in 0..256 {
registry.register(7)?;
}
let (snapshot, _) = stored_snapshot(memory.as_ref())?.ok_or("no snapshot at 256")?;
assert_eq!(snapshot.covered_next_seq, 256);
assert_eq!(snapshot.sets.live, BTreeSet::from([7]));
assert_eq!(ConversationRegistry::new(memory).restore()?, vec![7]);
Ok(())
}