use std::cell::Cell;
use std::rc::Rc;
use crate::{Cc, collect_cycles, Context, Finalize, Trace};
use super::reset_state;
use crate::cleaners::{Cleanable, Cleaner};
#[cfg(not(miri))] use std::panic::{AssertUnwindSafe, catch_unwind};
#[test]
fn clean_after_drop() {
reset_state();
struct ToClean {
cleaner: Cleaner,
}
unsafe impl Trace for ToClean {
fn trace(&self, ctx: &mut Context<'_>) {
self.cleaner.trace(ctx);
}
}
impl Finalize for ToClean {}
let to_clean = Cc::new(ToClean {
cleaner: Cleaner::new(),
});
let already_cleaned = Rc::new(Cell::new(false));
let cleaner = register_cleaner(&already_cleaned, &to_clean.cleaner);
assert!(!already_cleaned.get());
drop(to_clean);
assert!(already_cleaned.get());
cleaner.clean();
assert!(already_cleaned.get());
}
#[test]
fn clean_before_drop() {
reset_state();
struct ToClean {
cleaner: Cleaner,
}
unsafe impl Trace for ToClean {
fn trace(&self, ctx: &mut Context<'_>) {
self.cleaner.trace(ctx);
}
}
impl Finalize for ToClean {}
let to_clean = Cc::new(ToClean {
cleaner: Cleaner::new(),
});
let already_cleaned = Rc::new(Cell::new(false));
let cleaner = register_cleaner(&already_cleaned, &to_clean.cleaner);
assert!(!already_cleaned.get());
cleaner.clean();
assert!(already_cleaned.get());
drop(to_clean);
assert!(already_cleaned.get());
}
fn register_cleaner(already_cleaned: &Rc<Cell<bool>>, cleaner: &Cleaner) -> Cleanable {
assert!(!cleaner.has_allocated());
let already_cleaned = already_cleaned.clone();
let cleanable = cleaner.register(move || {
assert!(!already_cleaned.get(), "Already cleaned!");
already_cleaned.set(true);
});
assert!(cleaner.has_allocated());
cleanable
}
#[test]
fn clean_with_cyclic_cc() {
reset_state();
struct ToClean {
cleaner: Cleaner,
}
unsafe impl Trace for ToClean {
fn trace(&self, ctx: &mut Context<'_>) {
self.cleaner.trace(ctx);
}
}
impl Finalize for ToClean {}
let to_clean = Cc::new(ToClean {
cleaner: Cleaner::new(),
});
let already_cleaned = Rc::new(Cell::new(false));
assert!(!to_clean.cleaner.has_allocated());
let cleaner = {
let cloned = to_clean.clone();
let cloned_ac = already_cleaned.clone();
to_clean.cleaner.register(move || {
let _cc = cloned; assert!(!cloned_ac.get(), "Already cleaned!");
cloned_ac.set(true);
})
};
assert!(to_clean.cleaner.has_allocated());
assert!(!already_cleaned.get());
drop(to_clean);
assert!(!already_cleaned.get());
collect_cycles();
assert!(!already_cleaned.get());
cleaner.clean();
assert!(already_cleaned.get());
}
#[cfg(not(miri))] #[test]
fn simple_panic_on_clean() {
reset_state();
let cleaner = Cleaner::new();
let already_cleaned = Rc::new(Cell::new(false));
let already_cleaned_clone = already_cleaned.clone();
let cleanable = cleaner.register(move || {
already_cleaned_clone.set(true);
panic!("Panic inside registered cleaner!");
});
assert!(catch_unwind(AssertUnwindSafe(|| {
cleanable.clean();
})).is_err());
assert!(already_cleaned.get());
}
#[cfg(not(miri))] #[test]
fn simple_panic_on_cleaner_drop() {
reset_state();
let cleaner = Cleaner::new();
let already_cleaned = Rc::new(Cell::new(false));
let already_cleaned_clone = already_cleaned.clone();
cleaner.register(move || {
already_cleaned_clone.set(true);
panic!("Panic inside registered cleaner!");
});
assert!(catch_unwind(AssertUnwindSafe(|| {
drop(cleaner);
})).is_err());
assert!(already_cleaned.get());
}
#[cfg(not(miri))] #[test]
fn panic_on_clean() {
reset_state();
struct ToClean {
cleaner: Cleaner,
}
unsafe impl Trace for ToClean {
fn trace(&self, ctx: &mut Context<'_>) {
self.cleaner.trace(ctx);
}
}
impl Finalize for ToClean {}
let to_clean = Cc::new(ToClean {
cleaner: Cleaner::new(),
});
let already_cleaned = Rc::new(Cell::new(false));
let already_cleaned_clone = already_cleaned.clone();
to_clean.cleaner.register(move || {
already_cleaned_clone.set(true);
panic!("Panic inside registered cleaner!");
});
assert!(catch_unwind(AssertUnwindSafe(|| {
drop(to_clean);
})).is_err());
assert!(already_cleaned.get());
}
#[test]
fn clean_multiple_times() {
reset_state();
let rc = Rc::new(Cell::new(false));
let cleaner = Cleaner::new();
let cleanable = cleaner.register({
let rc = rc.clone();
move || {
assert!(!rc.replace(true));
}
});
cleanable.clean();
assert!(rc.get());
cleanable.clean();
assert!(rc.get());
}