use std::cell::RefCell;
use std::convert::Infallible;
use std::path::PathBuf;
use std::sync::Arc;
use super::commit::{BranchCommitError, CommitDurability, CommitRequest, commit_branch};
use super::handle::DEFAULT_SHARD_ID;
use super::lifecycle::create_branch;
use super::refstore::{BranchRefError, BranchRefStore};
use super::registry::BranchRegistry;
use crate::store::{MemoryStore, NodeStore};
use crate::tree::{Hash, LeafNode, Node, TreeError, insert};
type TestResult = Result<(), BranchCommitError>;
const BRANCH: &str = "spied";
fn build_tree(store: &mut MemoryStore, entries: &[(&[u8], &[u8])]) -> Result<Hash, TreeError> {
let leaf = LeafNode::new(Vec::new())?;
let mut root = store.put(&Node::Leaf(leaf));
for (key, value) in entries {
root = insert(store, root, key, value)?;
}
Ok(root)
}
fn tempdir() -> Result<tempfile::TempDir, BranchCommitError> {
tempfile::tempdir().map_err(|error| BranchCommitError::Ref(BranchRefError::Io(error)))
}
#[derive(Debug)]
struct BarrierSpyStore {
inner: MemoryStore,
refs_dir: PathBuf,
barrier_seqs: RefCell<Vec<Option<u64>>>,
}
impl BarrierSpyStore {
fn new(inner: MemoryStore, refs_dir: PathBuf) -> Self {
Self {
inner,
refs_dir,
barrier_seqs: RefCell::new(Vec::new()),
}
}
fn barrier_seqs(&self) -> Vec<Option<u64>> {
self.barrier_seqs.borrow().clone()
}
}
impl NodeStore for BarrierSpyStore {
type Error = Infallible;
fn get(&self, hash: &Hash) -> Result<Option<Arc<Node>>, Self::Error> {
Ok(MemoryStore::get(&self.inner, hash))
}
fn put(&mut self, node: &Node) -> Result<Hash, Self::Error> {
Ok(MemoryStore::put(&mut self.inner, node))
}
fn sync_dirty_dirs(&self) -> Result<(), Self::Error> {
let on_disk_seq = BranchRefStore::open(&self.refs_dir)
.ok()
.and_then(|refs| refs.get(BRANCH).map(|record| record.seq));
self.barrier_seqs.borrow_mut().push(on_disk_seq);
Ok(())
}
}
#[test]
fn barrier_runs_exactly_once_strictly_before_the_install_per_durable_commit() -> TestResult {
let mut base = MemoryStore::new();
let root = build_tree(&mut base, &[(b"base", b"tree")])?;
let dir = tempdir()?;
let mut store = BarrierSpyStore::new(base, dir.path().to_path_buf());
let registry = BranchRegistry::new();
let mut refs = BranchRefStore::open(dir.path())?;
let branch = create_branch(BRANCH, [(DEFAULT_SHARD_ID, root)], &mut refs, ®istry, 10)?;
for (round, expected_pre_install_seq) in (0u64..3).enumerate() {
branch.put(DEFAULT_SHARD_ID, b"key", round.to_le_bytes())?;
let outcome = commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Durable { refs: &mut refs },
extra_parents: &[],
timestamp: 20 + expected_pre_install_seq,
},
)?;
assert_eq!(outcome.seq, Some(expected_pre_install_seq + 1));
let observed = store.barrier_seqs();
assert_eq!(
observed.len(),
round + 1,
"round {round}: exactly one barrier per durable commit"
);
assert_eq!(
observed[round],
Some(expected_pre_install_seq),
"round {round}: the barrier must observe the pre-install record"
);
}
Ok(())
}
#[test]
fn volatile_commits_and_durable_noops_never_fence() -> TestResult {
let mut base = MemoryStore::new();
let root = build_tree(&mut base, &[(b"base", b"tree")])?;
let dir = tempdir()?;
let mut store = BarrierSpyStore::new(base, dir.path().to_path_buf());
let registry = BranchRegistry::new();
let mut refs = BranchRefStore::open(dir.path())?;
let branch = create_branch(BRANCH, [(DEFAULT_SHARD_ID, root)], &mut refs, ®istry, 10)?;
branch.put(DEFAULT_SHARD_ID, b"key", b"volatile")?;
commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Volatile,
extra_parents: &[],
timestamp: 20,
},
)?;
assert!(
store.barrier_seqs().is_empty(),
"a volatile commit must not fence directory entries"
);
let outcome = commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Durable { refs: &mut refs },
extra_parents: &[],
timestamp: 30,
},
)?;
assert!(!outcome.advanced);
assert!(
store.barrier_seqs().is_empty(),
"a durable no-op must not fence directory entries"
);
Ok(())
}
#[test]
fn record_only_commit_still_fences_before_its_install() -> TestResult {
let mut base = MemoryStore::new();
let root = build_tree(&mut base, &[(b"base", b"tree")])?;
let dir = tempdir()?;
let mut store = BarrierSpyStore::new(base, dir.path().to_path_buf());
let registry = BranchRegistry::new();
let mut refs = BranchRefStore::open(dir.path())?;
let branch = create_branch(BRANCH, [(DEFAULT_SHARD_ID, root)], &mut refs, ®istry, 10)?;
let converge = Hash::from_bytes([9; 32]);
let outcome = commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Durable { refs: &mut refs },
extra_parents: std::slice::from_ref(&converge),
timestamp: 20,
},
)?;
assert!(outcome.advanced);
assert_eq!(outcome.seq, Some(1));
assert_eq!(
store.barrier_seqs(),
vec![Some(0)],
"one barrier, observing the pre-install record"
);
Ok(())
}