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 d92e2a5499 - Show all commits

View file

@ -3014,3 +3014,173 @@ fn alloc_alternates_between_failure_and_success() {
assert_eq!(echoed, 3, "should echo on even-numbered alloc calls only");
}
// ── Truncated WASM: module bytes cut mid-section ────────────────────────────
#[test]
fn truncated_wasm_bytes_returns_error() {
// Take a valid WASM module and truncate it. Should fail to compile.
let valid_wasm = guest_wasm("echo");
let truncated = valid_wasm[..valid_wasm.len() / 2].to_vec();
let engine = SharedEngine::new().unwrap();
let result = WasmActorBuilder::new(engine, truncated).build();
assert!(result.is_err(), "truncated WASM should fail to compile");
}
// ── Module with no import of swactor.send: handle that never sends ──────────
#[test]
fn module_without_send_import_can_still_process_messages() {
// A module that doesn't import swactor.send at all.
// It should build successfully (linker defines send but module doesn't import it).
// Handle can process messages without sending.
let wat = r#"
(module
(memory (export "memory") 1)
(func (export "alloc") (param i32) (result i32) i32.const 256)
(func (export "handle") (param i32 i32)
;; Process the message but never send anything
;; (No import of swactor.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();
// Actor processed message, didn't send anything, survives
rt.send_to(addr, ByteMessage(vec![4, 5, 6])).unwrap();
rt.tick();
}
// ── Multi-worker stress: 10 WASM actors on 4-thread runtime ─────────────────
#[test]
fn ten_wasm_actors_on_four_thread_runtime() {
// Spawn 10 WASM echo actors on a 4-thread runtime, send a message to each,
// and verify all responses arrive.
let engine = SharedEngine::new().unwrap();
let wasm_bytes = guest_wasm("echo");
let config = RuntimeConfig {
num_threads: 4,
..RuntimeConfig::default()
};
let rt = Runtime::new(config);
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let mut addrs = Vec::new();
for _ in 0..10 {
let actor = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone())
.build()
.unwrap();
addrs.push(rt.spawn(actor).unwrap());
}
for (i, addr) in addrs.iter().enumerate() {
rt.send_to(*addr, framed_msg(inbox.addr(), &[i as u8])).unwrap();
}
let handle = rt.run().unwrap();
// Poll for all 10 responses
let mut received = Vec::new();
for _ in 0..40 {
std::thread::sleep(std::time::Duration::from_millis(25));
while let Some(msg) = inbox.try_recv() {
received.push(msg.0[0]);
}
if received.len() == 10 {
break;
}
}
handle.shutdown();
received.sort();
assert_eq!(received, (0..10u8).collect::<Vec<_>>(), "all 10 actors should echo");
}
// ── alloc with i32::MIN: most negative value ────────────────────────────────
#[test]
fn alloc_returns_i32_min_drops_message_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 -2147483648 ;; i32::MIN
)
(func (export "handle") (param i32 i32))
)
"#;
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(); // ptr < 0 guard catches i32::MIN
rt.send_to(addr, ByteMessage(vec![2])).unwrap();
rt.tick();
}
// ── Watch integration: native watcher + WASM watcher observing same death ───
struct DeathCounter {
count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
#[derive(Clone)]
struct WatchAddr(ActorAddress);
impl ActorInterface for DeathCounter {
type Incoming = WatchAddr;
type Response = ();
fn handle(&mut self, ctx: &Ctx, msg: WatchAddr) {
ctx.watch(msg.0);
}
fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) {
self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
#[test]
fn two_watchers_both_notified_when_wasm_actor_dies() {
let engine = SharedEngine::new().unwrap();
let target = WasmActorBuilder::new(engine, guest_wasm("silent"))
.build()
.unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let count_a = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let count_b = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let target_addr = rt.spawn(target).unwrap();
let watcher_a = rt.spawn(DeathCounter { count: count_a.clone() }).unwrap();
let watcher_b = rt.spawn(DeathCounter { count: count_b.clone() }).unwrap();
// Both watchers watch the target
rt.send_to(watcher_a, WatchAddr(target_addr)).unwrap();
rt.send_to(watcher_b, WatchAddr(target_addr)).unwrap();
for _ in 0..3 { rt.tick(); }
// Kill the target
rt.stop_actor(target_addr).unwrap();
for _ in 0..5 { rt.tick(); }
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");
}