From 00051f85d8b6f9f41a7e5b81e4210749ae16d22e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:42:51 +0000 Subject: [PATCH] =?UTF-8?q?test(wasm-actor):=20cycle=2077=20=E2=80=94=20re?= =?UTF-8?q?verse+echo,=20shared=20budget,=20f64,=20replacement=20cycle=20(?= =?UTF-8?q?341=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added 4 tests: guest reverses payload then echoes, actors share budget setting, f64 arithmetic, 5-round actor replacement cycle. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 5e61af3..a84860f 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11135,4 +11135,127 @@ fn guest_early_return_from_handle() { rt.tick(); let resp = inbox.try_recv().expect("long message should echo"); assert_eq!(resp.0, b"long-enough"); +} + +// ── Cycle 77 ───────────────────────────────────────────────────────────────── + +// Guest that responds with byte at each index: response[0] = payload[len-1], etc. +// (reverse payload using a loop, then send) +#[test] +fn guest_reverses_and_echoes_payload() { + let wat = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (global $heap (mut i32) (i32.const 2048)) + (func (export "alloc") (param $len i32) (result i32) + (local $ptr i32) + (local.set $ptr (global.get $heap)) + (global.set $heap (i32.add (global.get $heap) (local.get $len))) + (local.get $ptr) + ) + (func (export "handle") (param $ptr i32) (param $len i32) + (local $i i32) + (local $payload_start i32) + (local $payload_len i32) + (local $out_ptr i32) + ;; Need >= 33 bytes + (if (i32.lt_u (local.get $len) (i32.const 33)) (then return)) + (local.set $payload_start (i32.add (local.get $ptr) (i32.const 32))) + (local.set $payload_len (i32.sub (local.get $len) (i32.const 32))) + ;; Allocate output buffer at offset 900 + (local.set $out_ptr (i32.const 900)) + ;; Reverse loop + (local.set $i (i32.const 0)) + (block $exit + (loop $loop + (br_if $exit (i32.ge_u (local.get $i) (local.get $payload_len))) + (i32.store8 + (i32.add (local.get $out_ptr) (local.get $i)) + (i32.load8_u + (i32.add (local.get $payload_start) + (i32.sub (i32.sub (local.get $payload_len) (i32.const 1)) (local.get $i))))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $loop) + ) + ) + (call $send (local.get $ptr) (local.get $out_ptr) (local.get $payload_len)) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .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"abcde")).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("reversed echo"); + assert_eq!(resp.0, b"edcba"); +} + +// Multiple actors with different budgets in same runtime +#[test] +fn actors_share_single_budget_setting() { + let engine = SharedEngine::new().unwrap(); + let e1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let e2 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let mut cfg = RuntimeConfig::default(); + cfg.actor_message_budget = 3; + let rt = Runtime::new(cfg); + let inbox = rt.new_inbox::().unwrap(); + let a1 = rt.spawn(e1).unwrap(); + let a2 = rt.spawn(e2).unwrap(); + + // Send 5 to each + for i in 0u8..5 { + rt.send_to(a1, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.send_to(a2, framed_msg(inbox.addr(), &[i + 100])).unwrap(); + } + rt.tick(); + + // With budget=3, each actor processes at most 3 per tick + let count = std::iter::from_fn(|| inbox.try_recv()).count(); + assert!(count <= 6, "at most 3 per actor × 2 actors = 6, got {count}"); + assert!(count >= 2, "at least 1 per actor"); +} + +// Guest uses f64 arithmetic +#[test] +fn guest_f64_arithmetic() { + 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) + (f64.store (local.get $ptr) + (f64.mul (f64.const 2.5) (f64.const 4.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(vec![0u8; 8])).unwrap(); + rt.tick(); // stores 10.0 as f64 +} + +// Spawn, send, stop, respawn with new actor — complete replacement +#[test] +fn actor_replacement_cycle() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + for round in 0..5u8 { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), &[round])).unwrap(); + rt.tick(); + let resp = inbox.try_recv().expect("each round should echo"); + assert_eq!(resp.0, vec![round]); + rt.stop_actor(addr); + rt.tick(); + } } \ No newline at end of file