use crate::{Expectation, Model, Path, Property};
use crate::actor::{
Actor,
ActorModelState,
Command,
Envelope,
is_no_op,
Id,
Network,
Out,
};
use std::borrow::Cow;
use std::fmt::{Debug, Display, Formatter};
use std::hash::Hash;
use std::ops::Range;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone)]
pub struct ActorModel<A, C = (), H = ()>
where A: Actor,
H: Clone + Debug + Hash,
{
pub actors: Vec<A>,
pub cfg: C,
pub init_history: H,
pub init_network: Network<A::Msg>,
pub lossy_network: LossyNetwork,
pub properties: Vec<Property<ActorModel<A, C, H>>>,
pub record_msg_in: fn(cfg: &C, history: &H, envelope: Envelope<&A::Msg>) -> Option<H>,
pub record_msg_out: fn(cfg: &C, history: &H, envelope: Envelope<&A::Msg>) -> Option<H>,
pub within_boundary: fn(cfg: &C, state: &ActorModelState<A, H>) -> bool,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ActorModelAction<Msg> {
Deliver { src: Id, dst: Id, msg: Msg },
Drop(Envelope<Msg>),
Timeout(Id),
}
#[derive(Copy, Clone, PartialEq)]
pub enum LossyNetwork { Yes, No }
pub fn model_timeout() -> Range<Duration> {
Duration::from_micros(0)..Duration::from_micros(0)
}
pub fn model_peers(self_ix: usize, count: usize) -> Vec<Id> {
(0..count)
.filter(|j| *j != self_ix)
.map(Into::into)
.collect()
}
impl<A, C, H> ActorModel<A, C, H>
where A: Actor,
H: Clone + Debug + Hash,
{
pub fn new(cfg: C, init_history: H) -> ActorModel<A, C, H> {
ActorModel {
actors: Vec::new(),
cfg,
init_history,
init_network: Network::new_unordered_duplicating([]),
lossy_network: LossyNetwork::No,
properties: Default::default(),
record_msg_in: |_, _, _| None,
record_msg_out: |_, _, _| None,
within_boundary: |_, _| true,
}
}
pub fn actor(mut self, actor: A) -> Self {
self.actors.push(actor);
self
}
pub fn actors(mut self, actors: impl IntoIterator<Item=A>) -> Self {
for actor in actors {
self.actors.push(actor);
}
self
}
pub fn init_network(mut self, init_network: Network<A::Msg>) -> Self {
self.init_network = init_network;
self
}
pub fn lossy_network(mut self, lossy_network: LossyNetwork) -> Self {
self.lossy_network = lossy_network;
self
}
#[allow(clippy::type_complexity)]
pub fn property(mut self, expectation: Expectation, name: &'static str,
condition: fn(&ActorModel<A, C, H>, &ActorModelState<A, H>) -> bool) -> Self {
self.properties.push(Property { expectation, name, condition });
self
}
pub fn record_msg_in(mut self,
record_msg_in: fn(cfg: &C, history: &H, Envelope<&A::Msg>) -> Option<H>)
-> Self
{
self.record_msg_in = record_msg_in;
self
}
pub fn record_msg_out(mut self,
record_msg_out: fn(cfg: &C, history: &H, Envelope<&A::Msg>) -> Option<H>)
-> Self
{
self.record_msg_out = record_msg_out;
self
}
pub fn within_boundary(mut self,
within_boundary: fn(cfg: &C, state: &ActorModelState<A, H>) -> bool)
-> Self
{
self.within_boundary = within_boundary;
self
}
fn process_commands(&self, id: Id, commands: Out<A>, state: &mut ActorModelState<A, H>) {
let index = usize::from(id);
for c in commands {
match c {
Command::Send(dst, msg) => {
if let Some(history) = (self.record_msg_out)(
&self.cfg,
&state.history,
Envelope { src: id, dst, msg: &msg })
{
state.history = history;
}
state.network.send(Envelope { src: id, dst, msg });
},
Command::SetTimer(_) => {
if state.is_timer_set.len() <= index {
state.is_timer_set.resize(index + 1, false);
}
state.is_timer_set[index] = true;
},
Command::CancelTimer => {
state.is_timer_set[index] = false;
},
}
}
}
}
impl<A, C, H> Model for ActorModel<A, C, H>
where A: Actor,
H: Clone + Debug + Hash,
{
type State = ActorModelState<A, H>;
type Action = ActorModelAction<A::Msg>;
fn init_states(&self) -> Vec<Self::State> {
let mut init_sys_state = ActorModelState {
actor_states: Vec::with_capacity(self.actors.len()),
history: self.init_history.clone(),
is_timer_set: vec![false; self.actors.len()],
network: self.init_network.clone(),
};
for (index, actor) in self.actors.iter().enumerate() {
let id = Id::from(index);
let mut out = Out::new();
let state = actor.on_start(id, &mut out);
init_sys_state.actor_states.push(Arc::new(state));
self.process_commands(id, out, &mut init_sys_state);
}
vec![init_sys_state]
}
fn actions(&self, state: &Self::State, actions: &mut Vec<Self::Action>) {
let mut prev_channel = None; for env in state.network.iter_deliverable() {
if self.lossy_network == LossyNetwork::Yes {
actions.push(ActorModelAction::Drop(env.to_cloned_msg()));
}
if usize::from(env.dst) < self.actors.len() { if matches!(self.init_network, Network::Ordered(_)) {
let curr_channel = (env.src, env.dst);
if prev_channel == Some(curr_channel) { continue; } prev_channel = Some(curr_channel);
}
actions.push(ActorModelAction::Deliver { src: env.src, dst: env.dst, msg: env.msg.clone() });
}
}
for (index, &is_scheduled) in state.is_timer_set.iter().enumerate() {
if is_scheduled {
actions.push(ActorModelAction::Timeout(Id::from(index)));
}
}
}
fn next_state(&self, last_sys_state: &Self::State, action: Self::Action) -> Option<Self::State> {
match action {
ActorModelAction::Drop(env) => {
let mut next_state = last_sys_state.clone();
next_state.network.on_drop(env);
Some(next_state)
},
ActorModelAction::Deliver { src, dst: id, msg } => {
let index = usize::from(id);
let last_actor_state = &last_sys_state.actor_states.get(index);
if last_actor_state.is_none() { return None; }
let last_actor_state = &**last_actor_state.unwrap();
let mut state = Cow::Borrowed(last_actor_state);
let mut out = Out::new();
self.actors[index].on_msg(id, &mut state, src, msg.clone(), &mut out);
if is_no_op(&state, &out) { return None; }
let history = (self.record_msg_in)(
&self.cfg,
&last_sys_state.history,
Envelope { src, dst: id, msg: &msg });
let mut next_sys_state = last_sys_state.clone();
let env = Envelope { src, dst: id, msg };
next_sys_state.network.on_deliver(env);
if let Cow::Owned(next_actor_state) = state {
next_sys_state.actor_states[index] = Arc::new(next_actor_state);
}
if let Some(history) = history {
next_sys_state.history = history;
}
self.process_commands(id, out, &mut next_sys_state);
Some(next_sys_state)
},
ActorModelAction::Timeout(id) => {
let index = usize::from(id);
let mut state = Cow::Borrowed(&*last_sys_state.actor_states[index]);
let mut out = Out::new();
self.actors[index].on_timeout(id, &mut state, &mut out);
let keep_timer = out.iter().any(|c| matches!(c, Command::SetTimer(_)));
if is_no_op(&state, &out) && keep_timer { return None }
let mut next_sys_state = last_sys_state.clone();
next_sys_state.is_timer_set[index] = false;
if let Cow::Owned(next_actor_state) = state {
next_sys_state.actor_states[index] = Arc::new(next_actor_state);
}
self.process_commands(id, out, &mut next_sys_state);
Some(next_sys_state)
},
}
}
fn format_action(&self, action: &Self::Action) -> String {
if let ActorModelAction::Deliver { src, dst, msg } = action {
format!("{:?} → {:?} → {:?}", src, msg, dst)
} else {
format!("{:?}", action)
}
}
fn format_step(&self, last_state: &Self::State, action: Self::Action) -> Option<String>
where Self::State: Debug
{
struct ActorStep<'a, A: Actor> {
last_state: &'a A::State,
next_state: Option<A::State>,
out: Out<A>,
}
impl<'a, A: Actor> Display for ActorStep<'a, A> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "OUT: {:?}", self.out)?;
writeln!(f)?;
if let Some(next_state) = &self.next_state {
writeln!(f, "NEXT_STATE: {:#?}", next_state)?;
writeln!(f)?;
writeln!(f, "PREV_STATE: {:#?}", self.last_state)
} else {
writeln!(f, "UNCHANGED: {:#?}", self.last_state)
}
}
}
match action {
ActorModelAction::Drop(env) => {
Some(format!("DROP: {:?}", env))
},
ActorModelAction::Deliver { src, dst: id, msg } => {
let index = usize::from(id);
let last_actor_state = match last_state.actor_states.get(index) {
None => return None,
Some(last_actor_state) => &**last_actor_state,
};
let mut actor_state = Cow::Borrowed(last_actor_state);
let mut out = Out::new();
self.actors[index].on_msg(id, &mut actor_state, src, msg, &mut out);
Some(format!("{}", ActorStep {
last_state: last_actor_state,
next_state: match actor_state {
Cow::Borrowed(_) => None,
Cow::Owned(next_actor_state) => Some(next_actor_state),
},
out,
}))
},
ActorModelAction::Timeout(id) => {
let index = usize::from(id);
let last_actor_state = match last_state.actor_states.get(index) {
None => return None,
Some(last_actor_state) => &**last_actor_state,
};
let mut actor_state = Cow::Borrowed(last_actor_state);
let mut out = Out::new();
self.actors[index].on_timeout(id, &mut actor_state, &mut out);
Some(format!("{}", ActorStep {
last_state: last_actor_state,
next_state: match actor_state {
Cow::Borrowed(_) => None,
Cow::Owned(next_actor_state) => Some(next_actor_state),
},
out,
}))
},
}
}
fn as_svg(&self, path: Path<Self::State, Self::Action>) -> Option<String> {
use std::collections::HashMap;
use std::fmt::Write;
let plot = |x, y| (x as u64 * 100, y as u64 * 30);
let actor_count = path.last_state().actor_states.len();
let path = path.into_vec();
let (mut svg_w, svg_h) = plot(actor_count, path.len());
svg_w += 300; let mut svg = format!("<svg version='1.1' baseProfile='full' \
width='{}' height='{}' viewbox='-20 -20 {} {}' \
xmlns='http://www.w3.org/2000/svg'>",
svg_w, svg_h, svg_w + 20, svg_h + 20);
write!(&mut svg, "\
<defs>\
<marker class='svg-event-shape' id='arrow' markerWidth='12' markerHeight='10' refX='12' refY='5' orient='auto'>\
<polygon points='0 0, 12 5, 0 10' />\
</marker>\
</defs>").unwrap();
for actor_index in 0..actor_count {
let (x1, y1) = plot(actor_index, 0);
let (x2, y2) = plot(actor_index, path.len());
writeln!(&mut svg, "<line x1='{}' y1='{}' x2='{}' y2='{}' class='svg-actor-timeline' />",
x1, y1, x2, y2).unwrap();
writeln!(&mut svg, "<text x='{}' y='{}' class='svg-actor-label'>{:?}</text>",
x1, y1, actor_index).unwrap();
}
let mut send_time = HashMap::new();
for (time, (state, action)) in path.clone().into_iter().enumerate() {
let time = time + 1; match action {
Some(ActorModelAction::Deliver { src, dst: id, msg }) => {
let src_time = *send_time.get(&(src, id, msg.clone())).unwrap_or(&0);
let (x1, y1) = plot(src.into(), src_time);
let (x2, y2) = plot(id.into(), time);
writeln!(&mut svg, "<line x1='{}' x2='{}' y1='{}' y2='{}' marker-end='url(#arrow)' class='svg-event-line' />",
x1, x2, y1, y2).unwrap();
let index = usize::from(id);
if let Some(actor_state) = state.actor_states.get(index) {
let mut actor_state = Cow::Borrowed(&**actor_state);
let mut out = Out::new();
self.actors[index].on_msg(id, &mut actor_state, src, msg, &mut out);
for command in out {
if let Command::Send(dst, msg) = command {
send_time.insert((id, dst, msg), time);
}
}
}
}
Some(ActorModelAction::Timeout(actor_id)) => {
let (x, y) = plot(actor_id.into(), time);
writeln!(&mut svg, "<circle cx='{}' cy='{}' r='10' class='svg-event-shape' />",
x, y).unwrap();
let index = usize::from(actor_id);
if let Some(actor_state) = state.actor_states.get(index) {
let mut actor_state = Cow::Borrowed(&**actor_state);
let mut out = Out::new();
self.actors[index].on_timeout(actor_id, &mut actor_state, &mut out);
for command in out {
if let Command::Send(dst, msg) = command {
send_time.insert((actor_id, dst, msg), time);
}
}
}
}
_ => {}
}
}
for (time, (_state, action)) in path.into_iter().enumerate() {
let time = time + 1; match action {
Some(ActorModelAction::Deliver { dst: id, msg, .. }) => {
let (x, y) = plot(id.into(), time);
writeln!(&mut svg, "<text x='{}' y='{}' class='svg-event-label'>{:?}</text>",
x, y, msg).unwrap();
}
Some(ActorModelAction::Timeout(id)) => {
let (x, y) = plot(id.into(), time);
writeln!(&mut svg, "<text x='{}' y='{}' class='svg-event-label'>Timeout</text>",
x, y).unwrap();
}
_ => {}
}
}
writeln!(&mut svg, "</svg>").unwrap();
Some(svg)
}
fn properties(&self) -> Vec<Property<Self>> {
self.properties.clone()
}
fn within_boundary(&self, state: &Self::State) -> bool {
(self.within_boundary)(&self.cfg, state)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Checker, PathRecorder, StateRecorder};
use crate::actor::actor_test_util::ping_pong::{PingPongCfg, PingPongMsg::*};
use crate::actor::ActorModelAction::*;
use std::collections::HashSet;
use std::sync::Arc;
#[test]
fn visits_expected_states() {
use std::iter::FromIterator;
let states_and_network = |states: Vec<u32>, envelopes: Vec<Envelope<_>>| {
let is_timer_set = vec![false; states.len()];
ActorModelState {
actor_states: states.into_iter().map(|s| Arc::new(s)).collect::<Vec<_>>(),
network: Network::new_unordered_duplicating(envelopes),
is_timer_set,
history: (0_u32, 0_u32), }
};
let (recorder, accessor) = StateRecorder::new_with_accessor();
let checker = PingPongCfg {
maintains_history: false,
max_nat: 1,
}
.into_model()
.lossy_network(LossyNetwork::Yes)
.checker().visitor(recorder).spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 14);
let state_space = accessor();
assert_eq!(state_space.len(), 14); assert_eq!(HashSet::<_>::from_iter(state_space), HashSet::from_iter(vec![
states_and_network(
vec![0, 0],
vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }]),
states_and_network(
vec![0, 1],
vec![
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
]),
states_and_network(
vec![1, 1],
vec![
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
]),
states_and_network(
vec![0, 0],
Vec::new()),
states_and_network(
vec![0, 1],
vec![Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) }]),
states_and_network(
vec![0, 1],
vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }]),
states_and_network(
vec![0, 1],
Vec::new()),
states_and_network(
vec![1, 1],
vec![
Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
]),
states_and_network(
vec![1, 1],
vec![
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
]),
states_and_network(
vec![1, 1],
vec![
Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
]),
states_and_network(
vec![1, 1],
vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) }]),
states_and_network(
vec![1, 1],
vec![Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) }]),
states_and_network(
vec![1, 1],
vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }]),
states_and_network(
vec![1, 1],
Vec::new()),
]));
}
#[test]
fn maintains_fixed_delta_despite_lossy_duplicating_network() {
let checker = PingPongCfg {
max_nat: 5,
maintains_history: false,
}
.into_model()
.lossy_network(LossyNetwork::Yes)
.checker().spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 4_094);
checker.assert_no_discovery("delta within 1");
}
#[test]
fn may_never_reach_max_on_lossy_network() {
let checker = PingPongCfg {
max_nat: 5,
maintains_history: false,
}
.into_model()
.lossy_network(LossyNetwork::Yes)
.checker().spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 4_094);
checker.assert_discovery("must reach max", vec![
Drop(Envelope { src: Id(0), dst: Id(1), msg: Ping(0) }),
]);
}
#[test]
fn eventually_reaches_max_on_perfect_delivery_network() {
let checker = PingPongCfg {
max_nat: 5,
maintains_history: false,
}
.into_model()
.init_network(Network::new_unordered_nonduplicating([]))
.lossy_network(LossyNetwork::No)
.checker().spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 11);
checker.assert_no_discovery("must reach max");
}
#[test]
fn can_reach_max() {
let checker = PingPongCfg {
max_nat: 5,
maintains_history: false,
}
.into_model()
.lossy_network(LossyNetwork::No)
.checker().spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 11);
assert_eq!(
checker.discovery("can reach max").unwrap().last_state().actor_states,
vec![Arc::new(4), Arc::new(5)]);
}
#[test]
fn might_never_reach_beyond_max() {
let checker = PingPongCfg {
max_nat: 5,
maintains_history: false,
}
.into_model()
.init_network(Network::new_unordered_nonduplicating([]))
.lossy_network(LossyNetwork::No)
.checker().spawn_bfs().join();
assert_eq!(checker.unique_state_count(), 11);
assert_eq!(
checker.discovery("must exceed max").unwrap().last_state().actor_states,
vec![Arc::new(5), Arc::new(5)]);
}
#[test]
fn handles_undeliverable_messages() {
assert_eq!(
ActorModel::new((), ())
.actor(())
.property(Expectation::Always, "unused", |_, _| true) .init_network(Network::new_unordered_duplicating([Envelope { src: 0.into(), dst: 99.into(), msg: () }]))
.checker().spawn_bfs().join()
.unique_state_count(),
1);
}
#[test]
fn handles_ordered_network_flag() {
#[derive(Clone)]
struct OrderedNetworkActor;
impl Actor for OrderedNetworkActor {
type Msg = u8;
type State = Vec<u8>;
fn on_start(&self, id: Id, o: &mut Out<Self>) -> Self::State {
if id == 0.into() {
o.send(1.into(), 2);
o.send(1.into(), 1);
}
Vec::new()
}
fn on_msg(&self, _: Id, state: &mut Cow<Self::State>,
_: Id, msg: Self::Msg, _: &mut Out<Self>)
{
state.to_mut().push(msg);
}
}
let model = ActorModel::new((), ())
.actors(vec![OrderedNetworkActor, OrderedNetworkActor])
.property(Expectation::Always, "", |_, _| true)
.init_network(Network::new_unordered_nonduplicating([]));
let (recorder, accessor) = StateRecorder::new_with_accessor();
model.clone()
.init_network(Network::new_ordered([]))
.checker().visitor(recorder).spawn_bfs().join();
let recipient_states: Vec<Vec<u8>> = accessor().into_iter().map(|s| {
(*s.actor_states[1]).clone()
}).collect();
assert_eq!(recipient_states, vec![
vec![],
vec![2],
vec![2, 1],
]);
let (recorder, accessor) = StateRecorder::new_with_accessor();
model.clone()
.init_network(Network::new_unordered_nonduplicating([]))
.checker().visitor(recorder).spawn_bfs().join();
let recipient_states: Vec<Vec<u8>> = accessor().into_iter().map(|s| {
(*s.actor_states[1]).clone()
}).collect();
assert_eq!(recipient_states, vec![
vec![],
vec![2],
vec![1],
vec![2, 1],
vec![1, 2],
]);
}
#[test]
fn unordered_network_has_a_bug() {
fn enumerate_action_sequences(lossy: LossyNetwork, init_network: Network<()>) -> HashSet<Vec<ActorModelAction<()>>> {
struct A;
impl Actor for A {
type Msg = ();
type State = usize;
fn on_start(&self, id: Id, o: &mut Out<Self>) -> Self::State {
if id == 0.into() {
o.send(1.into(), ());
o.send(1.into(), ());
}
0
}
fn on_msg(&self, _: Id, state: &mut Cow<Self::State>, _: Id, _: Self::Msg, _: &mut Out<Self>) {
*state.to_mut() += 1;
}
}
let (recorder, accessor) = PathRecorder::new_with_accessor();
ActorModel::new((), ())
.actors([A, A])
.init_network(init_network)
.lossy_network(lossy)
.property(Expectation::Always, "force visiting all states", |_, _| true)
.within_boundary(|_, s| *s.actor_states[1] < 4)
.checker()
.visitor(recorder)
.spawn_dfs()
.join();
accessor()
.into_iter()
.map(|p| p.into_actions())
.collect()
}
let deliver = ActorModelAction::<()>::Deliver { src: 0.into(), msg: (), dst: 1.into() };
let drop = ActorModelAction::<()>::Drop(Envelope { src: 0.into(), msg: (), dst: 1.into() });
let ordered_lossless = enumerate_action_sequences(LossyNetwork::No, Network::new_ordered([]));
assert!(ordered_lossless.contains(&vec![deliver, deliver]));
assert!(!ordered_lossless.contains(&vec![deliver, deliver, deliver]));
let ordered_lossy = enumerate_action_sequences(LossyNetwork::Yes, Network::new_ordered([]));
assert!(ordered_lossy.contains(&vec![deliver, deliver]));
assert!(ordered_lossy.contains(&vec![deliver, drop])); assert!(ordered_lossy.contains(&vec![drop, drop]));
let unord_dup_lossless = enumerate_action_sequences(
LossyNetwork::No, Network::new_unordered_duplicating([]));
assert!(unord_dup_lossless.contains(&vec![deliver, deliver, deliver]));
let unord_dup_lossy = enumerate_action_sequences(
LossyNetwork::Yes, Network::new_unordered_duplicating([]));
assert!(unord_dup_lossy.contains(&vec![deliver, deliver, deliver]));
assert!(unord_dup_lossy.contains(&vec![deliver, deliver, drop]));
assert!(unord_dup_lossy.contains(&vec![deliver, drop]));
assert!(unord_dup_lossy.contains(&vec![drop]));
assert!(!unord_dup_lossy.contains(&vec![drop, deliver]));
let unord_nondup_lossless = enumerate_action_sequences(
LossyNetwork::No, Network::new_unordered_nonduplicating([]));
assert!(unord_nondup_lossless.contains(&vec![deliver, deliver]));
let unord_nondup_lossy = enumerate_action_sequences(
LossyNetwork::Yes, Network::new_unordered_nonduplicating([]));
assert!(unord_nondup_lossy.contains(&vec![deliver, drop]));
assert!(unord_nondup_lossy.contains(&vec![drop, drop]));
}
#[test]
fn resets_timer() {
struct TestActor;
impl Actor for TestActor {
type State = ();
type Msg = ();
fn on_start(&self, _: Id, o: &mut Out<Self>) {
o.set_timer(model_timeout());
()
}
fn on_msg(&self, _: Id, _: &mut Cow<Self::State>, _: Id, _: Self::Msg, _: &mut Out<Self>) {}
}
assert_eq!(
ActorModel::new((), ())
.actor(TestActor)
.property(Expectation::Always, "unused", |_, _| true) .checker().spawn_bfs().join()
.unique_state_count(),
2);
}
}
#[cfg(test)]
mod choice_test {
use choice::Choice;
use crate::{Checker, Model, StateRecorder};
use super::*;
#[derive(Clone)]
struct A { b: Id }
impl Actor for A {
type State = u8;
type Msg = ();
fn on_start(&self, _: Id, _: &mut Out<Self>) -> Self::State {
1
}
fn on_msg(&self, _: Id, state: &mut Cow<Self::State>,
_: Id, _: Self::Msg, o: &mut Out<Self>) {
*state.to_mut() = state.wrapping_add(1);
o.send(self.b, ());
}
}
#[derive(Clone)]
struct B { c: Id }
impl Actor for B {
type State = char;
type Msg = ();
fn on_start(&self, _: Id, _: &mut Out<Self>) -> Self::State {
'a'
}
fn on_msg(&self, _: Id, state: &mut Cow<Self::State>,
_: Id, _: Self::Msg, o: &mut Out<Self>) {
*state.to_mut() = (**state as u8).wrapping_add(1) as char;
o.send(self.c, ());
}
}
#[derive(Clone)]
struct C { a: Id }
impl Actor for C {
type State = String;
type Msg = ();
fn on_start(&self, _: Id, o: &mut Out<Self>) -> Self::State {
o.send(self.a, ());
"I".to_string()
}
fn on_msg(&self, _: Id, state: &mut Cow<Self::State>,
_: Id, _: Self::Msg, o: &mut Out<Self>) {
state.to_mut().push('I');
o.send(self.a, ());
}
}
#[test]
fn choice_correctly_implements_actor() {
use choice::choice;
let sys = ActorModel::<choice![A, B, C], (), u8>::new((), 0)
.actor(Choice::new(A { b: Id::from(1) }))
.actor(Choice::new(B { c: Id::from(2) }).or())
.actor(Choice::new(C { a: Id::from(0) }).or().or())
.init_network(Network::new_unordered_nonduplicating([]))
.record_msg_out(|_, out_count, _| Some(out_count + 1))
.property(Expectation::Always, "true", |_, _| true)
.within_boundary(|_, state| state.history < 8);
let (recorder, accessor) = StateRecorder::new_with_accessor();
sys.checker()
.visitor(recorder)
.spawn_dfs().join();
let states: Vec<Vec<choice![u8, char, String]>> = accessor().into_iter()
.map(|s| s.actor_states.into_iter().map(|a| (&*a).clone()).collect())
.collect();
assert_eq!(states, vec![
vec![
Choice::new(1),
Choice::new('a').or(),
Choice::new("I".to_string()).or().or(),
],
vec![
Choice::new(2),
Choice::new('a').or(),
Choice::new("I".to_string()).or().or(),
],
vec![
Choice::new(2),
Choice::new('b').or(),
Choice::new("I".to_string()).or().or(),
],
vec![
Choice::new(2),
Choice::new('b').or(),
Choice::new("II".to_string()).or().or(),
],
vec![
Choice::new(3),
Choice::new('b').or(),
Choice::new("II".to_string()).or().or(),
],
vec![
Choice::new(3),
Choice::new('c').or(),
Choice::new("II".to_string()).or().or(),
],
vec![
Choice::new(3),
Choice::new('c').or(),
Choice::new("III".to_string()).or().or(),
],
]);
}
}