use std::cell::Cell;
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use super::commit::{BranchCommitError, CommitDurability, CommitRequest};
use super::handle::BranchError;
use super::refstore::{BranchRefError, BranchRefStore};
use crate::store::{DiskStore, MemoryStore, NodeStore, StoreError};
use crate::tree::{Cursor, Hash, LeafNode, Node, NodeError, TreeError, TreePolicy, insert};
#[derive(Debug)]
pub(super) enum TestError {
Commit(BranchCommitError),
Refs(BranchRefError),
Store(StoreError),
Tree(TreeError),
Node(NodeError),
Branch(BranchError),
Io(std::io::Error),
Put(String),
Missing(&'static str),
}
impl fmt::Display for TestError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Commit(error) => write!(formatter, "commit error: {error}"),
Self::Refs(error) => write!(formatter, "ref store error: {error}"),
Self::Store(error) => write!(formatter, "disk store error: {error}"),
Self::Tree(error) => write!(formatter, "tree error: {error}"),
Self::Node(error) => write!(formatter, "node error: {error}"),
Self::Branch(error) => write!(formatter, "branch error: {error}"),
Self::Io(error) => write!(formatter, "I/O error: {error}"),
Self::Put(reason) => write!(formatter, "store put error: {reason}"),
Self::Missing(what) => write!(formatter, "missing: {what}"),
}
}
}
impl std::error::Error for TestError {}
impl From<BranchCommitError> for TestError {
fn from(error: BranchCommitError) -> Self {
Self::Commit(error)
}
}
impl From<BranchRefError> for TestError {
fn from(error: BranchRefError) -> Self {
Self::Refs(error)
}
}
impl From<StoreError> for TestError {
fn from(error: StoreError) -> Self {
Self::Store(error)
}
}
impl From<TreeError> for TestError {
fn from(error: TreeError) -> Self {
Self::Tree(error)
}
}
impl From<NodeError> for TestError {
fn from(error: NodeError) -> Self {
Self::Node(error)
}
}
impl From<BranchError> for TestError {
fn from(error: BranchError) -> Self {
Self::Branch(error)
}
}
impl From<std::io::Error> for TestError {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
#[derive(Debug)]
pub(super) enum FailpointError {
Injected,
Store(StoreError),
}
impl fmt::Display for FailpointError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Injected => write!(formatter, "injected crash point"),
Self::Store(error) => write!(formatter, "disk store error: {error}"),
}
}
}
impl std::error::Error for FailpointError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Store(error) => Some(error),
Self::Injected => None,
}
}
}
#[derive(Debug)]
pub(super) struct FailpointStore {
pub(super) inner: DiskStore,
pub(super) puts_allowed: Cell<Option<usize>>,
pub(super) fail_barrier: bool,
}
impl NodeStore for FailpointStore {
type Error = FailpointError;
fn get(&self, hash: &Hash) -> Result<Option<Arc<Node>>, Self::Error> {
self.inner.get(hash).map_err(FailpointError::Store)
}
fn put(&mut self, node: &Node) -> Result<Hash, Self::Error> {
if let Some(remaining) = self.puts_allowed.get() {
if remaining == 0 {
return Err(FailpointError::Injected);
}
self.puts_allowed.set(Some(remaining - 1));
}
self.inner.put(node).map_err(FailpointError::Store)
}
fn sync_dirty_dirs(&self) -> Result<(), Self::Error> {
if self.fail_barrier {
return Err(FailpointError::Injected);
}
self.inner.sync_dirty_dirs().map_err(FailpointError::Store)
}
}
pub(super) fn build_tree<S: NodeStore + ?Sized>(
store: &mut S,
entries: &[(&[u8], &[u8])],
) -> Result<Hash, TestError> {
let leaf = LeafNode::new(Vec::new())?;
let mut root = store
.put(&Node::Leaf(leaf))
.map_err(|error| TestError::Put(error.to_string()))?;
for (key, value) in entries {
root = insert(store, root, key, value, TreePolicy::V1_DEFAULT)?;
}
Ok(root)
}
pub(super) fn expected_head(
base: &[(&[u8], &[u8])],
extra: &[(&[u8], &[u8])],
) -> Result<Hash, TestError> {
let mut store = MemoryStore::new();
let mut root = build_tree(&mut store, base)?;
for (key, value) in extra {
root = insert(&mut store, root, key, value, TreePolicy::V1_DEFAULT)?;
}
Ok(root)
}
pub(super) fn read_at(
store: &DiskStore,
root: Hash,
key: &[u8],
) -> Result<Option<Vec<u8>>, TestError> {
Cursor::new(store, root).get(key).map_err(TestError::from)
}
pub(super) fn durable(refs: &mut BranchRefStore, timestamp: u64) -> CommitRequest<'_> {
CommitRequest {
durability: CommitDurability::Durable { refs },
extra_parents: &[],
timestamp,
}
}
pub(super) fn sole_ref_file(refs_dir: &Path) -> Result<PathBuf, TestError> {
for entry in fs::read_dir(refs_dir)? {
let path = entry?.path();
if path.extension().and_then(|extension| extension.to_str()) == Some("ref") {
return Ok(path);
}
}
Err(TestError::Missing("a .ref file"))
}
pub(super) const BASE: &[(&[u8], &[u8])] = &[(b"base", b"tree")];