bin-runner #36

Merged
zacheryasc merged 103 commits from bin-runner into master 2026-02-13 14:11:40 +00:00
Showing only changes of commit 89a22f4b5f - Show all commits

View file

@ -3184,3 +3184,158 @@ fn two_watchers_both_notified_when_wasm_actor_dies() {
assert_eq!(count_a.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher A should be notified");
assert_eq!(count_b.load(std::sync::atomic::Ordering::SeqCst), 1, "watcher B should be notified");
}
// ── Division by zero: WASM trap, actor survives ─────────────────────────────
#[test]
fn guest_division_by_zero_traps_actor_survives() {
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 256)
(func (export "handle") (param $ptr i32) (param $len i32)
;; Division by zero is a trap in WASM
local.get $len
i32.const 0
i32.div_u
drop
)
)
"#;
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 addr = rt.spawn(actor).unwrap();
rt.send_to(addr, ByteMessage(vec![1])).unwrap();
rt.tick(); // div by zero trap
// Actor survives
rt.send_to(addr, ByteMessage(vec![2])).unwrap();
rt.tick();
}
// ── Module with extra exports: globals and extra functions ──────────────────
#[test]
fn module_with_extra_exports_builds_and_works() {
// Module exports extra globals and functions beyond the required ones.
// Builder should ignore them.
let wat = r#"
(module
(import "swactor" "send" (func $send (param i32 i32 i32)))
(memory (export "memory") 1)
(global (export "version") i32 (i32.const 42))
(global (export "magic") i64 (i64.const 12345))
(func (export "alloc") (param i32) (result i32) i32.const 256)
(func (export "handle") (param $ptr i32) (param $len i32)
(i32.store8 (i32.const 200) (i32.const 7))
local.get $ptr
i32.const 200
i32.const 1
call $send
)
(func (export "extra_func") (param i32) (result i32)
local.get 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"extras")).unwrap();
rt.tick();
let received = inbox.try_recv().expect("module with extras should work");
assert_eq!(received.0, vec![7]);
}
// ── Send with dest_ptr=0, all zeroes in memory: valid but unroutable ────────
#[test]
fn send_with_all_zero_dest_from_uninitialized_memory() {
// Guest reads dest address from offset 500 (uninitialized, all zeros).
// The zero address isn't routable. ctx.send fails silently.
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 256)
(func (export "handle") (param i32 i32)
;; Read dest from uninitialized region (offset 500, all zeros)
i32.const 500 ;; dest_ptr
i32.const 32 ;; payload_ptr
i32.const 1 ;; payload_len
call $send
)
)
"#;
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 addr = rt.spawn(actor).unwrap();
rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap();
rt.tick(); // send to zero-address fails silently
// Actor survives
rt.send_to(addr, ByteMessage(vec![4])).unwrap();
rt.tick();
}
// ── Property: WASM actor survives any sequence of operations ────────────────
proptest! {
#[test]
fn prop_actor_survives_any_operation_sequence(
ops in proptest::collection::vec(
prop_oneof![
Just("send"),
Just("empty"),
Just("large"),
],
1..20
)
) {
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();
for op in &ops {
match *op {
"send" => {
rt.send_to(addr, framed_msg(inbox.addr(), b"msg")).unwrap();
}
"empty" => {
rt.send_to(addr, ByteMessage(vec![])).unwrap();
}
"large" => {
rt.send_to(addr, ByteMessage(vec![0u8; 60000])).unwrap();
}
_ => unreachable!(),
}
rt.tick();
// Drain inbox
while inbox.try_recv().is_some() {}
}
// Actor should still be alive
let result = rt.send_to(addr, ByteMessage(vec![99]));
prop_assert!(result.is_ok(), "actor must survive any operation sequence");
}
}