test(wasm-actor): cycle 60 — three destinations, tick-per-msg, 10-page memory (260 tests)

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
This commit is contained in:
Claude 2026-02-13 10:14:00 +00:00
parent b13a846b1f
commit 77529b75eb

View file

@ -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::<ByteMessage>().unwrap();
let inbox_b = rt.new_inbox::<ByteMessage>().unwrap();
let inbox_c = rt.new_inbox::<ByteMessage>().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::<ByteMessage>().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::<ByteMessage>().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");
}