use std::sync::Arc;
use anyhow::Result;
use wasm_sql::SqlHostStateView;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
use wasm_sql::core::bindings::SqlHostState;
use wasm_sql::sqldb::SqlDB;
pub struct TestState {
sql: SqlHostState,
wasi: WasiCtx,
table: ResourceTable,
}
impl WasiView for TestState {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
impl SqlHostStateView for TestState {
fn sql_host_state(&mut self) -> &mut SqlHostState {
&mut self.sql
}
}
pub struct TestHarness {
engine: Engine,
sql_db: Arc<SqlDB>,
#[allow(dead_code)]
_keepalive: Box<dyn std::any::Any + Send>,
}
impl TestHarness {
pub fn new(sql_db: Arc<SqlDB>, keepalive: impl std::any::Any + Send) -> Result<Self> {
let mut config = Config::new();
config.async_support(true);
config.wasm_component_model(true);
config.wasm_component_model_async(true);
let engine = Engine::new(&config)?;
Ok(Self {
engine,
sql_db,
_keepalive: Box::new(keepalive),
})
}
pub fn create_linker(&self) -> Result<Linker<TestState>> {
let mut linker: Linker<TestState> = Linker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
wasm_sql::add_to_linker(&mut linker)?;
Ok(linker)
}
pub fn load_component(&self, wasm_path: &str) -> Result<Component> {
Component::from_file(&self.engine, wasm_path)
}
pub fn create_store(&self) -> Store<TestState> {
let state = TestState {
sql: SqlHostState::new(self.sql_db.clone()),
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
table: ResourceTable::new(),
};
Store::new(&self.engine, state)
}
}
#[macro_export]
macro_rules! wasm_sql_tests {
($instance_type:ty, $interface:ident { $($test:ident),* $(,)? }) => {
$(
paste::paste! {
#[tokio::test]
async fn $test() {
let harness = instantiate().await.expect("harness setup failed");
let linker = harness.create_linker().expect("linker setup failed");
let component = harness.load_component(WASM_PATH).expect("component load failed");
let mut store = harness.create_store();
let instance = <$instance_type>::instantiate_async(&mut store, &component, &linker)
.await
.expect("instantiate failed");
let result = store
.run_concurrent(async move |accessor| {
instance.$interface().[<call_ $test>](&accessor).await
})
.await
.expect("run_concurrent failed")
.unwrap();
if let Err(e) = result {
panic!("Test failed: {}", e);
}
}
}
)*
};
}