test(wasm-actor): cycle 63 — zero-len send, overlapping regions, alloc exhaustion (274 tests)
Added 4 tests: zero-length payload from guest, overlapping dest/payload regions, alloc exhaustion drops gracefully, stop all actors in runtime. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
c304ad5960
commit
8d020bbf1e
1 changed files with 141 additions and 0 deletions
|
|
@ -9223,4 +9223,145 @@ fn thirty_two_kb_payload_echo() {
|
|||
let resp = inbox.try_recv().expect("32KB echo");
|
||||
assert_eq!(resp.0.len(), 32768);
|
||||
assert_eq!(resp.0, payload);
|
||||
}
|
||||
|
||||
// ── Cycle 63 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// Guest sends with payload_len=0 but valid payload_ptr — zero-length payload delivered
|
||||
#[test]
|
||||
fn send_zero_length_payload_from_guest() {
|
||||
let wat = r#"(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param $len i32) (result i32) i32.const 1024)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Send with payload_len=0 — should deliver empty payload
|
||||
(call $send (local.get $ptr) (i32.const 0) (i32.const 0))
|
||||
)
|
||||
)"#;
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap())
|
||||
.build().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Frame message: first 32 bytes are dest (inbox addr)
|
||||
let mut msg = Vec::with_capacity(32);
|
||||
msg.extend_from_slice(&inbox.addr().0);
|
||||
rt.send_to(addr, ByteMessage(msg)).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let resp = inbox.try_recv().expect("zero-length payload should arrive");
|
||||
assert!(resp.0.is_empty(), "payload should be empty");
|
||||
}
|
||||
|
||||
// Guest sends with overlapping dest and payload regions (payload starts inside dest)
|
||||
#[test]
|
||||
fn send_with_overlapping_dest_and_payload_regions() {
|
||||
let wat = r#"(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param $len i32) (result i32) i32.const 1024)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; dest_ptr=1024, payload_ptr=1040 (inside the 32-byte dest region), payload_len=8
|
||||
;; This overlaps: dest is [1024..1056], payload is [1040..1048]
|
||||
(call $send (i32.const 1024) (i32.const 1040) (i32.const 8))
|
||||
)
|
||||
)"#;
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap())
|
||||
.build().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Write the inbox address into memory at offset 1024 (via the message)
|
||||
let mut msg = Vec::with_capacity(64);
|
||||
msg.extend_from_slice(&inbox.addr().0);
|
||||
msg.extend_from_slice(&[0u8; 32]); // padding
|
||||
rt.send_to(addr, ByteMessage(msg)).unwrap();
|
||||
rt.tick();
|
||||
|
||||
// The send should succeed — overlapping reads are fine (read-only)
|
||||
let resp = inbox.try_recv().expect("overlapping regions should work");
|
||||
assert_eq!(resp.0.len(), 8);
|
||||
}
|
||||
|
||||
// Sequential alloc exhaustion: echo actor with tiny heap eventually can't alloc
|
||||
#[test]
|
||||
fn alloc_exhaustion_drops_message_gracefully() {
|
||||
let wat = r#"(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
;; Tiny heap: starts at 60000, only 5536 bytes before page end
|
||||
(global $heap (mut i32) (i32.const 60000))
|
||||
(func (export "alloc") (param $len i32) (result i32)
|
||||
(local $ptr i32)
|
||||
(local.set $ptr (global.get $heap))
|
||||
;; Check if allocation would exceed page
|
||||
(if (i32.gt_u
|
||||
(i32.add (global.get $heap) (local.get $len))
|
||||
(i32.const 65536))
|
||||
(then (return (i32.const -1))) ;; OOM signal
|
||||
)
|
||||
(global.set $heap (i32.add (global.get $heap) (local.get $len)))
|
||||
(local.get $ptr)
|
||||
)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Echo: send first 32 bytes as dest, rest as payload
|
||||
(if (i32.ge_u (local.get $len) (i32.const 33))
|
||||
(then
|
||||
(call $send
|
||||
(local.get $ptr)
|
||||
(i32.add (local.get $ptr) (i32.const 32))
|
||||
(i32.sub (local.get $len) (i32.const 32))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)"#;
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap())
|
||||
.build().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Send messages that consume heap space
|
||||
for i in 0..100 {
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &vec![i as u8; 100])).unwrap();
|
||||
}
|
||||
rt.tick();
|
||||
|
||||
// Count how many were echoed (some should be dropped due to OOM)
|
||||
let delivered = std::iter::from_fn(|| inbox.try_recv()).count();
|
||||
assert!(delivered < 100, "some messages should be dropped due to OOM (got {delivered})");
|
||||
assert!(delivered > 0, "at least some messages should succeed");
|
||||
}
|
||||
|
||||
// Stop all actors in a runtime — runtime should be empty
|
||||
#[test]
|
||||
fn stop_all_wasm_actors_in_runtime() {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let mut addrs = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("silent"))
|
||||
.build().unwrap();
|
||||
addrs.push(rt.spawn(actor).unwrap());
|
||||
}
|
||||
rt.tick();
|
||||
|
||||
for addr in &addrs {
|
||||
rt.stop_actor(*addr);
|
||||
}
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
// All actors stopped — sending should fail or be silently dropped
|
||||
for addr in &addrs {
|
||||
let _ = rt.send_to(*addr, ByteMessage(vec![]));
|
||||
}
|
||||
rt.tick(); // no panics
|
||||
}
|
||||
Loading…
Reference in a new issue