test: Cycle 17 — truncated WASM, no-import module, 4-thread stress, i32::MIN, dual watcher
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
fd0181291c
commit
d92e2a5499
1 changed files with 170 additions and 0 deletions
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue