some_executor 0.7.2

A trait for libraries that abstract over any executor
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

//! The crate's own unit tests.
//!
//! Split out of `lib.rs` so the file a reader opens to learn what this crate
//! offers is the trait surface, not the assertions about it.

use super::*;
use crate::DynExecutor;
use crate::thread_executor::thread_executor;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
fn test_is_objsafe() {
    #[allow(unused)]
    fn is_objsafe(_obj: &DynExecutor) {}
}

/// An executor that overrides `block_on_objsafe` and counts the calls.
///
/// Only `block_on` is exercised, so the spawn half is left unimplemented; the point
/// is to prove which `block_on_objsafe` a caller reaches, not to execute anything.
#[derive(Debug, Clone)]
struct RecordingExecutor(Arc<AtomicUsize>);

impl SomeExecutor for RecordingExecutor {
    type ExecutorNotifier = Infallible;

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

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

    fn spawn_objsafe(&mut self, _task: ObjSafeTask) -> BoxedSendObserver {
        unimplemented!()
    }

    fn spawn_objsafe_async<'s>(&'s mut self, _task: ObjSafeTask) -> BoxedSendObserverFuture<'s> {
        unimplemented!()
    }

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

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

    fn block_on_objsafe(&mut self, future: BoxedBlockOnFuture<'_>) -> Box<dyn Any + Send> {
        self.0.fetch_add(1, Ordering::Relaxed);
        crate::block_on(future)
    }
}

impl SomeExecutorExt for RecordingExecutor {}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn block_on_returns_the_output() {
    let mut executor = crate::last_resort::LastResortExecutor::new();
    assert_eq!(executor.block_on(async { 6 * 7 }), 42);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn block_on_installs_itself_and_then_restores() {
    let before = thread_executor(|e| e.is_some());
    let mut executor = crate::last_resort::LastResortExecutor::new();

    let visible = executor.block_on(async { thread_executor(|e| e.is_some()) });

    assert!(visible, "the executor should be current inside the block");
    assert_eq!(
        thread_executor(|e| e.is_some()),
        before,
        "the previous thread executor should be back afterwards"
    );
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn block_on_restores_the_thread_executor_after_a_panic() {
    let before = thread_executor(|e| e.is_some());
    let result = std::panic::catch_unwind(|| {
        let mut executor = crate::last_resort::LastResortExecutor::new();
        executor.block_on(async { panic!("boom") })
    });
    assert!(result.is_err());
    assert_eq!(thread_executor(|e| e.is_some()), before);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn block_on_through_a_box_reaches_the_underlying_override() {
    // The reason `block_on` lives on `SomeExecutor` rather than `SomeExecutorExt`:
    // `current_executor()` and `global_executor()` hand out `Box<DynExecutor>`, and
    // blocking on one has to reach the real executor's implementation -- otherwise a
    // current-thread backend would silently get the parking default and deadlock.
    let calls = Arc::new(AtomicUsize::new(0));
    let mut boxed: Box<DynExecutor> = Box::new(RecordingExecutor(calls.clone()));

    assert_eq!(boxed.block_on(async { "hello" }), "hello");
    assert_eq!(calls.load(Ordering::Relaxed), 1);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn block_on_objsafe_erases_and_downcasts() {
    let calls = Arc::new(AtomicUsize::new(0));
    let mut executor = RecordingExecutor(calls.clone());
    let erased: BoxedBlockOnFuture<'_> = Box::pin(async { Box::new(7u32) as Box<dyn Any + Send> });

    let out = executor.block_on_objsafe(erased);

    assert_eq!(*out.downcast::<u32>().expect("wrong type"), 7);
    assert_eq!(calls.load(Ordering::Relaxed), 1);
}