From 4fa4a7232891d4232f0c22ab97f21d33c2a452e2 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 16 Jul 2021 09:31:37 -0700 Subject: [PATCH] wiggle: expand test suite sync test: show the dummy executor will trap (rather than panic) when a future inside it pends. async test: show that the executor is hooked up to a future that pends for a trivial amount of time. this adds tokio to the dev-dependencies of wiggle, it shouldn't end up increasing the build burden for the project as a whole since its already a dev-dependency. --- Cargo.lock | 1 + crates/wiggle/Cargo.toml | 1 + crates/wiggle/src/lib.rs | 7 ++- crates/wiggle/tests/wasmtime_async.rs | 72 +++++++++------------------ crates/wiggle/tests/wasmtime_sync.rs | 44 ++++++++++++++++ 5 files changed, 74 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a20b49686..01f374cc11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3923,6 +3923,7 @@ dependencies = [ "bitflags", "proptest", "thiserror", + "tokio", "tracing", "wasmtime", "wiggle-macro", diff --git a/crates/wiggle/Cargo.toml b/crates/wiggle/Cargo.toml index ae5633e90f..8a8d6ae46c 100644 --- a/crates/wiggle/Cargo.toml +++ b/crates/wiggle/Cargo.toml @@ -27,6 +27,7 @@ maintenance = { status = "actively-developed" } wiggle-test = { path = "test-helpers" } anyhow = "1" proptest = "1.0.0" +tokio = { version = "1", features = ["rt-multi-thread","time", "macros"] } [[test]] name = "wasmtime_async" diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs index 83e6b83b5f..87847d4863 100644 --- a/crates/wiggle/src/lib.rs +++ b/crates/wiggle/src/lib.rs @@ -933,7 +933,10 @@ impl From for Trap { } } -pub fn run_in_dummy_executor(future: F) -> Result { +#[cfg(feature = "wasmtime")] +pub fn run_in_dummy_executor( + future: F, +) -> Result { use std::pin::Pin; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; @@ -943,7 +946,7 @@ pub fn run_in_dummy_executor(future: F) -> Result return Ok(val), Poll::Pending => - return Err(Trap::String("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store".to_owned())) + return Err(wasmtime_crate::Trap::new("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store")) } fn dummy_waker() -> Waker { diff --git a/crates/wiggle/tests/wasmtime_async.rs b/crates/wiggle/tests/wasmtime_async.rs index 471a8fd3fd..51a0e4aa82 100644 --- a/crates/wiggle/tests/wasmtime_async.rs +++ b/crates/wiggle/tests/wasmtime_async.rs @@ -1,6 +1,3 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; use wasmtime::{Config, Engine, Linker, Module, Store}; wiggle::from_witx!({ @@ -27,23 +24,30 @@ impl atoms::Atoms for Ctx { &mut self, an_int: u32, ) -> Result { + // Do something inside this test that is Pending for a trivial amount of time, + // to make sure we are hooked up to the tokio executor properly. + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; Ok((an_int as f32) * 2.0) } } -#[test] -fn test_sync_host_func() { +#[tokio::test] +async fn test_sync_host_func() { let mut store = async_store(); let mut linker = Linker::new(store.engine()); atoms::add_to_linker(&mut linker, |cx| cx).unwrap(); let shim_mod = shim_module(linker.engine()); - let shim_inst = run(linker.instantiate_async(&mut store, &shim_mod)).unwrap(); + let shim_inst = linker + .instantiate_async(&mut store, &shim_mod) + .await + .unwrap(); - let results = run(shim_inst + let results = shim_inst .get_func(&mut store, "int_float_args_shim") .unwrap() - .call_async(&mut store, &[0i32.into(), 123.45f32.into()])) - .unwrap(); + .call_async(&mut store, &[0i32.into(), 123.45f32.into()]) + .await + .unwrap(); assert_eq!(results.len(), 1, "one return value"); assert_eq!( @@ -53,23 +57,27 @@ fn test_sync_host_func() { ); } -#[test] -fn test_async_host_func() { +#[tokio::test] +async fn test_async_host_func() { let mut store = async_store(); let mut linker = Linker::new(store.engine()); atoms::add_to_linker(&mut linker, |cx| cx).unwrap(); let shim_mod = shim_module(linker.engine()); - let shim_inst = run(linker.instantiate_async(&mut store, &shim_mod)).unwrap(); + let shim_inst = linker + .instantiate_async(&mut store, &shim_mod) + .await + .unwrap(); let input: i32 = 123; let result_location: i32 = 0; - let results = run(shim_inst + let results = shim_inst .get_func(&mut store, "double_int_return_float_shim") .unwrap() - .call_async(&mut store, &[input.into(), result_location.into()])) - .unwrap(); + .call_async(&mut store, &[input.into(), result_location.into()]) + .await + .unwrap(); assert_eq!(results.len(), 1, "one return value"); assert_eq!( @@ -87,40 +95,6 @@ fn test_async_host_func() { assert_eq!((input * 2) as f32, result); } -fn run(future: F) -> F::Output { - let mut f = Pin::from(Box::new(future)); - let waker = dummy_waker(); - let mut cx = Context::from_waker(&waker); - loop { - match f.as_mut().poll(&mut cx) { - Poll::Ready(val) => break val, - Poll::Pending => {} - } - } -} - -fn dummy_waker() -> Waker { - return unsafe { Waker::from_raw(clone(5 as *const _)) }; - - unsafe fn clone(ptr: *const ()) -> RawWaker { - assert_eq!(ptr as usize, 5); - const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); - RawWaker::new(ptr, &VTABLE) - } - - unsafe fn wake(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } - - unsafe fn wake_by_ref(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } - - unsafe fn drop(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } -} - fn async_store() -> Store { Store::new( &Engine::new(Config::new().async_support(true)).unwrap(), diff --git a/crates/wiggle/tests/wasmtime_sync.rs b/crates/wiggle/tests/wasmtime_sync.rs index d961870668..8332b19a55 100644 --- a/crates/wiggle/tests/wasmtime_sync.rs +++ b/crates/wiggle/tests/wasmtime_sync.rs @@ -14,6 +14,8 @@ impl wiggle::GuestErrorType for types::Errno { } } +const TRIGGER_PENDING: u32 = 0; + #[wiggle::async_trait] impl atoms::Atoms for Ctx { fn int_float_args(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { @@ -24,6 +26,22 @@ impl atoms::Atoms for Ctx { &mut self, an_int: u32, ) -> Result { + if an_int == TRIGGER_PENDING { + // Define a Future that is pending forever. This is `futures::future::pending()` + // without incurring the dep. + use std::future::Future; + use std::pin::Pin; + use std::task::{Context, Poll}; + struct Pending; + impl Future for Pending { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Pending + } + } + // This await will pend, which should cause the dummy executor to Trap. + Pending.await; + } Ok((an_int as f32) * 2.0) } } @@ -86,6 +104,32 @@ fn test_async_host_func() { assert_eq!((input * 2) as f32, result); } +#[test] +fn test_async_host_func_pending() { + let engine = Engine::default(); + let mut linker = Linker::new(&engine); + atoms::add_to_linker(&mut linker, |cx| cx).unwrap(); + let mut store = store(&engine); + + let shim_mod = shim_module(&engine); + let shim_inst = linker.instantiate(&mut store, &shim_mod).unwrap(); + + let result_location: i32 = 0; + + // This input triggers the host func pending forever + let input: i32 = TRIGGER_PENDING as i32; + let trap = shim_inst + .get_func(&mut store, "double_int_return_float_shim") + .unwrap() + .call(&mut store, &[input.into(), result_location.into()]) + .unwrap_err(); + assert!( + format!("{}", trap).contains("Cannot wait on pending future"), + "expected get a pending future Trap from dummy executor, got: {}", + trap + ); +} + fn store(engine: &Engine) -> Store { Store::new(engine, Ctx) }