From 4e743479eedc2e12f8aafaece278846ce022f17d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:41:31 +0000 Subject: [PATCH] =?UTF-8?q?test(wasm-actor):=20cycle=2076=20=E2=80=94=20du?= =?UTF-8?q?al-address=20send,=20500=20messages,=20early=20return=20(337=20?= =?UTF-8?q?tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added 4 tests: guest sends to two addresses in one handle, engine clone builds independent actors, 500 messages in one tick, guest early return from handle. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 133 ++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index cba9a32..5e61af3 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11002,4 +11002,137 @@ fn echo_after_hundred_idle_ticks() { rt.send_to(addr, framed_msg(inbox.addr(), b"after")).unwrap(); rt.tick(); assert_eq!(inbox.try_recv().unwrap().0, b"after"); +} + +// ── Cycle 76 ───────────────────────────────────────────────────────────────── + +// Guest with complex send pattern: send to two different addresses in one handle +#[test] +fn guest_sends_to_two_addresses_in_one_handle() { + let wat = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Message layout: [addr1:32][addr2:32][payload:rest] + ;; Send payload to both addresses + (if (i32.ge_u (local.get $len) (i32.const 65)) + (then + ;; Send to addr1 + (call $send + (local.get $ptr) + (i32.add (local.get $ptr) (i32.const 64)) + (i32.sub (local.get $len) (i32.const 64)) + ) + ;; Send to addr2 + (call $send + (i32.add (local.get $ptr) (i32.const 32)) + (i32.add (local.get $ptr) (i32.const 64)) + (i32.sub (local.get $len) (i32.const 64)) + ) + ) + ) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox1 = rt.new_inbox::().unwrap(); + let inbox2 = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + let mut msg = Vec::new(); + msg.extend_from_slice(&inbox1.addr().0); // addr1 + msg.extend_from_slice(&inbox2.addr().0); // addr2 + msg.extend_from_slice(b"shared-data"); // payload + rt.send_to(addr, ByteMessage(msg)).unwrap(); + rt.tick(); + + let r1 = inbox1.try_recv().expect("inbox1 should receive"); + let r2 = inbox2.try_recv().expect("inbox2 should receive"); + assert_eq!(r1.0, b"shared-data"); + assert_eq!(r2.0, b"shared-data"); +} + +// Build actor, clone engine, build another actor — verify independence +#[test] +fn engine_clone_builds_independent_actors() { + let engine = SharedEngine::new().unwrap(); + let clone = engine.clone(); + let a1 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let a2 = WasmActorBuilder::new(clone, guest_wasm("double")).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr1 = rt.spawn(a1).unwrap(); + let addr2 = rt.spawn(a2).unwrap(); + + rt.send_to(addr1, framed_msg(inbox.addr(), b"A")).unwrap(); + rt.send_to(addr2, framed_msg(inbox.addr(), b"B")).unwrap(); + rt.tick(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 3, "1 echo + 2 double = 3"); +} + +// Guest handles 500 messages in a single tick +#[test] +fn five_hundred_messages_in_one_tick() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + for i in 0..500u16 { + let payload = i.to_le_bytes().to_vec(); + rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap(); + } + // May need multiple ticks depending on budget + for _ in 0..20 { + rt.tick(); + } + + let msgs: Vec<_> = std::iter::from_fn(|| inbox.try_recv()).collect(); + assert_eq!(msgs.len(), 500, "all 500 messages echoed"); +} + +// Guest with return in the middle of handle — early exit +#[test] +fn guest_early_return_from_handle() { + let wat = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; If len < 33, return early (no send) + (if (i32.lt_u (local.get $len) (i32.const 33)) + (then return) + ) + ;; Only reaches here for long messages + (call $send + (local.get $ptr) + (i32.add (local.get $ptr) (i32.const 32)) + (i32.sub (local.get $len) (i32.const 32)) + ) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // Short message — no response + rt.send_to(addr, ByteMessage(vec![0u8; 10])).unwrap(); + rt.tick(); + assert!(inbox.try_recv().is_none()); + + // Long message — response + rt.send_to(addr, framed_msg(inbox.addr(), b"long-enough")).unwrap(); + rt.tick(); + let resp = inbox.try_recv().expect("long message should echo"); + assert_eq!(resp.0, b"long-enough"); } \ No newline at end of file