diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index c4f37b6..4dcbf5f 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11854,4 +11854,138 @@ fn echo_single_byte_payload_integrity() { for (i, &b) in responses.iter().enumerate() { assert_eq!(b, i as u8); } +} + +// ── Cycle 83 ───────────────────────────────────────────────────────────────── + +// Spawn echo, send 1000 messages, verify all received over multiple ticks +#[test] +fn thousand_message_echo_stress() { + 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(); + + for i in 0u16..1000 { + rt.send_to(addr, framed_msg(inbox.addr(), &i.to_le_bytes())).unwrap(); + } + for _ in 0..50 { + rt.tick(); + } + + let count = std::iter::from_fn(|| inbox.try_recv()).count(); + assert_eq!(count, 1000, "all 1000 messages echoed"); +} + +// Guest with type annotations on all locals (verbose WAT) +#[test] +fn guest_with_many_typed_locals() { + let wat = r#"(module + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (local $a i32) (local $b i32) (local $c i32) + (local $d i64) (local $e f32) (local $f f64) + (local.set $a (local.get $len)) + (local.set $b (i32.mul (local.get $a) (i32.const 2))) + (local.set $c (i32.add (local.get $a) (local.get $b))) + (local.set $d (i64.extend_i32_u (local.get $c))) + (local.set $e (f32.convert_i32_s (local.get $c))) + (local.set $f (f64.promote_f32 (local.get $e))) + (i32.store (local.get $ptr) (local.get $c)) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, ByteMessage(vec![0u8; 4])).unwrap(); + rt.tick(); +} + +// Same actor handles empty and non-empty messages alternately +#[test] +fn alternate_empty_and_nonempty_messages() { + 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(); + + for i in 0..10 { + if i % 2 == 0 { + rt.send_to(addr, ByteMessage(vec![])).unwrap(); // empty + } else { + rt.send_to(addr, framed_msg(inbox.addr(), &[i as u8])).unwrap(); + } + } + rt.tick(); + + // Only the non-empty framed messages should produce echo responses + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 5, "5 framed messages echoed"); +} + +// WasmActorError variants have distinct Display representations +#[test] +fn error_variants_have_distinct_display() { + let missing = WasmActorError::MissingExport("memory"); + let wasmtime_err = WasmActorError::Wasmtime(wasmtime::Error::msg("test error")); + + let s1 = format!("{missing}"); + let s2 = format!("{wasmtime_err}"); + + assert_ne!(s1, s2, "error variants should have different display"); + assert!(s1.contains("memory")); + assert!(s2.contains("test error")); +} + +// Two different WAT modules on same runtime — heterogeneous actors +#[test] +fn heterogeneous_wat_actors_on_same_runtime() { + // Actor 1: echoes + let wat_echo = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (if (i32.ge_u (local.get $len) (i32.const 33)) + (then (call $send (local.get $ptr) (i32.add (local.get $ptr) (i32.const 32)) + (i32.sub (local.get $len) (i32.const 32)))))) + )"#; + // Actor 2: always sends byte 0x42 + let wat_const = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (data (i32.const 900) "\42") + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (if (i32.ge_u (local.get $len) (i32.const 32)) + (then (call $send (local.get $ptr) (i32.const 900) (i32.const 1))))) + )"#; + + let engine = SharedEngine::new().unwrap(); + let a1 = WasmActorBuilder::new(engine.clone(), wat::parse_str(wat_echo).unwrap()) + .build().unwrap(); + let a2 = WasmActorBuilder::new(engine, wat::parse_str(wat_const).unwrap()) + .build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr1 = rt.spawn(a1).unwrap(); + let addr2 = rt.spawn(a2).unwrap(); + + rt.send_to(addr1, framed_msg(inbox.addr(), b"hello")).unwrap(); + let mut msg2 = Vec::new(); + msg2.extend_from_slice(&inbox.addr().0); + rt.send_to(addr2, ByteMessage(msg2)).unwrap(); + rt.tick(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 2); + // One is "hello", other is [0x42] + assert!(msgs.iter().any(|m| m == b"hello")); + assert!(msgs.iter().any(|m| m == &[0x42])); } \ No newline at end of file