use std::sync::Arc;
use tensor_wasm_core::types::TenantId;
use tensor_wasm_exec::engine::{EngineConfig, MemoryBackend, TensorWasmEngine};
use tensor_wasm_exec::executor::{ExecError, SpawnConfig, TensorWasmExecutor};
const ENGINE_CAP: usize = 256 * 1024 * 1024;
const FOUR_GIB_PAGES: u32 = 65_536;
fn four_gib_module() -> Vec<u8> {
let src = format!("(module (memory (export \"mem\") {FOUR_GIB_PAGES}))");
wat::parse_str(&src).expect("valid wat")
}
fn four_gib_module_with_max() -> Vec<u8> {
let src = format!("(module (memory (export \"mem\") 1 {FOUR_GIB_PAGES}))");
wat::parse_str(&src).expect("valid wat")
}
fn imported_huge_memory_module() -> Vec<u8> {
let src = format!("(module (import \"env\" \"mem\" (memory {FOUR_GIB_PAGES})))",);
wat::parse_str(&src).expect("valid wat")
}
fn make_executor() -> TensorWasmExecutor {
let cfg = EngineConfig {
max_memory_bytes: ENGINE_CAP,
backend: MemoryBackend::PoolingMpk {
max_memories: 4,
memory_bytes: ENGINE_CAP,
},
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
TensorWasmExecutor::new(engine)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_rejects_exported_4gib_memory_min() {
let exec = make_executor();
let err = exec
.spawn_instance(SpawnConfig::for_tenant(TenantId(1)), &four_gib_module())
.await
.expect_err("4 GiB initial memory must be rejected before instantiation");
match err {
ExecError::ModuleMemoryTooLarge {
requested_bytes,
limit_bytes,
} => {
assert_eq!(limit_bytes as usize, ENGINE_CAP);
assert!(
requested_bytes as usize > ENGINE_CAP,
"requested {requested_bytes} bytes must exceed cap {ENGINE_CAP}"
);
assert_eq!(requested_bytes, 4 * 1024 * 1024 * 1024);
}
other => panic!("expected ModuleMemoryTooLarge, got {other:?}"),
}
assert_eq!(
exec.live_count(),
0,
"no instance must have been created when the cap is tripped",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_rejects_exported_4gib_memory_max() {
let exec = make_executor();
let err = exec
.spawn_instance(
SpawnConfig::for_tenant(TenantId(1)),
&four_gib_module_with_max(),
)
.await
.expect_err("4 GiB declared maximum must be rejected");
assert!(matches!(err, ExecError::ModuleMemoryTooLarge { .. }));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_rejects_imported_huge_memory() {
let exec = make_executor();
let err = exec
.spawn_instance(
SpawnConfig::for_tenant(TenantId(1)),
&imported_huge_memory_module(),
)
.await
.expect_err("imported 4 GiB memory must be rejected at the import site");
assert!(matches!(err, ExecError::ModuleMemoryTooLarge { .. }));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_admits_module_under_cap() {
let exec = make_executor();
let small_wasm = wat::parse_str(r#"(module (memory (export "mem") 1))"#).unwrap();
let id = exec
.spawn_instance(SpawnConfig::for_tenant(TenantId(2)), &small_wasm)
.await
.expect("1-page module must spawn under any reasonable engine cap");
exec.terminate(id).await.expect("terminate");
}
#[test]
fn module_memory_too_large_maps_to_memory_exhausted() {
use tensor_wasm_core::error::TensorWasmError;
let e = ExecError::ModuleMemoryTooLarge {
requested_bytes: 4 * 1024 * 1024 * 1024,
limit_bytes: ENGINE_CAP as u64,
};
let b: TensorWasmError = e.into();
match b {
TensorWasmError::MemoryExhausted { requested, limit } => {
assert_eq!(requested, 4 * 1024 * 1024 * 1024);
assert_eq!(limit, ENGINE_CAP as u64);
}
other => panic!("expected MemoryExhausted, got {other:?}"),
}
}