swactor/tests/runtime_stress.rs
Zachery Aaron Shores-Chmielewski df72b4ff60 chore(test): enforce complete local test barrier
Deny workspace warnings and lint suppressions, consolidate Rust and Python coverage under cargo xtask test with 60-second per-test limits, and remove stale flaky, stateful, Docker, and orphaned test artifacts.
2026-08-23 15:51:24 +04:00

236 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Runtime Stress Tests — delivery at scale, panic isolation, and sustained throughput.
//!
//! Covers: high-volume delivery, panic isolation under load, and sustained
//! throughput with no message loss. All tests are tick-driven (single worker).
pub mod common;
use common::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
// ── Tests ────────────────────────────────────────────────────────────────────
/// Single-worker runtime basics.
///
/// Story: A runtime driven by tick() does nothing before the first tick,
/// then processes messages correctly.
#[test]
fn runtime_basics() {
let (rt, mut host) = std_host(RuntimeConfig::default());
let addr = rt.spawn(PingPongActor).unwrap();
let inbox = rt.new_inbox::<Pong>().unwrap();
rt.send_to(
addr,
Ping {
reply_to: *inbox.addr(),
},
)
.unwrap();
assert!(inbox.try_recv().is_none(), "no processing before tick");
tick_n(&mut host, 2);
assert!(inbox.try_recv().is_some(), "tick() drives processing");
// Delegation (spawn child from handler) works on a single worker.
let addr2 = rt.spawn(DelegatorActor).unwrap();
let done_inbox = rt.new_inbox::<Done>().unwrap();
rt.send_to(
addr2,
Forward {
value: 3,
reply_to: *done_inbox.addr(),
},
)
.unwrap();
tick_n(&mut host, 3);
assert_eq!(
done_inbox.try_recv(),
Some(Done(6)),
"delegation delivers reply"
);
}
/// High-volume delivery.
///
/// Story: We throw large workloads at the runtime — 50 senders each firing
/// 100 messages at one receiver, 200 concurrent spawn+send pairs, and a
/// 50-level chain. All messages must be accounted for.
#[test]
fn high_volume_delivery() {
let cfg = || RuntimeConfig {
max_actors: 5_000,
channel_buffer_size: 10_000,
..Default::default()
};
// ── Part A: 50 senders × 100 messages → one receiver ──
{
let (rt, mut host) = std_host(cfg());
let counter = Arc::new(AtomicUsize::new(0));
let dummy = rt.new_inbox::<Pong>().unwrap();
let receiver = rt
.spawn(CountingPingActor {
counter: counter.clone(),
})
.unwrap();
let total_expected = 50 * 100;
for _ in 0..50 {
for _ in 0..100 {
rt.send_to(
receiver,
Ping {
reply_to: *dummy.addr(),
},
)
.unwrap();
}
}
tick_n(&mut host, 200);
let processed = counter.load(Ordering::SeqCst);
assert_eq!(
processed, total_expected,
"all 5000 messages delivered to single receiver"
);
}
// ── Part B: 200 concurrent spawn+send pairs ──
{
let (rt, mut host) = std_host(cfg());
let counter = Arc::new(AtomicUsize::new(0));
let dummy = rt.new_inbox::<Pong>().unwrap();
for _ in 0..200 {
let a = rt
.spawn(CountingPingActor {
counter: counter.clone(),
})
.unwrap();
rt.send_to(
a,
Ping {
reply_to: *dummy.addr(),
},
)
.unwrap();
}
tick_n(&mut host, 50);
let received = counter.load(Ordering::SeqCst);
assert_eq!(received, 200, "all 200 spawn+send pairs complete");
}
// ── Part C: 50-level chain ──
{
let (rt, mut host) = std_host(cfg());
let addr = rt.spawn(ChainActor).unwrap();
let inbox = rt.new_inbox::<Done>().unwrap();
rt.send_to(
addr,
ChainMsg {
remaining: 50,
depth: 0,
reply_to: *inbox.addr(),
},
)
.unwrap();
tick_n(&mut host, 100);
assert_eq!(inbox.try_recv(), Some(Done(50)), "50-level chain completes");
}
}
/// Panic isolation under load.
///
/// Story: 10 panicking actors and 10 healthy actors — every panic is isolated
/// and all 1000 healthy messages are still processed.
#[test]
fn panic_isolation_under_load() {
let (rt, mut host) = std_host(RuntimeConfig {
max_actors: 5_000,
channel_buffer_size: 10_000,
..Default::default()
});
let counter = Arc::new(AtomicUsize::new(0));
let dummy = rt.new_inbox::<Pong>().unwrap();
let mut panic_addrs = Vec::new();
let mut healthy_addrs = Vec::new();
for _ in 0..10 {
panic_addrs.push(rt.spawn(PanicActor).unwrap());
healthy_addrs.push(
rt.spawn(CountingPingActor {
counter: counter.clone(),
})
.unwrap(),
);
}
// Trigger panics and flood healthy actors.
for &addr in &panic_addrs {
rt.send_to(addr, PanicMsg).unwrap();
}
for &addr in &healthy_addrs {
for _ in 0..100 {
rt.send_to(
addr,
Ping {
reply_to: *dummy.addr(),
},
)
.unwrap();
}
}
tick_n(&mut host, 200);
let expected = 10 * 100;
let processed = counter.load(Ordering::SeqCst);
assert_eq!(
processed, expected,
"all {expected} healthy messages processed despite panicking peers"
);
}
/// Sustained throughput with no message loss.
///
/// Story: We send 10 batches of 100 messages, ticking between batches.
/// Each batch must make forward progress, and after draining, all 1000
/// messages are accounted for.
#[test]
fn sustained_throughput_no_message_loss() {
let (rt, mut host) = std_host(RuntimeConfig::default());
let counter = Arc::new(AtomicUsize::new(0));
let dummy = rt.new_inbox::<Pong>().unwrap();
let addr = rt
.spawn(CountingPingActor {
counter: counter.clone(),
})
.unwrap();
for batch in 0..10 {
for _ in 0..100 {
rt.send_to(
addr,
Ping {
reply_to: *dummy.addr(),
},
)
.unwrap();
}
tick_n(&mut host, 5);
let processed = counter.load(Ordering::SeqCst);
assert!(
processed > batch * 50,
"batch {batch}: expected progress, only {processed} processed"
);
}
// Drain remaining.
tick_n(&mut host, 100);
let total = counter.load(Ordering::SeqCst);
assert_eq!(total, 1000, "sustained load should not drop any messages");
}