test_executors/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2/*!
3This crate provides extremely simple, yet useful, async executors. They are primarily useful for writing unit tests
4without bringing in a full-blown executor such as [tokio](https://tokio.rs).
5
6![logo](../../../art/logo.png)
7
8# Quick Start
9
10```
11use test_executors::{spin_on, sleep_on};
12
13// Run a simple async function
14let result = spin_on(async {
15    42
16});
17assert_eq!(result, 42);
18
19// Run an async function that sleeps
20let result = sleep_on(async {
21    // Your async code here
22    "Hello, async!"
23});
24assert_eq!(result, "Hello, async!");
25```
26
27# Available Executors
28
29The crate provides three main executors:
30
31* [`spin_on`] - Polls a future in a busy loop on the current thread. Best for CPU-bound tasks or when latency is critical.
32* [`sleep_on`] - Polls a future on the current thread, sleeping between polls. Best for I/O-bound tasks to avoid burning CPU.
33* [`spawn_on`] - Spawns a future on a new thread, polling it there. Best for fire-and-forget tasks.
34
35# Platform Support
36
37## Native Platforms
38All executors work as described above on native platforms (Linux, macOS, Windows, etc.).
39
40## WebAssembly Support
41This crate has special support for `wasm32` targets:
42- The `async_test` macro automatically adapts to use `wasm-bindgen-test` on WASM
43- `spawn_local` uses `wasm_bindgen_futures::spawn_local` on WASM targets
44
45# Features
46
47## `async_test` Macro
48The [`async_test`] macro allows you to write async tests that work on both native and WASM targets:
49
50```
51use test_executors::async_test;
52
53#[async_test]
54async fn my_test() {
55    let value = async { 42 }.await;
56    assert_eq!(value, 42);
57}
58```
59
60## Integration with `some_executor`
61This crate implements the [some_executor](https://crates.io/crates/some_executor) trait for all executors,
62allowing them to be used in executor-agnostic code:
63
64```
65use test_executors::aruntime::SpinRuntime;
66use some_executor::SomeExecutor;
67
68let mut runtime = SpinRuntime::new();
69// Use runtime with some_executor traits
70```
71
72# Utilities
73
74The crate also provides utility functions and types:
75- [`poll_once`] and [`poll_once_pin`] - Poll a future exactly once
76- [`spawn_local`] - Platform-aware spawning that works on both native and WASM
77- [`pend_forever::PendForever`] - A future that is always pending (useful for testing)
78
79*/
80
81pub mod aruntime;
82mod noop_waker;
83pub mod pend_forever;
84mod sys;
85
86use crate::noop_waker::new_context;
87use blocking_semaphore::one::Semaphore;
88use std::future::Future;
89use std::pin::Pin;
90use std::sync::Arc;
91use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
92
93pub use test_executors_proc::async_test;
94
95extern crate self as test_executors;
96
97/// Blocks the calling thread until a future is ready, using a spinloop.
98///
99/// This executor continuously polls the future in a tight loop without yielding the thread.
100/// It's the most responsive executor but also the most CPU-intensive.
101///
102/// # When to Use
103/// - When you need minimal latency
104/// - For CPU-bound async tasks
105/// - In tests where you want deterministic behavior
106/// - When the future is expected to complete quickly
107///
108/// # Example
109/// ```
110/// use test_executors::spin_on;
111///
112/// let result = spin_on(async {
113///     // Simulate some async work
114///     let value = async { 21 }.await;
115///     value * 2
116/// });
117/// assert_eq!(result, 42);
118/// ```
119///
120/// # Performance Note
121/// This executor will consume 100% CPU while waiting. For I/O-bound tasks or
122/// long-running futures, consider using [`sleep_on`] instead.
123pub fn spin_on<F: Future>(mut future: F) -> F::Output {
124    //we inherit the parent dlog::context here.
125    let mut context = new_context();
126    let mut future = unsafe { Pin::new_unchecked(&mut future) };
127    loop {
128        if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
129            return val;
130        }
131        std::hint::spin_loop();
132    }
133}
134
135struct SimpleWakeShared {
136    semaphore: Semaphore,
137}
138
139static CONDVAR_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
140    |ctx| {
141        let ctx = unsafe { Arc::from_raw(ctx as *const SimpleWakeShared) };
142        let ctx2 = ctx.clone();
143        std::mem::forget(ctx);
144        RawWaker::new(Arc::into_raw(ctx2) as *const (), &CONDVAR_WAKER_VTABLE)
145    },
146    |ctx| {
147        let ctx = unsafe { Arc::from_raw(ctx as *const SimpleWakeShared) };
148        logwise::trace_sync!("waking");
149        ctx.semaphore.signal_if_needed();
150    },
151    |ctx| {
152        let ctx = unsafe { Arc::from_raw(ctx as *const SimpleWakeShared) };
153        logwise::trace_sync!("waking (by ref)");
154        ctx.semaphore.signal_if_needed();
155        std::mem::forget(ctx);
156    },
157    |ctx| {
158        let ctx = unsafe { Arc::from_raw(ctx as *const SimpleWakeShared) };
159        std::mem::drop(ctx);
160    },
161);
162/// Blocks the calling thread until a future is ready, sleeping between polls.
163///
164/// This executor uses a condition variable to sleep the thread when the future
165/// returns `Poll::Pending`, waking up only when the waker is triggered.
166/// This is more CPU-efficient than [`spin_on`] but may have higher latency.
167///
168/// # When to Use
169/// - For I/O-bound async tasks
170/// - When you want to avoid burning CPU cycles
171/// - For longer-running futures
172/// - In tests that involve actual async I/O or timers
173///
174/// # Example
175/// ```
176/// use test_executors::sleep_on;
177/// use std::future::Future;
178/// use std::pin::Pin;
179/// use std::task::{Context, Poll};
180///
181/// # struct Counter {
182/// #     count: u32,
183/// # }
184/// #
185/// # impl Future for Counter {
186/// #     type Output = u32;
187/// #     
188/// #     fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
189/// #         self.count += 1;
190/// #         if self.count >= 3 {
191/// #             Poll::Ready(self.count)
192/// #         } else {
193/// #             cx.waker().wake_by_ref();
194/// #             Poll::Pending
195/// #         }
196/// #     }
197/// # }
198/// let result = sleep_on(Counter { count: 0 });
199/// assert_eq!(result, 3);
200/// ```
201///
202/// # Implementation Details
203/// The executor will properly handle spurious wakeups and re-poll the future
204/// as needed. The waker implementation uses a semaphore to signal readiness.
205pub fn sleep_on<F: Future>(mut future: F) -> F::Output {
206    //we inherit the parent dlog::context here.
207    let shared = Arc::new(SimpleWakeShared {
208        semaphore: Semaphore::new(false),
209    });
210    let local = shared.clone();
211    let raw_waker = RawWaker::new(Arc::into_raw(shared) as *const (), &CONDVAR_WAKER_VTABLE);
212    let waker = unsafe { Waker::from_raw(raw_waker) };
213    let mut context = Context::from_waker(&waker);
214    /*
215    per docs,
216    any calls to notify_one or notify_all which happen logically
217    after the mutex is unlocked are candidates to wake this thread
218
219    ergo, the lock must be locked when polling.
220     */
221    let mut future = unsafe { Pin::new_unchecked(&mut future) };
222
223    loop {
224        logwise::trace_sync!("polling future");
225        if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
226            logwise::trace_sync!("future is ready");
227            return val;
228        }
229        logwise::trace_sync!("future is not ready");
230        local.semaphore.wait();
231        logwise::trace_sync!("woken");
232    }
233}
234
235/// Spawns a future on a new thread and returns immediately without waiting for completion.
236///
237/// This function creates a new OS thread with the given name and runs the future on that
238/// thread using [`sleep_on`]. The calling thread returns immediately, making this useful
239/// for fire-and-forget tasks.
240///
241/// # Parameters
242/// - `thread_name`: The name to give to the spawned thread (must be a static string)
243/// - `future`: The future to execute on the new thread
244///
245/// # Requirements
246/// - The future must be `Send` because it will be moved to another thread
247/// - The future must be `'static` because the spawned thread may outlive the caller
248///
249/// # Example
250/// ```
251/// use test_executors::spawn_on;
252/// use std::sync::{Arc, Mutex};
253/// use std::time::Duration;
254///
255/// let data = Arc::new(Mutex::new(Vec::new()));
256/// let data_clone = data.clone();
257///
258/// spawn_on("worker", async move {
259///     // Simulate some async work
260///     data_clone.lock().unwrap().push(42);
261/// });
262///
263/// // Give the spawned thread time to complete
264/// std::thread::sleep(Duration::from_millis(50));
265///
266/// // Check the result
267/// assert_eq!(*data.lock().unwrap(), vec![42]);
268/// ```
269///
270/// # Panics
271/// Panics if the thread cannot be spawned (e.g., due to resource exhaustion).
272///
273/// # See Also
274/// - [`spawn_local`] for a platform-aware version that works on WASM
275pub fn spawn_on<F: Future + Send + 'static>(thread_name: &'static str, future: F) {
276    let prior_context = logwise::context::Context::current();
277    let new_context = logwise::context::Context::new_task(Some(prior_context), thread_name);
278    std::thread::Builder::new()
279        .name(thread_name.to_string())
280        .spawn(move || {
281            let pushed_id = new_context.context_id();
282            logwise::context::Context::set_current(new_context);
283
284            sleep_on(future);
285            logwise::context::Context::pop(pushed_id);
286        })
287        .expect("Cant spawn thread");
288}
289
290/// Spawns a future in a platform-appropriate way without waiting for completion.
291///
292/// This function automatically selects the appropriate executor based on the target platform:
293/// - On native platforms (Linux, macOS, Windows, etc.): Uses [`sleep_on`] to run the future
294///   on the current thread
295/// - On `wasm32` targets: Uses `wasm_bindgen_futures::spawn_local` to integrate with the
296///   browser's event loop
297///
298/// # Parameters
299/// - `future`: The future to execute
300/// - `_debug_label`: A label for debugging purposes (used for logging context on WASM)
301///
302/// # Example
303/// ```
304/// use test_executors::spawn_local;
305///
306/// spawn_local(async {
307///     // This will run correctly on both native and WASM platforms
308///     println!("Hello from async!");
309/// }, "example_task");
310/// ```
311///
312/// # Platform Behavior
313/// ## Native Platforms
314/// The future is executed immediately on the current thread using [`sleep_on`].
315/// This blocks until the future completes.
316///
317/// ## WebAssembly
318/// The future is scheduled to run on the browser's event loop and this function
319/// returns immediately. The future will run when the JavaScript runtime is ready.
320///
321/// # Note
322/// Unlike [`spawn_on`], this function does not require the future to be `Send`
323/// since it may run on the current thread.
324pub fn spawn_local<F: Future + 'static>(future: F, _debug_label: &'static str) {
325    #[cfg(not(target_arch = "wasm32"))]
326    sleep_on(future);
327    #[cfg(target_arch = "wasm32")]
328    {
329        let c = logwise::context::Context::current();
330        let new_context = logwise::context::Context::new_task(Some(c), _debug_label);
331        wasm_bindgen_futures::spawn_local(async move {
332            logwise::context::ApplyContext::new(new_context, future).await;
333        });
334    }
335}
336
337/// Polls a pinned future exactly once and returns the result.
338///
339/// This function is useful for testing futures or implementing custom executors.
340/// It creates a no-op waker that does nothing when `wake()` is called.
341///
342/// # Parameters
343/// - `future`: A pinned mutable reference to the future to poll
344///
345/// # Returns
346/// - `Poll::Ready(value)` if the future completed on this poll
347/// - `Poll::Pending` if the future is not yet ready
348///
349/// # Example
350/// ```
351/// use test_executors::{poll_once, pend_forever::PendForever};
352/// use std::task::Poll;
353///
354/// let mut future = PendForever;
355/// let result = poll_once(std::pin::Pin::new(&mut future));
356/// assert_eq!(result, Poll::Pending);
357/// ```
358///
359/// # Testing Example
360/// ```
361/// use test_executors::poll_once;
362/// use std::future::Future;
363/// use std::pin::Pin;
364/// use std::task::{Context, Poll};
365///
366/// struct CounterFuture {
367///     count: u32,
368/// }
369///
370/// impl Future for CounterFuture {
371///     type Output = u32;
372///     
373///     fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
374///         self.count += 1;
375///         if self.count >= 3 {
376///             Poll::Ready(self.count)
377///         } else {
378///             Poll::Pending
379///         }
380///     }
381/// }
382///
383/// let mut future = CounterFuture { count: 0 };
384/// let mut pinned = std::pin::pin!(future);
385///
386/// assert_eq!(poll_once(pinned.as_mut()), Poll::Pending);
387/// assert_eq!(poll_once(pinned.as_mut()), Poll::Pending);
388/// assert_eq!(poll_once(pinned.as_mut()), Poll::Ready(3));
389/// ```
390///
391/// # See Also
392/// - [`poll_once_pin`] for a version that takes ownership and pins the future for you
393pub fn poll_once<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
394    let mut context = new_context();
395    future.poll(&mut context)
396}
397
398/// Polls a future exactly once after pinning it.
399///
400/// This is a convenience function that takes ownership of the future, pins it,
401/// and polls it once. Unlike [`poll_once`], you don't need to pin the future yourself.
402///
403/// # Parameters
404/// - `future`: The future to poll (takes ownership)
405///
406/// # Returns
407/// - `Poll::Ready(value)` if the future completed on this poll
408/// - `Poll::Pending` if the future is not yet ready
409///
410/// # Example
411/// ```
412/// use test_executors::{poll_once_pin, pend_forever::PendForever};
413/// use std::task::Poll;
414///
415/// let future = PendForever;
416/// let result = poll_once_pin(future);
417/// assert_eq!(result, Poll::Pending);
418/// ```
419///
420/// # Comparison with `poll_once`
421/// ```
422/// use test_executors::{poll_once, poll_once_pin};
423/// use std::task::Poll;
424///
425/// // Using poll_once_pin (takes ownership)
426/// let future1 = async { 42 };
427/// assert_eq!(poll_once_pin(future1), Poll::Ready(42));
428///
429/// // Using poll_once (borrows)
430/// let mut future2 = async { 42 };
431/// let mut pinned = std::pin::pin!(future2);
432/// assert_eq!(poll_once(pinned.as_mut()), Poll::Ready(42));
433/// ```
434///
435/// # Limitations
436/// Since this function takes ownership of the future, you cannot poll it again
437/// after calling this function. If you need to poll a future multiple times,
438/// use [`poll_once`] instead.
439pub fn poll_once_pin<F: Future>(future: F) -> Poll<F::Output> {
440    let mut context = new_context();
441    let pinned = std::pin::pin!(future);
442    pinned.poll(&mut context)
443}
444
445#[cfg(test)]
446mod tests {
447    use crate::pend_forever::PendForever;
448    use std::future::Future;
449    use std::task::Poll;
450
451    #[cfg(target_arch = "wasm32")]
452    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
453
454    #[test]
455    fn test_sleep_reentrant() {
456        struct F(bool);
457        impl Future for F {
458            type Output = ();
459            fn poll(
460                mut self: std::pin::Pin<&mut Self>,
461                cx: &mut std::task::Context<'_>,
462            ) -> std::task::Poll<Self::Output> {
463                if !self.0 {
464                    self.0 = true;
465                    cx.waker().wake_by_ref();
466                    Poll::Pending
467                } else {
468                    Poll::Ready(())
469                }
470            }
471        }
472        let f = F(false);
473        super::sleep_on(f);
474    }
475
476    #[crate::async_test]
477    async fn hello_world() {
478        let f = async { "hello world" };
479        assert_eq!(f.await, "hello world");
480    }
481
482    #[test]
483    fn poll_once_test() {
484        let f = PendForever;
485        let mut pinned = std::pin::pin!(f);
486        let result = super::poll_once(pinned.as_mut());
487        assert_eq!(result, Poll::Pending);
488
489        let result2 = super::poll_once(pinned.as_mut());
490        assert_eq!(result2, Poll::Pending);
491    }
492}