yash-executor 1.0.1

single-threaded concurrent task executor
Documentation
// This file is part of yash, an extended POSIX shell.
// Copyright (C) 2024 WATANABE Yuki

use std::cell::Cell;
use std::pin::pin;
use std::task::Context;
use std::task::Waker;
use yash_executor::forwarder::TryReceiveError;
use yash_executor::{Executor, Spawner};

mod spawn_pinned {
    use super::*;

    #[test]
    fn dead_spawner_does_nothing() {
        let run = Cell::new(false);
        let spawner = Spawner::dead();

        let result = unsafe { spawner.spawn_pinned(Box::pin(async { run.set(true) })) };
        assert!(!run.get());

        // Make sure the returned future is the same as the one passed in
        let mut future = result.unwrap_err().0;
        let mut context = Context::from_waker(Waker::noop());
        let poll = future.as_mut().poll(&mut context);
        assert!(poll.is_ready());
        assert!(run.get());
    }

    #[test]
    fn spawner_does_nothing_after_executor_was_dropped() {
        let run = Cell::new(false);
        let spawner = Executor::new().spawner();

        let result = unsafe { spawner.spawn_pinned(Box::pin(async { run.set(true) })) };
        assert!(!run.get());

        // Make sure the returned future is the same as the one passed in
        let mut future = result.unwrap_err().0;
        let mut context = Context::from_waker(Waker::noop());
        let poll = future.as_mut().poll(&mut context);
        assert!(poll.is_ready());
        assert!(run.get());
    }

    #[test]
    fn spawning_task_outside_task() {
        let run = Cell::new(false);
        let executor = Executor::new();
        let spawner = executor.spawner();

        let result = unsafe { spawner.spawn_pinned(Box::pin(async { run.set(true) })) };
        assert!(result.is_ok());
        assert!(!run.get());
        assert_eq!(executor.wake_count(), 1);

        executor.step();
        assert!(run.get());
    }

    #[test]
    fn spawning_task_inside_task() {
        let executor = Executor::new();
        let spawner = executor.spawner();
        unsafe {
            executor.spawn_pinned(Box::pin(async move {
                let result = spawner.spawn_pinned(Box::pin(async {}));
                assert!(result.is_ok());
            }));
        }

        assert_eq!(executor.run_until_stalled(), 2);
    }
}

mod spawn {
    use super::*;

    #[test]
    fn dead_spawner_does_nothing() {
        let run = Cell::new(false);
        let spawner = Spawner::dead();

        let result = unsafe { spawner.spawn(async { run.set(true) }) };
        assert!(!run.get());

        // Make sure the returned future is the same as the one passed in
        let mut future = pin!(result.unwrap_err().0);
        let mut context = Context::from_waker(Waker::noop());
        let poll = future.as_mut().poll(&mut context);
        assert!(poll.is_ready());
        assert!(run.get());
    }

    #[test]
    fn spawner_does_nothing_after_executor_was_dropped() {
        let run = Cell::new(false);
        let spawner = Executor::new().spawner();

        let result = unsafe { spawner.spawn(async { run.set(true) }) };
        assert!(!run.get());

        // Make sure the returned future is the same as the one passed in
        let mut future = pin!(result.unwrap_err().0);
        let mut context = Context::from_waker(Waker::noop());
        let poll = future.as_mut().poll(&mut context);
        assert!(poll.is_ready());
        assert!(run.get());
    }

    #[test]
    fn spawning_task_outside_task() {
        let executor = Executor::new();
        let spawner = executor.spawner();

        let receiver = unsafe { spawner.spawn(async { 123 }) }.unwrap();
        assert_eq!(executor.wake_count(), 1);
        assert_eq!(receiver.try_receive(), Err(TryReceiveError::NotSent));

        executor.step();
        assert_eq!(receiver.try_receive(), Ok(123));
    }

    #[test]
    fn spawning_task_inside_task() {
        let executor = Executor::new();
        let spawner = executor.spawner();
        unsafe {
            executor.spawn(async move {
                let result = spawner.spawn(async { 123 }).unwrap().await;
                assert_eq!(result, 123);
            });
        }

        assert_eq!(executor.run_until_stalled(), 2);
    }
}