diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index b5d9cc1..c18dfd0 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -6400,3 +6400,138 @@ fn fan_in_from_multiple_native_senders() { assert_eq!(msg.0, vec![i as u8], "inbox {i} should get correct payload"); } } + +// ── Module with multiple functions calling each other ──────────────────────── + +#[test] +fn module_with_internal_function_calls() { + // Guest has helper functions called from handle. + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func $double (param $x i32) (result i32) + (i32.mul (local.get $x) (i32.const 2)) + ) + (func $add_ten (param $x i32) (result i32) + (i32.add (local.get $x) (i32.const 10)) + ) + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Read first payload byte, double it, add 10 + (i32.store8 (i32.const 200) + (call $add_ten + (call $double + (i32.load8_u (i32.add (local.get $ptr) (i32.const 32))) + ) + ) + ) + (call $send (local.get $ptr) (i32.const 200) (i32.const 1)) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // input=5: double(5)=10, add_ten(10)=20 + rt.send_to(addr, framed_msg(inbox.addr(), &[5])).unwrap(); + // input=100: double(100)=200, add_ten(200)=210 + rt.send_to(addr, framed_msg(inbox.addr(), &[100])).unwrap(); + rt.tick(); + + let msg1 = inbox.try_recv().unwrap(); + let msg2 = inbox.try_recv().unwrap(); + assert_eq!(msg1.0[0], 20); + assert_eq!(msg2.0[0], 210); +} + +// ── Build from pre-compiled WAT bytes (no guest dir needed) ───────────────── + +#[test] +fn build_from_raw_wat_bytes() { + let wat = r#" + (module + (memory (export "memory") 1) + (func (export "alloc") (param i32) (result i32) i32.const 0) + (func (export "handle") (param i32 i32)) + ) + "#; + // Convert WAT → WASM at runtime + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + // Verify it works (silent actor — no sends) + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"silent")).unwrap(); + rt.tick(); + assert!(inbox.try_recv().is_none(), "silent actor should not send anything"); +} + +// ── Rapidly build and discard actors without spawning ──────────────────────── + +#[test] +fn build_and_discard_100_actors() { + // Build 100 actors but don't spawn them. Tests that WasmActor drops cleanly. + let engine = SharedEngine::new().unwrap(); + for _ in 0..100 { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")) + .build() + .unwrap(); + drop(actor); + } + // No panic = success +} + +// ── Guest with f32/f64 floating point ops ─────────────────────────────────── + +#[test] +fn handle_uses_floating_point() { + // Guest performs f64 arithmetic and stores result as i32. + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Compute floor(3.14 * 10) = floor(31.4) = 31 + (i32.store8 (i32.const 200) + (i32.trunc_f64_s + (f64.mul (f64.const 3.14) (f64.const 10.0)) + ) + ) + (call $send (local.get $ptr) (i32.const 200) (i32.const 1)) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"float")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0[0], 31, "floor(3.14 * 10) = 31"); +} + +// ── Empty WASM bytes produces error ───────────────────────────────────────── + +#[test] +fn empty_wasm_bytes_error() { + let engine = SharedEngine::new().unwrap(); + let result = WasmActorBuilder::new(engine, Vec::::new()).build(); + assert!(result.is_err(), "empty bytes should fail"); +}