test: Cycle 46 — 200 TEST MILESTONE: mixed actors, sums, determinism, lifecycle
- wasm_and_native_alternate_in_same_tick: 5 WASM + 5 native msgs in same tick - guest_sums_two_payload_bytes: sum computation with value duplication - single_byte_payload_echo: minimum meaningful payload round-trip - module_with_no_functions_fails: module with only memory export rejected - response_varies_by_message_size: 4 different sizes produce correct length reports - prop_echo_is_deterministic: same input to two fresh actors → same output - comprehensive_lifecycle_all_guest_types_200th: echo+double+silent lifecycle **200 tests pass** — 3 bugs found/fixed, 13 property tests, ~7,150 lines. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
2d24737026
commit
5e925a9fc1
1 changed files with 216 additions and 0 deletions
|
|
@ -6929,4 +6929,220 @@ fn idle_ticks_dont_affect_wasm_actor() {
|
||||||
|
|
||||||
let msg = inbox.try_recv().unwrap();
|
let msg = inbox.try_recv().unwrap();
|
||||||
assert_eq!(msg.0, b"after-idle");
|
assert_eq!(msg.0, b"after-idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── WASM + native actors alternate processing in same tick ──────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasm_and_native_alternate_in_same_tick() {
|
||||||
|
struct NativeEcho2;
|
||||||
|
impl ActorInterface for NativeEcho2 {
|
||||||
|
type Incoming = ByteMessage;
|
||||||
|
type Response = ();
|
||||||
|
fn handle(&mut self, ctx: &Ctx, msg: ByteMessage) {
|
||||||
|
if msg.0.len() >= 32 {
|
||||||
|
let mut addr_bytes = [0u8; 32];
|
||||||
|
addr_bytes.copy_from_slice(&msg.0[..32]);
|
||||||
|
let dest = ActorAddress(addr_bytes);
|
||||||
|
let payload = msg.0[32..].to_vec();
|
||||||
|
let _ = ctx.send(dest, ByteMessage(payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let wasm_echo = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
let wasm_addr = rt.spawn(wasm_echo).unwrap();
|
||||||
|
let native_addr = rt.spawn(NativeEcho2).unwrap();
|
||||||
|
|
||||||
|
for i in 0u8..10 {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
rt.send_to(wasm_addr, framed_msg(inbox.addr(), &[i])).unwrap();
|
||||||
|
} else {
|
||||||
|
rt.send_to(native_addr, framed_msg(inbox.addr(), &[i])).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let mut received = Vec::new();
|
||||||
|
while let Some(msg) = inbox.try_recv() {
|
||||||
|
received.push(msg.0[0]);
|
||||||
|
}
|
||||||
|
received.sort();
|
||||||
|
let expected: Vec<u8> = (0..10).collect();
|
||||||
|
assert_eq!(received, expected, "all 10 messages from both types should deliver");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Guest sums two payload bytes ────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn guest_sums_two_payload_bytes() {
|
||||||
|
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)
|
||||||
|
(local $val i32)
|
||||||
|
(local.set $val
|
||||||
|
(i32.add
|
||||||
|
(i32.load8_u (i32.add (local.get $ptr) (i32.const 32)))
|
||||||
|
(i32.load8_u (i32.add (local.get $ptr) (i32.const 33)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(i32.store8 (i32.const 200) (local.get $val))
|
||||||
|
(i32.store8 (i32.const 201) (local.get $val))
|
||||||
|
(call $send (local.get $ptr) (i32.const 200) (i32.const 2))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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();
|
||||||
|
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[30, 12])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg = inbox.try_recv().unwrap();
|
||||||
|
assert_eq!(msg.0, vec![42, 42], "30+12=42 duplicated");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single byte payload echo ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_byte_payload_echo() {
|
||||||
|
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::<ByteMessage>().unwrap();
|
||||||
|
let addr = rt.spawn(actor).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[0x42])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg = inbox.try_recv().unwrap();
|
||||||
|
assert_eq!(msg.0, vec![0x42]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Module with no functions fails ──────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_with_no_functions_fails() {
|
||||||
|
let wat = "(module (memory (export \"memory\") 1))";
|
||||||
|
let wasm = wat::parse_str(wat).unwrap();
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
assert!(WasmActorBuilder::new(engine, wasm).build().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Response varies by message size ─────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_varies_by_message_size() {
|
||||||
|
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)
|
||||||
|
(i32.store8 (i32.const 200) (i32.sub (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();
|
||||||
|
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[])).unwrap();
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[1])).unwrap();
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[0; 50])).unwrap();
|
||||||
|
rt.send_to(addr, framed_msg(inbox.addr(), &[0; 200])).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let sizes: Vec<u8> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0[0])).collect();
|
||||||
|
assert_eq!(sizes, vec![0, 1, 50, 200]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Property: echo is deterministic ─────────────────────────────────────────
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_echo_is_deterministic(
|
||||||
|
payload in proptest::collection::vec(proptest::num::u8::ANY, 0..128)
|
||||||
|
) {
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
|
||||||
|
let actor1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
|
||||||
|
let addr1 = rt.spawn(actor1).unwrap();
|
||||||
|
rt.send_to(addr1, framed_msg(inbox.addr(), &payload)).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
let result1 = inbox.try_recv().map(|m| m.0);
|
||||||
|
|
||||||
|
let actor2 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
|
||||||
|
let addr2 = rt.spawn(actor2).unwrap();
|
||||||
|
rt.send_to(addr2, framed_msg(inbox.addr(), &payload)).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
let result2 = inbox.try_recv().map(|m| m.0);
|
||||||
|
|
||||||
|
assert_eq!(result1, result2, "same input should produce same output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 200th test: comprehensive lifecycle with all guest types ────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comprehensive_lifecycle_all_guest_types_200th() {
|
||||||
|
// Spawn one of each guest type (echo, double, silent), send messages,
|
||||||
|
// verify outputs, stop them all, check cleanup.
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
|
||||||
|
let double = WasmActorBuilder::new(engine.clone(), guest_wasm("double")).build().unwrap();
|
||||||
|
let silent = WasmActorBuilder::new(engine, guest_wasm("silent")).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
|
||||||
|
let echo_addr = rt.spawn(echo).unwrap();
|
||||||
|
let double_addr = rt.spawn(double).unwrap();
|
||||||
|
let silent_addr = rt.spawn(silent).unwrap();
|
||||||
|
|
||||||
|
// Send to all three
|
||||||
|
rt.send_to(echo_addr, framed_msg(inbox.addr(), b"E")).unwrap();
|
||||||
|
rt.send_to(double_addr, framed_msg(inbox.addr(), b"D")).unwrap();
|
||||||
|
rt.send_to(silent_addr, framed_msg(inbox.addr(), b"S")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let mut payloads: Vec<Vec<u8>> = Vec::new();
|
||||||
|
while let Some(msg) = inbox.try_recv() {
|
||||||
|
payloads.push(msg.0);
|
||||||
|
}
|
||||||
|
payloads.sort();
|
||||||
|
// Echo → "E" (1x), Double → "D" (2x), Silent → nothing
|
||||||
|
assert_eq!(payloads, vec![b"D".to_vec(), b"D".to_vec(), b"E".to_vec()]);
|
||||||
|
|
||||||
|
// Stop all
|
||||||
|
rt.stop_actor(echo_addr);
|
||||||
|
rt.stop_actor(double_addr);
|
||||||
|
rt.stop_actor(silent_addr);
|
||||||
|
rt.tick();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
// Send to stopped actors — silently dropped
|
||||||
|
rt.send_to(echo_addr, framed_msg(inbox.addr(), b"gone")).ok();
|
||||||
|
rt.tick();
|
||||||
|
assert!(inbox.try_recv().is_none(), "stopped actors should not deliver");
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue