From 77529b75ebfee1fa66e58bcea4c6df95993389c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:14:00 +0000 Subject: [PATCH] =?UTF-8?q?test(wasm-actor):=20cycle=2060=20=E2=80=94=20th?= =?UTF-8?q?ree=20destinations,=20tick-per-msg,=2010-page=20memory=20(260?= =?UTF-8?q?=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added 5 tests: three destinations from three echo actors, one-message-per-tick for 10 ticks, 10-page initial memory, fill+copy together, stop with pending messages. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index c5dfa8c..bc9cec5 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -8813,4 +8813,131 @@ proptest! { let result = WasmActorBuilder::new(engine, wat::parse_str(&wat).unwrap()).build(); assert!(result.is_ok(), "valid WAT should build"); } +} + +// ── Cycle 60 ───────────────────────────────────────────────────────────────── + +// Guest that sends 3 messages to 3 different destinations in one handle +#[test] +fn guest_sends_to_three_destinations_in_one_handle() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox_a = rt.new_inbox::().unwrap(); + let inbox_b = rt.new_inbox::().unwrap(); + let inbox_c = rt.new_inbox::().unwrap(); + + // Build a message with 3 framed destinations: [addr_a][addr_b][addr_c] + "hi" + // The echo guest echoes entire payload to addr embedded in first 32 bytes. + // Instead, spawn 3 separate echo actors and send to each. + let e1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let e2 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let e3 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let a1 = rt.spawn(e1).unwrap(); + let a2 = rt.spawn(e2).unwrap(); + let a3 = rt.spawn(e3).unwrap(); + + rt.send_to(a1, framed_msg(inbox_a.addr(), b"msg-a")).unwrap(); + rt.send_to(a2, framed_msg(inbox_b.addr(), b"msg-b")).unwrap(); + rt.send_to(a3, framed_msg(inbox_c.addr(), b"msg-c")).unwrap(); + rt.tick(); + + assert_eq!(inbox_a.try_recv().unwrap().0, b"msg-a"); + assert_eq!(inbox_b.try_recv().unwrap().0, b"msg-b"); + assert_eq!(inbox_c.try_recv().unwrap().0, b"msg-c"); +} + +// Actor handles 10 messages across 10 ticks, one per tick +#[test] +fn one_message_per_tick_for_ten_ticks() { + 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 0u8..10 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.tick(); + let msg = inbox.try_recv().expect("should get reply each tick"); + assert_eq!(msg.0, vec![i]); + } +} + +// Guest with memory of 10 pages (640KB) — larger initial memory +#[test] +fn guest_with_ten_page_initial_memory() { + let wat = r#"(module + (memory (export "memory") 10) + (global $heap (mut i32) (i32.const 655360)) + (func (export "alloc") (param $len i32) (result i32) + global.get $heap + global.get $heap + local.get $len + i32.add + global.set $heap) + (func (export "handle") (param $ptr i32) (param $len i32)) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + + // Send a message — the large initial memory should work fine + let payload = vec![42u8; 1000]; + rt.send_to(addr, ByteMessage(payload)).unwrap(); + rt.tick(); +} + +// Guest with both memory.fill and memory.copy in same handle +#[test] +fn guest_uses_fill_and_copy_together() { + let wat = r#"(module + (memory (export "memory") 1) + (global $heap (mut i32) (i32.const 65536)) + (func (export "alloc") (param $len i32) (result i32) + global.get $heap + global.get $heap + local.get $len + i32.add + global.set $heap) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Fill 10 bytes at ptr+1000 with 0xAA + (memory.fill (i32.const 1000) (i32.const 0xAA) (i32.const 10)) + ;; Copy those 10 bytes to ptr+2000 + (memory.copy (i32.const 2000) (i32.const 1000) (i32.const 10)) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, ByteMessage(b"test".to_vec())).unwrap(); + rt.tick(); // no trap +} + +// Stop actor mid-stream: send 5, tick 1 (processes some), stop, tick again +#[test] +fn stop_actor_with_pending_messages_in_mailbox() { + 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 0u8..5 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + // Process first tick (may handle some or all) + rt.tick(); + // Stop — remaining messages are dropped + rt.stop_actor(addr); + rt.tick(); + // Actor should be dead now — no more processing + let count_before: usize = std::iter::from_fn(|| inbox.try_recv()).count(); + rt.tick(); + let count_after: usize = std::iter::from_fn(|| inbox.try_recv()).count(); + assert_eq!(count_after, 0, "no more messages after stop"); + assert!(count_before <= 5, "at most 5 echoes received"); } \ No newline at end of file