test: Cycle 27 — mixed outbox partial delivery, drop-oldest mailbox, full inbox drop

- 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
This commit is contained in:
Claude 2026-02-13 09:30:36 +00:00
parent 57ff6f1d81
commit 3f9d27e1bd

View file

@ -4213,3 +4213,134 @@ fn double_guest_with_minimal_payload() {
assert!(second.0.is_empty(), "payload should be empty"); assert!(second.0.is_empty(), "payload should be empty");
assert!(inbox.try_recv().is_none(), "exactly two messages"); 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::<ByteMessage>().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::<ByteMessage>().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::<ByteMessage>().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");
}