From a340cd0b294597300c47c2852547a0688025dc28 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:08:17 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2057=20=E2=80=94=20immutable=20gl?= =?UTF-8?q?obal,=20multi=20round-trip,=20builder=20consume,=20empty=20tick?= =?UTF-8?q?=20order?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - guest_with_immutable_global: i32 global constant used in response - multiple_echo_round_trips: 5 sequential send-receive-send cycles - builder_is_consumed_on_build: verify builder ownership semantics - empty_tick_between_sends_preserves_order: A-B-C with empty ticks between All 244 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 0683108..41915e0 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -8405,4 +8405,100 @@ fn drop_runtime_with_live_actors() { // Drop runtime without stopping actors — should not panic or leak drop(rt); +} + +// ── Guest with immutable global ───────────────────────────────────────────── + +#[test] +fn guest_with_immutable_global() { + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (global $magic i32 (i32.const 0xBE)) + + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + (i32.store8 (i32.const 200) (global.get $magic)) + (call $send (local.get $ptr) (i32.const 200) (i32.const 1)) + ) + ) + "#; + 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"g")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0[0], 0xBE, "immutable global should return configured value"); +} + +// ── Multiple echo round trips (message ping-pong through actor) ───────────── + +#[test] +fn multiple_echo_round_trips() { + 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, receive, send the response back to actor, repeat 5 times + let mut current_payload = b"ping-0".to_vec(); + for i in 0..5 { + rt.send_to(addr, framed_msg(inbox.addr(), ¤t_payload)).unwrap(); + rt.tick(); + let msg = inbox.try_recv().unwrap_or_else(|| panic!("round {i} should echo")); + assert_eq!(msg.0, current_payload); + current_payload = format!("ping-{}", i + 1).into_bytes(); + } +} + +// ── WasmActorBuilder consumed on build — verify not Clone ─────────────────── + +#[test] +fn builder_is_consumed_on_build() { + // This test verifies the builder pattern by building twice from same config. + // Each build call consumes the builder. + let engine = SharedEngine::new().unwrap(); + let wasm = guest_wasm("echo"); + + let builder1 = WasmActorBuilder::new(engine.clone(), wasm.clone()); + let _actor1 = builder1.build().unwrap(); + // builder1 is consumed — can't call build again (compile error if tried) + + let builder2 = WasmActorBuilder::new(engine, wasm); + let _actor2 = builder2.build().unwrap(); + // Both built successfully from same config +} + +// ── Empty tick between messages doesn't lose them ─────────────────────────── + +#[test] +fn empty_tick_between_sends_preserves_order() { + 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(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"A")).unwrap(); + rt.tick(); + rt.tick(); // empty tick + rt.send_to(addr, framed_msg(inbox.addr(), b"B")).unwrap(); + rt.tick(); + rt.tick(); // empty tick + rt.send_to(addr, framed_msg(inbox.addr(), b"C")).unwrap(); + rt.tick(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs, vec![b"A".to_vec(), b"B".to_vec(), b"C".to_vec()]); } \ No newline at end of file