extern crate core;
mod config;
mod map;
mod module;
mod ordered_key;
mod store;
mod tx_scope;
mod version;
mod wrapper;
pub use config::*;
pub use module::*;
#[cfg(test)]
mod tests {
use crate::map::FjallMap;
use crate::store::FjallStore;
use fjall::{TxKeyspace, TxPartitionHandle};
use nitrite::common::NitritePluginProvider;
use nitrite::store::NitriteMapProvider;
#[derive(Clone)]
pub struct Context {
path: String,
keyspace: Option<TxKeyspace>,
partition_handle: Option<TxPartitionHandle>,
fjall_store: Option<FjallStore>,
fjall_map: Option<FjallMap>,
}
impl Context {
pub fn new(
path: String,
keyspace: Option<TxKeyspace>,
partition_handle: Option<TxPartitionHandle>,
fjall_store: Option<FjallStore>,
fjall_map: Option<FjallMap>,
) -> Self {
Context {
path,
keyspace,
partition_handle,
fjall_store,
fjall_map,
}
}
pub fn path(&self) -> String {
self.path.clone()
}
pub fn keyspace(&self) -> Result<TxKeyspace, String> {
self.keyspace
.clone()
.ok_or_else(|| "Keyspace not available".to_string())
}
pub fn partition_handle(&self) -> Result<TxPartitionHandle, String> {
self.partition_handle
.clone()
.ok_or_else(|| "Partition handle not available".to_string())
}
pub fn fjall_store(&self) -> Result<FjallStore, String> {
self.fjall_store
.clone()
.ok_or_else(|| "FjallStore not available".to_string())
}
pub fn fjall_map(&self) -> Result<FjallMap, String> {
self.fjall_map
.clone()
.ok_or_else(|| "FjallMap not available".to_string())
}
#[cfg(test)]
pub fn fjall_store_unsafe(&self) -> FjallStore {
self.fjall_store.clone().expect("FjallStore not available")
}
#[cfg(test)]
pub fn fjall_map_unsafe(&self) -> FjallMap {
self.fjall_map.clone().expect("FjallMap not available")
}
}
impl Drop for Context {
fn drop(&mut self) {
let keyspace = self.keyspace.take();
let partition_handle = self.partition_handle.take();
let fjall_store = self.fjall_store.take();
let fjall_map = self.fjall_map.take();
if let Some(map) = fjall_map {
match map.close() {
Ok(_) => {
drop(map);
}
Err(e) => {
log::error!("Failed to close FjallMap during cleanup: {}", e);
}
}
}
if let Some(store) = fjall_store {
match store.close() {
Ok(_) => {
drop(store);
}
Err(e) => {
log::error!("Failed to close FjallStore during cleanup: {}", e);
}
}
}
if let Some(handle) = partition_handle {
drop(handle)
}
if let Some(ks) = keyspace {
drop(ks)
}
}
}
pub fn run_test<T, B, A>(before: B, test: T, after: A)
where
T: FnOnce(Context) + std::panic::UnwindSafe,
B: FnOnce() -> Context + std::panic::UnwindSafe,
A: FnOnce(Context) + std::panic::UnwindSafe,
{
let result = std::panic::catch_unwind(|| {
let ctx = before();
test(ctx.clone());
after(ctx.clone());
drop(ctx);
});
match result {
Ok(_) => (),
Err(e) => {
let panic_msg = if let Some(msg) = e.downcast_ref::<String>() {
msg.clone()
} else if let Some(msg) = e.downcast_ref::<&str>() {
msg.to_string()
} else {
format!("{:?}", e)
};
eprintln!("Test execution failed with panic: {}", panic_msg);
panic!("Test execution failed with panic: {}", panic_msg);
}
}
}
#[cfg(test)]
mod context_tests {
use super::*;
#[test]
fn test_context_keyspace_returns_result_on_error() {
let ctx = Context::new("test".to_string(), None, None, None, None);
let result = ctx.keyspace();
assert!(result.is_err());
}
#[test]
fn test_context_partition_handle_returns_result_on_error() {
let ctx = Context::new("test".to_string(), None, None, None, None);
let result = ctx.partition_handle();
assert!(result.is_err());
}
#[test]
fn test_context_fjall_store_returns_result_on_error() {
let ctx = Context::new("test".to_string(), None, None, None, None);
let result = ctx.fjall_store();
assert!(result.is_err());
}
#[test]
fn test_context_fjall_map_returns_result_on_error() {
let ctx = Context::new("test".to_string(), None, None, None, None);
let result = ctx.fjall_map();
assert!(result.is_err());
}
#[test]
fn test_context_drop_handles_empty_state() {
let _ctx = Context::new("test".to_string(), None, None, None, None);
}
#[test]
fn test_context_drop_handles_error_gracefully() {
let ctx = Context::new("test".to_string(), None, None, None, None);
drop(ctx);
}
#[test]
fn test_harness_preserves_string_panic_message() {
let test_msg = "Expected string panic message";
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {
panic!("{}", test_msg);
},
|_ctx| {},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains(test_msg),
"Panic message should contain original message, got: {}",
panic_msg
);
} else {
panic!("Expected String panic message");
}
}
#[test]
fn test_harness_does_not_mask_assertion_failures() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {
panic!("Assertion failure with custom message");
},
|_ctx| {},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains("Assertion failure with custom message"),
"Should preserve assertion failure message: {}",
panic_msg
);
}
}
#[test]
fn test_harness_preserves_panic_from_before_hook() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| {
panic!("Setup failed: database connection error");
},
|_ctx| {},
|_ctx| {},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains("Setup failed"),
"Should preserve setup failure message: {}",
panic_msg
);
}
}
#[test]
fn test_harness_preserves_panic_from_after_hook() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {},
|_ctx| {
panic!("Cleanup failed: resource leak detected");
},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains("Cleanup failed"),
"Should preserve cleanup failure message: {}",
panic_msg
);
}
}
#[test]
fn test_harness_succeeds_with_no_panic() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {
},
|_ctx| {},
);
});
assert!(caught.is_ok(), "Test should succeed without panic");
}
#[test]
fn test_harness_error_message_format_includes_panic_prefix() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {
panic!("Specific error condition");
},
|_ctx| {},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains("Test execution failed with panic"),
"Should include error prefix: {}",
panic_msg
);
assert!(
panic_msg.contains("Specific error condition"),
"Should include original panic message: {}",
panic_msg
);
}
}
#[test]
fn test_harness_handles_generic_panic_without_message() {
let caught = std::panic::catch_unwind(|| {
run_test(
|| Context::new("test".to_string(), None, None, None, None),
|_ctx| {
std::panic::panic_any(42); },
|_ctx| {},
);
});
assert!(caught.is_err());
let err = caught.err().unwrap();
if let Some(panic_msg) = err.downcast_ref::<String>() {
assert!(
panic_msg.contains("Test execution failed with panic"),
"Should provide error prefix for generic panics: {}",
panic_msg
);
}
}
}
}