use std::fs;
use super::commit::{CommitDurability, CommitRequest, commit_branch};
use super::crash_support::{BASE, TestError, build_tree, durable, read_at, sole_ref_file};
use super::lifecycle::{create_branch, open_branch};
use super::refstore::BranchRefStore;
use super::registry::BranchRegistry;
use crate::store::{DiskStore, NodeStore};
#[test]
fn crash_after_install_recovers_new_heads_and_skips_volatile_intermediates() -> Result<(), TestError>
{
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let (anchor, durable_head, volatile_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"landed", b"yes")?;
commit_branch(&branch, &mut store, ®istry, durable(&mut refs, 20))?;
let durable_head = branch.current_root();
branch.put(0, b"speculative", b"lost")?;
commit_branch(
&branch,
&mut store,
®istry,
CommitRequest {
durability: CommitDurability::Volatile,
extra_parents: &[],
timestamp: 30,
},
)?;
(anchor, durable_head, 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, 1);
assert_eq!(
record.parents,
vec![anchor],
"parents chain durable-to-durable"
);
assert_eq!(record.shards[0].fork_anchor, anchor);
let reopened = open_branch("wal", &refs, ®istry)?;
assert_eq!(reopened.current_root(), durable_head);
assert_eq!(reopened.fork_point(), anchor);
assert_eq!(
read_at(&store, durable_head, b"landed")?,
Some(b"yes".to_vec())
);
assert_eq!(read_at(&store, durable_head, b"speculative")?, None);
assert_ne!(volatile_head, durable_head);
assert!(store.get(&volatile_head)?.is_some());
Ok(())
}
#[test]
fn crash_mid_install_temp_file_is_swept_and_old_record_survives() -> Result<(), TestError> {
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let (anchor, 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, durable(&mut refs, 20))?;
(anchor, branch.current_root())
};
let ref_file = sole_ref_file(&refs_dir)?;
let stranded = refs_dir.join(".branch-crashed00.tmp");
fs::write(&stranded, fs::read(&ref_file)?)?;
let refs = BranchRefStore::open(&refs_dir)?;
assert!(
!stranded.exists(),
"reopen must sweep the stranded temp file"
);
let record = refs.get("wal").ok_or(TestError::Missing("wal record"))?;
assert_eq!(record.seq, 1);
assert_eq!(record.shards[0].head, head);
assert_eq!(record.shards[0].fork_anchor, anchor);
Ok(())
}
#[test]
fn multi_shard_durable_commit_recovers_all_new_heads() -> Result<(), TestError> {
let dir = tempfile::tempdir()?;
let (store_dir, refs_dir) = (dir.path().join("store"), dir.path().join("refs"));
let (heads, anchors) = {
let mut store = DiskStore::new(&store_dir)?;
let anchor_zero = build_tree(&mut store, &[(b"zero", b"base")])?;
let anchor_three = build_tree(&mut store, &[(b"three", b"base")])?;
store.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 outcome = commit_branch(&branch, &mut store, ®istry, durable(&mut refs, 20))?;
(outcome.heads, (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, 1);
assert_eq!(record.parents, vec![anchors.0, anchors.1]);
let reopened = open_branch("wal", &refs, ®istry)?;
for &(shard_id, head) in &heads {
assert_eq!(reopened.shard_current_root(shard_id), Some(head));
}
assert_eq!(
read_at(&store, heads[0].1, b"zero-new")?,
Some(b"v0".to_vec())
);
assert_eq!(
read_at(&store, heads[1].1, b"three-new")?,
Some(b"v3".to_vec())
);
Ok(())
}