From c8bf78231b8ae30a0d629e46722c3221d8b78cc8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:50:34 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2042=20=E2=80=94=20internal=20cal?= =?UTF-8?q?ls,=20raw=20WAT,=20build-discard,=20float=20ops,=20empty=20byte?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - module_with_internal_function_calls: helper functions (double, add_ten) composed in handle - build_from_raw_wat_bytes: WAT-compiled silent actor works - build_and_discard_100_actors: WasmActor drops cleanly 100 times - handle_uses_floating_point: f64 arithmetic with i32.trunc_f64_s - empty_wasm_bytes_error: empty Vec produces build error All 180 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 135 ++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) 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"); +}