use std::{
future::Future,
panic::catch_unwind,
pin::Pin,
sync::{Arc, mpsc},
task::{Context, Poll, Wake, Waker},
thread,
time::Duration,
};
use futures::executor::block_on;
use ntest::timeout;
use crate::unblock;
mod io;
mod pool;
#[test]
#[timeout(15000)]
fn the_work_hands_its_value_back() {
assert_eq!(block_on(unblock(|| 42)), 42);
}
#[test]
#[timeout(15000)]
fn the_work_starts_without_the_future_being_polled() {
let (report, reported) = mpsc::channel();
let future = unblock(move || report.send(()).expect("the test waits for the work"));
reported.recv().expect("the work runs without a poll");
drop(future);
}
#[test]
#[timeout(15000)]
fn a_panic_in_the_work_reaches_the_caller() {
let panicked = catch_unwind(|| block_on(unblock(|| panic!("the blocking work panicked"))))
.expect_err("awaiting work that panicked panics in turn");
assert_eq!(
panicked.downcast_ref::<&str>(),
Some(&"the blocking work panicked"),
);
}
#[test]
#[timeout(15000)]
fn the_work_runs_on_a_thread_named_for_it() {
let name = block_on(unblock(|| thread::current().name().map(ToOwned::to_owned)));
assert_eq!(name.as_deref(), Some("zruntime blocking work"));
}
#[test]
#[timeout(15000)]
fn a_future_prints_without_its_value() {
struct NotDebug;
let future = unblock(|| NotDebug);
assert_eq!(format!("{future:?}"), "BlockingWork { .. }");
block_on(future);
}
#[test]
#[timeout(15000)]
fn a_dropped_future_takes_its_waker_back_while_the_work_runs_on() {
let probe = Arc::new(Unpark(thread::current()));
let (release, released) = mpsc::channel::<()>();
let (report, reported) = mpsc::channel();
{
let waker = Waker::from(probe.clone());
let mut cx = Context::from_waker(&waker);
let mut future = unblock(move || {
released.recv().expect("the test lets the work go on");
report.send(()).expect("the test waits for the work");
});
assert!(Pin::new(&mut future).poll(&mut cx).is_pending());
}
assert_eq!(
Arc::strong_count(&probe),
1,
"the thread kept the waker of a future that was dropped",
);
release.send(()).expect("the work waits for the test");
reported.recv().expect("the work runs to its end");
}
#[test]
#[timeout(15000)]
fn the_thread_drops_the_waker_before_the_future_it_wakes_can_resolve() {
let probe = Arc::new(SlowWake);
let value = {
let waker = Waker::from(probe.clone());
let mut cx = Context::from_waker(&waker);
let mut future = unblock(|| {
thread::sleep(Duration::from_millis(20));
42
});
loop {
match Pin::new(&mut future).poll(&mut cx) {
Poll::Ready(value) => break value,
Poll::Pending => thread::sleep(Duration::from_millis(1)),
}
}
};
assert_eq!(value, 42);
assert_eq!(
Arc::strong_count(&probe),
1,
"the thread's waker outlived the future it woke",
);
}
struct Unpark(thread::Thread);
impl Wake for Unpark {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
}
struct SlowWake;
impl Wake for SlowWake {
fn wake(self: Arc<Self>) {
thread::sleep(Duration::from_millis(50));
}
}