use wapc::errors::Error;
use wapc::WapcHost;
#[cfg(feature = "async")]
use wapc::WapcHostAsync;
use wasmtime_provider::{ResourceLimits, WasmtimeEngineProviderBuilder};
const WASM_PAGE_SIZE: usize = 65536;
const GROW_MEMORY_WAT: &str = r#"
(module
(import "wapc" "__guest_response" (func $guest_response (param i32 i32)))
(memory (export "memory") 1 16)
(func (export "__guest_call") (param i32 i32) (result i32)
(local $pages i32)
(block $done
(loop $loop
(local.set $pages (memory.grow (i32.const 1)))
(br_if $done (i32.lt_s (local.get $pages) (i32.const 0)))
(br $loop)
)
)
(i32.store (i32.const 0) (memory.size))
(call $guest_response (i32.const 0) (i32.const 4))
(i32.const 1)
)
)
"#;
const GROW_TABLE_WAT: &str = r#"
(module
(import "wapc" "__guest_response" (func $guest_response (param i32 i32)))
(memory (export "memory") 1)
(table (export "tbl") 1 2000 funcref)
(func (export "__guest_call") (param i32 i32) (result i32)
(local $n i32)
(block $done
(loop $loop
(local.set $n (table.grow (ref.null func) (i32.const 1)))
(br_if $done (i32.lt_s (local.get $n) (i32.const 0)))
(br $loop)
)
)
(i32.store (i32.const 0) (table.size))
(call $guest_response (i32.const 0) (i32.const 4))
(i32.const 1)
)
)
"#;
fn noop_callback(
_id: u64,
_bd: &str,
_ns: &str,
_op: &str,
_payload: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
Ok(vec![])
}
fn create_guest(wat: &str, resource_limits: Option<ResourceLimits>) -> Result<WapcHost, Error> {
let mut builder = WasmtimeEngineProviderBuilder::new().module_bytes(wat.as_bytes());
if let Some(resource_limits) = resource_limits {
builder = builder.enable_resource_limits(resource_limits);
}
let engine = builder.build().expect("Cannot create WebAssemblyEngineProvider");
WapcHost::new(Box::new(engine), Some(Box::new(noop_callback)))
}
fn call_and_read_u32(guest: &WapcHost, op: &str) -> u32 {
let callresult = guest.call(op, &[]).expect("call should succeed");
u32::from_le_bytes(callresult.try_into().expect("response should be 4 bytes"))
}
#[cfg(feature = "async")]
async fn noop_callback_async(
_id: u64,
_bd: String,
_ns: String,
_op: String,
_payload: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
Ok(vec![])
}
#[cfg(feature = "async")]
async fn create_guest_async(wat: &str, resource_limits: Option<ResourceLimits>) -> Result<WapcHostAsync, Error> {
let mut builder = WasmtimeEngineProviderBuilder::new().module_bytes(wat.as_bytes());
if let Some(resource_limits) = resource_limits {
builder = builder.enable_resource_limits(resource_limits);
}
let engine = builder
.build_async()
.expect("Cannot create WebAssemblyEngineProviderAsync");
let callback: Box<wapc::HostCallbackAsync> =
Box::new(move |id, bd, ns, op, payload| Box::pin(noop_callback_async(id, bd, ns, op, payload)));
WapcHostAsync::new(Box::new(engine), Some(callback)).await
}
#[cfg(feature = "async")]
async fn call_and_read_u32_async(guest: &WapcHostAsync, op: &str) -> u32 {
let callresult = guest.call(op, &[]).await.expect("call should succeed");
u32::from_le_bytes(callresult.try_into().expect("response should be 4 bytes"))
}
#[test]
fn memory_growth_is_unbounded_without_resource_limits() -> Result<(), Error> {
let guest = create_guest(GROW_MEMORY_WAT, None)?;
let pages = call_and_read_u32(&guest, "grow");
assert_eq!(pages, 16);
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn memory_growth_is_unbounded_without_resource_limits_async() -> Result<(), Error> {
let guest = create_guest_async(GROW_MEMORY_WAT, None).await?;
let pages = call_and_read_u32_async(&guest, "grow").await;
assert_eq!(pages, 16);
Ok(())
}
#[test]
fn memory_growth_is_capped_by_resource_limits() -> Result<(), Error> {
let resource_limits = ResourceLimits {
max_memory_size: Some(4 * WASM_PAGE_SIZE),
..Default::default()
};
let guest = create_guest(GROW_MEMORY_WAT, Some(resource_limits))?;
let pages = call_and_read_u32(&guest, "grow");
assert_eq!(pages, 4);
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn memory_growth_is_capped_by_resource_limits_async() -> Result<(), Error> {
let resource_limits = ResourceLimits {
max_memory_size: Some(4 * WASM_PAGE_SIZE),
..Default::default()
};
let guest = create_guest_async(GROW_MEMORY_WAT, Some(resource_limits)).await?;
let pages = call_and_read_u32_async(&guest, "grow").await;
assert_eq!(pages, 4);
Ok(())
}
#[test]
fn table_growth_is_unbounded_without_resource_limits() -> Result<(), Error> {
let guest = create_guest(GROW_TABLE_WAT, None)?;
let elements = call_and_read_u32(&guest, "grow");
assert_eq!(elements, 2000);
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn table_growth_is_unbounded_without_resource_limits_async() -> Result<(), Error> {
let guest = create_guest_async(GROW_TABLE_WAT, None).await?;
let elements = call_and_read_u32_async(&guest, "grow").await;
assert_eq!(elements, 2000);
Ok(())
}
#[test]
fn table_growth_is_capped_by_resource_limits() -> Result<(), Error> {
let resource_limits = ResourceLimits {
max_table_elements: Some(50),
..Default::default()
};
let guest = create_guest(GROW_TABLE_WAT, Some(resource_limits))?;
let elements = call_and_read_u32(&guest, "grow");
assert_eq!(elements, 50);
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn table_growth_is_capped_by_resource_limits_async() -> Result<(), Error> {
let resource_limits = ResourceLimits {
max_table_elements: Some(50),
..Default::default()
};
let guest = create_guest_async(GROW_TABLE_WAT, Some(resource_limits)).await?;
let elements = call_and_read_u32_async(&guest, "grow").await;
assert_eq!(elements, 50);
Ok(())
}
#[test]
fn initial_memory_above_limit_fails_at_init() {
let resource_limits = ResourceLimits {
max_memory_size: Some(WASM_PAGE_SIZE / 2),
..Default::default()
};
let err = create_guest(GROW_MEMORY_WAT, Some(resource_limits))
.expect_err("instantiation should fail because initial memory exceeds the limit");
assert!(err.to_string().contains("memory"), "unexpected error: {err}");
}
#[cfg(feature = "async")]
#[tokio::test]
async fn initial_memory_above_limit_fails_at_init_async() {
let resource_limits = ResourceLimits {
max_memory_size: Some(WASM_PAGE_SIZE / 2),
..Default::default()
};
let err = create_guest_async(GROW_MEMORY_WAT, Some(resource_limits))
.await
.expect_err("instantiation should fail because initial memory exceeds the limit");
assert!(err.to_string().contains("memory"), "unexpected error: {err}");
}