some_executor 0.7.2

A trait for libraries that abstract over any executor
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Verifies that an external crate can drive a [`SpawnedStaticTask`] through its
//! `Future` implementation and still observe the task's value.
//!
//! Before `impl Future for SpawnedStaticTask` existed, downstream executors had to
//! reach for `into_future()`, which drops the `ObserverSender` and makes the
//! observer report `Cancelled` instead of the value.

use some_executor::observer::{
    ExecutorNotified, FinishedObservation, Observation, Observer, ObserverNotified,
};
use some_executor::task::{Configuration, Task};
use some_executor::{
    BoxedStaticObserver, BoxedStaticObserverFuture, DynStaticExecutor, ObjSafeStaticTask,
    SomeStaticExecutor,
};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};

/// The kind of minimal executor a downstream crate writes: it only needs the
/// spawned task, and drives it itself.
#[derive(Clone, Debug)]
struct TestStaticExecutor;

impl SomeStaticExecutor for TestStaticExecutor {
    type ExecutorNotifier = Box<dyn ExecutorNotified>;

    fn spawn_static<F, Notifier: ObserverNotified<F::Output>>(
        &mut self,
        _task: Task<F, Notifier>,
    ) -> impl Observer<Value = F::Output>
    where
        Self: Sized,
        F: Future + 'static,
        F::Output: 'static + Unpin,
    {
        #[allow(unreachable_code)]
        {
            unimplemented!()
                as some_executor::observer::TypedObserver<F::Output, Self::ExecutorNotifier>
        }
    }

    fn spawn_static_async<F, Notifier: ObserverNotified<F::Output>>(
        &mut self,
        _task: Task<F, Notifier>,
    ) -> impl Future<Output = impl Observer<Value = F::Output>>
    where
        Self: Sized,
        F: Future + 'static,
        F::Output: 'static + Unpin,
    {
        #[allow(unreachable_code)]
        #[allow(clippy::async_yields_async)]
        {
            async {
                unimplemented!()
                    as some_executor::observer::TypedObserver<F::Output, Self::ExecutorNotifier>
            }
        }
    }

    fn spawn_static_objsafe(&mut self, _task: ObjSafeStaticTask) -> BoxedStaticObserver {
        unimplemented!()
    }

    fn spawn_static_objsafe_async<'s>(
        &'s mut self,
        _task: ObjSafeStaticTask,
    ) -> BoxedStaticObserverFuture<'s> {
        unimplemented!()
    }

    fn clone_box(&self) -> Box<DynStaticExecutor> {
        Box::new(self.clone())
    }

    fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier> {
        None
    }
}

/// Polls a future to completion by spinning; the futures under test make progress
/// on every poll, so no real wake plumbing is needed.
fn spin_to_completion<F: Future<Output = ()>>(future: F) {
    let mut context = Context::from_waker(Waker::noop());
    let mut pinned = Box::pin(future);
    loop {
        if pinned.as_mut().poll(&mut context).is_ready() {
            return;
        }
    }
}

/// A future that must be polled twice, so we exercise the `Pending` path too.
struct PendOnce {
    polled: bool,
}

impl Future for PendOnce {
    type Output = u32;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> {
        if self.polled {
            Poll::Ready(42)
        } else {
            self.polled = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

#[wasm_lite::wasm_lite_test]
fn spawned_static_task_future_delivers_value() {
    let mut executor = TestStaticExecutor;
    let task = Task::without_notifications(
        "static-future".to_string(),
        Configuration::default(),
        PendOnce { polled: false },
    );

    let (spawned, observer) = task.spawn_static(&mut executor);

    // Drive the task purely through `Future`, the way a downstream executor would.
    spin_to_completion(spawned);

    match observer.observe() {
        Observation::Ready(value) => assert_eq!(value, 42),
        other => panic!("expected the task's value, got {other:?}"),
    }
}

#[wasm_lite::wasm_lite_test]
async fn spawned_static_task_future_delivers_value_to_async_observer() {
    let mut executor = TestStaticExecutor;
    let task = Task::without_notifications(
        "static-future-async".to_string(),
        Configuration::default(),
        async { 7u32 },
    );

    let (spawned, observer) = task.spawn_static(&mut executor);
    spin_to_completion(spawned);

    match observer.await {
        FinishedObservation::Ready(value) => assert_eq!(value, 7),
        other => panic!("expected the task's value, got {other:?}"),
    }
}