Files
tokio/tokio-test/src/task.rs
T

283 lines
7.0 KiB
Rust
Raw Normal View History

2022-10-28 13:55:13 -04:00
//! Futures task based helpers to easily test futures and manually written futures.
//!
//! The [`Spawn`] type is used as a mock task harness that allows you to poll futures
//! without needing to setup pinning or context. Any future can be polled but if the
//! future requires the tokio async context you will need to ensure that you poll the
//! [`Spawn`] within a tokio context, this means that as long as you are inside the
//! runtime it will work and you can poll it via [`Spawn`].
//!
//! [`Spawn`] also supports [`Stream`] to call `poll_next` without pinning
//! or context.
//!
//! In addition to circumventing the need for pinning and context, [`Spawn`] also tracks
//! the amount of times the future/task was woken. This can be useful to track if some
//! leaf future notified the root task correctly.
//!
//! # Example
//!
//! ```
//! use tokio_test::task;
//!
//! let fut = async {};
//!
//! let mut task = task::spawn(fut);
//!
//! assert!(task.poll().is_ready(), "Task was not ready!");
//! ```
2019-04-23 23:17:57 -04:00
#![allow(clippy::mutex_atomic)]
2019-06-24 12:34:30 -07:00
use std::future::Future;
use std::mem;
use std::ops;
use std::pin::Pin;
2019-04-23 23:17:57 -04:00
use std::sync::{Arc, Condvar, Mutex};
2019-06-24 12:34:30 -07:00
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
2019-04-23 23:17:57 -04:00
2020-12-15 23:24:38 -05:00
use tokio_stream::Stream;
2019-12-18 22:57:22 +03:00
2022-10-28 13:55:13 -04:00
/// Spawn a future into a [`Spawn`] which wraps the future in a mocked executor.
///
/// This can be used to spawn a [`Future`] or a [`Stream`].
///
/// For more information, check the module docs.
pub fn spawn<T>(task: T) -> Spawn<T> {
Spawn {
task: MockTask::new(),
future: Box::pin(task),
}
2019-04-23 23:17:57 -04:00
}
2022-10-28 13:55:13 -04:00
/// Future spawned on a mock task that can be used to poll the future or stream
/// without needing pinning or context types.
#[derive(Debug)]
pub struct Spawn<T> {
task: MockTask,
future: Pin<Box<T>>,
}
#[derive(Debug, Clone)]
struct MockTask {
waker: Arc<ThreadWaker>,
}
2019-04-23 23:17:57 -04:00
#[derive(Debug)]
2019-06-24 12:34:30 -07:00
struct ThreadWaker {
state: Mutex<usize>,
2019-04-23 23:17:57 -04:00
condvar: Condvar,
}
const IDLE: usize = 0;
2019-06-24 12:34:30 -07:00
const WAKE: usize = 1;
2019-04-23 23:17:57 -04:00
const SLEEP: usize = 2;
impl<T> Spawn<T> {
2020-01-24 20:31:13 +03:00
/// Consumes `self` returning the inner value
pub fn into_inner(self) -> T
where
T: Unpin,
{
*Pin::into_inner(self.future)
}
/// Returns `true` if the inner future has received a wake notification
/// since the last call to `enter`.
pub fn is_woken(&self) -> bool {
self.task.is_woken()
}
/// Returns the number of references to the task waker
///
/// The task itself holds a reference. The return value will never be zero.
pub fn waker_ref_count(&self) -> usize {
self.task.waker_ref_count()
}
/// Enter the task context
pub fn enter<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Context<'_>, Pin<&mut T>) -> R,
{
let fut = self.future.as_mut();
self.task.enter(|cx| f(cx, fut))
}
}
impl<T: Unpin> ops::Deref for Spawn<T> {
type Target = T;
fn deref(&self) -> &T {
&self.future
}
}
impl<T: Unpin> ops::DerefMut for Spawn<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.future
}
}
impl<T: Future> Spawn<T> {
2022-10-28 13:55:13 -04:00
/// If `T` is a [`Future`] then poll it. This will handle pinning and the context
/// type for the future.
pub fn poll(&mut self) -> Poll<T::Output> {
let fut = self.future.as_mut();
self.task.enter(|cx| fut.poll(cx))
}
}
impl<T: Stream> Spawn<T> {
2022-10-28 13:55:13 -04:00
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
/// type for the stream.
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
let stream = self.future.as_mut();
self.task.enter(|cx| stream.poll_next(cx))
}
}
impl<T: Future> Future for Spawn<T> {
type Output = T::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.future.as_mut().poll(cx)
}
}
impl<T: Stream> Stream for Spawn<T> {
type Item = T::Item;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.future.as_mut().poll_next(cx)
}
}
2019-04-23 23:17:57 -04:00
impl MockTask {
2020-01-24 20:31:13 +03:00
/// Creates new mock task
fn new() -> Self {
2019-04-23 23:17:57 -04:00
MockTask {
2019-06-24 12:34:30 -07:00
waker: Arc::new(ThreadWaker::new()),
2019-04-23 23:17:57 -04:00
}
}
2020-01-24 20:31:13 +03:00
/// Runs a closure from the context of the task.
2019-04-23 23:17:57 -04:00
///
2019-06-24 12:34:30 -07:00
/// Any wake notifications resulting from the execution of the closure are
2019-04-23 23:17:57 -04:00
/// tracked.
fn enter<F, R>(&mut self, f: F) -> R
2019-04-23 23:17:57 -04:00
where
2019-06-24 12:34:30 -07:00
F: FnOnce(&mut Context<'_>) -> R,
2019-04-23 23:17:57 -04:00
{
2019-06-24 12:34:30 -07:00
self.waker.clear();
let waker = self.waker();
let mut cx = Context::from_waker(&waker);
2019-04-23 23:17:57 -04:00
2019-06-24 12:34:30 -07:00
f(&mut cx)
2019-04-23 23:17:57 -04:00
}
2019-06-24 12:34:30 -07:00
/// Returns `true` if the inner future has received a wake notification
2019-04-23 23:17:57 -04:00
/// since the last call to `enter`.
fn is_woken(&self) -> bool {
2019-06-24 12:34:30 -07:00
self.waker.is_woken()
2019-04-23 23:17:57 -04:00
}
2019-06-24 12:34:30 -07:00
/// Returns the number of references to the task waker
2019-04-23 23:17:57 -04:00
///
/// The task itself holds a reference. The return value will never be zero.
fn waker_ref_count(&self) -> usize {
2019-06-24 12:34:30 -07:00
Arc::strong_count(&self.waker)
}
fn waker(&self) -> Waker {
unsafe {
let raw = to_raw(self.waker.clone());
Waker::from_raw(raw)
}
2019-04-23 23:17:57 -04:00
}
}
2019-07-26 03:47:14 +09:00
impl Default for MockTask {
fn default() -> Self {
Self::new()
}
}
2019-06-24 12:34:30 -07:00
impl ThreadWaker {
2019-04-23 23:17:57 -04:00
fn new() -> Self {
2019-06-24 12:34:30 -07:00
ThreadWaker {
state: Mutex::new(IDLE),
2019-04-23 23:17:57 -04:00
condvar: Condvar::new(),
}
}
2021-07-01 02:06:56 +09:00
/// Clears any previously received wakes, avoiding potential spurious
2019-06-24 12:34:30 -07:00
/// wake notifications. This should only be called immediately before running the
2019-04-23 23:17:57 -04:00
/// task.
fn clear(&self) {
2019-06-24 12:34:30 -07:00
*self.state.lock().unwrap() = IDLE;
2019-04-23 23:17:57 -04:00
}
2019-06-24 12:34:30 -07:00
fn is_woken(&self) -> bool {
match *self.state.lock().unwrap() {
2019-04-23 23:17:57 -04:00
IDLE => false,
2019-06-24 12:34:30 -07:00
WAKE => true,
2019-04-23 23:17:57 -04:00
_ => unreachable!(),
}
}
2019-06-24 12:34:30 -07:00
fn wake(&self) {
2020-01-24 20:31:13 +03:00
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
2019-06-24 12:34:30 -07:00
let mut state = self.state.lock().unwrap();
let prev = *state;
if prev == WAKE {
return;
2019-04-23 23:17:57 -04:00
}
2019-06-24 12:34:30 -07:00
*state = WAKE;
2019-04-23 23:17:57 -04:00
2019-06-24 12:34:30 -07:00
if prev == IDLE {
return;
2019-04-23 23:17:57 -04:00
}
2019-06-24 12:34:30 -07:00
// The other half is sleeping, so we wake it up.
assert_eq!(prev, SLEEP);
2019-04-23 23:17:57 -04:00
self.condvar.notify_one();
}
}
2019-06-24 12:34:30 -07:00
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
2019-06-24 12:34:30 -07:00
unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
}
unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
Arc::from_raw(raw as *const ThreadWaker)
}
unsafe fn clone(raw: *const ()) -> RawWaker {
let waker = from_raw(raw);
// Increment the ref count
mem::forget(waker.clone());
to_raw(waker)
}
unsafe fn wake(raw: *const ()) {
let waker = from_raw(raw);
waker.wake();
}
unsafe fn wake_by_ref(raw: *const ()) {
let waker = from_raw(raw);
waker.wake();
// We don't actually own a reference to the unparker
mem::forget(waker);
}
unsafe fn drop_waker(raw: *const ()) {
2019-06-24 12:34:30 -07:00
let _ = from_raw(raw);
}