test: Cycle 25 — in-place XOR transform, stop-respawn cycle, sequential WASM actor shutdown

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
Claude 2026-02-13 09:25:40 +00:00
parent 185c7ed046
commit 4f1fb2cb64

View file

@ -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());
}