wasm-sql 0.1.6

Wasmtime host implementation for a SQL component WIT interface. Enables Wasm components to interact with SQL databases via the WebAssembly Component Model.
Documentation
//! Shared test harness for wasm-sql integration tests.
//!
//! Provides database-agnostic host setup for loading and running WASM guest components.

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;

// === Test state ===

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
    }
}

// === Test harness ===

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)
    }
}

// === Test macro ===

#[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);
                    }
                }
            }
        )*
    };
}