test: Cycle 37 — mutual watch, select instr, wasm-wasm-native relay, dest addr fuzz
- mutual_watch_wasm_actors: watcher notified when watched WASM actor stops - select_instruction_in_handle: wasm select (ternary) based on payload presence - wasm_to_wasm_to_native_relay: 3-layer echo1→echo2→inbox relay - prop_any_dest_address_bytes_never_panic: arbitrary 32-byte dest address never panics All 158 tests pass (11 property tests). No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
0997cf06ed
commit
945f64ca0b
1 changed files with 150 additions and 0 deletions
|
|
@ -5693,3 +5693,153 @@ fn br_table_dispatch_in_handle() {
|
|||
assert_eq!(msg1.0, b"O");
|
||||
assert_eq!(msg2.0, b"D");
|
||||
}
|
||||
|
||||
// ── Two WASM actors watching each other — one stops, other gets notified ────
|
||||
|
||||
#[test]
|
||||
fn mutual_watch_wasm_actors() {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let echo1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
|
||||
let echo2 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||
|
||||
let exit_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
let exit_count_clone = exit_count.clone();
|
||||
|
||||
struct WatchAndCount {
|
||||
target: Option<ActorAddress>,
|
||||
count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||
}
|
||||
impl ActorInterface for WatchAndCount {
|
||||
type Incoming = ByteMessage;
|
||||
type Response = ();
|
||||
fn handle(&mut self, ctx: &Ctx, _msg: ByteMessage) {
|
||||
if let Some(t) = self.target.take() {
|
||||
ctx.watch(t);
|
||||
}
|
||||
}
|
||||
fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) {
|
||||
self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let a1 = rt.spawn(echo1).unwrap();
|
||||
let a2 = rt.spawn(echo2).unwrap();
|
||||
|
||||
// Spawn a watcher that watches a1
|
||||
let watcher = WatchAndCount {
|
||||
target: Some(a1),
|
||||
count: exit_count_clone,
|
||||
};
|
||||
let watcher_addr = rt.spawn(watcher).unwrap();
|
||||
|
||||
// Trigger the watcher to install the watch
|
||||
rt.send_to(watcher_addr, ByteMessage(vec![])).unwrap();
|
||||
rt.tick();
|
||||
|
||||
// Stop a1 — watcher should be notified
|
||||
rt.stop_actor(a1);
|
||||
rt.tick();
|
||||
rt.tick(); // death notification propagates
|
||||
|
||||
assert_eq!(
|
||||
exit_count.load(std::sync::atomic::Ordering::SeqCst), 1,
|
||||
"watcher should be notified when watched WASM actor stops"
|
||||
);
|
||||
|
||||
// a2 should still be alive and functional
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
rt.send_to(a2, framed_msg(inbox.addr(), b"alive")).unwrap();
|
||||
rt.tick();
|
||||
let msg = inbox.try_recv().expect("a2 should still be alive");
|
||||
assert_eq!(msg.0, b"alive");
|
||||
}
|
||||
|
||||
// ── Module with select instruction (ternary operator) ───────────────────────
|
||||
|
||||
#[test]
|
||||
fn select_instruction_in_handle() {
|
||||
// Guest uses `select` to choose between two values based on condition.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 4096)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; If payload > 32 bytes (has actual content), use 'Y', else 'N'
|
||||
(i32.store8 (i32.const 200)
|
||||
(select
|
||||
(i32.const 89) ;; 'Y' (true branch)
|
||||
(i32.const 78) ;; 'N' (false branch)
|
||||
(i32.gt_s (local.get $len) (i32.const 32))
|
||||
)
|
||||
)
|
||||
(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::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Message with payload (len > 32)
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"content")).unwrap();
|
||||
// Message without payload (len == 32)
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let msg1 = inbox.try_recv().unwrap();
|
||||
let msg2 = inbox.try_recv().unwrap();
|
||||
assert_eq!(msg1.0, b"Y", "message with content should select Y");
|
||||
assert_eq!(msg2.0, b"N", "message without content should select N");
|
||||
}
|
||||
|
||||
// ── WASM actor echoes to another WASM actor which echoes to native inbox ────
|
||||
|
||||
#[test]
|
||||
fn wasm_to_wasm_to_native_relay() {
|
||||
// echo1 → echo2 → inbox. Three-layer relay.
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let echo1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
|
||||
let echo2 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr2 = rt.spawn(echo2).unwrap();
|
||||
let addr1 = rt.spawn(echo1).unwrap();
|
||||
|
||||
// Send to echo1 with dest=echo2. Payload is a framed message for echo2→inbox.
|
||||
let inner_payload = framed_msg(inbox.addr(), b"relay-data");
|
||||
rt.send_to(addr1, framed_msg(&addr2, &inner_payload.0)).unwrap();
|
||||
rt.tick(); // echo1 sends to echo2
|
||||
rt.tick(); // echo2 sends to inbox
|
||||
|
||||
let msg = inbox.try_recv().expect("should receive relayed message");
|
||||
assert_eq!(msg.0, b"relay-data");
|
||||
}
|
||||
|
||||
// ── Property: any byte pattern in dest address never panics ─────────────────
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn prop_any_dest_address_bytes_never_panic(
|
||||
addr_bytes in proptest::collection::vec(proptest::num::u8::ANY, 32..=32),
|
||||
payload in proptest::collection::vec(proptest::num::u8::ANY, 0..64),
|
||||
) {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Construct a message with arbitrary 32-byte dest address + payload
|
||||
let mut msg_bytes = addr_bytes;
|
||||
msg_bytes.extend_from_slice(&payload);
|
||||
rt.send_to(addr, ByteMessage(msg_bytes)).unwrap();
|
||||
rt.tick();
|
||||
// No panic = success. Message either delivers or is silently dropped.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue