Skip to content

Add server tests #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

.idea
19 changes: 13 additions & 6 deletions test-programs/artifacts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,17 @@ fn main() -> Result<()> {
.join("debug")
.join(format!("{target}.wasm"));

let path = compile_component(&wasm)?;
let adapter = match target.as_str() {
s if s.starts_with("client_") => {
include_bytes!("wasi_snapshot_preview1.command.wasm").to_vec()
}
s if s.starts_with("server_") => {
include_bytes!("wasi_snapshot_preview1.proxy.wasm").to_vec()
}
other => panic!("unknown type {other}"),
};

let path = compile_component(&wasm, &adapter)?;
generated_code += &format!("pub const {camel}_COMPONENT: &str = {path:?};\n");
}

Expand All @@ -51,15 +61,12 @@ fn main() -> Result<()> {
}

// Compile a component, return the path of the binary
fn compile_component(wasm: &Path) -> Result<PathBuf> {
fn compile_component(wasm: &Path, adapter: &[u8]) -> Result<PathBuf> {
let module = fs::read(wasm)?;
let component = ComponentEncoder::default()
.module(module.as_slice())?
.validate(true)
.adapter(
"wasi_snapshot_preview1",
include_bytes!("wasi_snapshot_preview1.command.wasm"),
)?
.adapter("wasi_snapshot_preview1", adapter)?
.encode()?;
let out_dir = wasm.parent().unwrap();
let stem = wasm.file_stem().unwrap().to_str().unwrap();
Expand Down
Binary file not shown.
19 changes: 19 additions & 0 deletions test-programs/src/bin/server_hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use waki::{handler, ErrorCode, Request, Response};

#[handler]
fn hello(req: Request) -> Result<Response, ErrorCode> {
let query = req.query();
Response::builder()
.body(
format!(
"Hello, {}!",
query.get("name").unwrap_or(&"WASI".to_string())
)
.as_bytes(),
)
.build()
}

// Technically this should not be here for a proxy, but given the current
// framework for tests it's required since this file is built as a `bin`
fn main() {}
7 changes: 7 additions & 0 deletions waki/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ wit-deps = "0.3.1"

[dev-dependencies]
test-programs-artifacts = { path = "../test-programs/artifacts" }

wasmtime = "21.0.1"
wasmtime-wasi = "21.0.1"
wasmtime-wasi-http = "21.0.1"
hyper = "1.3.1"
http-body-util = "0.1.1"
tokio = { version = "1.38.0", features = ["macros"] }
35 changes: 15 additions & 20 deletions waki/tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,45 @@ fn wasmtime() -> Command {
}

#[test]
fn get_chunk() {
let mut cmd = wasmtime();
let status = cmd
.arg(test_programs_artifacts::GET_CHUNK_COMPONENT)
fn client_get_chunk() {
let status = wasmtime()
.arg(test_programs_artifacts::CLIENT_GET_CHUNK_COMPONENT)
.status()
.unwrap();
assert!(status.success());
}

#[test]
fn get_with_query() {
let mut cmd = wasmtime();
let status = cmd
.arg(test_programs_artifacts::GET_WITH_QUERY_COMPONENT)
fn client_get_with_query() {
let status = wasmtime()
.arg(test_programs_artifacts::CLIENT_GET_WITH_QUERY_COMPONENT)
.status()
.unwrap();
assert!(status.success());
}

#[test]
fn post_with_form_data() {
let mut cmd = wasmtime();
let status = cmd
.arg(test_programs_artifacts::POST_WITH_FORM_DATA_COMPONENT)
fn client_post_with_form_data() {
let status = wasmtime()
.arg(test_programs_artifacts::CLIENT_POST_WITH_FORM_DATA_COMPONENT)
.status()
.unwrap();
assert!(status.success());
}

#[test]
fn post_with_json_data() {
let mut cmd = wasmtime();
let status = cmd
.arg(test_programs_artifacts::POST_WITH_JSON_DATA_COMPONENT)
fn client_post_with_json_data() {
let status = wasmtime()
.arg(test_programs_artifacts::CLIENT_POST_WITH_JSON_DATA_COMPONENT)
.status()
.unwrap();
assert!(status.success());
}

#[test]
fn post_with_multipart_form_data() {
let mut cmd = wasmtime();
let status = cmd
.arg(test_programs_artifacts::POST_WITH_MULTIPART_FORM_DATA_COMPONENT)
fn client_post_with_multipart_form_data() {
let status = wasmtime()
.arg(test_programs_artifacts::CLIENT_POST_WITH_MULTIPART_FORM_DATA_COMPONENT)
.status()
.unwrap();
assert!(status.success());
Expand Down
97 changes: 97 additions & 0 deletions waki/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use anyhow::{Context, Result};
use http_body_util::Collected;
use hyper::body::Bytes;
use wasmtime::{
component::{Component, Linker, ResourceTable},
Config, Engine, Store,
};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
use wasmtime_wasi_http::{
bindings::http::types::ErrorCode, body::HyperIncomingBody, WasiHttpCtx, WasiHttpView,
};

struct Ctx {
table: ResourceTable,
wasi: WasiCtx,
http: WasiHttpCtx,
}

impl WasiView for Ctx {
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
}

impl WasiHttpView for Ctx {
fn ctx(&mut self) -> &mut WasiHttpCtx {
&mut self.http
}

fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}

// ref: https://github.com/bytecodealliance/wasmtime/blob/af59c4d568d487b7efbb49d7d31a861e7c3933a6/crates/wasi-http/tests/all/main.rs#L129
pub async fn run_wasi_http(
component_filename: &str,
req: hyper::Request<HyperIncomingBody>,
) -> Result<Result<hyper::Response<Collected<Bytes>>, ErrorCode>> {
let mut config = Config::new();
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
config.wasm_component_model(true);
config.async_support(true);

let engine = Engine::new(&config)?;
let component = Component::from_file(&engine, component_filename)?;

let ctx = Ctx {
table: ResourceTable::new(),
wasi: WasiCtxBuilder::new().build(),
http: WasiHttpCtx::new(),
};

let mut store = Store::new(&engine, ctx);
let mut linker = Linker::new(&engine);
wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?;
let (proxy, _) =
wasmtime_wasi_http::proxy::Proxy::instantiate_async(&mut store, &component, &linker)
.await?;

let req = store.data_mut().new_incoming_request(req)?;

let (sender, receiver) = tokio::sync::oneshot::channel();
let out = store.data_mut().new_response_outparam(sender)?;

let handle = wasmtime_wasi::runtime::spawn(async move {
proxy
.wasi_http_incoming_handler()
.call_handle(&mut store, req, out)
.await?;

Ok::<_, anyhow::Error>(())
});

let resp = match receiver.await {
Ok(Ok(resp)) => {
use http_body_util::BodyExt;
let (parts, body) = resp.into_parts();
let collected = BodyExt::collect(body).await?;
Some(Ok(hyper::Response::from_parts(parts, collected)))
}
Ok(Err(e)) => Some(Err(e)),

// Fall through below to the `resp.expect(...)` which will hopefully
// return a more specific error from `handle.await`.
Err(_) => None,
};

// Now that the response has been processed, we can wait on the wasm to
// finish without deadlocking.
handle.await.context("Component execution")?;

Ok(resp.expect("wasm never called set-response-outparam"))
}
23 changes: 23 additions & 0 deletions waki/tests/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
mod common;

use crate::common::run_wasi_http;

use anyhow::Result;
use wasmtime_wasi_http::body::HyperIncomingBody;

#[tokio::test]
async fn server_hello_query() -> Result<()> {
let req = hyper::Request::builder()
.uri("http://localhost?name=ia")
.body(HyperIncomingBody::default())?;

match run_wasi_http(test_programs_artifacts::SERVER_HELLO_COMPONENT, req).await? {
Ok(resp) => {
let body = resp.into_body().to_bytes();
let body = std::str::from_utf8(&body)?;
assert_eq!(body, "Hello, ia!")
}
Err(e) => panic!("Error given in response: {e:?}"),
};
Ok(())
}