#![allow(clippy::unwrap_used)]
use super::AsyncTasks;
use crate::test_utils::TestBackend;
use std::time::Duration;
async fn render_until(
tb: &mut TestBackend,
mut frame: impl FnMut(&mut crate::Context),
mut cond: impl FnMut() -> bool,
) {
for _ in 0..200 {
tb.render(&mut frame);
if cond() {
return;
}
tokio::time::sleep(Duration::from_millis(5)).await;
}
panic!("condition not satisfied within timeout");
}
#[tokio::test]
async fn spawn_then_poll_returns_result_once() {
let mut tb = TestBackend::new(40, 3);
tb.set_async_runtime(tokio::runtime::Handle::current());
let mut handle = None;
tb.render(|ui| {
handle = Some(ui.spawn(async { 7u32 }));
});
let handle = handle.expect("spawn returns a handle");
let got: std::cell::Cell<Option<u32>> = std::cell::Cell::new(None);
render_until(
&mut tb,
|ui| {
if got.get().is_none() {
got.set(ui.poll(&handle));
}
},
|| got.get().is_some(),
)
.await;
assert_eq!(got.get(), Some(7));
let mut second: Option<u32> = Some(0);
tb.render(|ui| {
second = ui.poll(&handle);
});
assert_eq!(second, None, "result must only be delivered once");
}
#[tokio::test]
async fn poll_returns_none_while_pending() {
let mut tb = TestBackend::new(40, 3);
tb.set_async_runtime(tokio::runtime::Handle::current());
let mut handle = None;
tb.render(|ui| {
handle = Some(ui.spawn(async {
tokio::time::sleep(Duration::from_secs(60)).await;
1u8
}));
});
let handle = handle.expect("spawn returns a handle");
let mut got: Option<u8> = Some(0);
tb.render(|ui| {
got = ui.poll(&handle);
});
assert_eq!(got, None, "pending task must poll to None");
}
#[tokio::test]
async fn two_handles_same_type_do_not_cross_results() {
let mut tb = TestBackend::new(40, 3);
tb.set_async_runtime(tokio::runtime::Handle::current());
let mut h1 = None;
let mut h2 = None;
tb.render(|ui| {
h1 = Some(ui.spawn(async { "first".to_string() }));
h2 = Some(ui.spawn(async { "second".to_string() }));
});
let h1 = h1.unwrap();
let h2 = h2.unwrap();
let r1: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
let r2: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
render_until(
&mut tb,
|ui| {
if r1.borrow().is_none() {
*r1.borrow_mut() = ui.poll(&h1);
}
if r2.borrow().is_none() {
*r2.borrow_mut() = ui.poll(&h2);
}
},
|| r1.borrow().is_some() && r2.borrow().is_some(),
)
.await;
assert_eq!(r1.borrow().as_deref(), Some("first"));
assert_eq!(r2.borrow().as_deref(), Some("second"));
}
#[tokio::test]
async fn dropping_handle_cancels_task() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let mut tb = TestBackend::new(40, 3);
tb.set_async_runtime(tokio::runtime::Handle::current());
let completed = Arc::new(AtomicBool::new(false));
let completed_in_task = Arc::clone(&completed);
tb.render(|ui| {
let handle = ui.spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
completed_in_task.store(true, Ordering::SeqCst);
});
drop(handle);
});
tb.render(|_ui| {});
tokio::time::sleep(Duration::from_millis(120)).await;
assert!(
!completed.load(Ordering::SeqCst),
"dropping the handle must cancel the in-flight task before it completes"
);
}
#[tokio::test]
#[should_panic(expected = "requires an active Tokio runtime")]
async fn spawn_without_runtime_panics() {
let mut tb = TestBackend::new(40, 3);
tb.render(|ui| {
let _ = ui.spawn(async { 1u32 });
});
}
#[test]
fn default_registry_has_no_runtime_and_polls_none() {
let mut tasks = AsyncTasks::default();
assert_eq!(tasks.poll::<u32>(999), None);
}