use crate::paths::{self, get_repo_root};
use derive_more::Deref;
use rstest::fixture;
use std::{env, error::Error, fmt::Debug, fs, io, path::PathBuf};
use tracing::{error, level_filters::LevelFilter};
use tracing_subscriber::{
Layer, filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt,
util::SubscriberInitExt,
};
use uuid::Uuid;
pub trait Factory<Param = ()> {
fn factory(param: Param) -> Self;
}
#[fixture]
pub fn test_data_dir() -> PathBuf {
get_repo_root().join("test_data")
}
#[fixture]
pub fn temp_dir() -> TempDir {
TempDir::new()
}
#[derive(Debug, Deref)]
#[deref(forward)]
pub struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
let path = env::temp_dir().join(Uuid::new_v4().to_string());
fs::create_dir(&path).unwrap();
Self(path)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
if let Err(error) = fs::remove_dir_all(&self.0) {
error!(
error = &error as &dyn Error,
"Error deleting temporary directory `{}`",
self.0.display()
);
}
}
}
#[fixture]
pub fn data_dir() -> DataDir {
DataDir::new()
}
#[derive(Debug, Deref)]
#[deref(forward)]
pub struct DataDir(TempDir);
impl DataDir {
fn new() -> Self {
let temp_dir = TempDir::new();
paths::set_data_directory(temp_dir.to_owned());
Self(temp_dir)
}
}
impl Drop for DataDir {
fn drop(&mut self) {
paths::reset_data_directory();
}
}
#[macro_export]
macro_rules! assert_err {
($result:expr, $msg:expr) => {{
let error = $result.unwrap_err();
let msg = $msg;
let actual = format!("{error:#}");
assert!(
actual.contains(msg),
"Expected error message to contain {msg:?}, but was: {actual:?}"
)
}};
}
#[track_caller]
pub fn assert_err<T, E>(result: Result<T, E>, expected_error: &str)
where
T: Debug,
E: 'static + Debug + Error,
{
let error = result.unwrap_err();
let actual = format_error_chain(error);
assert!(
actual.contains(expected_error),
"Expected error message to contain {expected_error:?}, but was: \
{actual:?}"
);
}
#[track_caller]
pub fn assert_result<TA, TE, E>(
result: Result<TA, E>,
expected: Result<TE, &str>,
) where
TA: Debug + PartialEq<TE>,
TE: Debug,
E: 'static + Debug + Error,
{
match expected {
Ok(expected) => {
let value = result.unwrap();
assert_eq!(value, expected);
}
Err(expected) => assert_err(result, expected),
}
}
#[deprecated(note = "Debugging only; remove when done")]
pub fn initialize_test_tracing() {
#[expect(deprecated)]
initialize_test_tracing_with(LevelFilter::TRACE);
}
#[deprecated(note = "Debugging only; remove when done")]
pub fn initialize_test_tracing_with(level: LevelFilter) {
let subscriber = tracing_subscriber::fmt::layer()
.with_writer(io::stderr)
.with_target(true)
.with_span_events(FmtSpan::NONE)
.without_time()
.with_filter(
Targets::new()
.with_target("test_", level) .with_target("slumber", level)
.with_default(LevelFilter::WARN),
);
tracing_subscriber::registry().with(subscriber).init();
}
#[macro_export]
macro_rules! tracing {
() => {
$crate::tracing!(TRACE)
};
($level:ident) => {
$crate::initialize_tracing(tracing::level_filters::LevelFilter::$level);
};
}
fn format_error_chain(error: impl Error) -> String {
let mut s = error.to_string();
let mut source = error.source();
while let Some(error) = source {
s.push_str(": ");
s.push_str(&error.to_string());
source = error.source();
}
s
}
#[macro_export]
macro_rules! assert_matches {
($expr:expr, $pattern:pat $(if $condition:expr)? $(,)?) => {
$crate::assert_matches!($expr, $pattern $(if $condition)? => ());
};
($expr:expr, $pattern:pat $(if $condition:expr)? => $output:expr $(,)?) => {
match $expr {
$(ref value @ $pattern if !$condition => {
panic!(
"Value {value:?} does not match condition {condition}",
condition = stringify!($condition),
);
})?
#[expect(unused_variables)]
$pattern => $output,
value => panic!(
"Unexpected value {value:?} does not match pattern {expected}",
expected = stringify!($pattern),
),
}
};
}