diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 8b49808..cba9a32 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -10875,4 +10875,131 @@ fn build_error_display_contains_export_name() { .build().err().expect("should fail"); let msg = format!("{err}"); assert!(msg.contains("alloc"), "error should mention missing 'alloc' export: {msg}"); +} + +// ── Cycle 75 ───────────────────────────────────────────────────────────────── + +// Guest with max function params — handle still only takes (i32, i32) though +// Extra internal functions can have many params +#[test] +fn guest_internal_function_with_many_params() { + let wat = r#"(module + (memory (export "memory") 1) + (func $helper (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + ;; Sum all 8 params + (i32.add (local.get 0) (i32.add (local.get 1) (i32.add (local.get 2) + (i32.add (local.get 3) (i32.add (local.get 4) (i32.add (local.get 5) + (i32.add (local.get 6) (local.get 7)))))))) + ) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (i32.store (local.get $ptr) + (call $helper + (i32.const 1) (i32.const 2) (i32.const 3) (i32.const 4) + (i32.const 5) (i32.const 6) (i32.const 7) (i32.const 8))) + ) + )"#; + 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(); // stores 36 at ptr +} + +// Echo actor relays message through native forwarder back to inbox +#[test] +fn wasm_to_native_to_inbox_relay() { + let engine = SharedEngine::new().unwrap(); + let echo = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + // Native forwarder: receives ByteMessage, sends to inbox + struct NativeForward(ActorAddress); + impl ActorInterface for NativeForward { + type Incoming = ByteMessage; + type Response = (); + fn handle(&mut self, ctx: &Ctx, msg: ByteMessage) { + let _ = ctx.send(self.0, msg); + } + } + + let fwd_addr = rt.spawn(NativeForward(*inbox.addr())).unwrap(); + let echo_addr = rt.spawn(echo).unwrap(); + + // Send to echo, echo sends to forwarder, forwarder sends to inbox + rt.send_to(echo_addr, framed_msg(&fwd_addr, &inbox.addr().0.to_vec())).unwrap(); + rt.tick(); // echo → forwarder + rt.tick(); // forwarder → inbox + + let resp = inbox.try_recv().expect("relayed through native forwarder"); + assert_eq!(resp.0, inbox.addr().0.to_vec()); +} + +// Property: any message size from 0 to 10000 round-trips through echo +proptest! { + #[test] + fn prop_any_message_size_round_trips(size in 0usize..10000) { + 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(); + + let payload = vec![0x42u8; size]; + rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("echo should respond"); + assert_eq!(resp.0.len(), size); + } +} + +// Guest with i32 conversion: extend 16-bit signed +#[test] +fn guest_sign_extend_16() { + 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) + ;; Sign-extend 16-bit value: 0xFFFF -> -1 + (i32.store (local.get $ptr) + (i32.extend16_s (i32.const 0xFFFF)) + ) + ) + )"#; + 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(); +} + +// Echo actor processes message, then gets more after 100 idle ticks +#[test] +fn echo_after_hundred_idle_ticks() { + 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(); + + // First message + rt.send_to(addr, framed_msg(inbox.addr(), b"before")).unwrap(); + rt.tick(); + assert_eq!(inbox.try_recv().unwrap().0, b"before"); + + // 100 idle ticks + for _ in 0..100 { + rt.tick(); + } + + // Second message — still works + rt.send_to(addr, framed_msg(inbox.addr(), b"after")).unwrap(); + rt.tick(); + assert_eq!(inbox.try_recv().unwrap().0, b"after"); } \ No newline at end of file