test: Cycle 41 — address-only msg, last byte, overlapping alloc, wrong name, fan-in
- handle_receives_just_address_no_payload: 32-byte message echos empty payload - guest_writes_last_byte_of_memory: write/read at offset 65535 - alloc_returns_overlapping_region: static alloc region, outbox snapshots correctly - memory_exported_with_wrong_name_fails: memory as "heap" not "memory" rejected - fan_in_from_multiple_native_senders: 5 inboxes all receive correct echo responses All 175 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
5c42cad8da
commit
8e8ea693a6
1 changed files with 140 additions and 0 deletions
|
|
@ -6260,3 +6260,143 @@ fn two_hundred_actors_from_same_engine() {
|
||||||
}
|
}
|
||||||
assert_eq!(count, 200, "all 200 actors should echo");
|
assert_eq!(count, 200, "all 200 actors should echo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Handle receives exactly 32 bytes (just address, no payload) ─────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handle_receives_just_address_no_payload() {
|
||||||
|
// Send a message that is exactly 32 bytes (just the address header).
|
||||||
|
// The echo guest will try to send payload of len-32=0 bytes.
|
||||||
|
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();
|
||||||
|
|
||||||
|
// framed_msg with empty payload = just 32-byte address
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), b"")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg = inbox.try_recv().expect("should receive empty echo");
|
||||||
|
assert!(msg.0.is_empty(), "echo of empty payload should be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Guest writes to last byte of linear memory ──────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn guest_writes_last_byte_of_memory() {
|
||||||
|
// Guest writes to offset 65535 (last byte of 1 page).
|
||||||
|
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 4096)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
;; Write to last byte of memory
|
||||||
|
(i32.store8 (i32.const 65535) (i32.const 77))
|
||||||
|
;; Send that byte
|
||||||
|
(call $send (local.get $ptr) (i32.const 65535) (i32.const 1))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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 msg = inbox.try_recv().unwrap();
|
||||||
|
assert_eq!(msg.0, vec![77], "should read from last byte of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Alloc returns ptr in middle of previously allocated region ───────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_returns_overlapping_region() {
|
||||||
|
// Guest's alloc always returns 4096 regardless of previous calls.
|
||||||
|
// When the host writes message bytes, they always go to the same spot.
|
||||||
|
// Second message in same tick overwrites first message's data.
|
||||||
|
// But outbox snapshots, so both sends deliver their respective data.
|
||||||
|
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 4096)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
;; Echo: send payload back
|
||||||
|
(call $send
|
||||||
|
(local.get $ptr)
|
||||||
|
(i32.add (local.get $ptr) (i32.const 32))
|
||||||
|
(i32.sub (local.get $len) (i32.const 32))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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"AAA")).unwrap();
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), b"BB")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg1 = inbox.try_recv().unwrap();
|
||||||
|
let msg2 = inbox.try_recv().unwrap();
|
||||||
|
// Both should reflect their original data despite using same alloc region
|
||||||
|
assert_eq!(msg1.0, b"AAA", "first echo should have first payload");
|
||||||
|
assert_eq!(msg2.0, b"BB", "second echo should have second payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Module exports memory with non-default name (should fail) ───────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn memory_exported_with_wrong_name_fails() {
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "heap") 1)
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 0)
|
||||||
|
(func (export "handle") (param i32 i32))
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let wasm = wat::parse_str(wat).unwrap();
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let result = WasmActorBuilder::new(engine, wasm).build();
|
||||||
|
assert!(result.is_err(), "memory exported as 'heap' should fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── WASM actor receives messages from multiple senders (fan-in) ─────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fan_in_from_multiple_native_senders() {
|
||||||
|
// 5 native actors all send to the same WASM echo actor.
|
||||||
|
// Echo sends responses back to their respective inboxes.
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let echo = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let echo_addr = rt.spawn(echo).unwrap();
|
||||||
|
|
||||||
|
let mut inboxes = Vec::new();
|
||||||
|
for i in 0u8..5 {
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
rt.send_to(echo_addr, framed_msg(inbox.addr(), &[i])).unwrap();
|
||||||
|
inboxes.push(inbox);
|
||||||
|
}
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
for (i, inbox) in inboxes.iter().enumerate() {
|
||||||
|
let msg = inbox.try_recv().unwrap_or_else(|| panic!("inbox {i} should receive"));
|
||||||
|
assert_eq!(msg.0, vec![i as u8], "inbox {i} should get correct payload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue