swactor/tests/common/mod.rs
2026-02-13 14:48:35 +00:00

255 lines
6.9 KiB
Rust

// Shared types and helpers for runtime test files.
#![allow(dead_code, unused_imports)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
pub use swactor::actor::{
ActorAddress, ActorExited, ActorInterface, Down, ExitReason, MonitorRef, StopReason,
};
pub use swactor::runtime::{Ctx, Inbox, MailboxOverflow, Runtime, RuntimeConfig};
pub use swactor_std::{
ChildSpec, CtxGroups, CtxMonitoring, CtxNaming, CtxTimers, CtxWatching, RestartPolicy, Router,
RoutingStrategy, RuntimeGroups, RuntimeNaming, RuntimeWatching, StdExtension, Supervisor,
SupervisorStrategy,
};
// ── Messages ────────────────────────────────────────────────────────────────
#[derive(Clone)]
pub struct Ping {
pub reply_to: ActorAddress,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Pong;
#[derive(Clone)]
pub struct Increment {
pub reply_to: ActorAddress,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Count(pub usize);
#[derive(Clone)]
pub struct Forward {
pub value: usize,
pub reply_to: ActorAddress,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Done(pub usize);
/// Ask an actor for its own address.
#[derive(Clone)]
pub struct WhoAreYou {
pub reply_to: ActorAddress,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MyAddr(pub ActorAddress);
#[derive(Clone)]
pub struct PanicMsg;
/// Tells FanOutActor to distribute work.
#[derive(Clone)]
pub struct FanOut {
pub count: usize,
pub reply_to: ActorAddress,
}
/// Message used in the chain test -- carries remaining hops and final reply address.
#[derive(Clone)]
pub struct ChainMsg {
pub remaining: usize,
pub depth: usize,
pub reply_to: ActorAddress,
}
// ── Actors ──────────────────────────────────────────────────────────────────
/// Replies Pong to every Ping. Stateless.
pub struct PingPongActor;
impl ActorInterface for PingPongActor {
type Incoming = Ping;
type Response = Pong;
fn handle(&mut self, ctx: &Ctx, msg: Ping) {
let _ = ctx.send(msg.reply_to, Pong);
}
}
/// Counts Increment messages, replies Count(n) after each.
pub struct CounterActor {
pub count: usize,
}
impl ActorInterface for CounterActor {
type Incoming = Increment;
type Response = Count;
fn handle(&mut self, ctx: &Ctx, msg: Increment) {
self.count += 1;
let _ = ctx.send(msg.reply_to, Count(self.count));
}
}
/// Replies Done(value * 2).
pub struct DoubleActor;
impl ActorInterface for DoubleActor {
type Incoming = Forward;
type Response = Done;
fn handle(&mut self, ctx: &Ctx, msg: Forward) {
let _ = ctx.send(msg.reply_to, Done(msg.value * 2));
}
}
/// Spawns a DoubleActor child and forwards the work to it.
pub struct DelegatorActor;
impl ActorInterface for DelegatorActor {
type Incoming = Forward;
type Response = ();
fn handle(&mut self, ctx: &Ctx, msg: Forward) {
let child = ctx.spawn(DoubleActor).unwrap();
let _ = ctx.send(child, Forward { value: msg.value, reply_to: msg.reply_to });
}
}
/// Spawns a child chain: each level spawns the next until remaining == 0,
/// then the leaf replies Done(depth).
pub struct ChainActor;
impl ActorInterface for ChainActor {
type Incoming = ChainMsg;
type Response = Done;
fn handle(&mut self, ctx: &Ctx, msg: ChainMsg) {
if msg.remaining == 0 {
let _ = ctx.send(msg.reply_to, Done(msg.depth));
} else {
let child = ctx.spawn(ChainActor).unwrap();
let _ = ctx.send(
child,
ChainMsg {
remaining: msg.remaining - 1,
depth: msg.depth + 1,
reply_to: msg.reply_to,
},
);
}
}
}
/// Spawns N DoubleActor children, sends Forward { value: i, reply_to } to each.
pub struct FanOutActor;
impl ActorInterface for FanOutActor {
type Incoming = FanOut;
type Response = ();
fn handle(&mut self, ctx: &Ctx, msg: FanOut) {
for i in 1..=msg.count {
let child = ctx.spawn(DoubleActor).unwrap();
let _ = ctx.send(child, Forward { value: i, reply_to: msg.reply_to });
}
}
}
/// Replies with its own address.
pub struct SelfAddrActor;
impl ActorInterface for SelfAddrActor {
type Incoming = WhoAreYou;
type Response = MyAddr;
fn handle(&mut self, ctx: &Ctx, msg: WhoAreYou) {
let _ = ctx.send(msg.reply_to, MyAddr(ctx.self_addr()));
}
}
/// Panics on every message. Used to test panic isolation.
pub struct PanicActor;
impl ActorInterface for PanicActor {
type Incoming = PanicMsg;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, _msg: PanicMsg) {
panic!("intentional test panic");
}
}
/// Increments a shared counter on each Ping. Used to observe processing from outside.
pub struct CountingPingActor {
pub counter: Arc<AtomicUsize>,
}
impl ActorInterface for CountingPingActor {
type Incoming = Ping;
type Response = Pong;
fn handle(&mut self, ctx: &Ctx, msg: Ping) {
self.counter.fetch_add(1, Ordering::SeqCst);
let _ = ctx.send(msg.reply_to, Pong);
}
}
/// Null actor that accepts Ping but does nothing visible.
pub struct NullActor;
impl ActorInterface for NullActor {
type Incoming = Ping;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, _msg: Ping) {}
}
/// Actor that replies with its inbox address.
pub struct InboxReplyActor;
impl ActorInterface for InboxReplyActor {
type Incoming = Ping;
type Response = Pong;
fn handle(&mut self, ctx: &Ctx, msg: Ping) {
let _ = ctx.send(msg.reply_to, Pong);
}
}
// ── Helpers ─────────────────────────────────────────────────────────────────
/// Helper: construct a Runtime with StdExtension installed.
pub fn std_runtime(config: RuntimeConfig) -> Runtime {
Runtime::new(config).with_extension(Arc::new(StdExtension::new()))
}
/// Tick up to `max` times, returning as soon as `inbox` has a message.
pub fn tick_until_recv<M: swactor::actor::Message>(
rt: &Runtime,
inbox: &Inbox<M>,
max: usize,
) -> Option<M> {
for _ in 0..max {
rt.tick();
if let Some(msg) = inbox.try_recv() {
return Some(msg);
}
}
None
}
/// Tick exactly `n` times (no inbox polling).
pub fn tick_n(rt: &Runtime, n: usize) {
for _ in 0..n {
rt.tick();
}
}
/// Tick `n` times, then drain all messages from the inbox.
pub fn tick_and_drain<M: swactor::actor::Message>(
rt: &Runtime,
inbox: &Inbox<M>,
ticks: usize,
) -> Vec<M> {
for _ in 0..ticks {
rt.tick();
}
std::iter::from_fn(|| inbox.try_recv()).collect()
}