#[cfg(test)]
mod tests {
use std::{
io,
panic::{AssertUnwindSafe, catch_unwind},
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use qubit_dcl::double_checked::ExecutorError;
use qubit_dcl::{DoubleCheckedLock, double_checked::ExecutionResult};
use qubit_lock::{ArcMutex, lock::Lock};
mod test_double_checked_lock {
use super::*;
fn increment_unit_task(value: &mut i32) -> Result<(), io::Error> {
*value += 1;
Ok(())
}
#[test]
fn test_call_with_runs_without_manual_executor_build() {
let data = ArcMutex::new(10);
let skip = Arc::new(AtomicBool::new(false));
let updated = DoubleCheckedLock::on(data.clone())
.when({
let skip = skip.clone();
move || !skip.load(Ordering::Acquire)
})
.call_with(|value: &mut i32| {
*value += 5;
Ok::<i32, io::Error>(*value)
})
.get_result();
assert!(matches!(updated, ExecutionResult::Success(15)));
assert_eq!(data.read(|value| *value), 15);
}
#[test]
fn test_execute_with_returns_unmet_when_condition_fails() {
let data = ArcMutex::new(10);
let result = DoubleCheckedLock::on(data.clone())
.when(|| false)
.execute_with(increment_unit_task as fn(&mut i32) -> Result<(), io::Error>)
.get_result();
assert!(matches!(result, ExecutionResult::ConditionNotMet));
assert_eq!(data.read(|value| *value), 10);
}
#[test]
fn test_execute_with_runs_function_pointer_task() {
let data = ArcMutex::new(10);
let result = DoubleCheckedLock::on(data.clone())
.when(|| true)
.execute_with(increment_unit_task as fn(&mut i32) -> Result<(), io::Error>)
.get_result();
assert!(matches!(result, ExecutionResult::Success(())));
assert_eq!(data.read(|value| *value), 11);
}
#[test]
fn test_prepare_pipeline_works_in_convenience_mode() {
let data = ArcMutex::new(10);
let prepared = Arc::new(AtomicBool::new(false));
let committed = Arc::new(AtomicBool::new(false));
let rolled_back = Arc::new(AtomicBool::new(false));
let result = DoubleCheckedLock::on(data.clone())
.when(|| true)
.prepare({
let prepared = prepared.clone();
move || {
prepared.store(true, Ordering::Release);
Ok::<(), io::Error>(())
}
})
.rollback_prepare({
let rolled_back = rolled_back.clone();
move || {
rolled_back.store(true, Ordering::Release);
Ok::<(), io::Error>(())
}
})
.commit_prepare({
let committed = committed.clone();
move || {
committed.store(true, Ordering::Release);
Ok::<(), io::Error>(())
}
})
.call_with(|value: &mut i32| {
*value += 3;
Ok::<i32, io::Error>(*value)
})
.get_result();
assert!(matches!(result, ExecutionResult::Success(13)));
assert!(prepared.load(Ordering::Acquire));
assert!(committed.load(Ordering::Acquire));
assert!(!rolled_back.load(Ordering::Acquire));
}
#[test]
fn test_build_still_exposes_reusable_executor() {
let data = ArcMutex::new(1);
let executor = DoubleCheckedLock::on(data.clone()).when(|| true).build();
let first = executor
.call_with(|value: &mut i32| {
*value += 1;
Ok::<i32, io::Error>(*value)
})
.get_result();
let second = executor
.call_with(|value: &mut i32| {
*value += 1;
Ok::<i32, io::Error>(*value)
})
.get_result();
assert!(matches!(first, ExecutionResult::Success(2)));
assert!(matches!(second, ExecutionResult::Success(3)));
assert_eq!(data.read(|value| *value), 3);
}
#[test]
fn test_lock_builder_log_methods_are_chainable_before_call() {
let data = ArcMutex::new(1);
let result = DoubleCheckedLock::on(data)
.log_unmet_condition(log::Level::Info, "condition not met")
.log_prepare_failure(log::Level::Warn, "prepare failed")
.log_prepare_commit_failure(log::Level::Error, "prepare commit failed")
.log_prepare_rollback_failure(log::Level::Debug, "prepare rollback failed")
.disable_unmet_condition_logging()
.disable_prepare_failure_logging()
.disable_prepare_commit_failure_logging()
.disable_prepare_rollback_failure_logging()
.when(|| true)
.call(|| Ok::<i32, io::Error>(42))
.get_result();
assert!(matches!(result, ExecutionResult::Success(42)));
}
#[test]
fn test_ready_builder_log_methods_are_chainable_before_execute() {
let data = ArcMutex::new(1);
let executed = Arc::new(AtomicBool::new(false));
let result = DoubleCheckedLock::on(data)
.when(|| true)
.log_unmet_condition(log::Level::Info, "condition not met")
.log_prepare_failure(log::Level::Warn, "prepare failed")
.log_prepare_commit_failure(log::Level::Error, "prepare commit failed")
.log_prepare_rollback_failure(log::Level::Debug, "prepare rollback failed")
.disable_unmet_condition_logging()
.disable_prepare_failure_logging()
.disable_prepare_commit_failure_logging()
.disable_prepare_rollback_failure_logging()
.execute({
let executed = executed.clone();
move || {
executed.store(true, Ordering::Release);
Ok::<(), io::Error>(())
}
})
.get_result();
assert!(matches!(result, ExecutionResult::Success(())));
assert!(executed.load(Ordering::Acquire));
}
#[test]
fn test_catch_panics_on_ready_builder_catches_task_panic() {
let data = ArcMutex::new(10);
let result = DoubleCheckedLock::on(data)
.when(|| true)
.catch_panics()
.prepare(|| Ok::<(), io::Error>(()))
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic in convenient API task");
})
.get_result();
assert!(matches!(
result,
ExecutionResult::Failed(ExecutorError::Panic(_))
));
}
#[test]
fn test_catch_panics_on_lock_builder_catches_task_panic() {
let data = ArcMutex::new(10);
let result = DoubleCheckedLock::on(data)
.catch_panics()
.when(|| true)
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic in lock builder");
})
.get_result();
assert!(matches!(
result,
ExecutionResult::Failed(ExecutorError::Panic(_))
));
}
#[test]
fn test_set_catch_panics_on_lock_builder_catches_task_panic() {
let data = ArcMutex::new(10);
let result = DoubleCheckedLock::on(data)
.set_catch_panics(true)
.when(|| true)
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic with set_catch_panics");
})
.get_result();
assert!(matches!(
result,
ExecutionResult::Failed(ExecutorError::Panic(_))
));
}
#[test]
fn test_disable_catch_panics_on_ready_builder_allows_panic() {
let data = ArcMutex::new(10);
let result = catch_unwind(AssertUnwindSafe(|| {
DoubleCheckedLock::on(data)
.when(|| true)
.set_catch_panics(true)
.set_catch_panics(false)
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic should propagate");
})
.get_result()
}));
assert!(result.is_err());
}
#[test]
fn test_disable_catch_panics_on_ready_builder_allows_panic_with_disable_method() {
let data = ArcMutex::new(10);
let caught = catch_unwind(AssertUnwindSafe(|| {
DoubleCheckedLock::on(data)
.when(|| true)
.catch_panics()
.disable_catch_panics()
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic from ready builder disable_catch_panics");
})
.get_result();
}));
assert!(caught.is_err());
}
#[test]
fn test_disable_catch_panics_on_lock_builder_allows_panic() {
let data = ArcMutex::new(10);
let caught = catch_unwind(AssertUnwindSafe(|| {
DoubleCheckedLock::on(data)
.catch_panics()
.disable_catch_panics()
.when(|| true)
.execute_with(|_value: &mut i32| -> Result<(), io::Error> {
panic!("panic from lock builder disable_catch_panics");
})
.get_result();
}));
assert!(caught.is_err());
}
}
}