diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index aef7d46ec24b..f64082e77370 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -44,7 +44,7 @@ cap-std = { workspace = true } cap-rand = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -wasmtime-wasi-http = { workspace = true } +wasmtime-wasi-http = { workspace = true, features = ["sync"] } [features] test_programs = [] diff --git a/crates/test-programs/src/http_server.rs b/crates/test-programs/src/http_server.rs index b4e2a324b9e9..936dc9cef701 100644 --- a/crates/test-programs/src/http_server.rs +++ b/crates/test-programs/src/http_server.rs @@ -175,6 +175,29 @@ pub async fn setup_http1(f: impl Future>) -> anyhow: result } +pub fn setup_http1_sync(f: F) -> anyhow::Result<()> +where + F: FnOnce() -> anyhow::Result<()> + Send + 'static, +{ + tracing::debug!("preparing http1 server synchronously"); + let server = ServerHttp1::new(); + + let (tx, rx) = mpsc::channel::>(); + tracing::debug!("running inner function in a dedicated thread"); + std::thread::spawn(move || { + tx.send(f()) + .expect("value sent from http1 server dedicated thread"); + }); + let result = rx + .recv() + .expect("value received from request dedicated thread"); + + if let Err(err) = server.shutdown() { + tracing::error!("[host/server] failure {:?}", err); + } + result +} + pub async fn setup_http2(f: impl Future>) -> anyhow::Result<()> { tracing::debug!("preparing http2 server asynchronously"); let server = ServerHttp2::new(); @@ -187,3 +210,26 @@ pub async fn setup_http2(f: impl Future>) -> anyhow: } result } + +pub fn setup_http2_sync(f: F) -> anyhow::Result<()> +where + F: FnOnce() -> anyhow::Result<()> + Send + 'static, +{ + tracing::debug!("preparing http2 server synchronously"); + let server = ServerHttp2::new(); + + let (tx, rx) = mpsc::channel::>(); + tracing::debug!("running inner function in a dedicated thread"); + std::thread::spawn(move || { + tx.send(f()) + .expect("value sent from http2 server dedicated thread"); + }); + let result = rx + .recv() + .expect("value received from request dedicated thread"); + + if let Err(err) = server.shutdown() { + tracing::error!("[host/server] failure {:?}", err); + } + result +} diff --git a/crates/test-programs/tests/wasi-http-components-sync.rs b/crates/test-programs/tests/wasi-http-components-sync.rs new file mode 100644 index 000000000000..2ae6885e96e0 --- /dev/null +++ b/crates/test-programs/tests/wasi-http-components-sync.rs @@ -0,0 +1,160 @@ +#![cfg(all(feature = "test_programs", not(skip_wasi_http_tests)))] +use wasmtime::{ + component::{Component, Linker}, + Config, Engine, Store, +}; +use wasmtime_wasi::preview2::{ + command::sync::{add_to_linker, Command}, + pipe::MemoryOutputPipe, + IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, +}; +use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; + +use test_programs::http_server::{setup_http1_sync, setup_http2_sync}; + +lazy_static::lazy_static! { + static ref ENGINE: Engine = { + let mut config = Config::new(); + config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); + config.wasm_component_model(true); + let engine = Engine::new(&config).unwrap(); + engine + }; +} +// uses ENGINE, creates a fn get_module(&str) -> Module +include!(concat!(env!("OUT_DIR"), "/wasi_http_tests_components.rs")); + +struct Ctx { + table: Table, + wasi: WasiCtx, + http: WasiHttpCtx, +} + +impl WasiView for Ctx { + fn table(&self) -> &Table { + &self.table + } + fn table_mut(&mut self) -> &mut Table { + &mut self.table + } + fn ctx(&self) -> &WasiCtx { + &self.wasi + } + fn ctx_mut(&mut self) -> &mut WasiCtx { + &mut self.wasi + } +} + +impl WasiHttpView for Ctx { + fn http_ctx(&self) -> &WasiHttpCtx { + &self.http + } + fn http_ctx_mut(&mut self) -> &mut WasiHttpCtx { + &mut self.http + } +} + +fn instantiate_component( + component: Component, + ctx: Ctx, +) -> Result<(Store, Command), anyhow::Error> { + let mut linker = Linker::new(&ENGINE); + add_to_linker(&mut linker)?; + wasmtime_wasi_http::proxy::sync::add_to_linker(&mut linker)?; + + let mut store = Store::new(&ENGINE, ctx); + + let (command, _instance) = Command::instantiate(&mut store, &component, &linker)?; + Ok((store, command)) +} + +fn run(name: &str) -> anyhow::Result<()> { + let stdout = MemoryOutputPipe::new(); + let stderr = MemoryOutputPipe::new(); + let r = { + let mut table = Table::new(); + let component = get_component(name); + + // Create our wasi context. + let mut builder = WasiCtxBuilder::new(); + builder.stdout(stdout.clone(), IsATTY::No); + builder.stderr(stderr.clone(), IsATTY::No); + builder.arg(name); + for (var, val) in test_programs::wasi_tests_environment() { + builder.env(var, val); + } + let wasi = builder.build(&mut table)?; + let http = WasiHttpCtx::new(); + + let (mut store, command) = instantiate_component(component, Ctx { table, wasi, http })?; + command + .wasi_cli_run() + .call_run(&mut store)? + .map_err(|()| anyhow::anyhow!("run returned a failure"))?; + Ok(()) + }; + r.map_err(move |trap: anyhow::Error| { + let stdout = stdout.try_into_inner().expect("single ref to stdout"); + if !stdout.is_empty() { + println!("[guest] stdout:\n{}\n===", String::from_utf8_lossy(&stdout)); + } + let stderr = stderr.try_into_inner().expect("single ref to stderr"); + if !stderr.is_empty() { + println!("[guest] stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); + } + trap.context(format!( + "error while testing wasi-tests {} with http-components-sync", + name + )) + })?; + Ok(()) +} + +#[test_log::test] +fn outbound_request_get() { + setup_http1_sync(|| run("outbound_request_get")).unwrap(); +} + +#[test_log::test] +#[ignore = "test is currently flaky in ci and needs to be debugged"] +fn outbound_request_post() { + setup_http1_sync(|| run("outbound_request_post")).unwrap(); +} + +#[test_log::test] +fn outbound_request_put() { + setup_http1_sync(|| run("outbound_request_put")).unwrap(); +} + +#[test_log::test] +#[cfg_attr( + windows, + ignore = "test is currently flaky in ci and needs to be debugged" +)] +fn outbound_request_invalid_version() { + setup_http2_sync(|| run("outbound_request_invalid_version")).unwrap(); +} + +#[test_log::test] +fn outbound_request_unknown_method() { + run("outbound_request_unknown_method").unwrap(); +} + +#[test_log::test] +fn outbound_request_unsupported_scheme() { + run("outbound_request_unsupported_scheme").unwrap(); +} + +#[test_log::test] +fn outbound_request_invalid_port() { + run("outbound_request_invalid_port").unwrap(); +} + +#[test_log::test] +#[cfg_attr( + windows, + ignore = "test is currently flaky in ci and needs to be debugged" +)] +fn outbound_request_invalid_dnsname() { + run("outbound_request_invalid_dnsname").unwrap(); +}