Path: blob/main/crates/wasi/tests/all/p2/api.rs
1693 views
use anyhow::Result;1use std::io::Write;2use std::sync::Mutex;3use std::time::Duration;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_api!(assert_test_exists);3031async fn instantiate(path: &str, ctx: CommandCtx) -> Result<(Store<CommandCtx>, Command)> {32let engine = test_programs_artifacts::engine(|config| {33config.async_support(true);34});35let mut linker = Linker::new(&engine);36add_to_linker_async(&mut linker)?;3738let mut store = Store::new(&engine, ctx);39let component = Component::from_file(&engine, path)?;40let command = Command::instantiate_async(&mut store, &component, &linker).await?;41Ok((store, command))42}4344#[test_log::test(tokio::test(flavor = "multi_thread"))]45async fn api_time() -> Result<()> {46struct FakeWallClock;4748impl HostWallClock for FakeWallClock {49fn resolution(&self) -> Duration {50Duration::from_secs(1)51}5253fn now(&self) -> Duration {54Duration::new(1431648000, 100)55}56}5758struct FakeMonotonicClock {59now: Mutex<u64>,60}6162impl HostMonotonicClock for FakeMonotonicClock {63fn resolution(&self) -> u64 {641_000_000_00065}6667fn now(&self) -> u64 {68let mut now = self.now.lock().unwrap();69let then = *now;70*now += 42 * 1_000_000_000;71then72}73}7475let table = ResourceTable::new();76let wasi = WasiCtxBuilder::new()77.monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) })78.wall_clock(FakeWallClock)79.build();8081let (mut store, command) = instantiate(API_TIME_COMPONENT, CommandCtx { table, wasi }).await?;8283command84.wasi_cli_run()85.call_run(&mut store)86.await?87.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))88}8990#[test_log::test(tokio::test(flavor = "multi_thread"))]91async fn api_read_only() -> Result<()> {92let dir = tempfile::tempdir()?;9394std::fs::File::create(dir.path().join("bar.txt"))?.write_all(b"And stood awhile in thought")?;95std::fs::create_dir(dir.path().join("sub"))?;9697let table = ResourceTable::new();98let wasi = WasiCtxBuilder::new()99.preopened_dir(dir.path(), "/", DirPerms::READ, FilePerms::READ)?100.build();101102let (mut store, command) =103instantiate(API_READ_ONLY_COMPONENT, CommandCtx { table, wasi }).await?;104105command106.wasi_cli_run()107.call_run(&mut store)108.await?109.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))110}111112#[expect(113dead_code,114reason = "tested in the wasi-http crate, satisfying foreach_api! macro"115)]116fn api_proxy() {}117118#[expect(119dead_code,120reason = "tested in the wasi-http crate, satisfying foreach_api! macro"121)]122fn api_proxy_streaming() {}123124#[expect(125dead_code,126reason = "tested in the wasi-http crate, satisfying foreach_api! macro"127)]128fn api_proxy_forward_request() {}129130wasmtime::component::bindgen!({131path: "src/p2/wit",132world: "test-reactor",133imports: { default: async },134exports: { default: async },135require_store_data_send: true,136with: { "wasi": wasmtime_wasi::p2::bindings },137ownership: Borrowing {138duplicate_if_necessary: false139}140});141142#[test_log::test(tokio::test)]143async fn api_reactor() -> Result<()> {144let table = ResourceTable::new();145let wasi = WasiCtxBuilder::new().env("GOOD_DOG", "gussie").build();146let engine = test_programs_artifacts::engine(|config| {147config.async_support(true);148});149let mut linker = Linker::new(&engine);150add_to_linker_async(&mut linker)?;151152let mut store = Store::new(&engine, CommandCtx { table, wasi });153let component = Component::from_file(&engine, API_REACTOR_COMPONENT)?;154let reactor = TestReactor::instantiate_async(&mut store, &component, &linker).await?;155156// Show that integration with the WASI context is working - the guest will157// interpolate $GOOD_DOG to gussie here using the environment:158let r = reactor159.call_add_strings(&mut store, &["hello", "$GOOD_DOG"])160.await?;161assert_eq!(r, 2);162163let contents = reactor.call_get_strings(&mut store).await?;164assert_eq!(contents, &["hello", "gussie"]);165166// Show that we can pass in a resource type whose impls are defined in the167// `host` and `wasi-common` crate.168// Note, this works because of the add_to_linker invocations using the169// `host` crate for `streams`, not because of `with` in the bindgen macro.170let writepipe = wasmtime_wasi::p2::pipe::MemoryOutputPipe::new(4096);171let stream: wasmtime_wasi::p2::DynOutputStream = Box::new(writepipe.clone());172let table_ix = store.data_mut().table.push(stream)?;173let r = reactor.call_write_strings_to(&mut store, table_ix).await?;174assert_eq!(r, Ok(()));175176assert_eq!(writepipe.contents().as_ref(), b"hellogussie");177178// Show that the `with` invocation in the macro means we get to re-use the179// type definitions from inside the `host` crate for these structures:180let ds = filesystem::DescriptorStat {181data_access_timestamp: Some(wall_clock::Datetime {182nanoseconds: 123,183seconds: 45,184}),185data_modification_timestamp: Some(wall_clock::Datetime {186nanoseconds: 789,187seconds: 10,188}),189link_count: 0,190size: 0,191status_change_timestamp: Some(wall_clock::Datetime {192nanoseconds: 0,193seconds: 1,194}),195type_: filesystem::DescriptorType::Unknown,196};197let expected = format!("{ds:?}");198let got = reactor.call_pass_an_imported_record(&mut store, ds).await?;199assert_eq!(expected, got);200201Ok(())202}203204205