test: Cycle 15 — outbox flood (1000 msgs), mixed actor cleanup, i32::MAX alloc, size-varied fuzz
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
d41ceafba9
commit
4a0cde4b41
1 changed files with 139 additions and 0 deletions
|
|
@ -2736,3 +2736,142 @@ fn byte_message_traits() {
|
||||||
let debug_str = format!("{:?}", msg1);
|
let debug_str = format!("{:?}", msg1);
|
||||||
assert!(debug_str.contains("ByteMessage"));
|
assert!(debug_str.contains("ByteMessage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Malicious guest: massive outbox (memory exhaustion defense) ─────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn guest_sending_1000_messages_in_one_handle_all_delivered() {
|
||||||
|
// A malicious guest could flood the outbox with thousands of messages.
|
||||||
|
// The host should handle this without crashing. Each message is small.
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
(local $i i32)
|
||||||
|
(local.set $i (i32.const 0))
|
||||||
|
(block $break
|
||||||
|
(loop $loop
|
||||||
|
(br_if $break (i32.ge_u (local.get $i) (i32.const 1000)))
|
||||||
|
(call $send (local.get $ptr) (i32.const 32) (i32.const 0))
|
||||||
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||||
|
(br $loop)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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"flood")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
while inbox.try_recv().is_some() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(count, 1000, "all 1000 messages should be delivered");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Interleaved message types: ByteMessage + watch in same tick ─────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasm_actor_processes_messages_and_receives_watch_notification() {
|
||||||
|
// WASM echo actor processes a message and then receives a watch
|
||||||
|
// notification for a stopped actor — both in a short sequence.
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let silent = WasmActorBuilder::new(engine, guest_wasm("silent"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
|
||||||
|
let echo_addr = rt.spawn(echo).unwrap();
|
||||||
|
let silent_addr = rt.spawn(silent).unwrap();
|
||||||
|
rt.tick(); // let actors initialize
|
||||||
|
|
||||||
|
// Echo processes a message
|
||||||
|
rt.send_to(echo_addr, framed_msg(inbox.addr(), b"before-death")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
let received = inbox.try_recv().expect("echo should work before death notification");
|
||||||
|
assert_eq!(received.0, b"before-death");
|
||||||
|
|
||||||
|
// Stop silent actor — echo doesn't watch it, so no notification expected
|
||||||
|
// But this tests that the runtime handles mixed actor types during cleanup
|
||||||
|
rt.stop_actor(silent_addr).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
// Echo still works after another actor died
|
||||||
|
rt.send_to(echo_addr, framed_msg(inbox.addr(), b"after-death")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
let received = inbox.try_recv().expect("echo should work after other actor dies");
|
||||||
|
assert_eq!(received.0, b"after-death");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Alloc returns i32::MAX: maximum positive value ──────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_returns_i32_max_drops_message_actor_survives() {
|
||||||
|
// alloc returns i32::MAX (2147483647). (ptr as usize).saturating_add(len)
|
||||||
|
// produces a huge value, bounds check rejects. Actor survives.
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32)
|
||||||
|
i32.const 2147483647
|
||||||
|
)
|
||||||
|
(func (export "handle") (param i32 i32))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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 addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
// Actor survives
|
||||||
|
rt.send_to(addr, ByteMessage(vec![4])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Property: echo preserves message integrity under varied sizes ───────────
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_echo_preserves_payloads_of_varied_sizes(size in 1usize..2000) {
|
||||||
|
// Messages of varying sizes should echo perfectly through the pipeline.
|
||||||
|
// Tests allocation alignment and copy correctness at many sizes.
|
||||||
|
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();
|
||||||
|
|
||||||
|
let payload: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let received = inbox.try_recv().expect("echo should return payload");
|
||||||
|
prop_assert_eq!(received.0, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue