bin-runner #36
1 changed files with 129 additions and 0 deletions
|
|
@ -3980,3 +3980,132 @@ fn wasm_actor_is_send() {
|
|||
fn assert_send<T: Send>() {}
|
||||
assert_send::<WasmActor>();
|
||||
}
|
||||
|
||||
// ── Guest writes to alloc pointer region before sending ─────────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_modifies_received_message_before_echoing() {
|
||||
// Guest receives a message, XORs each payload byte with 0xFF, then
|
||||
// echoes the modified payload. Verifies that the guest can mutate
|
||||
// linear memory and the modified data is what gets sent.
|
||||
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 $i i32)
|
||||
(local $payload_start i32)
|
||||
(local $payload_len i32)
|
||||
|
||||
;; payload starts at ptr+32, length is len-32
|
||||
(local.set $payload_start (i32.add (local.get $ptr) (i32.const 32)))
|
||||
(local.set $payload_len (i32.sub (local.get $len) (i32.const 32)))
|
||||
|
||||
;; Skip if no payload
|
||||
(br_if 0 (i32.lt_s (local.get $payload_len) (i32.const 1)))
|
||||
|
||||
;; XOR each byte with 0xFF
|
||||
(local.set $i (i32.const 0))
|
||||
(block $break
|
||||
(loop $loop
|
||||
(br_if $break (i32.ge_u (local.get $i) (local.get $payload_len)))
|
||||
(i32.store8
|
||||
(i32.add (local.get $payload_start) (local.get $i))
|
||||
(i32.xor
|
||||
(i32.load8_u (i32.add (local.get $payload_start) (local.get $i)))
|
||||
(i32.const 255)
|
||||
)
|
||||
)
|
||||
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||
(br $loop)
|
||||
)
|
||||
)
|
||||
|
||||
;; Send modified payload
|
||||
local.get $ptr
|
||||
local.get $payload_start
|
||||
local.get $payload_len
|
||||
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 = vec![0x00, 0x0F, 0xF0, 0xFF];
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("XOR transform should send");
|
||||
let expected: Vec<u8> = payload.iter().map(|b| b ^ 0xFF).collect();
|
||||
assert_eq!(received.0, expected, "payload should be XOR'd with 0xFF");
|
||||
}
|
||||
|
||||
// ── Stop and re-spawn at same conceptual slot ───────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn stop_and_respawn_same_type_repeatedly() {
|
||||
// Stop and respawn the same type of WASM actor 5 times.
|
||||
// Each new instance should work independently.
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let wasm_bytes = guest_wasm("echo");
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
|
||||
for round in 0u8..5 {
|
||||
let actor = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone())
|
||||
.build().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[round])).unwrap();
|
||||
rt.tick();
|
||||
let received = inbox.try_recv().expect("respawned actor should echo");
|
||||
assert_eq!(received.0, vec![round]);
|
||||
|
||||
rt.stop_actor(addr).unwrap();
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
}
|
||||
}
|
||||
|
||||
// ── WASM actor watches another WASM actor ───────────────────────────────────
|
||||
// Note: WasmActor doesn't implement on_actor_exit, so watch notifications
|
||||
// are received but unhandled (default no-op). The important thing is no crash.
|
||||
|
||||
#[test]
|
||||
fn wasm_actor_watching_another_wasm_actor_doesnt_crash() {
|
||||
// Two WASM actors. We can't make one watch the other through the WASM
|
||||
// ABI (ctx.watch isn't exposed to guests). But we can have a native
|
||||
// watcher confirm the runtime handles WASM actors in the watch system.
|
||||
// (Already covered by native_watcher_notified_when_wasm_actor_stops,
|
||||
// but let's verify with two WASM actors dying in sequence.)
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let wasm_bytes = guest_wasm("silent");
|
||||
|
||||
let actor_a = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone()).build().unwrap();
|
||||
let actor_b = WasmActorBuilder::new(engine, wasm_bytes).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr_a = rt.spawn(actor_a).unwrap();
|
||||
let addr_b = rt.spawn(actor_b).unwrap();
|
||||
rt.tick();
|
||||
|
||||
// Stop both in sequence
|
||||
rt.stop_actor(addr_a).unwrap();
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
rt.stop_actor(addr_b).unwrap();
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
// Both gone, no crash
|
||||
assert!(rt.send_to(addr_a, ByteMessage(vec![1])).is_err());
|
||||
assert!(rt.send_to(addr_b, ByteMessage(vec![1])).is_err());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue