use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use wasmtime::component::ResourceTable;
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
use yosh_plugin_api::pattern::CommandPattern;
pub mod commands;
pub mod files;
pub mod filesystem;
pub mod io;
pub mod variables;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecRecord {
pub program: String,
pub args: Vec<String>,
pub exit_code: i32,
pub stdout_len: usize,
pub stderr_len: usize,
}
#[derive(Debug, Default, Clone)]
pub struct TestState {
pub caps: u32,
pub vars: HashMap<String, String>,
pub exported: HashSet<String>,
pub cwd: PathBuf,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub files: HashMap<PathBuf, Vec<u8>>,
pub sandbox_root: Option<PathBuf>,
pub allow_exec: Vec<CommandPattern>,
pub exec_log: Vec<ExecRecord>,
pub set_log: Vec<(String, String)>,
pub export_log: Vec<(String, String)>,
pub write_log: Vec<(PathBuf, usize)>,
}
impl TestState {
pub fn with_caps(caps: u32) -> Self {
TestState {
caps,
..TestState::default()
}
}
}
pub struct TestCtx {
pub state: TestState,
pub(crate) table: ResourceTable,
pub(crate) wasi: WasiCtx,
}
impl Default for TestCtx {
fn default() -> Self {
let wasi = WasiCtxBuilder::new().build();
TestCtx {
state: TestState::default(),
table: ResourceTable::new(),
wasi,
}
}
}
impl TestCtx {
pub fn new(state: TestState) -> Self {
TestCtx {
state,
table: ResourceTable::new(),
wasi: WasiCtxBuilder::new().build(),
}
}
}
impl WasiView for TestCtx {
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
use wasmtime::Engine;
use wasmtime::component::Linker;
pub fn build_linker(engine: &Engine) -> wasmtime::Result<Linker<TestCtx>> {
let mut linker = Linker::<TestCtx>::new(engine);
register_wasi(&mut linker)?;
Ok(linker)
}
fn register_wasi(linker: &mut Linker<TestCtx>) -> wasmtime::Result<()> {
wasmtime_wasi::add_to_linker_sync(linker)
}
use crate::generated::yosh::plugin::commands::ExecOutput;
use crate::generated::yosh::plugin::files::{DirEntry, FileStat};
use crate::generated::yosh::plugin::types::{ErrorCode, IoStream};
pub fn register_imports(linker: &mut Linker<TestCtx>) -> wasmtime::Result<()> {
let mut vars = linker.instance("yosh:plugin/variables@0.2.1")?;
vars.func_wrap(
"get",
|store: wasmtime::StoreContextMut<'_, TestCtx>, (name,): (String,)| {
Ok::<_, wasmtime::Error>((variables::host_get(&store.data().state, &name),))
},
)?;
vars.func_wrap(
"set",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (name, value): (String, String)| {
Ok::<_, wasmtime::Error>((variables::host_set(
&mut store.data_mut().state,
&name,
&value,
),))
},
)?;
vars.func_wrap(
"export-env",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (name, value): (String, String)| {
Ok::<_, wasmtime::Error>((variables::host_export_env(
&mut store.data_mut().state,
&name,
&value,
),))
},
)?;
let mut fs = linker.instance("yosh:plugin/filesystem@0.2.1")?;
fs.func_wrap(
"cwd",
|store: wasmtime::StoreContextMut<'_, TestCtx>, (): ()| {
Ok::<_, wasmtime::Error>((filesystem::host_cwd(&store.data().state),))
},
)?;
fs.func_wrap(
"set-cwd",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path,): (String,)| {
Ok::<_, wasmtime::Error>(
(filesystem::host_set_cwd(&mut store.data_mut().state, &path),),
)
},
)?;
let mut io_inst = linker.instance("yosh:plugin/io@0.2.1")?;
io_inst.func_wrap(
"write",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (target, data): (IoStream, Vec<u8>)| {
Ok::<_, wasmtime::Error>((io::host_write(&mut store.data_mut().state, target, &data),))
},
)?;
let mut f = linker.instance("yosh:plugin/files@0.2.1")?;
f.func_wrap(
"read-file",
|store: wasmtime::StoreContextMut<'_, TestCtx>, (path,): (String,)| {
Ok::<_, wasmtime::Error>((files::host_read_file(&store.data().state, &path),))
},
)?;
f.func_wrap(
"read-dir",
|store: wasmtime::StoreContextMut<'_, TestCtx>, (path,): (String,)| {
Ok::<_, wasmtime::Error>((files::host_read_dir(&store.data().state, &path),))
},
)?;
f.func_wrap(
"metadata",
|store: wasmtime::StoreContextMut<'_, TestCtx>, (path,): (String,)| {
Ok::<_, wasmtime::Error>((files::host_metadata(&store.data().state, &path),))
},
)?;
f.func_wrap(
"write-file",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path, data): (String, Vec<u8>)| {
Ok::<_, wasmtime::Error>((files::host_write_file(
&mut store.data_mut().state,
&path,
&data,
),))
},
)?;
f.func_wrap(
"append-file",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path, data): (String, Vec<u8>)| {
Ok::<_, wasmtime::Error>((files::host_append_file(
&mut store.data_mut().state,
&path,
&data,
),))
},
)?;
f.func_wrap(
"create-dir",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path, recursive): (String, bool)| {
Ok::<_, wasmtime::Error>((files::host_create_dir(
&mut store.data_mut().state,
&path,
recursive,
),))
},
)?;
f.func_wrap(
"remove-file",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path,): (String,)| {
Ok::<_, wasmtime::Error>((files::host_remove_file(&mut store.data_mut().state, &path),))
},
)?;
f.func_wrap(
"remove-dir",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>, (path, recursive): (String, bool)| {
Ok::<_, wasmtime::Error>((files::host_remove_dir(
&mut store.data_mut().state,
&path,
recursive,
),))
},
)?;
let mut cmds = linker.instance("yosh:plugin/commands@0.2.1")?;
cmds.func_wrap(
"exec",
|mut store: wasmtime::StoreContextMut<'_, TestCtx>,
(program, args): (String, Vec<String>)| {
Ok::<_, wasmtime::Error>((commands::host_exec(
&mut store.data_mut().state,
&program,
&args,
),))
},
)?;
let _ = (
std::marker::PhantomData::<ExecOutput>,
std::marker::PhantomData::<DirEntry>,
std::marker::PhantomData::<FileStat>,
std::marker::PhantomData::<ErrorCode>,
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_default_is_empty() {
let s = TestState::default();
assert!(s.vars.is_empty());
assert!(s.exported.is_empty());
assert_eq!(s.cwd.as_os_str(), "");
assert!(s.stdout.is_empty());
assert!(s.stderr.is_empty());
assert_eq!(s.caps, 0);
}
#[test]
fn test_ctx_default_constructs() {
let _ctx = TestCtx::default();
}
#[test]
fn linker_construction_smoke() {
let engine = crate::precompile::make_engine().expect("engine");
let _linker = build_linker(&engine).expect("linker");
}
#[test]
fn linker_with_yosh_imports_constructs() {
let engine = crate::precompile::make_engine().unwrap();
let mut linker = build_linker(&engine).unwrap();
register_imports(&mut linker).expect("yosh imports");
}
}