Path: blob/main/crates/wasi/tests/all/p2/api.rs
3139 views
use std::io::Write;1use std::sync::Mutex;2use std::time::Duration;3use wasmtime::Result;4use wasmtime::Store;5use wasmtime::component::{Component, Linker, ResourceTable};6use wasmtime_wasi::p2::add_to_linker_async;7use wasmtime_wasi::p2::bindings::{Command, clocks::wall_clock, filesystem::types as filesystem};8use wasmtime_wasi::{9DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiCtxView,10WasiView,11};1213struct CommandCtx {14table: ResourceTable,15wasi: WasiCtx,16}1718impl WasiView for CommandCtx {19fn ctx(&mut self) -> WasiCtxView<'_> {20WasiCtxView {21ctx: &mut self.wasi,22table: &mut self.table,23}24}25}2627use test_programs_artifacts::*;2829foreach_p2_api!(assert_test_exists);3031async fn instantiate(path: &str, ctx: CommandCtx) -> Result<(Store<CommandCtx>, Command)> {32let engine = test_programs_artifacts::engine(|_config| {});33let mut linker = Linker::new(&engine);34add_to_linker_async(&mut linker)?;3536let mut store = Store::new(&engine, ctx);37let component = Component::from_file(&engine, path)?;38let command = Command::instantiate_async(&mut store, &component, &linker).await?;39Ok((store, command))40}4142#[test_log::test(tokio::test(flavor = "multi_thread"))]43async fn p2_api_time() -> Result<()> {44struct FakeWallClock;4546impl HostWallClock for FakeWallClock {47fn resolution(&self) -> Duration {48Duration::from_secs(1)49}5051fn now(&self) -> Duration {52Duration::new(1431648000, 100)53}54}5556struct FakeMonotonicClock {57now: Mutex<u64>,58}5960impl HostMonotonicClock for FakeMonotonicClock {61fn resolution(&self) -> u64 {621_000_000_00063}6465fn now(&self) -> u64 {66let mut now = self.now.lock().unwrap();67let then = *now;68*now += 42 * 1_000_000_000;69then70}71}7273let table = ResourceTable::new();74let wasi = WasiCtxBuilder::new()75.monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) })76.wall_clock(FakeWallClock)77.build();7879let (mut store, command) =80instantiate(P2_API_TIME_COMPONENT, CommandCtx { table, wasi }).await?;8182command83.wasi_cli_run()84.call_run(&mut store)85.await?86.map_err(|()| wasmtime::format_err!("command returned with failing exit status"))87}8889#[test_log::test(tokio::test(flavor = "multi_thread"))]90async fn p2_api_read_only() -> Result<()> {91let dir = tempfile::tempdir()?;9293std::fs::File::create(dir.path().join("bar.txt"))?.write_all(b"And stood awhile in thought")?;94std::fs::create_dir(dir.path().join("sub"))?;9596let table = ResourceTable::new();97let wasi = WasiCtxBuilder::new()98.preopened_dir(dir.path(), "/", DirPerms::READ, FilePerms::READ)?99.build();100101let (mut store, command) =102instantiate(P2_API_READ_ONLY_COMPONENT, CommandCtx { table, wasi }).await?;103104command105.wasi_cli_run()106.call_run(&mut store)107.await?108.map_err(|()| wasmtime::format_err!("command returned with failing exit status"))109}110111#[expect(112dead_code,113reason = "tested in the wasi-http crate, satisfying foreach_api! macro"114)]115fn p2_api_proxy() {}116117#[expect(118dead_code,119reason = "tested in the wasi-http crate, satisfying foreach_api! macro"120)]121fn p2_api_proxy_streaming() {}122123#[expect(124dead_code,125reason = "tested in the wasi-http crate, satisfying foreach_api! macro"126)]127fn p2_api_proxy_forward_request() {}128129wasmtime::component::bindgen!({130path: "src/p2/wit",131world: "test-reactor",132imports: { default: async },133exports: { default: async },134require_store_data_send: true,135with: { "wasi": wasmtime_wasi::p2::bindings },136ownership: Borrowing {137duplicate_if_necessary: false138}139});140141#[test_log::test(tokio::test)]142async fn p2_api_reactor() -> Result<()> {143let table = ResourceTable::new();144let wasi = WasiCtxBuilder::new().env("GOOD_DOG", "gussie").build();145let engine = test_programs_artifacts::engine(|_config| {});146let mut linker = Linker::new(&engine);147add_to_linker_async(&mut linker)?;148149let mut store = Store::new(&engine, CommandCtx { table, wasi });150let component = Component::from_file(&engine, P2_API_REACTOR_COMPONENT)?;151let reactor = TestReactor::instantiate_async(&mut store, &component, &linker).await?;152153// Show that integration with the WASI context is working - the guest will154// interpolate $GOOD_DOG to gussie here using the environment:155let r = reactor156.call_add_strings(&mut store, &["hello", "$GOOD_DOG"])157.await?;158assert_eq!(r, 2);159160let contents = reactor.call_get_strings(&mut store).await?;161assert_eq!(contents, &["hello", "gussie"]);162163// Show that we can pass in a resource type whose impls are defined in the164// `host` and `wasi-common` crate.165// Note, this works because of the add_to_linker invocations using the166// `host` crate for `streams`, not because of `with` in the bindgen macro.167let writepipe = wasmtime_wasi::p2::pipe::MemoryOutputPipe::new(4096);168let stream: wasmtime_wasi::p2::DynOutputStream = Box::new(writepipe.clone());169let table_ix = store.data_mut().table.push(stream)?;170let r = reactor.call_write_strings_to(&mut store, table_ix).await?;171assert_eq!(r, Ok(()));172173assert_eq!(writepipe.contents().as_ref(), b"hellogussie");174175// Show that the `with` invocation in the macro means we get to re-use the176// type definitions from inside the `host` crate for these structures:177let ds = filesystem::DescriptorStat {178data_access_timestamp: Some(wall_clock::Datetime {179nanoseconds: 123,180seconds: 45,181}),182data_modification_timestamp: Some(wall_clock::Datetime {183nanoseconds: 789,184seconds: 10,185}),186link_count: 0,187size: 0,188status_change_timestamp: Some(wall_clock::Datetime {189nanoseconds: 0,190seconds: 1,191}),192type_: filesystem::DescriptorType::Unknown,193};194let expected = format!("{ds:?}");195let got = reactor.call_pass_an_imported_record(&mut store, ds).await?;196assert_eq!(expected, got);197198Ok(())199}200201202