1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
use anyhow::Result;
use std::io::Write;
use std::sync::Mutex;
use std::time::Duration;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::Store;
use wasmtime_wasi::bindings::Command;
use wasmtime_wasi::{
add_to_linker_async,
bindings::{clocks::wall_clock, filesystem::types as filesystem},
DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiView,
};
struct CommandCtx {
table: ResourceTable,
wasi: WasiCtx,
}
impl WasiView for CommandCtx {
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
}
use test_programs_artifacts::*;
foreach_api!(assert_test_exists);
async fn instantiate(path: &str, ctx: CommandCtx) -> Result<(Store<CommandCtx>, Command)> {
let engine = test_programs_artifacts::engine(|config| {
config.async_support(true);
});
let mut linker = Linker::new(&engine);
add_to_linker_async(&mut linker)?;
let mut store = Store::new(&engine, ctx);
let component = Component::from_file(&engine, path)?;
let command = Command::instantiate_async(&mut store, &component, &linker).await?;
Ok((store, command))
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn api_time() -> Result<()> {
struct FakeWallClock;
impl HostWallClock for FakeWallClock {
fn resolution(&self) -> Duration {
Duration::from_secs(1)
}
fn now(&self) -> Duration {
Duration::new(1431648000, 100)
}
}
struct FakeMonotonicClock {
now: Mutex<u64>,
}
impl HostMonotonicClock for FakeMonotonicClock {
fn resolution(&self) -> u64 {
1_000_000_000
}
fn now(&self) -> u64 {
let mut now = self.now.lock().unwrap();
let then = *now;
*now += 42 * 1_000_000_000;
then
}
}
let table = ResourceTable::new();
let wasi = WasiCtxBuilder::new()
.monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) })
.wall_clock(FakeWallClock)
.build();
let (mut store, command) = instantiate(API_TIME_COMPONENT, CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn api_read_only() -> Result<()> {
let dir = tempfile::tempdir()?;
std::fs::File::create(dir.path().join("bar.txt"))?.write_all(b"And stood awhile in thought")?;
std::fs::create_dir(dir.path().join("sub"))?;
let table = ResourceTable::new();
let wasi = WasiCtxBuilder::new()
.preopened_dir(dir.path(), "/", DirPerms::READ, FilePerms::READ)?
.build();
let (mut store, command) =
instantiate(API_READ_ONLY_COMPONENT, CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
}
// This is tested in the wasi-http crate, but need to satisfy the `foreach_api!`
// macro above.
#[allow(dead_code)]
fn api_proxy() {}
// This is tested in the wasi-http crate, but need to satisfy the `foreach_api!`
// macro above.
#[allow(dead_code)]
fn api_proxy_streaming() {}
// This is tested in the wasi-http crate, but need to satisfy the `foreach_api!`
// macro above.
#[allow(dead_code)]
fn api_proxy_forward_request() {}
wasmtime::component::bindgen!({
world: "test-reactor",
async: true,
with: { "wasi": wasmtime_wasi::bindings },
ownership: Borrowing {
duplicate_if_necessary: false
}
});
#[test_log::test(tokio::test)]
async fn api_reactor() -> Result<()> {
let table = ResourceTable::new();
let wasi = WasiCtxBuilder::new().env("GOOD_DOG", "gussie").build();
let engine = test_programs_artifacts::engine(|config| {
config.async_support(true);
});
let mut linker = Linker::new(&engine);
add_to_linker_async(&mut linker)?;
let mut store = Store::new(&engine, CommandCtx { table, wasi });
let component = Component::from_file(&engine, API_REACTOR_COMPONENT)?;
let reactor = TestReactor::instantiate_async(&mut store, &component, &linker).await?;
// Show that integration with the WASI context is working - the guest will
// interpolate $GOOD_DOG to gussie here using the environment:
let r = reactor
.call_add_strings(&mut store, &["hello", "$GOOD_DOG"])
.await?;
assert_eq!(r, 2);
let contents = reactor.call_get_strings(&mut store).await?;
assert_eq!(contents, &["hello", "gussie"]);
// Show that we can pass in a resource type whose impls are defined in the
// `host` and `wasi-common` crate.
// Note, this works because of the add_to_linker invocations using the
// `host` crate for `streams`, not because of `with` in the bindgen macro.
let writepipe = wasmtime_wasi::pipe::MemoryOutputPipe::new(4096);
let stream: wasmtime_wasi::OutputStream = Box::new(writepipe.clone());
let table_ix = store.data_mut().table().push(stream)?;
let r = reactor.call_write_strings_to(&mut store, table_ix).await?;
assert_eq!(r, Ok(()));
assert_eq!(writepipe.contents().as_ref(), b"hellogussie");
// Show that the `with` invocation in the macro means we get to re-use the
// type definitions from inside the `host` crate for these structures:
let ds = filesystem::DescriptorStat {
data_access_timestamp: Some(wall_clock::Datetime {
nanoseconds: 123,
seconds: 45,
}),
data_modification_timestamp: Some(wall_clock::Datetime {
nanoseconds: 789,
seconds: 10,
}),
link_count: 0,
size: 0,
status_change_timestamp: Some(wall_clock::Datetime {
nanoseconds: 0,
seconds: 1,
}),
type_: filesystem::DescriptorType::Unknown,
};
let expected = format!("{ds:?}");
let got = reactor.call_pass_an_imported_record(&mut store, ds).await?;
assert_eq!(expected, got);
Ok(())
}
|