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.
This commit is contained in:
Pat Hickey
2021-07-16 09:31:37 -07:00
parent 6f07c76c84
commit 4fa4a72328
5 changed files with 74 additions and 51 deletions

1
Cargo.lock generated
View File

@@ -3923,6 +3923,7 @@ dependencies = [
"bitflags",
"proptest",
"thiserror",
"tokio",
"tracing",
"wasmtime",
"wiggle-macro",

View File

@@ -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"

View File

@@ -933,7 +933,10 @@ impl From<GuestError> for Trap {
}
}
pub fn run_in_dummy_executor<F: std::future::Future>(future: F) -> Result<F::Output, Trap> {
#[cfg(feature = "wasmtime")]
pub fn run_in_dummy_executor<F: std::future::Future>(
future: F,
) -> Result<F::Output, wasmtime_crate::Trap> {
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
@@ -943,7 +946,7 @@ pub fn run_in_dummy_executor<F: std::future::Future>(future: F) -> Result<F::Out
match f.as_mut().poll(&mut cx) {
Poll::Ready(val) => 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 {

View File

@@ -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<types::AliasToFloat, types::Errno> {
// 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<F: Future>(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<Ctx> {
Store::new(
&Engine::new(Config::new().async_support(true)).unwrap(),

View File

@@ -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<types::AliasToFloat, types::Errno> {
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<Self::Output> {
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<Ctx> {
Store::new(engine, Ctx)
}