use packr::abi::Value;
use packr::runtime::{HostLinkerBuilder, LinkerError};
use packr::Runtime;
use wasmtime::Caller;
#[derive(Clone)]
struct TestState {
actor_id: i32,
}
impl TestState {
fn new(actor_id: i32) -> Self {
Self { actor_id }
}
}
#[test]
fn test_namespaced_interface_registration() {
let module_wat = r#"
(module
(import "theater:simple/runtime" "add_offset" (func $add_offset (param i32 i32) (result i32)))
(memory (export "memory") 1)
(func $compute (param $a i32) (param $b i32) (result i32)
(call $add_offset (local.get $a) (local.get $b))
)
(export "compute" (func $compute))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let state = TestState::new(100);
let mut instance = module
.instantiate_with_host(state, |builder| {
builder.interface("theater:simple/runtime")?.func_raw(
"add_offset",
|caller: Caller<'_, TestState>, a: i32, b: i32| -> i32 {
a + b + caller.data().actor_id
},
)?;
Ok(())
})
.expect("instantiate");
let result = instance
.call_i32_i32_to_i32("compute", 5, 10)
.expect("call");
assert_eq!(result, 115);
}
#[test]
fn test_func_typed_with_value() {
let module_wat = r#"
(module
(import "test" "double" (func $double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
;; Reserve space for result slots at fixed offset (16KB)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
;; Output data offset
(global $output_offset i32 (i32.const 16392))
;; Wrapper that calls host function with guest-allocates ABI
;; and copies result to caller's slots
(func $call_double (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
;; Call host function - it writes ptr/len to our slots
(local.set $status
(call $double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
;; If error, propagate
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
;; Read result ptr/len from slots
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
;; Copy result to our output area
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
;; Write our output location to caller's slots
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_double" (func $call_double))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.interface("test")?.func_typed(
"double",
|_ctx: &mut packr::Ctx<'_, ()>, input: Value| -> Value {
match input {
Value::S64(n) => Value::S64(n * 2),
other => other,
}
},
)?;
Ok(())
})
.expect("instantiate");
let input = Value::S64(21);
let output = instance
.call_with_value("call_double", &input)
.expect("call");
assert_eq!(output, Value::S64(42)); }
#[test]
fn test_multiple_interfaces() {
let module_wat = r#"
(module
(import "api:v1/math" "add" (func $add (param i32 i32) (result i32)))
(import "api:v1/util" "double" (func $double (param i32 i32) (result i32)))
(memory (export "memory") 1)
(func $compute (param $a i32) (param $b i32) (result i32)
;; double(add(a, b), 0)
(call $double (call $add (local.get $a) (local.get $b)) (i32.const 0))
)
(export "compute" (func $compute))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder
.interface("api:v1/math")?
.func_raw("add", |_: Caller<'_, ()>, a: i32, b: i32| a + b)?;
builder
.interface("api:v1/util")?
.func_raw("double", |_: Caller<'_, ()>, x: i32, _unused: i32| x * 2)?;
Ok(())
})
.expect("instantiate");
let result = instance
.call_i32_i32_to_i32("compute", 5, 10)
.expect("call");
assert_eq!(result, 30); }
#[test]
fn test_provider_pattern() {
use packr::runtime::HostFunctionProvider;
struct MathProvider;
impl HostFunctionProvider<()> for MathProvider {
fn register(&self, builder: &mut HostLinkerBuilder<'_, ()>) -> Result<(), LinkerError> {
builder
.interface("math")?
.func_raw("add", |_: Caller<'_, ()>, a: i32, b: i32| a + b)?
.func_raw("mul", |_: Caller<'_, ()>, a: i32, b: i32| a * b)?;
Ok(())
}
}
let module_wat = r#"
(module
(import "math" "add" (func $add (param i32 i32) (result i32)))
(import "math" "mul" (func $mul (param i32 i32) (result i32)))
(memory (export "memory") 1)
(func $calc (param $a i32) (param $b i32) (result i32)
(call $mul (call $add (local.get $a) (local.get $b)) (i32.const 2))
)
(export "calc" (func $calc))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.register_provider(&MathProvider)?;
Ok(())
})
.expect("instantiate");
let result = instance.call_i32_i32_to_i32("calc", 3, 4).expect("call");
assert_eq!(result, 14); }
#[test]
fn test_backward_compatibility() {
use packr::runtime::HostImports;
let module_wat = r#"
(module
(import "host" "log" (func $log (param i32 i32)))
(import "host" "alloc" (func $alloc (param i32) (result i32)))
(memory (export "memory") 1)
;; Takes two i32 args for API compatibility, ignores them
(func $test (param $a i32) (param $b i32) (result i32)
;; Store "test" at offset 0
(i32.store8 (i32.const 0) (i32.const 116))
(i32.store8 (i32.const 1) (i32.const 101))
(i32.store8 (i32.const 2) (i32.const 115))
(i32.store8 (i32.const 3) (i32.const 116))
(call $log (i32.const 0) (i32.const 4))
;; Allocate some memory
(call $alloc (i32.const 100))
)
(export "test" (func $test))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let imports = HostImports::new();
let mut instance = module
.instantiate_with_imports(imports)
.expect("instantiate");
let ptr = instance.call_i32_i32_to_i32("test", 0, 0).expect("call");
let logs = instance.get_logs();
assert_eq!(logs, vec!["test"]);
assert!(ptr >= 48 * 1024);
}
#[tokio::test]
async fn test_async_runtime_basic() {
use packr::AsyncRuntime;
let module_wat = r#"
(module
(memory (export "memory") 1)
;; Output data offset
(global $output_offset i32 (i32.const 16392))
;; Echo function: copies input to output area, writes ptr/len to slots
(func $echo (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
;; Copy input to output area
(memory.copy (global.get $output_offset) (local.get $in_ptr) (local.get $in_len))
;; Write output ptr/len to caller's slots
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $in_len))
;; Return success
(i32.const 0)
)
(export "echo" (func $echo))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = AsyncRuntime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module.instantiate_async().await.expect("instantiate");
let input = Value::S64(42);
let output = instance
.call_with_value_async("echo", &input)
.await
.expect("call");
assert_eq!(output, input);
}
#[tokio::test]
async fn test_func_async_registration() {
use packr::AsyncRuntime;
let module_wat = r#"
(module
(import "test" "async_double" (func $async_double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
;; Reserve space for result slots
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
;; Wrapper that calls async host function with guest-allocates ABI
(func $call_async (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
;; Call host function
(local.set $status
(call $async_double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
;; Read result
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
;; Copy to output area
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
;; Write to caller's slots
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_async" (func $call_async))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = AsyncRuntime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host_async((), |builder| {
builder.interface("test")?.func_async(
"async_double",
|_ctx: packr::AsyncCtx<()>, input: Value| async move {
match input {
Value::S64(n) => Value::S64(n * 2),
other => other,
}
},
)?;
Ok(())
})
.await
.expect("instantiate");
let input = Value::S64(21);
let output = instance
.call_with_value_async("call_async", &input)
.await
.expect("call");
assert_eq!(output, Value::S64(42)); }
#[tokio::test]
async fn test_async_ctx_state_access() {
use packr::AsyncRuntime;
#[derive(Clone)]
struct MultiplierState {
multiplier: i64,
}
let module_wat = r#"
(module
(import "math" "multiply" (func $multiply (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
;; Reserve space for result slots
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_multiply (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
;; Call host function
(local.set $status
(call $multiply
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
;; Read result
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
;; Copy to output area
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
;; Write to caller's slots
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_multiply" (func $call_multiply))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = AsyncRuntime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let state = MultiplierState { multiplier: 10 };
let mut instance = module
.instantiate_with_host_async(state, |builder| {
builder.interface("math")?.func_async(
"multiply",
|ctx: packr::AsyncCtx<MultiplierState>, input: Value| async move {
let multiplier = ctx.data().multiplier;
match input {
Value::S64(n) => Value::S64(n * multiplier),
other => other,
}
},
)?;
Ok(())
})
.await
.expect("instantiate");
let input = Value::S64(7);
let output = instance
.call_with_value_async("call_multiply", &input)
.await
.expect("call");
assert_eq!(output, Value::S64(70));
}
#[test]
fn test_error_handler_callback() {
use packr::{HostFunctionError, HostFunctionErrorKind};
use std::sync::{Arc, Mutex};
let errors: Arc<Mutex<Vec<HostFunctionError>>> = Arc::new(Mutex::new(Vec::new()));
let errors_clone = errors.clone();
let module_wat = r#"
(module
(import "test" "process" (func $process (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
;; Write garbage data to memory and call the host function
;; Returns i32 result directly (new calling convention)
(func $trigger_error (param $unused i32) (param $unused2 i32) (result i32)
;; Write invalid Graph ABI data at offset 100
(i32.store (i32.const 100) (i32.const 0xDEADBEEF))
;; Call host function with bad data
;; Args: in_ptr=100, in_len=4, out_ptr=200, out_cap=100
(call $process (i32.const 100) (i32.const 4) (i32.const 200) (i32.const 100))
)
(export "trigger_error" (func $trigger_error))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.on_error(move |err| {
errors_clone.lock().unwrap().push(err.clone());
});
builder.interface("test")?.func_typed(
"process",
|_ctx: &mut packr::Ctx<'_, ()>, input: Value| -> Value {
input
},
)?;
Ok(())
})
.expect("instantiate");
let result = instance
.call_i32_i32_to_i32("trigger_error", 0, 0)
.expect("call");
assert_eq!(result, -1);
let captured_errors = errors.lock().unwrap();
assert_eq!(captured_errors.len(), 1);
assert_eq!(captured_errors[0].interface, "test");
assert_eq!(captured_errors[0].function, "process");
assert!(matches!(
captured_errors[0].kind,
HostFunctionErrorKind::Decode(_)
));
}
#[test]
fn test_func_typed_result_encodes_as_value_result_ok() {
let module_wat = r#"
(module
(import "test" "maybe_double" (func $maybe_double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_maybe_double (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $maybe_double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_maybe_double" (func $call_maybe_double))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.interface("test")?.func_typed_result(
"maybe_double",
|_ctx: &mut packr::Ctx<'_, ()>, input: Value| -> Result<Value, Value> {
match input {
Value::S64(n) => Ok(Value::S64(n * 2)),
other => Ok(other),
}
},
)?;
Ok(())
})
.expect("instantiate");
let input = Value::S64(21);
let output = instance
.call_with_value("call_maybe_double", &input)
.expect("call");
match output {
Value::Result {
value: Ok(inner), ..
} => {
assert_eq!(*inner, Value::S64(42)); }
Value::Variant {
type_name,
case_name,
..
} => {
panic!(
"Expected Value::Result but got Value::Variant(type_name={}, case_name={})",
type_name, case_name
);
}
other => {
panic!("Expected Value::Result but got {:?}", other);
}
}
}
#[test]
fn test_func_typed_result_encodes_as_value_result_err() {
let module_wat = r#"
(module
(import "test" "maybe_double" (func $maybe_double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_maybe_double (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $maybe_double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_maybe_double" (func $call_maybe_double))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.interface("test")?.func_typed_result(
"maybe_double",
|_ctx: &mut packr::Ctx<'_, ()>, input: Value| -> Result<Value, Value> {
match input {
Value::S64(n) if n < 0 => {
Err(Value::String("negative not allowed".to_string()))
}
Value::S64(n) => Ok(Value::S64(n * 2)),
other => Ok(other),
}
},
)?;
Ok(())
})
.expect("instantiate");
let input = Value::S64(-5);
let output = instance
.call_with_value("call_maybe_double", &input)
.expect("call");
match output {
Value::Result {
value: Err(inner), ..
} => {
assert_eq!(*inner, Value::String("negative not allowed".to_string()));
}
Value::Variant {
type_name,
case_name,
..
} => {
panic!(
"Expected Value::Result but got Value::Variant(type_name={}, case_name={})",
type_name, case_name
);
}
other => {
panic!("Expected Value::Result but got {:?}", other);
}
}
}
#[tokio::test]
async fn test_func_async_result_encodes_as_value_result_ok() {
use packr::AsyncRuntime;
let module_wat = r#"
(module
(import "test" "async_maybe_double" (func $async_maybe_double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_async (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $async_maybe_double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_async" (func $call_async))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = AsyncRuntime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host_async((), |builder| {
builder.interface("test")?.func_async_result(
"async_maybe_double",
|_ctx: packr::AsyncCtx<()>, input: Value| async move {
let result: Result<Value, Value> = match input {
Value::S64(n) => Ok(Value::S64(n * 2)),
other => Ok(other),
};
result
},
)?;
Ok(())
})
.await
.expect("instantiate");
let input = Value::S64(21);
let output = instance
.call_with_value_async("call_async", &input)
.await
.expect("call");
match output {
Value::Result {
value: Ok(inner), ..
} => {
assert_eq!(*inner, Value::S64(42)); }
Value::Variant {
type_name,
case_name,
..
} => {
panic!(
"Expected Value::Result but got Value::Variant(type_name={}, case_name={})",
type_name, case_name
);
}
other => {
panic!("Expected Value::Result but got {:?}", other);
}
}
}
#[tokio::test]
async fn test_func_async_result_encodes_as_value_result_err() {
use packr::AsyncRuntime;
let module_wat = r#"
(module
(import "test" "async_maybe_double" (func $async_maybe_double (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_async (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $async_maybe_double
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_async" (func $call_async))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = AsyncRuntime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host_async((), |builder| {
builder.interface("test")?.func_async_result(
"async_maybe_double",
|_ctx: packr::AsyncCtx<()>, input: Value| async move {
let result: Result<Value, Value> = match input {
Value::S64(n) if n < 0 => {
Err(Value::String("negative not allowed".to_string()))
}
Value::S64(n) => Ok(Value::S64(n * 2)),
other => Ok(other),
};
result
},
)?;
Ok(())
})
.await
.expect("instantiate");
let input = Value::S64(-5);
let output = instance
.call_with_value_async("call_async", &input)
.await
.expect("call");
match output {
Value::Result {
value: Err(inner), ..
} => {
assert_eq!(*inner, Value::String("negative not allowed".to_string()));
}
Value::Variant {
type_name,
case_name,
..
} => {
panic!(
"Expected Value::Result but got Value::Variant(type_name={}, case_name={})",
type_name, case_name
);
}
other => {
panic!("Expected Value::Result but got {:?}", other);
}
}
}
#[test]
fn test_func_typed_result_preserves_ok_type_on_err() {
use packr::abi::ValueType;
let module_wat = r#"
(module
(import "test" "fetch_bytes" (func $fetch_bytes (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_fetch (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $fetch_bytes
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_fetch" (func $call_fetch))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.interface("test")?.func_typed_result(
"fetch_bytes",
|_ctx: &mut packr::Ctx<'_, ()>, _input: Value| -> Result<Vec<u8>, String> {
Err("Resource not found".to_string())
},
)?;
Ok(())
})
.expect("instantiate");
let input = Value::String("some_url".to_string());
let output = instance
.call_with_value("call_fetch", &input)
.expect("call");
match output {
Value::Result {
ok_type,
err_type,
value: Err(inner),
} => {
assert_eq!(
ok_type,
ValueType::List(Box::new(ValueType::U8)),
"ok_type should be List<U8>, not {:?}",
ok_type
);
assert_eq!(err_type, ValueType::String);
assert_eq!(*inner, Value::String("Resource not found".to_string()));
}
other => {
panic!("Expected Value::Result with Err, got {:?}", other);
}
}
}
#[test]
fn test_func_typed_result_preserves_err_type_on_ok() {
use packr::abi::ValueType;
let module_wat = r#"
(module
(import "test" "process" (func $process (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $result_ptr_offset i32 (i32.const 16384))
(global $result_len_offset i32 (i32.const 16388))
(global $output_offset i32 (i32.const 16392))
(func $call_process (param $in_ptr i32) (param $in_len i32) (param $out_ptr_ptr i32) (param $out_len_ptr i32) (result i32)
(local $status i32)
(local $result_ptr i32)
(local $result_len i32)
(local.set $status
(call $process
(local.get $in_ptr)
(local.get $in_len)
(global.get $result_ptr_offset)
(global.get $result_len_offset)))
(if (i32.ne (local.get $status) (i32.const 0))
(then (return (local.get $status))))
(local.set $result_ptr (i32.load (global.get $result_ptr_offset)))
(local.set $result_len (i32.load (global.get $result_len_offset)))
(memory.copy (global.get $output_offset) (local.get $result_ptr) (local.get $result_len))
(i32.store (local.get $out_ptr_ptr) (global.get $output_offset))
(i32.store (local.get $out_len_ptr) (local.get $result_len))
(i32.const 0)
)
(export "call_process" (func $call_process))
)
"#;
let wasm_bytes = wat::parse_str(module_wat).expect("parse WAT");
let runtime = Runtime::new();
let module = runtime.load_module(&wasm_bytes).expect("load module");
let mut instance = module
.instantiate_with_host((), |builder| {
builder.interface("test")?.func_typed_result(
"process",
|_ctx: &mut packr::Ctx<'_, ()>, _input: Value| -> Result<String, Vec<u8>> {
Ok("Success!".to_string())
},
)?;
Ok(())
})
.expect("instantiate");
let input = Value::String("data".to_string());
let output = instance
.call_with_value("call_process", &input)
.expect("call");
match output {
Value::Result {
ok_type,
err_type,
value: Ok(inner),
} => {
assert_eq!(ok_type, ValueType::String);
assert_eq!(
err_type,
ValueType::List(Box::new(ValueType::U8)),
"err_type should be List<U8>, not {:?}",
err_type
);
assert_eq!(*inner, Value::String("Success!".to_string()));
}
other => {
panic!("Expected Value::Result with Ok, got {:?}", other);
}
}
}