use std::cell::Cell;
use super::commit::{BranchCommitError, CommitDurability, CommitRequest, commit_branch};
use super::crash_support::{
BASE, FailpointStore, TestError, build_tree, durable, expected_head, read_at,
};
use super::lifecycle::{create_branch, open_branch};
use super::refstore::BranchRefStore;
use super::registry::BranchRegistry;
use crate::store::{DiskStore, NodeStore};
use crate::tree::TreePolicy;
#[test]
fn crash_before_commit_discards_buffered_intent() -> Result<(), TestError> {
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let anchor = {
let mut store = DiskStore::new(&store_dir)?;
let anchor = build_tree(&mut store, BASE)?;
store.sync_dirty_dirs()?;
let mut refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let branch = create_branch("wal", [(0, anchor)], &mut refs, ®istry, 10)?;
branch.put(0, b"pending", b"never committed")?;
anchor
};
let store = DiskStore::new(&store_dir)?;
let refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let reopened = open_branch("wal", &refs, ®istry)?;
assert_eq!(reopened.current_root(), anchor);
assert_eq!(reopened.fork_point(), anchor);
assert_eq!(read_at(&store, anchor, b"base")?, Some(b"tree".to_vec()));
assert_eq!(read_at(&store, anchor, b"pending")?, None);
Ok(())
}
#[test]
fn crash_mid_materialisation_leaves_old_record_and_orphans_only() -> Result<(), TestError> {
const BASE_ZERO: &[(&[u8], &[u8])] = &[(b"zero", b"base")];
const BASE_THREE: &[(&[u8], &[u8])] = &[(b"three", b"base")];
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let orphan = expected_head(BASE_ZERO, &[(b"zero-new", b"v0")])?;
let (anchor_zero, anchor_three) = {
let mut disk = DiskStore::new(&store_dir)?;
let anchor_zero = build_tree(&mut disk, BASE_ZERO)?;
let anchor_three = build_tree(&mut disk, BASE_THREE)?;
disk.sync_dirty_dirs()?;
let mut refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let branch = create_branch(
"wal",
[(0, anchor_zero), (3, anchor_three)],
&mut refs,
®istry,
10,
)?;
branch.put(0, b"zero-new", b"v0")?;
branch.put(3, b"three-new", b"v3")?;
let mut store = FailpointStore {
inner: disk,
puts_allowed: Cell::new(Some(1)),
fail_barrier: false,
};
let result = commit_branch(
&branch,
&mut store,
®istry,
durable(&mut refs, 20),
TreePolicy::V1_DEFAULT,
);
assert!(matches!(result, Err(BranchCommitError::Tree(_))));
assert!(store.inner.get(&orphan)?.is_some());
(anchor_zero, anchor_three)
};
let store = DiskStore::new(&store_dir)?;
let refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let record = refs.get("wal").ok_or(TestError::Missing("wal record"))?;
assert_eq!(record.seq, 0);
assert_eq!(record.shards[0].head, anchor_zero);
assert_eq!(record.shards[1].head, anchor_three);
let reopened = open_branch("wal", &refs, ®istry)?;
assert_eq!(reopened.shard_current_root(0), Some(anchor_zero));
assert_eq!(reopened.shard_current_root(3), Some(anchor_three));
assert_eq!(
read_at(&store, anchor_zero, b"zero")?,
Some(b"base".to_vec())
);
assert_eq!(read_at(&store, anchor_zero, b"zero-new")?, None);
assert!(store.get(&orphan)?.is_some());
Ok(())
}
#[test]
fn crash_at_the_barrier_leaves_old_record_valid() -> Result<(), TestError> {
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let orphan = expected_head(BASE, &[(b"key", b"value")])?;
let anchor = {
let mut disk = DiskStore::new(&store_dir)?;
let anchor = build_tree(&mut disk, BASE)?;
disk.sync_dirty_dirs()?;
let mut refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let branch = create_branch("wal", [(0, anchor)], &mut refs, ®istry, 10)?;
branch.put(0, b"key", b"value")?;
let mut store = FailpointStore {
inner: disk,
puts_allowed: Cell::new(None),
fail_barrier: true,
};
let result = commit_branch(
&branch,
&mut store,
®istry,
durable(&mut refs, 20),
TreePolicy::V1_DEFAULT,
);
assert!(matches!(result, Err(BranchCommitError::Barrier(_))));
anchor
};
let store = DiskStore::new(&store_dir)?;
let refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let record = refs.get("wal").ok_or(TestError::Missing("wal record"))?;
assert_eq!(record.seq, 0);
assert_eq!(record.shards[0].head, anchor);
let reopened = open_branch("wal", &refs, ®istry)?;
assert_eq!(reopened.current_root(), anchor);
assert_eq!(read_at(&store, anchor, b"key")?, None);
assert!(
store.get(&orphan)?.is_some(),
"orphan nodes survive, unreferenced"
);
Ok(())
}
#[test]
fn crash_between_barrier_and_install_leaves_old_record_valid() -> Result<(), TestError> {
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let (anchor, would_be_head) = {
let mut store = DiskStore::new(&store_dir)?;
let anchor = build_tree(&mut store, BASE)?;
store.sync_dirty_dirs()?;
let mut refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let branch = create_branch("wal", [(0, anchor)], &mut refs, ®istry, 10)?;
branch.put(0, b"key", b"value")?;
commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Volatile,
extra_parents: &[],
timestamp: 20,
},
TreePolicy::V1_DEFAULT,
)?;
store.sync_dirty_dirs()?;
(anchor, branch.current_root())
};
let store = DiskStore::new(&store_dir)?;
let refs = BranchRefStore::open(&refs_dir)?;
let registry = BranchRegistry::new();
let record = refs.get("wal").ok_or(TestError::Missing("wal record"))?;
assert_eq!(record.seq, 0, "no record advanced: the rename never ran");
let reopened = open_branch("wal", &refs, ®istry)?;
assert_eq!(reopened.current_root(), anchor);
assert_ne!(would_be_head, anchor);
assert!(store.get(&would_be_head)?.is_some());
assert_eq!(read_at(&store, anchor, b"key")?, None);
Ok(())
}