test: Cycle 31 — zero-length send, separate engines, 50-round stress, start function
- send_import_zero_length_payload_delivers_empty: payload_len=0 delivers empty msg - actors_from_separate_engines_coexist: two engines on same runtime work independently - rapid_spawn_send_stop_50_rounds: 50 sequential spawn-send-stop cycles all deliver - start_function_that_succeeds_allows_normal_operation: start initializes global, handle uses it All 135 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
55a8ddb97a
commit
0fa9ff0e77
1 changed files with 127 additions and 0 deletions
|
|
@ -4767,3 +4767,130 @@ fn global_counter_accumulates_across_messages() {
|
||||||
}
|
}
|
||||||
assert_eq!(counter_values, vec![1, 2, 3, 4, 5], "global state should persist across handle calls");
|
assert_eq!(counter_values, vec![1, 2, 3, 4, 5], "global state should persist across handle calls");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Send import: payload_len = 0 is valid zero-copy send ────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_import_zero_length_payload_delivers_empty() {
|
||||||
|
// Guest calls swactor.send with payload_len=0. This should deliver an
|
||||||
|
// empty payload (not a trap, not dropped).
|
||||||
|
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)
|
||||||
|
;; Send with payload_len=0
|
||||||
|
(call $send (local.get $ptr) (i32.const 0) (i32.const 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
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(), b"trigger")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg = inbox.try_recv().expect("zero-length send should deliver");
|
||||||
|
assert!(msg.0.is_empty(), "payload should be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Multiple engines with different configurations ──────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn actors_from_separate_engines_coexist() {
|
||||||
|
// Build two separate engines and spawn one actor from each.
|
||||||
|
// They should work independently on the same runtime.
|
||||||
|
let engine1 = SharedEngine::new().unwrap();
|
||||||
|
let engine2 = SharedEngine::new().unwrap();
|
||||||
|
let echo1 = WasmActorBuilder::new(engine1, guest_wasm("echo")).build().unwrap();
|
||||||
|
let echo2 = WasmActorBuilder::new(engine2, guest_wasm("echo")).build().unwrap();
|
||||||
|
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
let addr1 = rt.spawn(echo1).unwrap();
|
||||||
|
let addr2 = rt.spawn(echo2).unwrap();
|
||||||
|
|
||||||
|
rt.send_to(addr1, framed_msg(inbox.addr(), b"from-engine1")).unwrap();
|
||||||
|
rt.send_to(addr2, framed_msg(inbox.addr(), b"from-engine2")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let mut payloads: Vec<Vec<u8>> = Vec::new();
|
||||||
|
while let Some(msg) = inbox.try_recv() {
|
||||||
|
payloads.push(msg.0);
|
||||||
|
}
|
||||||
|
payloads.sort();
|
||||||
|
assert_eq!(payloads, vec![b"from-engine1".to_vec(), b"from-engine2".to_vec()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rapid spawn-send-stop stress test (50 rounds) ───────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rapid_spawn_send_stop_50_rounds() {
|
||||||
|
let engine = SharedEngine::new().unwrap();
|
||||||
|
let rt = Runtime::new(RuntimeConfig::default());
|
||||||
|
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||||
|
|
||||||
|
for round in 0u8..50 {
|
||||||
|
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();
|
||||||
|
rt.stop_actor(addr);
|
||||||
|
rt.tick(); // process stop
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut received = Vec::new();
|
||||||
|
while let Some(msg) = inbox.try_recv() {
|
||||||
|
received.push(msg.0[0]);
|
||||||
|
}
|
||||||
|
let expected: Vec<u8> = (0..50).collect();
|
||||||
|
assert_eq!(received, expected, "all 50 rounds should deliver");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Module with start function that succeeds ────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_function_that_succeeds_allows_normal_operation() {
|
||||||
|
// Module has a start function that initializes a global.
|
||||||
|
// After start, normal handle should work.
|
||||||
|
let wat = r#"
|
||||||
|
(module
|
||||||
|
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(global $initialized (mut i32) (i32.const 0))
|
||||||
|
|
||||||
|
(func $init
|
||||||
|
(global.set $initialized (i32.const 42))
|
||||||
|
)
|
||||||
|
(start $init)
|
||||||
|
|
||||||
|
(func (export "alloc") (param i32) (result i32) i32.const 4096)
|
||||||
|
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||||
|
;; Write initialized value as response
|
||||||
|
(i32.store8 (i32.const 200) (global.get $initialized))
|
||||||
|
(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(), b"check-init")).unwrap();
|
||||||
|
rt.tick();
|
||||||
|
|
||||||
|
let msg = inbox.try_recv().expect("should receive response after start");
|
||||||
|
assert_eq!(msg.0[0], 42, "start function should have initialized global to 42");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue