use std::sync::Arc;
use tensor_wasm_core::types::TenantId;
use tensor_wasm_exec::engine::{EngineConfig, TensorWasmEngine};
use tensor_wasm_exec::executor::{ExecError, SpawnConfig, TensorWasmExecutor};
fn trivial_wasm() -> Vec<u8> {
wat::parse_str(r#"(module (func (export "noop")))"#).expect("valid wat")
}
fn make_executor(max_per_tenant: usize) -> TensorWasmExecutor {
let cfg = EngineConfig {
max_instances: Some(1_000),
max_instances_per_tenant: Some(max_per_tenant),
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
TensorWasmExecutor::new(engine)
}
#[tokio::test]
async fn tenant_a_at_cap_does_not_block_tenant_b() {
let exec = make_executor(2);
let wasm = trivial_wasm();
let a = TenantId(1);
let b = TenantId(2);
let a1 = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("A first spawn admitted");
let a2 = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("A second spawn admitted");
assert_eq!(exec.tenant_instance_count(a), 2, "A holds two slots");
let err = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect_err("A third spawn must be refused at its per-tenant cap");
match err {
ExecError::TenantCapacityExhausted {
tenant,
active,
limit,
} => {
assert_eq!(tenant, a, "rejection must name the offending tenant");
assert_eq!(limit, 2, "limit must be A's per-tenant cap");
assert!(active > limit, "active={active} must exceed limit={limit}");
}
other => panic!("expected TenantCapacityExhausted, got {other:?}"),
}
assert_eq!(
exec.tenant_instance_count(a),
2,
"refused spawn leaks no A slot"
);
assert_eq!(
exec.instances_len(),
2,
"refused spawn leaks no engine slot"
);
let b1 = exec
.spawn_instance(SpawnConfig::for_tenant(b), &wasm)
.await
.expect("B first spawn admitted despite A at cap");
let b2 = exec
.spawn_instance(SpawnConfig::for_tenant(b), &wasm)
.await
.expect("B second spawn admitted despite A at cap");
assert_eq!(exec.tenant_instance_count(b), 2, "B holds two slots");
exec.terminate(a1).await.expect("terminate a1");
assert_eq!(
exec.tenant_instance_count(a),
1,
"A slot freed on terminate"
);
let a3 = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("A spawn admitted again after freeing a slot");
for id in [a2, a3, b1, b2] {
exec.terminate(id).await.expect("terminate");
}
assert_eq!(exec.instances_len(), 0);
assert_eq!(exec.tenant_instance_count(a), 0);
assert_eq!(exec.tenant_instance_count(b), 0);
}
#[tokio::test]
async fn failed_spawn_rolls_back_per_tenant_count() {
let exec = make_executor(2);
let wasm = trivial_wasm();
let a = TenantId(7);
let a1 = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("first valid spawn admitted");
assert_eq!(exec.tenant_instance_count(a), 1);
let garbage: &[u8] = b"\x00not-a-wasm-module";
let err = exec
.spawn_instance(SpawnConfig::for_tenant(a), garbage)
.await
.expect_err("invalid wasm must fail to spawn");
assert!(
matches!(err, ExecError::Wasmtime(_)),
"expected a wasmtime compile error, got {err:?}",
);
assert_eq!(
exec.tenant_instance_count(a),
1,
"failed spawn must roll back its per-tenant slot",
);
assert_eq!(
exec.instances_len(),
1,
"failed spawn must roll back engine slot"
);
let a2 = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("valid spawn after a failed one must still be admitted");
assert_eq!(exec.tenant_instance_count(a), 2);
exec.terminate(a1).await.expect("terminate a1");
exec.terminate(a2).await.expect("terminate a2");
assert_eq!(exec.tenant_instance_count(a), 0);
assert_eq!(exec.instances_len(), 0);
}
#[tokio::test]
async fn no_per_tenant_cap_means_unlimited_per_tenant() {
let cfg = EngineConfig {
max_instances: Some(5),
max_instances_per_tenant: None,
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
let exec = TensorWasmExecutor::new(engine);
let wasm = trivial_wasm();
let a = TenantId(1);
let mut ids = Vec::new();
for _ in 0..5 {
ids.push(
exec.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect("spawn admitted under engine-wide cap"),
);
}
assert_eq!(exec.tenant_instance_count(a), 0);
let err = exec
.spawn_instance(SpawnConfig::for_tenant(a), &wasm)
.await
.expect_err("sixth spawn must hit the engine-wide cap");
assert!(matches!(err, ExecError::CapacityExhausted { .. }));
for id in ids {
exec.terminate(id).await.expect("terminate");
}
}