From d92e2a5499a46ba5043e6bfea2d3a7fe4bd95e82 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:13:31 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2017=20=E2=80=94=20truncated=20WA?= =?UTF-8?q?SM,=20no-import=20module,=204-thread=20stress,=20i32::MIN,=20du?= =?UTF-8?q?al=20watcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 170 ++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 407705e..769c5f3 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -3014,3 +3014,173 @@ fn alloc_alternates_between_failure_and_success() { assert_eq!(echoed, 3, "should echo on even-numbered alloc calls only"); } + +// ── Truncated WASM: module bytes cut mid-section ──────────────────────────── + +#[test] +fn truncated_wasm_bytes_returns_error() { + // Take a valid WASM module and truncate it. Should fail to compile. + let valid_wasm = guest_wasm("echo"); + let truncated = valid_wasm[..valid_wasm.len() / 2].to_vec(); + + let engine = SharedEngine::new().unwrap(); + let result = WasmActorBuilder::new(engine, truncated).build(); + assert!(result.is_err(), "truncated WASM should fail to compile"); +} + +// ── Module with no import of swactor.send: handle that never sends ────────── + +#[test] +fn module_without_send_import_can_still_process_messages() { + // A module that doesn't import swactor.send at all. + // It should build successfully (linker defines send but module doesn't import it). + // Handle can process messages without sending. + let wat = r#" + (module + (memory (export "memory") 1) + (func (export "alloc") (param i32) (result i32) i32.const 256) + (func (export "handle") (param i32 i32) + ;; Process the message but never send anything + ;; (No import of swactor.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 addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap(); + rt.tick(); + + // Actor processed message, didn't send anything, survives + rt.send_to(addr, ByteMessage(vec![4, 5, 6])).unwrap(); + rt.tick(); +} + +// ── Multi-worker stress: 10 WASM actors on 4-thread runtime ───────────────── + +#[test] +fn ten_wasm_actors_on_four_thread_runtime() { + // Spawn 10 WASM echo actors on a 4-thread runtime, send a message to each, + // and verify all responses arrive. + let engine = SharedEngine::new().unwrap(); + let wasm_bytes = guest_wasm("echo"); + + let config = RuntimeConfig { + num_threads: 4, + ..RuntimeConfig::default() + }; + let rt = Runtime::new(config); + let inbox = rt.new_inbox::().unwrap(); + + let mut addrs = Vec::new(); + for _ in 0..10 { + let actor = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone()) + .build() + .unwrap(); + addrs.push(rt.spawn(actor).unwrap()); + } + + for (i, addr) in addrs.iter().enumerate() { + rt.send_to(*addr, framed_msg(inbox.addr(), &[i as u8])).unwrap(); + } + + let handle = rt.run().unwrap(); + + // Poll for all 10 responses + let mut received = Vec::new(); + for _ in 0..40 { + std::thread::sleep(std::time::Duration::from_millis(25)); + while let Some(msg) = inbox.try_recv() { + received.push(msg.0[0]); + } + if received.len() == 10 { + break; + } + } + + handle.shutdown(); + + received.sort(); + assert_eq!(received, (0..10u8).collect::>(), "all 10 actors should echo"); +} + +// ── alloc with i32::MIN: most negative value ──────────────────────────────── + +#[test] +fn alloc_returns_i32_min_drops_message_actor_survives() { + 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 -2147483648 ;; i32::MIN + ) + (func (export "handle") (param i32 i32)) + ) + "#; + 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 addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, ByteMessage(vec![1])).unwrap(); + rt.tick(); // ptr < 0 guard catches i32::MIN + + rt.send_to(addr, ByteMessage(vec![2])).unwrap(); + rt.tick(); +} + +// ── Watch integration: native watcher + WASM watcher observing same death ─── + +struct DeathCounter { + count: std::sync::Arc, +} + +#[derive(Clone)] +struct WatchAddr(ActorAddress); + +impl ActorInterface for DeathCounter { + type Incoming = WatchAddr; + type Response = (); + fn handle(&mut self, ctx: &Ctx, msg: WatchAddr) { + ctx.watch(msg.0); + } + fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) { + self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } +} + +#[test] +fn two_watchers_both_notified_when_wasm_actor_dies() { + let engine = SharedEngine::new().unwrap(); + let target = WasmActorBuilder::new(engine, guest_wasm("silent")) + .build() + .unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + + let count_a = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let count_b = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let target_addr = rt.spawn(target).unwrap(); + let watcher_a = rt.spawn(DeathCounter { count: count_a.clone() }).unwrap(); + let watcher_b = rt.spawn(DeathCounter { count: count_b.clone() }).unwrap(); + + // Both watchers watch the target + rt.send_to(watcher_a, WatchAddr(target_addr)).unwrap(); + rt.send_to(watcher_b, WatchAddr(target_addr)).unwrap(); + for _ in 0..3 { rt.tick(); } + + // Kill the target + rt.stop_actor(target_addr).unwrap(); + for _ in 0..5 { rt.tick(); } + + assert_eq!(count_a.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher A should be notified"); + assert_eq!(count_b.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher B should be notified"); +}