#![allow(clippy::unwrap_used)]
use melinoe::reentrant::{GuardedCell, Reentered, ReentrancyCell};
use melinoe::{brand_scope, MelinoeCell};
#[test]
fn nested_brands_grant_simultaneous_disjoint_mut() {
let result = brand_scope(|mut ta| {
brand_scope(|mut tb| {
let a = MelinoeCell::new(10_u64);
let b = MelinoeCell::new(32_u64);
let mut ma = a.borrow_mut(&mut ta);
let mb = b.borrow_mut(&mut tb);
*ma += *mb;
*a.borrow(&ta)
})
});
assert_eq!(result, 42);
}
#[test]
fn three_nested_brands_independent() {
let sum = brand_scope(|mut ta| {
brand_scope(|mut tb| {
brand_scope(|mut tc| {
let a = MelinoeCell::new(1_u64);
let b = MelinoeCell::new(2_u64);
let c = MelinoeCell::new(3_u64);
*a.borrow_mut(&mut ta) += 10;
*b.borrow_mut(&mut tb) += 10;
*c.borrow_mut(&mut tc) += 10;
*a.borrow(&ta) + *b.borrow(&tb) + *c.borrow(&tc)
})
})
});
assert_eq!(sum, 36);
}
#[test]
fn reentrancy_gate_grants_token_and_refuses_nested() {
let gate = ReentrancyCell::new();
assert!(!gate.is_active());
let out = gate.enter(|mut token| {
assert!(gate.is_active());
let slot = MelinoeCell::new(0_u64);
*slot.borrow_mut(&mut token) = 7;
assert_eq!(gate.enter(|_| ()).unwrap_err(), Reentered);
*slot.borrow(&token)
});
assert_eq!(out, Ok(7));
assert!(!gate.is_active()); }
#[test]
fn reentrancy_gate_clears_flag_after_panic() {
let gate = ReentrancyCell::new();
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = gate.enter(|_token| panic!("boom"));
}));
assert_eq!(panic_payload(r), "boom");
assert!(!gate.is_active());
assert_eq!(gate.enter(|_| 5), Ok(5));
}
#[test]
fn reentrancy_gate_sequential_reuse() {
let gate = ReentrancyCell::new();
let a = gate.enter(|_| 1).unwrap();
let b = gate.enter(|_| 2).unwrap();
assert_eq!(a + b, 3);
}
#[test]
fn guarded_cell_exclusive_mut_and_reentry_refused() {
let cache = GuardedCell::new(vec![1, 2, 3]);
let len = cache
.enter(|v| {
v.push(4);
assert_eq!(cache.enter(|_| ()).unwrap_err(), Reentered);
v.len()
})
.unwrap();
assert_eq!(len, 4);
assert_eq!(cache.into_inner(), vec![1, 2, 3, 4]);
}
#[test]
fn guarded_cell_clears_flag_after_panic() {
let cache = GuardedCell::new(0_u64);
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = cache.enter(|_| panic!("boom"));
}));
assert_eq!(panic_payload(r), "boom");
assert!(!cache.is_active());
assert_eq!(cache.enter(|n| *n + 9), Ok(9));
}
#[test]
fn guarded_cell_unguarded_skips_flag() {
let cache = GuardedCell::new(10_i32);
let doubled = unsafe { cache.enter_unguarded(|n| *n * 2) }.unwrap();
assert_eq!(doubled, 20);
assert!(!cache.is_active());
}
fn panic_payload(result: std::thread::Result<()>) -> &'static str {
let payload = match result {
Ok(()) => panic!("expected the reentrancy closure to panic"),
Err(payload) => payload,
};
payload
.downcast_ref::<&'static str>()
.expect("panic payload should be the test sentinel")
}