diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 4cd5bcbc12..7c4d9c3ad4 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -14,7 +14,7 @@ cfg-if = "1.0" wasi-common = { path = "../wasi-common", version = "0.26.0" } wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0" } wasmtime = { path = "../wasmtime", version = "0.26.0" } -wasmtime-wasi = { path = "../wasi", version = "0.26.0" } +wasmtime-wasi = { path = "../wasi", version = "0.26.0", features = ["tokio"] } target-lexicon = "0.12.0" pretty_env_logger = "0.4.0" tempfile = "3.1.0" @@ -22,6 +22,7 @@ os_pipe = "0.9" anyhow = "1.0.19" wat = "1.0.37" cap-std = "0.13" +tokio = { version = "1.5.0", features = ["rt-multi-thread"] } [features] test_programs = [] diff --git a/crates/test-programs/build.rs b/crates/test-programs/build.rs index abef0738f0..6894f66a81 100644 --- a/crates/test-programs/build.rs +++ b/crates/test-programs/build.rs @@ -40,7 +40,9 @@ mod wasi_tests { File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file"); build_tests("wasi-tests", &out_dir).expect("building tests"); test_directory(&mut out, "wasi-cap-std-sync", "cap_std_sync", &out_dir) - .expect("generating tests"); + .expect("generating wasi-cap-std-sync tests"); + test_directory(&mut out, "wasi-tokio", "tokio", &out_dir) + .expect("generating wasi-tokio tests"); } fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> { @@ -173,6 +175,7 @@ mod wasi_tests { match testsuite { "wasi-cap-std-sync" => cap_std_sync_ignore(name), "wasi-virtfs" => virtfs_ignore(name), + "wasi-tokio" => tokio_ignore(name), _ => panic!("unknown test suite: {}", testsuite), } } @@ -200,6 +203,10 @@ mod wasi_tests { .contains(&name) } + /// Tokio should support the same things as cap_std_sync + fn tokio_ignore(name: &str) -> bool { + cap_std_sync_ignore(name) + } /// Virtfs barely works at all and is not suitable for any purpose fn virtfs_ignore(name: &str) -> bool { [ @@ -260,7 +267,7 @@ mod wasi_tests { /// Mark tests which require inheriting parent process stdio fn inherit_stdio(testsuite: &str, name: &str) -> bool { match testsuite { - "wasi-cap-std-sync" => match name { + "wasi-cap-std-sync" | "wasi-tokio" => match name { "poll_oneoff_stdio" => true, _ => false, }, diff --git a/crates/test-programs/tests/wasm_tests/runtime/mod.rs b/crates/test-programs/tests/wasm_tests/runtime/mod.rs index 035b1e83ea..caabcf7b30 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/mod.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/mod.rs @@ -1 +1,2 @@ pub mod cap_std_sync; +pub mod tokio; diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs new file mode 100644 index 0000000000..11cf28336c --- /dev/null +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -0,0 +1,137 @@ +use anyhow::Context; +use std::path::Path; +use wasi_common::pipe::WritePipe; +use wasmtime::{Config, Engine, Linker, Module, Store}; +use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder}; + +pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { + let stdout = WritePipe::new_in_memory(); + let stdout_ = stdout.clone(); + let stderr = WritePipe::new_in_memory(); + let stderr_ = stderr.clone(); + + let r = tokio::runtime::Runtime::new() + .expect("create runtime") + .block_on(async move { + let mut config = Config::new(); + config.async_support(true); + config.consume_fuel(true); + Wasi::add_to_config(&mut config); + let engine = Engine::new(&config)?; + let store = Store::new(&engine); + + // Create our wasi context. + // Additionally register any preopened directories if we have them. + let mut builder = WasiCtxBuilder::new(); + + builder = builder + .arg(bin_name)? + .arg(".")? + .stdout(Box::new(stdout_)) + .stderr(Box::new(stderr_)); + + if let Some(workspace) = workspace { + println!("preopen: {:?}", workspace); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; + builder = builder.preopened_dir(preopen_dir, ".")?; + } + + #[cfg(windows)] + { + builder = builder + .env("ERRNO_MODE_WINDOWS", "1")? + .env("NO_DANGLING_FILESYSTEM", "1")? + .env("NO_FD_ALLOCATE", "1")? + .env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")? + } + #[cfg(all(unix, not(target_os = "macos")))] + { + builder = builder.env("ERRNO_MODE_UNIX", "1")?; + } + #[cfg(target_os = "macos")] + { + builder = builder + .env("ERRNO_MODE_MACOS", "1")? + .env("NO_FD_ALLOCATE", "1")?; + } + + // cap-std-sync does not yet support the sync family of fdflags + builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?; + + store.out_of_fuel_async_yield(u32::MAX, 10000); + Wasi::set_context(&store, builder.build()?) + .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; + + let module = + Module::new(store.engine(), &data).context("failed to create wasm module")?; + let linker = Linker::new(&store); + let instance = linker.instantiate_async(&module).await?; + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call_async(()).await.map_err(anyhow::Error::from) + }); + + match r { + Ok(()) => Ok(()), + Err(trap) => { + let stdout = stdout + .try_into_inner() + .expect("sole ref to stdout") + .into_inner(); + if !stdout.is_empty() { + println!("guest stdout:\n{}\n===", String::from_utf8_lossy(&stdout)); + } + let stderr = stderr + .try_into_inner() + .expect("sole ref to stderr") + .into_inner(); + if !stderr.is_empty() { + println!("guest stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); + } + Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))) + } + } +} + +pub fn instantiate_inherit_stdio( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, +) -> anyhow::Result<()> { + let r = tokio::runtime::Runtime::new() + .expect("create runtime") + .block_on(async { + let mut config = Config::new(); + config.async_support(true); + config.consume_fuel(true); + Wasi::add_to_config(&mut config); + let engine = Engine::new(&config)?; + let store = Store::new(&engine); + + // Create our wasi context. + // Additionally register any preopened directories if we have them. + let mut builder = WasiCtxBuilder::new(); + + builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio(); + + if let Some(workspace) = workspace { + println!("preopen: {:?}", workspace); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; + builder = builder.preopened_dir(preopen_dir, ".")?; + } + + Wasi::set_context(&store, builder.build()?) + .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; + + let module = + Module::new(store.engine(), &data).context("failed to create wasm module")?; + let linker = Linker::new(&store); + let instance = linker.instantiate_async(&module).await?; + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call_async(()).await.map_err(anyhow::Error::from) + }); + + match r { + Ok(()) => Ok(()), + Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))), + } +}