diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 60671e8..4f3962b 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -4213,3 +4213,134 @@ fn double_guest_with_minimal_payload() { assert!(second.0.is_empty(), "payload should be empty"); assert!(inbox.try_recv().is_none(), "exactly two messages"); } + +// ── Mixed outbox: some sends succeed, some fail ───────────────────────────── + +#[test] +fn mixed_outbox_partial_delivery() { + // Guest sends to a valid address (inbox) and an invalid address (garbage) + // in the same handle call. The valid send should deliver; the invalid one + // should silently fail. The actor should survive. + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + ;; Garbage address at offset 500 (all 0xDE bytes) + (data (i32.const 500) "\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de\de") + + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Send #1: to valid address (from message bytes) + (i32.store8 (i32.const 200) (i32.const 65)) ;; 'A' + local.get $ptr + i32.const 200 + i32.const 1 + call $send + + ;; Send #2: to garbage address at offset 500 + (i32.store8 (i32.const 201) (i32.const 66)) ;; 'B' + i32.const 500 ;; garbage dest + i32.const 201 + i32.const 1 + call $send + + ;; Send #3: back to valid address + (i32.store8 (i32.const 202) (i32.const 67)) ;; 'C' + local.get $ptr + i32.const 202 + i32.const 1 + call $send + ) + ) + "#; + 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"mixed-outbox")).unwrap(); + rt.tick(); + + // Should receive sends #1 and #3 (valid dest), but not #2 (garbage dest) + let mut received = Vec::new(); + while let Some(msg) = inbox.try_recv() { + received.push(msg.0[0]); + } + assert_eq!(received, vec![b'A', b'C'], "only valid-address sends should deliver"); +} + +// ── Drop-oldest mailbox policy with WASM actor ───────────────────────────── + +#[test] +fn drop_oldest_mailbox_with_wasm_actor() { + use swactor::runtime::MailboxOverflow; + + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")) + .build() + .unwrap(); + + let config = RuntimeConfig { + default_mailbox_capacity: 3, + mailbox_overflow: MailboxOverflow::DropOldest, + ..RuntimeConfig::default() + }; + let rt = Runtime::new(config); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // Send 5 messages before tick — DropOldest keeps the last 3 + for i in 0u8..5 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + rt.tick(); + + let mut received = Vec::new(); + while let Some(msg) = inbox.try_recv() { + received.push(msg.0[0]); + } + + assert_eq!(received.len(), 3, "should keep 3 messages"); + // DropOldest keeps the newest: [2, 3, 4] + assert_eq!(received, vec![2, 3, 4], "DropOldest should keep newest messages"); +} + +// ── WASM actor echoes to inbox, inbox full — message dropped ──────────────── + +#[test] +fn echo_to_full_inbox_silently_drops() { + // Echo sends to an inbox that has a bounded capacity. + // If the inbox is full, the send should silently fail. + use swactor::runtime::MailboxOverflow; + + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")) + .build() + .unwrap(); + + let config = RuntimeConfig { + default_mailbox_capacity: 2, + mailbox_overflow: MailboxOverflow::DropNewest, + ..RuntimeConfig::default() + }; + let rt = Runtime::new(config); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // Send 5 messages — actor has capacity 2, so only first 2 are kept + // Each message echoes to inbox (also capacity 2) + for i in 0u8..5 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + rt.tick(); + + // Inbox has capacity 2, so at most 2 messages received + let mut received = Vec::new(); + while let Some(msg) = inbox.try_recv() { + received.push(msg.0[0]); + } + assert!(received.len() <= 2, "inbox should be bounded to capacity 2"); +}