test: Cycle 4 — start trap, self-send, amplification, overlapping send
- Module with trapping start function correctly rejected by builder - Self-send feedback loop works (echo to own address, relay on next tick) - Guest sending 10 messages in one handle: all 10 delivered via outbox - Overlapping dest_ptr and payload_ptr in send: reads are independent Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
1be0281056
commit
06aadeb911
1 changed files with 140 additions and 0 deletions
|
|
@ -924,6 +924,146 @@ fn actor_survives_multiple_sequential_traps() {
|
|||
assert_eq!(received.0, payload);
|
||||
}
|
||||
|
||||
// ── Builder: module with start function that traps ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn module_with_trapping_start_function_returns_error() {
|
||||
// WASM modules can have a (start) function that runs during instantiation.
|
||||
// If it traps, build() should return an error.
|
||||
let wat = r#"
|
||||
(module
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param i32 i32))
|
||||
(func $init unreachable)
|
||||
(start $init)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let result = WasmActorBuilder::new(engine, wasm).build();
|
||||
assert!(result.is_err(), "module with trapping start function should fail to build");
|
||||
}
|
||||
|
||||
// ── Self-send: guest sends message back to own address ───────────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_self_send_creates_feedback_loop() {
|
||||
// Echo guest sends its payload to a destination. If we set the dest
|
||||
// to the actor's OWN address, it creates a feedback loop. The actor
|
||||
// should process the self-sent message on the next tick.
|
||||
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();
|
||||
|
||||
// Frame: dest=self, payload=[inbox_addr | "hello"]
|
||||
// Echo will send [inbox_addr | "hello"] back to itself.
|
||||
// On next tick, it receives [inbox_addr | "hello"], echoes "hello" to inbox.
|
||||
let inner_msg = framed_msg(inbox.addr(), b"hello");
|
||||
let self_msg = framed_msg(&addr, &inner_msg.0);
|
||||
|
||||
rt.send_to(addr, self_msg).unwrap();
|
||||
rt.tick(); // actor echoes inner_msg to self
|
||||
rt.tick(); // actor receives inner_msg, echoes "hello" to inbox
|
||||
|
||||
let received = inbox.try_recv().expect("should receive after self-send loop");
|
||||
assert_eq!(received.0, b"hello");
|
||||
}
|
||||
|
||||
// ── Amplification: guest sends many messages in one handle ───────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_sending_many_messages_in_one_handle_all_delivered() {
|
||||
// A guest that calls swactor.send N times in a single handle call.
|
||||
// All N messages should be delivered via the outbox drain.
|
||||
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)
|
||||
;; Send 10 messages, each with 0-byte payload
|
||||
;; dest_ptr = $ptr (first 32 bytes of the incoming message)
|
||||
(local $i i32)
|
||||
(local.set $i (i32.const 0))
|
||||
(block $break
|
||||
(loop $loop
|
||||
(br_if $break (i32.ge_u (local.get $i) (i32.const 10)))
|
||||
(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"x")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let mut count = 0;
|
||||
while inbox.try_recv().is_some() {
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(count, 10, "guest should have sent exactly 10 messages");
|
||||
}
|
||||
|
||||
// ── Overlapping send regions: dest_ptr and payload_ptr overlap ───────────────
|
||||
|
||||
#[test]
|
||||
fn overlapping_dest_and_payload_in_send_works() {
|
||||
// Guest calls send with dest_ptr=0, payload_ptr=16, payload_len=32.
|
||||
// The dest region [0..32] and payload region [16..48] overlap.
|
||||
// Both are read-only in the host, so this should work without corruption.
|
||||
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)
|
||||
;; Overlapping regions
|
||||
local.get $ptr ;; dest_ptr (first 32 bytes of message)
|
||||
local.get $ptr
|
||||
i32.const 16
|
||||
i32.add ;; payload_ptr = ptr + 16 (overlaps with dest)
|
||||
i32.const 32 ;; payload_len = 32
|
||||
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();
|
||||
|
||||
// Build a message where bytes [0..32] = inbox addr, [32..] = payload
|
||||
// Guest reads dest from [0..32] (inbox addr) and payload from [16..48]
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"overlap-test-padding!")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("overlapping send should deliver");
|
||||
assert_eq!(received.0.len(), 32, "payload should be 32 bytes from overlapping region");
|
||||
}
|
||||
|
||||
// ── Multi-worker: WASM actors across threads ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue