use std::error::Error;
use super::journal;
use crate::branch::{
BranchKind, BranchRefRecord, BranchRefStore, BranchShardRef, CommitLog, SnapshotRegistry,
};
use crate::db::{Database, DatabaseConfig, DatabaseError, ReadOnlyDatabase};
use crate::store::{DiskStore, NodeStore};
use crate::tree::{Hash, LeafNode, Node};
use crate::wal::{DurableWal, FsyncPolicy, WalError};
type TestResult = Result<(), Box<dyn Error>>;
fn config(data_dir: &std::path::Path) -> DatabaseConfig {
DatabaseConfig {
data_dir: data_dir.to_path_buf(),
shard_count: 1,
distributed: None,
executor_threads: None,
}
}
fn leaf(key: &[u8], value: &[u8]) -> Result<Node, Box<dyn Error>> {
Ok(Node::Leaf(LeafNode::new(vec![(
key.to_vec(),
value.to_vec(),
)])?))
}
fn record_at(name: &str, head: u8) -> BranchRefRecord {
BranchRefRecord {
name: name.to_owned(),
created: 1,
kind: BranchKind::Work,
namespace_lineage: None,
seq: 0,
timestamp: 1,
shards: vec![BranchShardRef {
shard_id: 0,
fork_anchor: Hash::from_bytes([1; 32]),
head: Hash::from_bytes([head; 32]),
}],
parents: Vec::new(),
}
}
#[test]
fn r4b_warm_commit_is_fsync_neutral() -> TestResult {
let _guard = journal::test_guard();
let shard = tempfile::tempdir()?;
let mut store = DiskStore::with_cache_capacity(shard.path().join("store"), 64)?;
store.put(&leaf(b"warm", b"value")?)?;
store.sync_dirty_dirs()?;
journal::reset_observer();
store.put(&leaf(b"warm", b"value")?)?;
store.sync_dirty_dirs()?;
assert_eq!(
journal::total_sync_count(),
0,
"a warm commit creating no new directory must perform zero directory fsyncs"
);
Ok(())
}
#[test]
fn r4b_observer_open_is_fsync_neutral() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let data_dir = temp.path().join("db");
let database = Database::create(config(&data_dir))?;
database.append(b"k".to_vec(), vec![b"v".to_vec()], 0)?;
drop(database);
journal::reset_observer();
let observer = ReadOnlyDatabase::open(&data_dir)?;
let _root = observer.committed_root(0)?;
assert_eq!(
journal::total_sync_count(),
0,
"an observer open must perform zero directory fsyncs"
);
Ok(())
}
#[test]
fn r4e_create_missing_parent_carries_d0_remedy() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let data_dir = temp.path().join("absent").join("db");
match Database::create(config(&data_dir)) {
Err(DatabaseError::DirectoryCreate(error)) => {
assert_eq!(
error.kind(),
std::io::ErrorKind::NotFound,
"NotFound preserved"
);
assert!(
error.to_string().contains("durably linked"),
"the D0 remedy text must be present: {error}"
);
}
other => return Err(format!("expected DirectoryCreate refusal, got {other:?}").into()),
}
Ok(())
}
#[test]
fn r4e_wal_missing_parent_preserves_notfound_and_remedy() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let wal_path = temp.path().join("absent").join("shard.wal");
match DurableWal::new(&wal_path, FsyncPolicy::CommitOnly) {
Err(WalError::Io(error)) => {
assert_eq!(
error.kind(),
std::io::ErrorKind::NotFound,
"NotFound preserved"
);
assert!(
error.to_string().contains("durably linked"),
"the D0 remedy text must be present in the WAL refusal: {error}"
);
}
other => return Err(format!("expected WalError::Io(NotFound), got {other:?}").into()),
}
Ok(())
}
#[test]
fn r4e_metadata_write_with_vanished_dir_surfaces_error() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let containing = temp.path().join("meta");
let path = containing.join("registry.bin");
let mut registry = SnapshotRegistry::open(&path)?;
std::fs::remove_dir_all(&containing)?;
let outcome = registry.name_at("x", Hash::from_bytes([1; 32]), 5);
assert!(
outcome.is_err(),
"a metadata write into a vanished containing dir must surface the owning error"
);
assert!(
!containing.exists(),
"the write must NOT silently recreate the vanished directory"
);
Ok(())
}
#[test]
fn r4f_existing_layouts_open_and_fence() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let data_dir = temp.path().join("db");
std::fs::create_dir(&data_dir)?;
drop(Database::create(config(&data_dir))?);
let temp = tempfile::tempdir()?;
let refs = temp.path().join("refs");
std::fs::create_dir(&refs)?;
drop(BranchRefStore::open(&refs)?);
let temp = tempfile::tempdir()?;
drop(SnapshotRegistry::open(temp.path().join("registry.bin"))?);
drop(CommitLog::open(temp.path().join("commit.log"))?);
Ok(())
}
#[test]
fn r4f_store_open_existing_observes_exactly_one_sync() -> TestResult {
let _guard = journal::test_guard();
let shard = tempfile::tempdir()?;
let store_dir = shard.path().join("store");
std::fs::create_dir(&store_dir)?;
journal::reset_observer();
let _store = DiskStore::with_cache_capacity(&store_dir, 16)?;
assert_eq!(
journal::sync_count(shard.path()),
1,
"opening an existing store must fence its entry exactly once"
);
assert_eq!(
journal::total_sync_count(),
1,
"and no other directory fsync"
);
Ok(())
}
#[test]
fn r4g_open_fences_data_dir_exactly_once() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let data_dir = temp.path().join("db");
drop(Database::create(config(&data_dir))?);
journal::reset_observer();
let opened = Database::open(&data_dir)?;
assert_eq!(
journal::sync_count(temp.path()),
1,
"Database::open must fence the data_dir entry exactly once"
);
assert_eq!(
journal::total_sync_count(),
1,
"and perform no other directory fsync at open"
);
drop(opened);
Ok(())
}
#[test]
fn r4g_open_fence_failure_removes_nothing() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let data_dir = temp.path().join("db");
drop(Database::create(config(&data_dir))?);
let config_before = std::fs::read(data_dir.join("config.json"))?;
journal::arm_failpoint();
let opened = Database::open(&data_dir);
assert!(
matches!(opened, Err(DatabaseError::DirectoryCreate(_))),
"an injected open-mode fence failure must refuse, got {opened:?}"
);
assert!(
data_dir.exists(),
"a refused open must NEVER remove the existing directory"
);
assert_eq!(
std::fs::read(data_dir.join("config.json"))?,
config_before,
"the existing database must survive byte-identically"
);
Ok(())
}
#[test]
fn r1a_journal_covers_both_native_install_sites() -> TestResult {
let _guard = journal::test_guard();
let temp = tempfile::tempdir()?;
let refs = temp.path().join("refs");
let mut store = BranchRefStore::open(&refs)?;
journal::reset();
journal::set_lossy(true);
store.create(record_at("branch", 0x11))?;
journal::set_lossy(false);
assert!(
journal::pending_len() >= 1,
"the create install must be journalled"
);
journal::cut()?;
assert!(
BranchRefStore::open(&refs)?.get("branch").is_none(),
"the cut must REMOVE the unfenced create install (noclobber site)"
);
let temp = tempfile::tempdir()?;
let refs = temp.path().join("refs");
let mut store = BranchRefStore::open(&refs)?;
store.create(record_at("branch", 0x22))?; journal::reset();
journal::set_lossy(true);
store.advance(
"branch",
1,
0,
&[(0, Hash::from_bytes([0x33; 32]))],
Vec::new(),
2,
)?;
journal::set_lossy(false);
assert!(
journal::pending_len() >= 1,
"the cas-replace install must be journalled"
);
journal::cut()?;
let reopened = BranchRefStore::open(&refs)?;
let head = reopened
.get("branch")
.ok_or("branch must still exist")?
.shards[0]
.head;
assert_eq!(
head,
Hash::from_bytes([0x22; 32]),
"the cut must RESTORE the cas-replace install's preimage (write_atomic_unfenced site)"
);
Ok(())
}