use super::{DropNotice, runtime}; use dever_runtime::task::{Error, Timed}; use std::{ future::{Future, poll_fn}, sync::{ Arc, atomic::{AtomicBool, Ordering}, }, task::Poll, time::Duration, }; #[test] fn dropped_stop_wait_keeps_started_work_tracked() { let finished = Arc::new(AtomicBool::new(false)); let observed = finished.clone(); let runtime = runtime(); runtime .run(async |scope| { let (started, ready) = tokio::sync::oneshot::channel(); let (release, blocked) = std::sync::mpsc::channel(); let task = scope .spawn_sync(move || { started.send(()).unwrap(); blocked.recv_timeout(Duration::from_secs(2)).unwrap(); finished.store(true, Ordering::SeqCst); }) .unwrap(); ready.await.unwrap(); { let mut stopped = std::pin::pin!(task.stop()); poll_fn(|context| { assert!(stopped.as_mut().poll(context).is_pending()); Poll::Ready(()) }) .await; } assert!(!observed.load(Ordering::SeqCst)); release.send(()).unwrap(); }) .unwrap(); assert!(observed.load(Ordering::SeqCst)); } #[test] fn nullable_and_unit_results_are_completions_not_expirations() { runtime() .run(async |scope| { let nullable = scope.spawn_leaf(|| async { None:: }).unwrap(); assert!(matches!( nullable.timeout(10000).await.unwrap(), Timed::Ready(None) )); let unit = scope.spawn_leaf(|| async {}).unwrap(); assert!(matches!( unit.timeout(10000).await.unwrap(), Timed::Ready(()) )); let failed = scope .spawn_leaf(|| async { Err::("original") }) .unwrap(); assert!(matches!( failed.timeout(10000).await.unwrap(), Timed::Ready(Err("original")) )); }) .unwrap(); } #[test] fn expiration_and_invalid_duration_wait_for_captured_resources() { for duration in [-1, 0, 1] { let dropped = Arc::new(AtomicBool::new(false)); let observed = dropped.clone(); runtime() .run(async |scope| { let (started, ready) = tokio::sync::oneshot::channel(); let owner = DropNotice(dropped); let pending = scope .spawn_leaf(move || async move { let _owner = owner; started.send(()).unwrap(); std::future::pending::<()>().await; }) .unwrap(); ready.await.unwrap(); let result = pending.timeout(duration).await; if duration > 0 { assert!(matches!(result, Ok(Timed::Expired(None)))); } else { assert!(matches!(result, Err(Error::InvalidTimeout))); } assert!(observed.load(Ordering::SeqCst)); }) .unwrap(); } } #[test] fn expiration_preserves_a_late_failure_from_started_blocking_work() { runtime() .run(async |scope| { let (started, ready) = tokio::sync::oneshot::channel(); let (release, blocked) = std::sync::mpsc::channel(); let pending = scope .spawn_sync(move || { started.send(()).unwrap(); blocked.recv_timeout(Duration::from_secs(2)).unwrap(); Err::("late failure") }) .unwrap(); ready.await.unwrap(); let mut timed = std::pin::pin!(pending.timeout(1)); poll_fn(|context| { assert!(timed.as_mut().poll(context).is_pending()); Poll::Ready(()) }) .await; tokio::time::sleep(Duration::from_millis(10)).await; poll_fn(|context| { assert!(timed.as_mut().poll(context).is_pending()); Poll::Ready(()) }) .await; release.send(()).unwrap(); assert!(matches!( timed.await.unwrap(), Timed::Expired(Some(Err("late failure"))) )); }) .unwrap(); } #[test] fn cancelling_a_timeout_wait_drains_the_nested_task() { let dropped = Arc::new(AtomicBool::new(false)); let observed = dropped.clone(); let (started, ready) = tokio::sync::oneshot::channel(); runtime() .run(async |scope| { let parent = scope .spawn(move |child| async move { let owner = DropNotice(dropped); let pending = child .spawn_leaf(move || async move { let _owner = owner; started.send(()).unwrap(); std::future::pending::<()>().await; }) .unwrap(); pending.timeout(60000).await.unwrap(); }) .unwrap(); ready.await.unwrap(); assert!(parent.stop().await.unwrap().is_none()); assert!(observed.load(Ordering::SeqCst)); }) .unwrap(); }