bin-runner #36
1 changed files with 170 additions and 0 deletions
|
|
@ -1889,3 +1889,173 @@ proptest! {
|
|||
prop_assert!(result.is_ok(), "actor must survive any send args: dest={dest_ptr} payload_ptr={payload_ptr} len={payload_len}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Guest sends to two different destinations in one handle ──────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_sends_to_two_destinations_both_delivered() {
|
||||
// Guest calls send twice with different destinations.
|
||||
// Both messages should be delivered in order.
|
||||
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)
|
||||
;; First send: dest from bytes [0..32], 1-byte payload "A" at offset 200
|
||||
(i32.store8 (i32.const 200) (i32.const 65)) ;; 'A'
|
||||
local.get $ptr
|
||||
i32.const 200
|
||||
i32.const 1
|
||||
call $send
|
||||
|
||||
;; Second send: dest from bytes [32..64], 1-byte payload "B" at offset 201
|
||||
(i32.store8 (i32.const 201) (i32.const 66)) ;; 'B'
|
||||
local.get $ptr
|
||||
i32.const 32
|
||||
i32.add ;; second dest address at offset 32
|
||||
i32.const 201
|
||||
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_a = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let inbox_b = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Build message with TWO destination addresses: [inbox_a_addr | inbox_b_addr | ...]
|
||||
let mut msg_bytes = Vec::new();
|
||||
msg_bytes.extend_from_slice(&inbox_a.addr().0);
|
||||
msg_bytes.extend_from_slice(&inbox_b.addr().0);
|
||||
msg_bytes.extend_from_slice(b"extra-padding");
|
||||
rt.send_to(addr, ByteMessage(msg_bytes)).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let recv_a = inbox_a.try_recv().expect("inbox_a should receive");
|
||||
assert_eq!(recv_a.0, b"A");
|
||||
let recv_b = inbox_b.try_recv().expect("inbox_b should receive");
|
||||
assert_eq!(recv_b.0, b"B");
|
||||
}
|
||||
|
||||
// ── Guest overwrites memory after send — outbox should have a copy ──────────
|
||||
|
||||
#[test]
|
||||
fn guest_overwriting_memory_after_send_does_not_corrupt_outbox() {
|
||||
// Guest calls send (which copies data into outbox), then overwrites
|
||||
// the same memory region. The outbox entry should be unaffected.
|
||||
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)
|
||||
;; Write "OK" at offset 200-201
|
||||
(i32.store8 (i32.const 200) (i32.const 79)) ;; 'O'
|
||||
(i32.store8 (i32.const 201) (i32.const 75)) ;; 'K'
|
||||
|
||||
;; Send payload from [200..202]
|
||||
local.get $ptr
|
||||
i32.const 200
|
||||
i32.const 2
|
||||
call $send
|
||||
|
||||
;; Now overwrite those bytes with "XX"
|
||||
(i32.store8 (i32.const 200) (i32.const 88)) ;; 'X'
|
||||
(i32.store8 (i32.const 201) (i32.const 88)) ;; 'X'
|
||||
)
|
||||
)
|
||||
"#;
|
||||
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"x")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("should receive original payload");
|
||||
assert_eq!(received.0, b"OK", "outbox should have copy, not overwritten data");
|
||||
}
|
||||
|
||||
// ── Large payload: near-capacity message through full pipeline ──────────────
|
||||
|
||||
#[test]
|
||||
fn large_payload_near_memory_capacity() {
|
||||
// Send a 60000-byte payload through the echo pipeline. This is close
|
||||
// to the 64KiB memory limit. The bump allocator needs enough space.
|
||||
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();
|
||||
|
||||
// 32-byte address + payload must fit in alloc. Echo guest starts alloc
|
||||
// at offset 1024, so we have 64512 bytes. 32 + payload must be ≤ 64512.
|
||||
// Use a 1000-byte payload (well within limits) for a realistic large message.
|
||||
let payload: Vec<u8> = (0..1000).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("large payload should echo");
|
||||
assert_eq!(received.0, payload);
|
||||
}
|
||||
|
||||
// ── alloc grows memory, returns pointer in new region ───────────────────────
|
||||
|
||||
#[test]
|
||||
fn alloc_that_grows_memory_works() {
|
||||
// alloc calls memory.grow before returning a pointer in the new region.
|
||||
// The host's bounds check uses memory.data_mut() AFTER alloc returns,
|
||||
// so it should see the grown memory.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1) ;; starts with 1 page (65536 bytes)
|
||||
|
||||
(func (export "alloc") (param $size i32) (result i32)
|
||||
;; Grow memory by 1 page, return pointer in the new region
|
||||
(drop (memory.grow (i32.const 1)))
|
||||
i32.const 65536 ;; start of new page
|
||||
)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Echo: send payload back to dest
|
||||
local.get $ptr
|
||||
local.get $ptr
|
||||
i32.const 32
|
||||
i32.add
|
||||
local.get $len
|
||||
i32.const 32
|
||||
i32.sub
|
||||
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();
|
||||
|
||||
let payload = b"grown-alloc";
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), payload)).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("alloc in grown region should work");
|
||||
assert_eq!(received.0, payload);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue