diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 10dfb72..ef4d6cb 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -6673,3 +6673,136 @@ fn wasm_actor_error_is_send_and_sync() { fn assert_send_sync() {} assert_send_sync::(); } + +// ── Message with all 256 byte values round-trips correctly ────────────────── + +#[test] +fn all_256_byte_values_round_trip() { + 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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // Send all 256 byte values as payload + let payload: Vec = (0..=255).collect(); + rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0.len(), 256, "should receive all 256 bytes"); + for (i, &byte) in msg.0.iter().enumerate() { + assert_eq!(byte, i as u8, "byte {i} should be {i}"); + } +} + +// ── Guest uses memory.fill to initialize a region ─────────────────────────── + +#[test] +fn guest_uses_memory_fill_for_response() { + // Guest fills a region with a constant byte and sends it. + 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) + ;; Fill 10 bytes at offset 200 with value 0x42 ('B') + (memory.fill (i32.const 200) (i32.const 0x42) (i32.const 10)) + (call $send (local.get $ptr) (i32.const 200) (i32.const 10)) + ) + ) + "#; + 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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"fill")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0, vec![0x42; 10], "should receive 10 'B' bytes"); +} + +// ── WASM actor processes messages after watcher is installed ───────────────── + +#[test] +fn wasm_actor_works_normally_while_being_watched() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + + let exit_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let exit_clone = exit_count.clone(); + + struct WatchAndCount2 { + target: Option, + count: std::sync::Arc, + } + impl ActorInterface for WatchAndCount2 { + type Incoming = ByteMessage; + type Response = (); + fn handle(&mut self, ctx: &Ctx, _msg: ByteMessage) { + if let Some(t) = self.target.take() { + ctx.watch(t); + } + } + fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) { + self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + } + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let wasm_addr = rt.spawn(actor).unwrap(); + + let watcher = WatchAndCount2 { target: Some(wasm_addr), count: exit_clone }; + let watcher_addr = rt.spawn(watcher).unwrap(); + + // Install the watch + rt.send_to(watcher_addr, ByteMessage(vec![])).unwrap(); + rt.tick(); + + // WASM actor should still work normally + for i in 0u8..5 { + rt.send_to(wasm_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]); + } + assert_eq!(received, vec![0, 1, 2, 3, 4], "actor should work normally while watched"); + + // Stop it — watcher should be notified + rt.stop_actor(wasm_addr); + rt.tick(); + rt.tick(); + assert_eq!(exit_count.load(std::sync::atomic::Ordering::SeqCst), 1); +} + +// ── Double guest processes 0xFF payload (boundary byte value) ─────────────── + +#[test] +fn double_guest_with_max_byte_value() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("double")).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[0xFF])).unwrap(); + rt.tick(); + + let msg1 = inbox.try_recv().expect("first double"); + let msg2 = inbox.try_recv().expect("second double"); + assert_eq!(msg1.0, vec![0xFF]); + assert_eq!(msg2.0, vec![0xFF]); + assert!(inbox.try_recv().is_none(), "exactly two copies"); +}