bin-runner #36
1 changed files with 138 additions and 0 deletions
|
|
@ -5843,3 +5843,141 @@ proptest! {
|
|||
// No panic = success. Message either delivers or is silently dropped.
|
||||
}
|
||||
}
|
||||
|
||||
// ── Spawn WASM actor from inside native actor's handle ──────────────────────
|
||||
|
||||
#[test]
|
||||
fn native_handler_spawns_wasm_actor_inline() {
|
||||
// A native actor receives a message and spawns a WASM echo actor in its handler,
|
||||
// then sends a message to the newly spawned actor.
|
||||
struct InlineSpawner {
|
||||
engine: SharedEngine,
|
||||
wasm_bytes: Vec<u8>,
|
||||
result_inbox: ActorAddress,
|
||||
}
|
||||
impl ActorInterface for InlineSpawner {
|
||||
type Incoming = ByteMessage;
|
||||
type Response = ();
|
||||
fn handle(&mut self, ctx: &Ctx, _msg: ByteMessage) {
|
||||
let actor = WasmActorBuilder::new(self.engine.clone(), self.wasm_bytes.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
let wasm_addr = ctx.spawn(actor).unwrap();
|
||||
let _ = ctx.send(wasm_addr, framed_msg(&self.result_inbox, b"from-spawner"));
|
||||
}
|
||||
}
|
||||
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
|
||||
let spawner = InlineSpawner {
|
||||
engine,
|
||||
wasm_bytes: guest_wasm("echo"),
|
||||
result_inbox: *inbox.addr(),
|
||||
};
|
||||
let spawner_addr = rt.spawn(spawner).unwrap();
|
||||
|
||||
rt.send_to(spawner_addr, ByteMessage(vec![])).unwrap();
|
||||
rt.tick(); // spawner creates WASM actor + sends message
|
||||
rt.tick(); // WASM actor processes message, echoes to inbox
|
||||
|
||||
let msg = inbox.try_recv().expect("spawned WASM actor should echo to inbox");
|
||||
assert_eq!(msg.0, b"from-spawner");
|
||||
}
|
||||
|
||||
// ── WASM actor with local variables (stack manipulation) ────────────────────
|
||||
|
||||
#[test]
|
||||
fn handle_uses_many_locals() {
|
||||
// Guest uses several local variables for computation.
|
||||
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)
|
||||
(local $a i32) (local $b i32) (local $c i32) (local $d i32)
|
||||
(local.set $a (i32.const 10))
|
||||
(local.set $b (i32.const 20))
|
||||
(local.set $c (i32.add (local.get $a) (local.get $b)))
|
||||
(local.set $d (i32.mul (local.get $c) (i32.const 2)))
|
||||
;; d = (10 + 20) * 2 = 60
|
||||
(i32.store8 (i32.const 200) (local.get $d))
|
||||
(call $send (local.get $ptr) (i32.const 200) (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"compute")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let msg = inbox.try_recv().unwrap();
|
||||
assert_eq!(msg.0[0], 60, "(10+20)*2 = 60");
|
||||
}
|
||||
|
||||
// ── WASM actor after many ticks still functions (no resource leak) ──────────
|
||||
|
||||
#[test]
|
||||
fn wasm_actor_survives_1000_ticks() {
|
||||
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();
|
||||
|
||||
// Send a message every 100 ticks
|
||||
for i in 0u8..10 {
|
||||
// 100 empty ticks
|
||||
for _ in 0..100 {
|
||||
rt.tick();
|
||||
}
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap();
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
let mut received = Vec::new();
|
||||
while let Some(msg) = inbox.try_recv() {
|
||||
received.push(msg.0[0]);
|
||||
}
|
||||
let expected: Vec<u8> = (0..10).collect();
|
||||
assert_eq!(received, expected, "actor should still work after 1000+ ticks");
|
||||
}
|
||||
|
||||
// ── Send to dead actor — message silently dropped ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn send_to_dead_wasm_actor_silently_dropped() {
|
||||
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();
|
||||
|
||||
// Verify it works
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"alive")).unwrap();
|
||||
rt.tick();
|
||||
assert!(inbox.try_recv().is_some());
|
||||
|
||||
// Stop and cleanup
|
||||
rt.stop_actor(addr);
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
// Send to dead actor — should not panic
|
||||
let result = rt.send_to(addr, framed_msg(inbox.addr(), b"dead"));
|
||||
// Either returns Ok (message silently dropped) or Err (dead address)
|
||||
// Both are acceptable — the key is no panic
|
||||
drop(result);
|
||||
rt.tick();
|
||||
assert!(inbox.try_recv().is_none(), "dead actor should not deliver");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue