use async_safe_defer::{defer, guard, DeferGuard, ScopeGuard, Strategy};
use std::{
cell::{Cell, RefCell},
panic::{catch_unwind, AssertUnwindSafe},
};
#[cfg(feature = "std")]
use async_safe_defer::{defer_on_success, defer_on_unwind, guard_on_success, guard_on_unwind};
enum Never {}
impl Strategy for Never {
fn should_run() -> bool {
false
}
}
enum Panics {}
impl Strategy for Panics {
fn should_run() -> bool {
panic!("policy failed")
}
}
struct DropProbe<'a>(&'a Cell<usize>);
impl Drop for DropProbe<'_> {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
}
}
struct PanickingDropProbe<'a>(&'a Cell<usize>);
impl Drop for PanickingDropProbe<'_> {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
panic!("drop failed");
}
}
#[test]
fn guard_runs_when_dropped() {
let value = Cell::new(0);
{
let _guard = defer(|| value.set(42));
assert_eq!(value.get(), 0);
}
assert_eq!(value.get(), 42);
}
#[test]
fn defer_guard_keeps_single_parameter_type_usage() {
let guard: DeferGuard<_> = defer(|| {});
drop(guard);
}
#[test]
fn guard_can_be_disarmed() {
let value = Cell::new(0);
let action = defer(|| value.set(42)).disarm();
assert_eq!(value.get(), 0);
action();
assert_eq!(value.get(), 42);
}
#[test]
fn guard_can_run_immediately() {
let value = Cell::new(0);
DeferGuard::new(|| value.set(42)).run_now();
assert_eq!(value.get(), 42);
}
#[test]
fn custom_strategy_can_skip_deferred_action() {
let calls = Cell::new(0);
let guard = DeferGuard::<_, Never>::with_strategy(|| calls.set(calls.get() + 1));
drop(guard);
assert_eq!(calls.get(), 0);
}
#[test]
fn immediate_and_disarmed_guards_do_not_evaluate_strategy() {
let calls = Cell::new(0);
DeferGuard::<_, Panics>::with_strategy(|| calls.set(calls.get() + 1)).run_now();
let action = DeferGuard::<_, Panics>::with_strategy(|| calls.set(calls.get() + 1)).disarm();
action();
assert_eq!(calls.get(), 2);
}
#[test]
fn captured_action_is_dropped_when_strategy_panics() {
let drops = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let probe = DropProbe(&drops);
drop(DeferGuard::<_, Panics>::with_strategy(move || drop(probe)));
}));
assert!(outcome.is_err());
assert_eq!(drops.get(), 1);
}
#[test]
fn panicking_deferred_action_runs_only_once() {
let calls = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
drop(defer(|| {
calls.set(calls.get() + 1);
panic!("action failed");
}));
}));
assert!(outcome.is_err());
assert_eq!(calls.get(), 1);
}
#[test]
fn macro_runs_actions_in_lifo_order() {
let order = RefCell::new(Vec::new());
{
async_safe_defer::defer!(order.borrow_mut().push(1));
async_safe_defer::defer!(order.borrow_mut().push(2));
}
assert_eq!(*order.borrow(), vec![2, 1]);
}
#[test]
fn macro_supports_move_capture() {
let output = RefCell::new(None);
{
let value = String::from("owned");
let output = &output;
async_safe_defer::defer!(move {
output.replace(Some(value));
});
}
assert_eq!(output.into_inner().as_deref(), Some("owned"));
}
#[test]
fn guard_runs_during_unwind() {
let ran = Cell::new(false);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let _guard = defer(|| ran.set(true));
panic!("trigger unwind");
}));
assert!(outcome.is_err());
assert!(ran.get());
}
#[test]
fn debug_output_reports_armed_state() {
let guard = defer(|| {});
assert_eq!(format!("{guard:?}"), "DeferGuard { armed: true }");
}
#[test]
fn scope_guard_exposes_and_passes_its_mutated_value() {
let output = RefCell::new(Vec::new());
{
let mut guarded = guard(vec![1], |value| {
output.replace(value);
});
guarded.push(2);
assert_eq!(&*guarded, &[1, 2]);
}
assert_eq!(output.into_inner(), vec![1, 2]);
}
#[test]
fn scope_guard_into_inner_skips_action() {
let calls = Cell::new(0);
let mut guarded = guard(String::from("value"), |_| calls.set(calls.get() + 1));
guarded.push('!');
assert_eq!(guarded.into_inner(), "value!");
assert_eq!(calls.get(), 0);
}
#[test]
fn scope_guard_into_parts_returns_value_and_action() {
let calls = Cell::new(0);
let guarded = guard(41, |value| calls.set(value));
let (value, action) = guarded.into_parts();
assert_eq!(value, 41);
assert_eq!(calls.get(), 0);
action(value + 1);
assert_eq!(calls.get(), 42);
}
#[test]
fn scope_guard_run_now_does_not_evaluate_strategy() {
let output = Cell::new(0);
ScopeGuard::<_, _, Panics>::with_strategy(42, |value| output.set(value)).run_now();
assert_eq!(output.get(), 42);
}
#[test]
fn value_is_dropped_if_action_drop_panics_during_into_inner() {
let value_drops = Cell::new(0);
let action_drops = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let value = DropProbe(&value_drops);
let captured = PanickingDropProbe(&action_drops);
let guarded = guard(value, move |_| drop(captured));
let _value = guarded.into_inner();
}));
assert!(outcome.is_err());
assert_eq!(value_drops.get(), 1);
assert_eq!(action_drops.get(), 1);
}
#[test]
fn value_is_dropped_if_skipped_action_drop_panics() {
let value_drops = Cell::new(0);
let action_drops = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let value = DropProbe(&value_drops);
let captured = PanickingDropProbe(&action_drops);
let guarded = ScopeGuard::<_, _, Never>::with_strategy(value, move |_| drop(captured));
drop(guarded);
}));
assert!(outcome.is_err());
assert_eq!(value_drops.get(), 1);
assert_eq!(action_drops.get(), 1);
}
#[test]
fn skipped_scope_guard_drops_value_and_action() {
let value_drops = Cell::new(0);
let action_drops = Cell::new(0);
let value = DropProbe(&value_drops);
let captured = DropProbe(&action_drops);
let guarded = ScopeGuard::<_, _, Never>::with_strategy(value, move |_| drop(captured));
drop(guarded);
assert_eq!(value_drops.get(), 1);
assert_eq!(action_drops.get(), 1);
}
#[test]
fn panicking_scope_strategy_drops_value_and_action() {
let value_drops = Cell::new(0);
let action_drops = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let value = DropProbe(&value_drops);
let captured = DropProbe(&action_drops);
let guarded = ScopeGuard::<_, _, Panics>::with_strategy(value, move |_| drop(captured));
drop(guarded);
}));
assert!(outcome.is_err());
assert_eq!(value_drops.get(), 1);
assert_eq!(action_drops.get(), 1);
}
#[test]
fn disarmed_scope_guard_does_not_evaluate_strategy() {
let guarded = ScopeGuard::<_, _, Panics>::with_strategy(42, |_| {});
assert_eq!(guarded.into_inner(), 42);
}
#[test]
fn panicking_scope_action_drops_value_and_runs_once() {
let value_drops = Cell::new(0);
let calls = Cell::new(0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let guarded = guard(DropProbe(&value_drops), |value| {
calls.set(calls.get() + 1);
drop(value);
panic!("action failed");
});
drop(guarded);
}));
assert!(outcome.is_err());
assert_eq!(calls.get(), 1);
assert_eq!(value_drops.get(), 1);
}
#[test]
fn scope_guard_debug_shows_guarded_value() {
let guarded = guard(42, |_| {});
assert_eq!(format!("{guarded:?}"), "ScopeGuard { value: 42 }");
}
#[cfg(feature = "std")]
#[test]
fn success_and_unwind_defer_policies_follow_panic_state() {
let successes = Cell::new(0);
let unwinds = Cell::new(0);
{
let _success = defer_on_success(|| successes.set(successes.get() + 1));
let _unwind = defer_on_unwind(|| unwinds.set(unwinds.get() + 1));
}
assert_eq!(successes.get(), 1);
assert_eq!(unwinds.get(), 0);
let outcome = catch_unwind(AssertUnwindSafe(|| {
let _success = defer_on_success(|| successes.set(successes.get() + 1));
let _unwind = defer_on_unwind(|| unwinds.set(unwinds.get() + 1));
panic!("trigger unwind");
}));
assert!(outcome.is_err());
assert_eq!(successes.get(), 1);
assert_eq!(unwinds.get(), 1);
}
#[cfg(feature = "std")]
#[test]
fn success_and_unwind_value_guards_follow_panic_state() {
let output = RefCell::new(Vec::new());
{
let _success = guard_on_success(1, |value| output.borrow_mut().push(value));
let _unwind = guard_on_unwind(2, |value| output.borrow_mut().push(value));
}
let outcome = catch_unwind(AssertUnwindSafe(|| {
let _success = guard_on_success(3, |value| output.borrow_mut().push(value));
let _unwind = guard_on_unwind(4, |value| output.borrow_mut().push(value));
panic!("trigger unwind");
}));
assert!(outcome.is_err());
assert_eq!(*output.borrow(), vec![1, 4]);
}
#[cfg(feature = "std")]
#[test]
fn conditional_macros_follow_panic_state_and_lifo_order() {
let output = RefCell::new(Vec::new());
{
async_safe_defer::defer_on_unwind!(output.borrow_mut().push(1));
async_safe_defer::defer_on_success!(output.borrow_mut().push(2));
}
let outcome = catch_unwind(AssertUnwindSafe(|| {
async_safe_defer::defer_on_success!(output.borrow_mut().push(3));
async_safe_defer::defer_on_unwind!(output.borrow_mut().push(4));
panic!("trigger unwind");
}));
assert!(outcome.is_err());
assert_eq!(*output.borrow(), vec![2, 4]);
}
#[cfg(feature = "std")]
#[test]
fn conditional_macro_supports_move_capture() {
let output = RefCell::new(None);
{
let value = String::from("owned");
let output = &output;
async_safe_defer::defer_on_success!(move {
output.replace(Some(value));
});
}
assert_eq!(output.into_inner().as_deref(), Some("owned"));
}
#[cfg(feature = "std")]
#[test]
fn returning_error_counts_as_successful_drop() {
fn fail(ran: &Cell<bool>) -> Result<(), ()> {
let _guard = defer_on_success(|| ran.set(true));
Err(())
}
let ran = Cell::new(false);
assert_eq!(fail(&ran), Err(()));
assert!(ran.get());
}