From 3f9d27e1bd7f5079e1efe6a4e79588ff506d43a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:30:36 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2027=20=E2=80=94=20mixed=20outbox?= =?UTF-8?q?=20partial=20delivery,=20drop-oldest=20mailbox,=20full=20inbox?= =?UTF-8?q?=20drop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mixed_outbox_partial_delivery: guest sends to valid + garbage addresses in same handle, verifies only valid sends deliver and actor survives - drop_oldest_mailbox_with_wasm_actor: DropOldest policy keeps newest 3 of 5 messages - echo_to_full_inbox_silently_drops: bounded inbox capacity silently drops excess echoes All 119 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) 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"); +}