From b13a846b1f40d532a626cfa1cd38fe5557f481cc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:13:06 +0000 Subject: [PATCH] =?UTF-8?q?test(wasm-actor):=20cycle=2059=20=E2=80=94=20lo?= =?UTF-8?q?cal.tee,=2016KB=20payload,=20dual=20runtime,=20i64=20wrap=20(25?= =?UTF-8?q?5=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added 5 tests: local.tee instruction, 16KB payload round-trip, two independent runtimes with WASM actors, i64-to-i32 wrap instruction, random WAT variations property test. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 135 ++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 4a1e99e..c5dfa8c 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -8678,4 +8678,139 @@ fn builder_accepts_slice_reference() { let addr = rt.spawn(actor).unwrap(); rt.send_to(addr, ByteMessage(vec![])).unwrap(); rt.tick(); +} + +// ── Cycle 59 ───────────────────────────────────────────────────────────────── + +// Guest that uses local.tee instruction (sets local and leaves value on stack) +#[test] +fn guest_uses_local_tee() { + let wat = r#"(module + (memory (export "memory") 1) + (global $heap (mut i32) (i32.const 65536)) + (func (export "alloc") (param $len i32) (result i32) + global.get $heap + global.get $heap + local.get $len + i32.add + global.set $heap) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Use local.tee: ptr2 = alloc_start, also keep on stack + (local $ptr2 i32) + global.get $heap + local.tee $ptr2 + drop ;; just exercising the instruction + ) + )"#; + 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(b"tee".to_vec())).unwrap(); + rt.tick(); // no trap +} + +// Guest that writes a 16KB response — tests larger-than-page payloads round-trip +#[test] +fn sixteen_kb_payload_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(); + + let payload: Vec = (0..16384u32).map(|i| (i % 251) as u8).collect(); + rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("should receive 16KB echo"); + assert_eq!(resp.0, payload); +} + +// Two runtimes with WASM actors operating independently at the same time +#[test] +fn two_independent_runtimes_with_wasm_actors() { + let engine = SharedEngine::new().unwrap(); + let actor1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let actor2 = WasmActorBuilder::new(engine, guest_wasm("double")).build().unwrap(); + + let rt1 = Runtime::new(RuntimeConfig::default()); + let rt2 = Runtime::new(RuntimeConfig::default()); + + let inbox1 = rt1.new_inbox::().unwrap(); + let inbox2 = rt2.new_inbox::().unwrap(); + + let addr1 = rt1.spawn(actor1).unwrap(); + let addr2 = rt2.spawn(actor2).unwrap(); + + rt1.send_to(addr1, framed_msg(inbox1.addr(), b"A")).unwrap(); + rt2.send_to(addr2, framed_msg(inbox2.addr(), b"B")).unwrap(); + + rt1.tick(); + rt2.tick(); + + let msgs1: Vec<_> = std::iter::from_fn(|| inbox1.try_recv().map(|m| m.0)).collect(); + let msgs2: Vec<_> = std::iter::from_fn(|| inbox2.try_recv().map(|m| m.0)).collect(); + + assert_eq!(msgs1.len(), 1, "echo from rt1"); + assert_eq!(msgs2.len(), 2, "double from rt2"); + assert_eq!(msgs1[0], b"A"); + assert!(msgs2.iter().all(|m| m == b"B")); +} + +// Guest that uses i32.wrap_i64 instruction +#[test] +fn guest_uses_i64_to_i32_wrap() { + let wat = r#"(module + (memory (export "memory") 1) + (global $heap (mut i32) (i32.const 65536)) + (func (export "alloc") (param $len i32) (result i32) + global.get $heap + global.get $heap + local.get $len + i32.add + global.set $heap) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Wrap i64 to i32: 0x1_0000_00FF -> 0xFF + i64.const 4294967551 ;; 0x1_000000FF + i32.wrap_i64 + ;; result is 255, store at ptr + local.get $ptr + i32.store8 offset=0 + ) + )"#; + 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(b"W".to_vec())).unwrap(); + rt.tick(); // no trap, wrapping is well-defined +} + +// Property test: building from random subsets of valid WAT always succeeds or fails cleanly +proptest! { + #[test] + fn prop_random_valid_wat_variations_never_panic( + alloc_offset in 1024u32..60000, + pages in 1u32..5, + handle_body in proptest::bool::ANY, + ) { + let store_body = if handle_body { + "local.get $ptr\nlocal.get $ptr\ni32.load8_u\ni32.store8" + } else { + "" + }; + let wat = format!(r#"(module + (memory (export "memory") {pages}) + (func (export "alloc") (param $len i32) (result i32) + i32.const {alloc_offset}) + (func (export "handle") (param $ptr i32) (param $len i32) + {store_body}) + )"#); + let engine = SharedEngine::new().unwrap(); + let result = WasmActorBuilder::new(engine, wat::parse_str(&wat).unwrap()).build(); + assert!(result.is_ok(), "valid WAT should build"); + } } \ No newline at end of file