use super::CloseObligation;
#[test]
fn a_fresh_obligation_is_not_owed() {
assert!(!CloseObligation::new().is_owed());
}
#[test]
fn going_live_owes() {
let obligation = CloseObligation::new();
obligation.record_live();
assert!(obligation.is_owed());
}
#[test]
fn settling_discharges() {
let obligation = CloseObligation::new();
obligation.record_live();
obligation.record_settled();
assert!(!obligation.is_owed());
}
#[test]
fn going_live_again_after_settling_owes_again() {
let obligation = CloseObligation::new();
obligation.record_live();
obligation.record_settled();
obligation.record_live();
assert!(obligation.is_owed());
}
#[test]
fn settling_an_unowed_obligation_is_harmless() {
let obligation = CloseObligation::new();
obligation.record_settled();
obligation.record_settled();
assert!(!obligation.is_owed());
}
#[test]
fn repeated_arming_owes_once() {
let obligation = CloseObligation::new();
for _ in 0..16 {
obligation.record_live();
}
assert!(obligation.is_owed());
obligation.record_settled();
assert!(!obligation.is_owed());
}
#[test]
fn the_obligation_is_recorded_before_the_arming_is_published() {
let obligation = CloseObligation::new();
let mut owed_when_published = None;
obligation.record_live_before(|| owed_when_published = Some(obligation.is_owed()));
assert_eq!(
owed_when_published,
Some(true),
"the arming was published while the obligation still read as not owed"
);
assert!(obligation.is_owed());
}
#[test]
fn a_settle_from_inside_the_publish_survives() {
let obligation = CloseObligation::new();
obligation.record_live_before(|| obligation.record_settled());
assert!(
!obligation.is_owed(),
"a settle that happened during the arming was overwritten by the record"
);
}
#[test]
fn the_event_tag_is_stable() {
assert_eq!(super::DROP_OBLIGATION_OWED, "drop-obligation-owed");
}
use crate::timer::{ThreadpoolPeriodicTimer, ThreadpoolTimer};
use crate::wait::{ThreadpoolWait, WaitableHandle};
use crate::work::ThreadpoolWork;
use std::os::windows::io::AsRawHandle;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use windows_sys::Win32::System::Threading::SetEvent;
fn signal(wait: &ThreadpoolWait) {
let ok = unsafe { SetEvent(wait.handle().as_raw_handle()) };
assert_ne!(ok, 0, "SetEvent failed");
}
fn wait_for(label: &str, mut done: impl FnMut() -> bool) {
let deadline = Instant::now() + Duration::from_secs(5);
while !done() {
assert!(Instant::now() < deadline, "timed out waiting for {label}");
std::thread::sleep(Duration::from_millis(1));
}
}
#[test]
fn a_wait_that_was_never_armed_owes_nothing() {
let event = WaitableHandle::event(true, false).expect("create event");
let wait = ThreadpoolWait::new(event, |_| {}, None).expect("create wait");
assert!(
!wait.obligation_owed(),
"a wait that was never armed has nothing for Drop to wait on"
);
}
#[test]
fn arming_a_wait_owes_a_drain() {
let event = WaitableHandle::event(true, false).expect("create event");
let wait = ThreadpoolWait::new(event, |_| {}, None).expect("create wait");
wait.arm(None);
assert!(wait.obligation_owed());
wait.stop_and_drain();
}
#[test]
fn stop_and_drain_discharges_a_waits_obligation() {
let event = WaitableHandle::event(true, false).expect("create event");
let wait = ThreadpoolWait::new(event, |_| {}, None).expect("create wait");
wait.arm(None);
wait.stop_and_drain();
assert!(
!wait.obligation_owed(),
"the caller drained it, so Drop has nothing to report"
);
}
#[test]
fn a_wait_that_fired_without_rearming_owes_nothing() {
let event = WaitableHandle::event(true, false).expect("create event");
let fired = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&fired);
let wait = ThreadpoolWait::new(
event,
move |_| {
seen.fetch_add(1, Ordering::SeqCst);
},
None,
)
.expect("create wait");
wait.arm(None);
signal(&wait);
wait_for("the wait to fire", || fired.load(Ordering::SeqCst) == 1);
assert!(
!wait.obligation_owed(),
"the activation consumed the arming, so nothing is outstanding"
);
}
#[test]
fn a_wait_rearmed_from_its_callback_owes_again() {
let event = WaitableHandle::event(false, false).expect("create event");
let fired = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&fired);
let wait = ThreadpoolWait::new(
event,
move |activation| {
if seen.fetch_add(1, Ordering::SeqCst) == 0 {
activation.rearm(None);
}
},
None,
)
.expect("create wait");
wait.arm(None);
signal(&wait);
wait_for("the wait to fire", || fired.load(Ordering::SeqCst) >= 1);
wait_for("the re-arm to land", || wait.obligation_owed());
wait.stop_and_drain();
}
#[test]
fn a_timer_that_was_never_set_owes_nothing() {
let timer = ThreadpoolTimer::new(|_| {}, None).expect("create timer");
assert!(!timer.obligation_owed());
}
#[test]
fn setting_a_timer_owes_a_drain() {
let timer = ThreadpoolTimer::new(|_| {}, None).expect("create timer");
timer.set_after(Duration::from_secs(60));
assert!(timer.obligation_owed());
timer.stop_and_drain();
assert!(!timer.obligation_owed());
}
#[test]
fn a_one_shot_timer_that_fired_owes_nothing() {
let fired = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&fired);
let timer = ThreadpoolTimer::new(
move |_| {
seen.fetch_add(1, Ordering::SeqCst);
},
None,
)
.expect("create timer");
timer.set_after(Duration::from_millis(1));
wait_for("the timer to fire", || fired.load(Ordering::SeqCst) == 1);
assert!(
!timer.obligation_owed(),
"a one-shot produces one callback per arming, so the firing settled it"
);
}
#[test]
fn a_periodic_timer_still_owes_after_ticking() {
let ticks = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&ticks);
let timer = ThreadpoolPeriodicTimer::new(
Duration::from_millis(1),
move |_| {
seen.fetch_add(1, Ordering::SeqCst);
},
None,
)
.expect("create timer");
assert!(!timer.obligation_owed(), "not started yet");
timer.start();
wait_for("three ticks", || ticks.load(Ordering::SeqCst) >= 3);
assert!(
timer.obligation_owed(),
"ticking does not clear the schedule, so the drain is still owed"
);
timer.stop_and_drain();
assert!(!timer.obligation_owed());
}
#[test]
fn work_owes_from_submit_until_wait() {
let work = ThreadpoolWork::new(|| {}, None).expect("create work");
assert!(!work.obligation_owed(), "nothing submitted yet");
work.submit();
assert!(work.obligation_owed());
work.wait();
assert!(
!work.obligation_owed(),
"`wait` is this type's drain, so it discharges the obligation"
);
}
#[test]
fn resubmitting_work_owes_again() {
let work = ThreadpoolWork::new(|| {}, None).expect("create work");
work.submit();
work.wait();
work.submit();
assert!(
work.obligation_owed(),
"a submission after the drain is a fresh obligation"
);
work.wait();
assert!(!work.obligation_owed());
}