stateright/actor/
model.rs

1//! Private module for selective re-export.
2
3use crate::actor::{
4    is_no_op, is_no_op_with_timer, Actor, ActorModelState, Command, Envelope, Id, Network, Out,
5    RandomChoices,
6};
7use crate::{Expectation, Model, Path, Property};
8use std::borrow::Cow;
9use std::collections::HashMap;
10use std::fmt::{Debug, Display, Formatter};
11use std::hash::Hash;
12use std::ops::Range;
13use std::sync::Arc;
14use std::time::Duration;
15
16use super::timers::Timers;
17
18/// Represents a system of [`Actor`]s that communicate over a network. `H` indicates the type of
19/// history to maintain as auxiliary state, if any.  See [Auxiliary Variables in
20/// TLA](https://lamport.azurewebsites.net/tla/auxiliary/auxiliary.html) for a thorough
21/// introduction to that concept. Use `()` if history is not needed to define the relevant
22/// properties of this system.
23#[derive(Clone)]
24pub struct ActorModel<A, C = (), H = ()>
25where
26    A: Actor,
27    A::Msg: Ord,
28    A::Timer: Ord,
29    H: Clone + Debug + Hash,
30{
31    pub actors: Vec<A>,
32    pub cfg: C,
33    pub init_history: H,
34    pub init_network: Network<A::Msg>,
35    pub lossy_network: LossyNetwork,
36    /// Maximum number of actors that can be contemporarily crashed
37    pub max_crashes: usize,
38    pub properties: Vec<Property<ActorModel<A, C, H>>>,
39    pub record_msg_in: fn(cfg: &C, history: &H, envelope: Envelope<&A::Msg>) -> Option<H>,
40    pub record_msg_out: fn(cfg: &C, history: &H, envelope: Envelope<&A::Msg>) -> Option<H>,
41    pub within_boundary: fn(cfg: &C, state: &ActorModelState<A, H>) -> bool,
42}
43
44/// Indicates possible steps that an actor system can take as it evolves.
45#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
46pub enum ActorModelAction<Msg, Timer, Random> {
47    /// A message can be delivered to an actor.
48    Deliver { src: Id, dst: Id, msg: Msg },
49    /// A message can be dropped if the network is lossy.
50    Drop(Envelope<Msg>),
51    /// An actor can be notified after a timeout.
52    Timeout(Id, Timer),
53    /// An actor can crash, i.e. losing all its volatile state.
54    Crash(Id),
55    /// An actor can recover, i.e. restoring all its non-volatile state in Self::Storage.
56    Recover(Id),
57    /// A random selection by an actor.
58    SelectRandom {
59        actor: Id,
60        key: String,
61        random: Random,
62    },
63}
64
65/// Indicates whether the network loses messages. Note that as long as invariants do not check
66/// the network state, losing a message is indistinguishable from an unlimited delay, so in
67/// many cases you can improve model checking performance by not modeling message loss.
68#[derive(Copy, Clone, PartialEq)]
69pub enum LossyNetwork {
70    Yes,
71    No,
72}
73
74/// The specific timeout value is not relevant for model checking, so this helper can be used to
75/// generate an arbitrary timeout range. The specific value is subject to change, so this helper
76/// must only be used for model checking.
77pub fn model_timeout() -> Range<Duration> {
78    Duration::from_micros(0)..Duration::from_micros(0)
79}
80
81/// A helper to generate a list of peer [`Id`]s given an actor count and the index of a particular
82/// actor.
83pub fn model_peers(self_ix: usize, count: usize) -> Vec<Id> {
84    (0..count)
85        .filter(|j| *j != self_ix)
86        .map(Into::into)
87        .collect()
88}
89
90impl<A, C, H> ActorModel<A, C, H>
91where
92    A: Actor,
93    A::Msg: Ord,
94    A::Timer: Ord,
95    H: Clone + Debug + Hash,
96{
97    /// Initializes an [`ActorModel`] with a specified configuration and history.
98    pub fn new(cfg: C, init_history: H) -> ActorModel<A, C, H> {
99        ActorModel {
100            actors: Vec::new(),
101            cfg,
102            init_history,
103            init_network: Network::new_unordered_duplicating([]),
104            lossy_network: LossyNetwork::No,
105            max_crashes: 0,
106            properties: Default::default(),
107            record_msg_in: |_, _, _| None,
108            record_msg_out: |_, _, _| None,
109            within_boundary: |_, _| true,
110        }
111    }
112
113    /// Adds another [`Actor`] to this model.
114    pub fn actor(mut self, actor: A) -> Self {
115        self.actors.push(actor);
116        self
117    }
118
119    /// Adds multiple [`Actor`]s to this model.
120    pub fn actors(mut self, actors: impl IntoIterator<Item = A>) -> Self {
121        for actor in actors {
122            self.actors.push(actor);
123        }
124        self
125    }
126
127    /// Defines the initial network.
128    pub fn init_network(mut self, init_network: Network<A::Msg>) -> Self {
129        self.init_network = init_network;
130        self
131    }
132
133    /// Defines whether the network loses messages or not.
134    pub fn lossy_network(mut self, lossy_network: LossyNetwork) -> Self {
135        self.lossy_network = lossy_network;
136        self
137    }
138
139    /// Specifies the maximum number of actors that can be contemporarily crashed
140    pub fn max_crashes(mut self, max_crashes: usize) -> Self {
141        self.max_crashes = max_crashes;
142        self
143    }
144
145    /// Adds a [`Property`] to this model.
146    #[allow(clippy::type_complexity)]
147    pub fn property(
148        mut self,
149        expectation: Expectation,
150        name: &'static str,
151        condition: fn(&ActorModel<A, C, H>, &ActorModelState<A, H>) -> bool,
152    ) -> Self {
153        assert!(
154            !self.properties.iter().any(|p| p.name == name),
155            "Property with name '{name}' already exists"
156        );
157        self.properties.push(Property {
158            expectation,
159            name,
160            condition,
161        });
162        self
163    }
164
165    /// Defines whether/how an incoming message contributes to relevant history. Returning
166    /// `Some(new_history)` updates the relevant history, while `None` does not.
167    pub fn record_msg_in(
168        mut self,
169        record_msg_in: fn(cfg: &C, history: &H, Envelope<&A::Msg>) -> Option<H>,
170    ) -> Self {
171        self.record_msg_in = record_msg_in;
172        self
173    }
174
175    /// Defines whether/how an outgoing message contributes to relevant history. Returning
176    /// `Some(new_history)` updates the relevant history, while `None` does not.
177    pub fn record_msg_out(
178        mut self,
179        record_msg_out: fn(cfg: &C, history: &H, Envelope<&A::Msg>) -> Option<H>,
180    ) -> Self {
181        self.record_msg_out = record_msg_out;
182        self
183    }
184
185    /// Indicates whether a state is within the state space that should be model checked.
186    pub fn within_boundary(
187        mut self,
188        within_boundary: fn(cfg: &C, state: &ActorModelState<A, H>) -> bool,
189    ) -> Self {
190        self.within_boundary = within_boundary;
191        self
192    }
193
194    /// Updates the actor state, sends messages, and configures the timers.
195    fn process_commands(&self, id: Id, commands: Out<A>, state: &mut ActorModelState<A, H>) {
196        let index = usize::from(id);
197        for c in commands {
198            match c {
199                Command::Send(dst, msg) => {
200                    if let Some(history) = (self.record_msg_out)(
201                        &self.cfg,
202                        &state.history,
203                        Envelope {
204                            src: id,
205                            dst,
206                            msg: &msg,
207                        },
208                    ) {
209                        state.history = history;
210                    }
211                    state.network.send(Envelope { src: id, dst, msg });
212                }
213                Command::SetTimer(timer, _) => {
214                    // must use the index to infer how large as actor state may not be initialized yet
215                    if state.timers_set.len() <= index {
216                        state.timers_set.resize_with(index + 1, Timers::new);
217                    }
218                    state.timers_set[index].set(timer);
219                }
220                Command::CancelTimer(timer) => {
221                    state.timers_set[index].cancel(&timer);
222                }
223                Command::ChooseRandom(key, random) => {
224                    if random.is_empty() {
225                        state.random_choices[index].remove(&key);
226                    } else {
227                        state.random_choices[index].insert(key, random)
228                    }
229                }
230                Command::Save(storage) => {
231                    // must use the index to infer how large as actor state may not be initialized yet
232                    if state.actor_storages.len() <= index {
233                        state
234                            .actor_storages
235                            .resize_with(index + 1, Default::default);
236                    }
237                    state.actor_storages[index] = Some(storage);
238                }
239            }
240        }
241    }
242}
243
244impl<A, C, H> Model for ActorModel<A, C, H>
245where
246    A: Actor,
247    A::Msg: Ord,
248    A::Timer: Ord,
249    H: Clone + Debug + Hash,
250{
251    type State = ActorModelState<A, H>;
252    type Action = ActorModelAction<A::Msg, A::Timer, A::Random>;
253
254    fn init_states(&self) -> Vec<Self::State> {
255        let mut init_sys_state = ActorModelState {
256            actor_states: Vec::with_capacity(self.actors.len()),
257            history: self.init_history.clone(),
258            timers_set: vec![Timers::new(); self.actors.len()],
259            random_choices: vec![RandomChoices::default(); self.actors.len()],
260            network: self.init_network.clone(),
261            crashed: vec![false; self.actors.len()],
262            actor_storages: vec![None; self.actors.len()],
263        };
264
265        // init each actor
266        for (index, actor) in self.actors.iter().enumerate() {
267            let id = Id::from(index);
268            let mut out = Out::new();
269            let state = actor.on_start(id, &init_sys_state.actor_storages[index], &mut out);
270            init_sys_state.actor_states.push(Arc::new(state));
271            self.process_commands(id, out, &mut init_sys_state);
272        }
273
274        vec![init_sys_state]
275    }
276
277    fn actions(&self, state: &Self::State, actions: &mut Vec<Self::Action>) {
278        let mut prev_channel = None; // Only deliver the head of a channel.
279        for env in state.network.iter_deliverable() {
280            // option 1: message is lost
281            if self.lossy_network == LossyNetwork::Yes {
282                actions.push(ActorModelAction::Drop(env.to_cloned_msg()));
283            }
284
285            // option 2: message is delivered
286            if usize::from(env.dst) < self.actors.len() {
287                // ignored if recipient DNE
288                if matches!(self.init_network, Network::Ordered(_)) {
289                    let curr_channel = (env.src, env.dst);
290                    if prev_channel == Some(curr_channel) {
291                        continue;
292                    } // queued behind previous
293                    prev_channel = Some(curr_channel);
294                }
295                actions.push(ActorModelAction::Deliver {
296                    src: env.src,
297                    dst: env.dst,
298                    msg: env.msg.clone(),
299                });
300            }
301        }
302
303        // option 3: actor timeout
304        for (index, timers) in state.timers_set.iter().enumerate() {
305            for timer in timers.iter() {
306                actions.push(ActorModelAction::Timeout(Id::from(index), timer.clone()));
307            }
308        }
309
310        // option 4: actor crash
311        let n_crashed = state.crashed.iter().filter(|&crashed| *crashed).count();
312        if n_crashed < self.max_crashes {
313            state
314                .crashed
315                .iter()
316                .enumerate()
317                .filter_map(|(index, &crashed)| if !crashed { Some(index) } else { None })
318                .for_each(|index| actions.push(ActorModelAction::Crash(Id::from(index))));
319        }
320
321        // option 5: actor recover
322        state
323            .crashed
324            .iter()
325            .enumerate()
326            .filter_map(|(index, &crashed)| if crashed { Some(index) } else { None })
327            .for_each(|index| actions.push(ActorModelAction::Recover(Id::from(index))));
328
329        // option 6: random choice
330        for (actor_index, random_decisions) in state.random_choices.iter().enumerate() {
331            for (key, decision) in random_decisions.map.into_iter() {
332                for choice in decision {
333                    actions.push(ActorModelAction::SelectRandom {
334                        actor: Id::from(actor_index),
335                        key: key.clone(),
336                        random: choice.clone(),
337                    });
338                }
339            }
340        }
341
342        // Action indices based `Path` construction relies on the consistent ordering of the returned actions.
343        // Some iterators like `HashableHashSet` do not guarantee a stable order.
344        // For simplicity, we sort actions here.
345        actions.sort_unstable_by(|a, b| {
346            use ActorModelAction::*;
347            let variant_order = |act: &ActorModelAction<_, _, _>| match act {
348                Drop(_) => 0,
349                Deliver { .. } => 1,
350                Timeout(_, _) => 2,
351                Crash(_) => 3,
352                Recover(_) => 4,
353                SelectRandom { .. } => 5,
354            };
355            let va = variant_order(a);
356            let vb = variant_order(b);
357            va.cmp(&vb).then_with(|| match (a, b) {
358                (
359                    Deliver {
360                        src: s1,
361                        dst: d1,
362                        msg: m1,
363                    },
364                    Deliver {
365                        src: s2,
366                        dst: d2,
367                        msg: m2,
368                    },
369                ) => s1.cmp(s2).then_with(|| d1.cmp(d2)).then_with(|| m1.cmp(m2)),
370                (Drop(e1), Drop(e2)) => e1.cmp(e2),
371                (Timeout(id1, t1), Timeout(id2, t2)) => id1.cmp(id2).then_with(|| t1.cmp(t2)),
372                (Crash(id1), Crash(id2)) => id1.cmp(id2),
373                (Recover(id1), Recover(id2)) => id1.cmp(id2),
374                (
375                    SelectRandom {
376                        actor: a1,
377                        key: k1,
378                        random: r1,
379                    },
380                    SelectRandom {
381                        actor: a2,
382                        key: k2,
383                        random: r2,
384                    },
385                ) => a1.cmp(a2).then_with(|| k1.cmp(k2)).then_with(|| r1.cmp(r2)),
386                _ => std::cmp::Ordering::Equal,
387            })
388        });
389    }
390
391    fn next_state(
392        &self,
393        last_sys_state: &Self::State,
394        action: Self::Action,
395    ) -> Option<Self::State> {
396        match action {
397            ActorModelAction::Drop(env) => {
398                let mut next_state = last_sys_state.clone();
399                next_state.network.on_drop(env);
400                Some(next_state)
401            }
402            ActorModelAction::Deliver { src, dst: id, msg } => {
403                let index = usize::from(id);
404                let last_actor_state = &last_sys_state.actor_states.get(index);
405
406                // Not all messages can be delivered, so ignore those.
407                if last_actor_state.is_none() {
408                    return None;
409                }
410                if last_sys_state.crashed[index] {
411                    return None;
412                }
413
414                let last_actor_state = &**last_actor_state.unwrap();
415                let mut state = Cow::Borrowed(last_actor_state);
416
417                // Some operations are no-ops, so ignore those as well.
418                let mut out = Out::new();
419                self.actors[index].on_msg(id, &mut state, src, msg.clone(), &mut out);
420                if is_no_op(&state, &out) && !matches!(self.init_network, Network::Ordered(_)) {
421                    return None;
422                }
423                let history = (self.record_msg_in)(
424                    &self.cfg,
425                    &last_sys_state.history,
426                    Envelope {
427                        src,
428                        dst: id,
429                        msg: &msg,
430                    },
431                );
432
433                // Update the state as necessary:
434                // - Drop delivered message if not a duplicating network.
435                // - Swap out revised actor state.
436                // - Track message input history.
437                // - Handle effect of commands on timers, network, and message output history.
438                //
439                // Strictly speaking, this state should be updated regardless of whether the
440                // actor and history updates are a no-op. The current implementation is only
441                // safe if invariants do not relate to the existence of envelopes on the
442                // network.
443                let mut next_sys_state = last_sys_state.clone();
444                let env = Envelope { src, dst: id, msg };
445                next_sys_state.network.on_deliver(env);
446                if let Cow::Owned(next_actor_state) = state {
447                    next_sys_state.actor_states[index] = Arc::new(next_actor_state);
448                }
449                if let Some(history) = history {
450                    next_sys_state.history = history;
451                }
452                self.process_commands(id, out, &mut next_sys_state);
453                Some(next_sys_state)
454            }
455            ActorModelAction::Timeout(id, timer) => {
456                // Clone new state if necessary (otherwise early exit).
457                let index = usize::from(id);
458                let mut state = Cow::Borrowed(&*last_sys_state.actor_states[index]);
459                let mut out = Out::new();
460                self.actors[index].on_timeout(id, &mut state, &timer, &mut out);
461                if is_no_op_with_timer(&state, &out, &timer) {
462                    return None;
463                }
464                let mut next_sys_state = last_sys_state.clone();
465
466                // Timer is no longer valid.
467                next_sys_state.timers_set[index].cancel(&timer);
468
469                if let Cow::Owned(next_actor_state) = state {
470                    next_sys_state.actor_states[index] = Arc::new(next_actor_state);
471                }
472                self.process_commands(id, out, &mut next_sys_state);
473                Some(next_sys_state)
474            }
475            ActorModelAction::Crash(id) => {
476                let index = usize::from(id);
477
478                let mut next_sys_state = last_sys_state.clone();
479                next_sys_state.timers_set[index].cancel_all();
480                next_sys_state.random_choices[index].map.clear();
481                next_sys_state.crashed[index] = true;
482
483                Some(next_sys_state)
484            }
485            ActorModelAction::Recover(id) => {
486                let index = usize::from(id);
487                assert!(last_sys_state.crashed[index]);
488                let mut out = Out::new();
489                let state = self.actors[index].on_start(
490                    id,
491                    &last_sys_state.actor_storages[index],
492                    &mut out,
493                );
494                let mut next_sys_state = last_sys_state.clone();
495                next_sys_state.actor_states[index] = Arc::new(state);
496                next_sys_state.crashed[index] = false;
497                self.process_commands(id, out, &mut next_sys_state);
498                Some(next_sys_state)
499            }
500            ActorModelAction::SelectRandom { actor, key, random } => {
501                let actor_index = usize::from(actor);
502                let mut state = Cow::Borrowed(&*last_sys_state.actor_states[actor_index]);
503                let mut out = Out::new();
504                self.actors[actor_index].on_random(actor, &mut state, &random, &mut out);
505                let mut next_sys_state = last_sys_state.clone();
506                // This random choice is no longer valid.
507                next_sys_state.random_choices[actor_index].remove(&key);
508
509                if let Cow::Owned(next_actor_state) = state {
510                    next_sys_state.actor_states[actor_index] = Arc::new(next_actor_state);
511                }
512                self.process_commands(actor, out, &mut next_sys_state);
513                Some(next_sys_state)
514            }
515        }
516    }
517
518    fn format_action(&self, action: &Self::Action) -> String {
519        match action {
520            ActorModelAction::Deliver { src, dst, msg } => {
521                format!("{src:?} → {msg:?} → {dst:?}")
522            }
523            ActorModelAction::SelectRandom {
524                actor,
525                key: _,
526                random,
527            } => format!("{actor:?} select random {random:?}"),
528            _ => format!("{action:?}"),
529        }
530    }
531
532    fn format_step(&self, last_state: &Self::State, action: Self::Action) -> Option<String>
533    where
534        Self::State: Debug,
535    {
536        struct ActorStep<'a, A: Actor> {
537            last_state: &'a A::State,
538            next_state: Option<A::State>,
539            out: Out<A>,
540        }
541        impl<A: Actor> Display for ActorStep<'_, A> {
542            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
543                writeln!(f, "OUT: {:?}", self.out)?;
544                writeln!(f)?;
545                if let Some(next_state) = &self.next_state {
546                    writeln!(f, "NEXT_STATE: {next_state:#?}")?;
547                    writeln!(f)?;
548                    writeln!(f, "PREV_STATE: {:#?}", self.last_state)
549                } else {
550                    writeln!(f, "UNCHANGED: {:#?}", self.last_state)
551                }
552            }
553        }
554
555        match action {
556            ActorModelAction::Drop(env) => Some(format!("DROP: {env:?}")),
557            ActorModelAction::Deliver { src, dst: id, msg } => {
558                let index = usize::from(id);
559                let last_actor_state = match last_state.actor_states.get(index) {
560                    None => return None,
561                    Some(last_actor_state) => &**last_actor_state,
562                };
563                let mut actor_state = Cow::Borrowed(last_actor_state);
564                let mut out = Out::new();
565                self.actors[index].on_msg(id, &mut actor_state, src, msg, &mut out);
566                Some(format!(
567                    "{}",
568                    ActorStep {
569                        last_state: last_actor_state,
570                        next_state: match actor_state {
571                            Cow::Borrowed(_) => None,
572                            Cow::Owned(next_actor_state) => Some(next_actor_state),
573                        },
574                        out,
575                    }
576                ))
577            }
578            ActorModelAction::Timeout(id, timer) => {
579                let index = usize::from(id);
580                let last_actor_state = match last_state.actor_states.get(index) {
581                    None => return None,
582                    Some(last_actor_state) => &**last_actor_state,
583                };
584                let mut actor_state = Cow::Borrowed(last_actor_state);
585                let mut out = Out::new();
586                self.actors[index].on_timeout(id, &mut actor_state, &timer, &mut out);
587                Some(format!(
588                    "{}",
589                    ActorStep {
590                        last_state: last_actor_state,
591                        next_state: match actor_state {
592                            Cow::Borrowed(_) => None,
593                            Cow::Owned(next_actor_state) => Some(next_actor_state),
594                        },
595                        out,
596                    }
597                ))
598            }
599            ActorModelAction::Crash(id) => {
600                let index = usize::from(id);
601                last_state.actor_states.get(index).map(|last_actor_state| {
602                    format!(
603                        "{}",
604                        ActorStep {
605                            last_state: &**Cow::Borrowed(last_actor_state),
606                            next_state: None,
607                            out: Out::new() as Out<A>,
608                        }
609                    )
610                })
611            }
612            ActorModelAction::Recover(id) => {
613                let index = usize::from(id);
614                let last_actor_state = match last_state.actor_states.get(index) {
615                    None => return None,
616                    Some(last_actor_state) => &**last_actor_state,
617                };
618                let mut out = Out::new();
619                let actor_state =
620                    self.actors[index].on_start(id, &last_state.actor_storages[index], &mut out);
621                Some(format!(
622                    "{}",
623                    ActorStep {
624                        last_state: last_actor_state,
625                        next_state: Some(actor_state),
626                        out,
627                    }
628                ))
629            }
630            ActorModelAction::SelectRandom {
631                actor,
632                key: _,
633                random,
634            } => {
635                let index = usize::from(actor);
636                let last_actor_state = match last_state.actor_states.get(index) {
637                    None => return None,
638                    Some(last_actor_state) => &**last_actor_state,
639                };
640                let mut actor_state = Cow::Borrowed(last_actor_state);
641                let mut out = Out::new();
642                self.actors[index].on_random(actor, &mut actor_state, &random, &mut out);
643                Some(format!(
644                    "{}",
645                    ActorStep {
646                        last_state: last_actor_state,
647                        next_state: match actor_state {
648                            Cow::Borrowed(_) => None,
649                            Cow::Owned(next_actor_state) => Some(next_actor_state),
650                        },
651                        out,
652                    }
653                ))
654            }
655        }
656    }
657
658    /// Draws a sequence diagram for the actor system.
659    fn as_svg(&self, path: Path<Self::State, Self::Action>) -> Option<String> {
660        use std::fmt::Write;
661
662        let approximate_letter_width_px = 10;
663        let actor_names = self
664            .actors
665            .iter()
666            .enumerate()
667            .map(|(i, a)| {
668                let name = a.name();
669                if name.is_empty() {
670                    i.to_string()
671                } else {
672                    format!("{i} {name}")
673                }
674            })
675            .collect::<Vec<_>>();
676        let max_name_len = actor_names
677            .iter()
678            .map(|n| n.len() as u64)
679            .max()
680            .unwrap_or_default()
681            * approximate_letter_width_px;
682        let spacing = std::cmp::max(100, max_name_len);
683        let plot = |x, y| (x as u64 * spacing, y as u64 * 30);
684        let actor_count = path.last_state().actor_states.len();
685        let path = path.into_vec();
686
687        // SVG wrapper.
688        let (mut svg_w, svg_h) = plot(actor_count, path.len());
689        svg_w += 300; // KLUDGE: extra width for event labels
690        let mut svg = format!(
691            "<svg version='1.1' baseProfile='full' \
692                  width='{}' height='{}' viewbox='-20 -20 {} {}' \
693                  xmlns='http://www.w3.org/2000/svg'>",
694            svg_w,
695            svg_h,
696            svg_w + 20,
697            svg_h + 20
698        );
699
700        // Definitions.
701        write!(&mut svg, "\
702            <defs>\
703              <marker class='svg-event-shape' id='arrow' markerWidth='12' markerHeight='10' refX='12' refY='5' orient='auto'>\
704                <polygon points='0 0, 12 5, 0 10' />\
705              </marker>\
706            </defs>").unwrap();
707
708        // Vertical timeline for each actor.
709        for (actor_index, actor_name) in actor_names.iter().enumerate() {
710            let (x1, y1) = plot(actor_index, 0);
711            let (x2, y2) = plot(actor_index, path.len());
712            writeln!(
713                &mut svg,
714                "<line x1='{x1}' y1='{y1}' x2='{x2}' y2='{y2}' class='svg-actor-timeline' />"
715            )
716            .unwrap();
717            writeln!(
718                &mut svg,
719                "<text x='{x1}' y='{y1}' class='svg-actor-label'>{actor_name}</text>"
720            )
721            .unwrap();
722        }
723
724        // Arrow for each delivery. Circle for other events.
725        let mut send_time = HashMap::new();
726        for (time, (state, action)) in path.clone().into_iter().enumerate() {
727            let time = time + 1; // action is for the next step
728            match action {
729                Some(ActorModelAction::Deliver { src, dst: id, msg }) => {
730                    let src_time = *send_time.get(&(src, id, msg.clone())).unwrap_or(&0);
731                    let (x1, y1) = plot(src.into(), src_time);
732                    let (x2, y2) = plot(id.into(), time);
733                    writeln!(&mut svg, "<line x1='{x1}' x2='{x2}' y1='{y1}' y2='{y2}' marker-end='url(#arrow)' class='svg-event-line' />").unwrap();
734
735                    // Track sends to facilitate building arrows.
736                    let index = usize::from(id);
737                    if let Some(actor_state) = state.actor_states.get(index) {
738                        let mut actor_state = Cow::Borrowed(&**actor_state);
739                        let mut out = Out::new();
740                        self.actors[index].on_msg(id, &mut actor_state, src, msg, &mut out);
741                        for command in out {
742                            if let Command::Send(dst, msg) = command {
743                                send_time.insert((id, dst, msg), time);
744                            }
745                        }
746                    }
747                }
748                Some(ActorModelAction::Timeout(actor_id, timer)) => {
749                    let (x, y) = plot(actor_id.into(), time);
750                    writeln!(
751                        &mut svg,
752                        "<circle cx='{x}' cy='{y}' r='10' class='svg-event-shape' />"
753                    )
754                    .unwrap();
755
756                    // Track sends to facilitate building arrows.
757                    let index = usize::from(actor_id);
758                    if let Some(actor_state) = state.actor_states.get(index) {
759                        let mut actor_state = Cow::Borrowed(&**actor_state);
760                        let mut out = Out::new();
761                        self.actors[index].on_timeout(actor_id, &mut actor_state, &timer, &mut out);
762                        for command in out {
763                            if let Command::Send(dst, msg) = command {
764                                send_time.insert((actor_id, dst, msg), time);
765                            }
766                        }
767                    }
768                }
769                Some(ActorModelAction::Crash(actor_id)) => {
770                    let (x, y) = plot(actor_id.into(), time);
771                    writeln!(
772                        &mut svg,
773                        "<circle cx='{x}' cy='{y}' r='10' class='svg-event-shape' />",
774                    )
775                    .unwrap();
776                }
777                Some(ActorModelAction::SelectRandom {
778                    actor,
779                    key: _,
780                    random,
781                }) => {
782                    let (x, y) = plot(actor.into(), time);
783                    writeln!(
784                        &mut svg,
785                        "<circle cx='{x}' cy='{y}' r='10' class='svg-event-shape' />"
786                    )
787                    .unwrap();
788
789                    // Track sends to facilitate building arrows.
790                    let index = usize::from(actor);
791                    if let Some(actor_state) = state.actor_states.get(index) {
792                        let mut actor_state = Cow::Borrowed(&**actor_state);
793                        let mut out = Out::new();
794                        self.actors[index].on_random(actor, &mut actor_state, &random, &mut out);
795                        for command in out {
796                            if let Command::Send(dst, msg) = command {
797                                send_time.insert((actor, dst, msg), time);
798                            }
799                        }
800                    }
801                }
802                Some(ActorModelAction::Recover(actor_id)) => {
803                    let (x, y) = plot(actor_id.into(), time);
804                    writeln!(
805                        &mut svg,
806                        "<circle cx='{x}' cy='{y}' r='10' class='svg-event-shape' />"
807                    )
808                    .unwrap();
809                }
810                _ => {}
811            }
812        }
813
814        // Handle event labels last to ensure they are drawn over shapes.
815        for (time, (_state, action)) in path.into_iter().enumerate() {
816            let time = time + 1; // action is for the next step
817            match action {
818                Some(ActorModelAction::Deliver { dst: id, msg, .. }) => {
819                    let (x, y) = plot(id.into(), time);
820                    writeln!(
821                        &mut svg,
822                        "<text x='{x}' y='{y}' class='svg-event-label'>{msg:?}</text>"
823                    )
824                    .unwrap();
825                }
826                Some(ActorModelAction::Timeout(id, timer)) => {
827                    let (x, y) = plot(id.into(), time);
828                    writeln!(
829                        &mut svg,
830                        "<text x='{x}' y='{y}' class='svg-event-label'>Timeout({timer:?})</text>"
831                    )
832                    .unwrap();
833                }
834                Some(ActorModelAction::Crash(id)) => {
835                    let (x, y) = plot(id.into(), time);
836                    writeln!(
837                        &mut svg,
838                        "<text x='{x}' y='{y}' class='svg-event-label'>Crash</text>"
839                    )
840                    .unwrap();
841                }
842                Some(ActorModelAction::SelectRandom {
843                    actor,
844                    key: _,
845                    random,
846                }) => {
847                    let (x, y) = plot(actor.into(), time);
848                    writeln!(
849                        &mut svg,
850                        "<text x='{x}' y='{y}' class='svg-event-label'>Random({random:?})</text>",
851                    )
852                    .unwrap();
853                }
854                Some(ActorModelAction::Recover(id)) => {
855                    let (x, y) = plot(id.into(), time);
856                    writeln!(
857                        &mut svg,
858                        "<text x='{x}' y='{y}' class='svg-event-label'>Recover</text>"
859                    )
860                    .unwrap();
861                }
862                _ => {}
863            }
864        }
865
866        writeln!(&mut svg, "</svg>").unwrap();
867        Some(svg)
868    }
869
870    fn properties(&self) -> Vec<Property<Self>> {
871        self.properties.clone()
872    }
873
874    fn within_boundary(&self, state: &Self::State) -> bool {
875        (self.within_boundary)(&self.cfg, state)
876    }
877}
878
879#[cfg(test)]
880mod test {
881    use super::*;
882    use crate::actor::actor_test_util::ping_pong::{PingPongCfg, PingPongMsg, PingPongMsg::*};
883    use crate::actor::ActorModelAction::*;
884    use crate::{Checker, PathRecorder, StateRecorder};
885    use std::collections::{BTreeSet, HashSet};
886    use std::sync::Arc;
887
888    #[test]
889    fn visits_expected_states() {
890        use std::iter::FromIterator;
891
892        // helper to make the test more concise
893        let states_and_network =
894            |states: Vec<u32>,
895             envelopes: Vec<Envelope<_>>,
896             last_msg: Option<Envelope<PingPongMsg>>| {
897                let states_len = states.len();
898                let timers_set = vec![Timers::new(); states_len];
899                let crashed = vec![false; states.len()];
900                ActorModelState {
901                    actor_states: states.into_iter().map(Arc::new).collect::<Vec<_>>(),
902                    network: Network::new_unordered_duplicating_with_last_msg(envelopes, last_msg),
903                    timers_set,
904                    random_choices: vec![RandomChoices::default(); states_len],
905                    crashed,
906                    history: (0_u32, 0_u32), // constant as `maintains_history: false`
907                    actor_storages: vec![None; states_len],
908                }
909            };
910
911        let (recorder, accessor) = StateRecorder::new_with_accessor();
912        let checker = PingPongCfg {
913            maintains_history: false,
914            max_nat: 1,
915        }
916        .into_model()
917        .lossy_network(LossyNetwork::Yes)
918        .checker()
919        .visitor(recorder)
920        .spawn_bfs()
921        .join();
922        assert_eq!(checker.unique_state_count(), 14);
923
924        let state_space = accessor();
925        assert_eq!(state_space.len(), 14); // same as the generated count
926        #[rustfmt::skip]
927        assert_eq!(HashSet::<_>::from_iter(state_space), HashSet::from_iter(vec![
928            // When the network loses no messages...
929            states_and_network(
930                vec![0, 0],
931                vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }],
932                None),
933            states_and_network(
934                vec![0, 1],
935                vec![
936                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
937                    Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
938                ],
939                Some(Envelope{ src: Id::from(0), dst: Id::from(1), msg: Ping(0) })),
940            states_and_network(
941                vec![1, 1],
942                vec![
943                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
944                    Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
945                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
946                ],
947                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
948
949            // When the network loses the message for pinger-ponger state (0, 0)...
950            states_and_network(
951                vec![0, 0],
952                Vec::new(),
953                None),
954
955            // When the network loses a message for pinger-ponger state (0, 1)
956            states_and_network(
957                vec![0, 1],
958                vec![Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) }],
959                Some(Envelope{ src: Id::from(0), dst: Id::from(1), msg: Ping(0) })),
960            states_and_network(
961                vec![0, 1],
962                vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }],
963                Some(Envelope{ src: Id::from(0), dst: Id::from(1), msg: Ping(0) })),
964            states_and_network(
965                vec![0, 1],
966                Vec::new(),
967                Some(Envelope{ src: Id::from(0), dst: Id::from(1), msg: Ping(0) })),
968
969            // When the network loses a message for pinger-ponger state (1, 1)
970            states_and_network(
971                vec![1, 1],
972                vec![
973                    Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
974                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
975                ],
976                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
977            states_and_network(
978                vec![1, 1],
979                vec![
980                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
981                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) },
982                ],
983                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
984            states_and_network(
985                vec![1, 1],
986                vec![
987                    Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) },
988                    Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) },
989                ],
990                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
991            states_and_network(
992                vec![1, 1],
993                vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(1) }],
994                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
995            states_and_network(
996                vec![1, 1],
997                vec![Envelope { src: Id::from(1), dst: Id::from(0), msg: Pong(0) }],
998                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
999            states_and_network(
1000                vec![1, 1],
1001                vec![Envelope { src: Id::from(0), dst: Id::from(1), msg: Ping(0) }],
1002                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
1003            states_and_network(
1004                vec![1, 1],
1005                Vec::new(),
1006                Some(Envelope{ src: Id::from(1), dst: Id::from(0), msg: Pong(0) })),
1007        ]));
1008    }
1009
1010    #[test]
1011    fn no_op_depends_on_network() {
1012        #[derive(Clone)]
1013        enum MyActor {
1014            Client { server: Id },
1015            Server,
1016        }
1017        #[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
1018        enum Msg {
1019            Ignored,
1020            Interesting,
1021        }
1022        impl Actor for MyActor {
1023            type Msg = Msg;
1024            type State = String;
1025            type Timer = ();
1026            type Random = ();
1027            type Storage = ();
1028            fn on_start(
1029                &self,
1030                _id: Id,
1031                _storage: &Option<Self::Storage>,
1032                o: &mut Out<Self>,
1033            ) -> Self::State {
1034                if let MyActor::Client { server } = self {
1035                    o.send(*server, Msg::Ignored);
1036                    o.send(*server, Msg::Interesting);
1037                }
1038                "Awaiting an interesting message.".to_string()
1039            }
1040            fn on_msg(
1041                &self,
1042                _: Id,
1043                state: &mut Cow<Self::State>,
1044                _: Id,
1045                msg: Self::Msg,
1046                _: &mut Out<Self>,
1047            ) {
1048                if msg == Msg::Interesting {
1049                    *state.to_mut() = "Got an interesting message.".to_string();
1050                }
1051            }
1052        }
1053
1054        let model = ActorModel::new((), ())
1055            .actor(MyActor::Client { server: 1.into() })
1056            .actor(MyActor::Server)
1057            .lossy_network(LossyNetwork::No)
1058            .property(Expectation::Always, "Check everything", |_, _| true);
1059        assert_eq!(
1060            model
1061                .clone()
1062                .init_network(Network::new_unordered_duplicating([]))
1063                .checker()
1064                .spawn_bfs()
1065                .join()
1066                .unique_state_count(),
1067            2 // initial and delivery of Interesting
1068        );
1069        assert_eq!(
1070            model
1071                .clone()
1072                .init_network(Network::new_unordered_nonduplicating([]))
1073                .checker()
1074                .spawn_bfs()
1075                .join()
1076                .unique_state_count(),
1077            2 // initial and delivery of Interesting
1078        );
1079        assert_eq!(
1080            model
1081                .clone()
1082                .init_network(Network::new_ordered([]))
1083                .checker()
1084                .spawn_bfs()
1085                .join()
1086                .unique_state_count(),
1087            3 // initial, delivery of Uninteresting, and subsequent delivery of Interesting
1088        );
1089    }
1090
1091    #[test]
1092    fn maintains_fixed_delta_despite_lossy_duplicating_network() {
1093        let checker = PingPongCfg {
1094            max_nat: 5,
1095            maintains_history: false,
1096        }
1097        .into_model()
1098        .lossy_network(LossyNetwork::Yes)
1099        .checker()
1100        .spawn_bfs()
1101        .join();
1102        assert_eq!(checker.unique_state_count(), 4_094);
1103        checker.assert_no_discovery("delta within 1");
1104    }
1105
1106    #[test]
1107    fn may_never_reach_max_on_lossy_network() {
1108        let checker = PingPongCfg {
1109            max_nat: 5,
1110            maintains_history: false,
1111        }
1112        .into_model()
1113        .lossy_network(LossyNetwork::Yes)
1114        .checker()
1115        .spawn_bfs()
1116        .join();
1117        assert_eq!(checker.unique_state_count(), 4_094);
1118
1119        // can lose the first message and get stuck, for example
1120        checker.assert_discovery(
1121            "must reach max",
1122            vec![Drop(Envelope {
1123                src: Id(0),
1124                dst: Id(1),
1125                msg: Ping(0),
1126            })],
1127        );
1128    }
1129
1130    #[test]
1131    fn eventually_reaches_max_on_perfect_delivery_network() {
1132        let checker = PingPongCfg {
1133            max_nat: 5,
1134            maintains_history: false,
1135        }
1136        .into_model()
1137        .init_network(Network::new_unordered_nonduplicating([]))
1138        .lossy_network(LossyNetwork::No)
1139        .checker()
1140        .spawn_bfs()
1141        .join();
1142        assert_eq!(checker.unique_state_count(), 11);
1143        checker.assert_no_discovery("must reach max");
1144    }
1145
1146    #[test]
1147    fn can_reach_max() {
1148        let checker = PingPongCfg {
1149            max_nat: 5,
1150            maintains_history: false,
1151        }
1152        .into_model()
1153        .lossy_network(LossyNetwork::No)
1154        .checker()
1155        .spawn_bfs()
1156        .join();
1157        assert_eq!(checker.unique_state_count(), 11);
1158        assert_eq!(
1159            checker
1160                .discovery("can reach max")
1161                .unwrap()
1162                .last_state()
1163                .actor_states,
1164            vec![Arc::new(5), Arc::new(5)]
1165        );
1166    }
1167
1168    #[test]
1169    fn might_never_reach_beyond_max() {
1170        // ^ and in fact will never. This is a subtle distinction: we're exercising a
1171        //   falsifiable liveness property here (eventually must exceed max), whereas "will never"
1172        //   refers to a verifiable safety property (always will not exceed).
1173
1174        let checker = PingPongCfg {
1175            max_nat: 5,
1176            maintains_history: false,
1177        }
1178        .into_model()
1179        .init_network(Network::new_unordered_nonduplicating([]))
1180        .lossy_network(LossyNetwork::No)
1181        .checker()
1182        .spawn_bfs()
1183        .join();
1184        assert_eq!(checker.unique_state_count(), 11);
1185
1186        // this is an example of a liveness property that fails to hold (due to the boundary)
1187        assert_eq!(
1188            checker
1189                .discovery("must exceed max")
1190                .unwrap()
1191                .last_state()
1192                .actor_states,
1193            vec![Arc::new(5), Arc::new(5)]
1194        );
1195    }
1196
1197    #[test]
1198    fn handles_undeliverable_messages() {
1199        assert_eq!(
1200            ActorModel::new((), ())
1201                .actor(())
1202                .property(Expectation::Always, "unused", |_, _| true) // force full traversal
1203                .init_network(Network::new_unordered_duplicating([Envelope {
1204                    src: 0.into(),
1205                    dst: 99.into(),
1206                    msg: ()
1207                }]))
1208                .checker()
1209                .spawn_bfs()
1210                .join()
1211                .unique_state_count(),
1212            1
1213        );
1214    }
1215
1216    #[test]
1217    fn handles_ordered_network_flag() {
1218        #[derive(Clone)]
1219        struct OrderedNetworkActor;
1220        impl Actor for OrderedNetworkActor {
1221            type Msg = u8;
1222            type State = Vec<u8>;
1223            type Timer = ();
1224            type Random = ();
1225            type Storage = ();
1226            fn on_start(
1227                &self,
1228                id: Id,
1229                _storage: &Option<Self::Storage>,
1230                o: &mut Out<Self>,
1231            ) -> Self::State {
1232                if id == 0.into() {
1233                    // Count down.
1234                    o.send(1.into(), 2);
1235                    o.send(1.into(), 1);
1236                }
1237                Vec::new()
1238            }
1239            fn on_msg(
1240                &self,
1241                _: Id,
1242                state: &mut Cow<Self::State>,
1243                _: Id,
1244                msg: Self::Msg,
1245                _: &mut Out<Self>,
1246            ) {
1247                state.to_mut().push(msg);
1248            }
1249        }
1250
1251        let model = ActorModel::new((), ())
1252            .actors(vec![OrderedNetworkActor, OrderedNetworkActor])
1253            .property(Expectation::Always, "", |_, _| true)
1254            .init_network(Network::new_unordered_nonduplicating([]));
1255
1256        // Fewer states if network is ordered.
1257        let (recorder, accessor) = StateRecorder::new_with_accessor();
1258        model
1259            .clone()
1260            .init_network(Network::new_ordered([]))
1261            .checker()
1262            .visitor(recorder)
1263            .spawn_bfs()
1264            .join();
1265        let recipient_states: BTreeSet<Vec<u8>> = accessor()
1266            .into_iter()
1267            .map(|s| (*s.actor_states[1]).clone())
1268            .collect();
1269        assert_eq!(
1270            recipient_states,
1271            BTreeSet::from([vec![], vec![2], vec![2, 1]])
1272        );
1273
1274        // More states if network is not ordered.
1275        let (recorder, accessor) = StateRecorder::new_with_accessor();
1276        model
1277            .init_network(Network::new_unordered_nonduplicating([]))
1278            .checker()
1279            .visitor(recorder)
1280            .spawn_bfs()
1281            .join();
1282        let recipient_states: BTreeSet<Vec<u8>> = accessor()
1283            .into_iter()
1284            .map(|s| (*s.actor_states[1]).clone())
1285            .collect();
1286        assert_eq!(
1287            recipient_states,
1288            BTreeSet::from([vec![], vec![1], vec![2], vec![1, 2], vec![2, 1]]),
1289        );
1290    }
1291
1292    #[test]
1293    fn unordered_network_has_a_bug() {
1294        // Imagine that actor 1 sends two copies of a message to actor 2. An earlier implementation
1295        // used a set to track envelopes in an unordered network even if it was lossy, and
1296        // therefore could not distinguish between dropping/delivering 1 message vs multiple
1297        // pending copies of the same message.
1298
1299        fn enumerate_action_sequences(
1300            lossy: LossyNetwork,
1301            init_network: Network<()>,
1302        ) -> HashSet<Vec<ActorModelAction<(), (), ()>>> {
1303            // There are two actors, and the first sends the same two messages to the second, which
1304            // counts them.
1305            struct A;
1306            impl Actor for A {
1307                type Msg = ();
1308                type State = usize; // receipt count
1309                type Timer = ();
1310                type Random = ();
1311                type Storage = ();
1312
1313                fn on_start(
1314                    &self,
1315                    id: Id,
1316                    _storage: &Option<Self::Storage>,
1317                    o: &mut Out<Self>,
1318                ) -> Self::State {
1319                    if id == 0.into() {
1320                        o.send(1.into(), ());
1321                        o.send(1.into(), ());
1322                    }
1323                    0
1324                }
1325                fn on_msg(
1326                    &self,
1327                    _: Id,
1328                    state: &mut Cow<Self::State>,
1329                    _: Id,
1330                    _: Self::Msg,
1331                    _: &mut Out<Self>,
1332                ) {
1333                    *state.to_mut() += 1;
1334                }
1335            }
1336
1337            // Return the actions taken for each path based on the specified network
1338            // characteristics.
1339            let (recorder, accessor) = PathRecorder::new_with_accessor();
1340            ActorModel::new((), ())
1341                .actors([A, A])
1342                .init_network(init_network)
1343                .lossy_network(lossy)
1344                .property(Expectation::Always, "force visiting all states", |_, _| {
1345                    true
1346                })
1347                .within_boundary(|_, s| *s.actor_states[1] < 4)
1348                .checker()
1349                .visitor(recorder)
1350                .spawn_dfs()
1351                .join();
1352            accessor().into_iter().map(|p| p.into_actions()).collect()
1353        }
1354
1355        // The actions are named here for brevity. These implement `Copy`.
1356        let deliver = ActorModelAction::<(), (), ()>::Deliver {
1357            src: 0.into(),
1358            msg: (),
1359            dst: 1.into(),
1360        };
1361        let drop = ActorModelAction::<(), (), ()>::Drop(Envelope {
1362            src: 0.into(),
1363            msg: (),
1364            dst: 1.into(),
1365        });
1366
1367        // Ordered networks can deliver/drop both messages.
1368        let ordered_lossless =
1369            enumerate_action_sequences(LossyNetwork::No, Network::new_ordered([]));
1370        assert!(ordered_lossless.contains(&vec![deliver.clone(), deliver.clone()]));
1371        assert!(!ordered_lossless.contains(&vec![
1372            deliver.clone(),
1373            deliver.clone(),
1374            deliver.clone()
1375        ]));
1376        let ordered_lossy = enumerate_action_sequences(LossyNetwork::Yes, Network::new_ordered([]));
1377        assert!(ordered_lossy.contains(&vec![deliver.clone(), deliver.clone()]));
1378        assert!(ordered_lossy.contains(&vec![deliver.clone(), drop.clone()])); // same state as "drop, deliver"
1379        assert!(ordered_lossy.contains(&vec![drop.clone(), drop.clone()]));
1380
1381        // Unordered duplicating networks can deliver/drop duplicates.
1382        //
1383        // IMPORTANT: in the context of a duplicating network, "dropping" must either entail:
1384        //            (1) a no-op or (2) never delivering again. This implementation favors the
1385        //            latter.
1386        let unord_dup_lossless =
1387            enumerate_action_sequences(LossyNetwork::No, Network::new_unordered_duplicating([]));
1388        assert!(unord_dup_lossless.contains(&vec![
1389            deliver.clone(),
1390            deliver.clone(),
1391            deliver.clone()
1392        ]));
1393        let unord_dup_lossy =
1394            enumerate_action_sequences(LossyNetwork::Yes, Network::new_unordered_duplicating([]));
1395        assert!(unord_dup_lossy.contains(&vec![deliver.clone(), deliver.clone(), deliver.clone()]));
1396        assert!(unord_dup_lossy.contains(&vec![deliver.clone(), deliver.clone(), drop.clone()]));
1397        assert!(unord_dup_lossy.contains(&vec![deliver.clone(), drop.clone()]));
1398        assert!(unord_dup_lossy.contains(&vec![drop.clone()]));
1399        assert!(!unord_dup_lossy.contains(&vec![drop.clone(), deliver.clone()])); // b/c drop means "never deliver again"
1400
1401        // Unordered nonduplicating networks can deliver/drop both messages.
1402        let unord_nondup_lossless =
1403            enumerate_action_sequences(LossyNetwork::No, Network::new_unordered_nonduplicating([]));
1404        assert!(unord_nondup_lossless.contains(&vec![deliver.clone(), deliver.clone()]));
1405        let unord_nondup_lossy = enumerate_action_sequences(
1406            LossyNetwork::Yes,
1407            Network::new_unordered_nonduplicating([]),
1408        );
1409        assert!(unord_nondup_lossy.contains(&vec![deliver.clone(), drop.clone()]));
1410        assert!(unord_nondup_lossy.contains(&vec![drop.clone(), drop.clone()]));
1411    }
1412
1413    #[test]
1414    fn resets_timer() {
1415        struct TestActor;
1416        impl Actor for TestActor {
1417            type State = ();
1418            type Msg = ();
1419            type Timer = ();
1420            type Random = ();
1421            type Storage = ();
1422            fn on_start(&self, _: Id, _: &Option<Self::Storage>, o: &mut Out<Self>) {
1423                o.set_timer((), model_timeout());
1424            }
1425            fn on_msg(
1426                &self,
1427                _: Id,
1428                _: &mut Cow<Self::State>,
1429                _: Id,
1430                _: Self::Msg,
1431                _: &mut Out<Self>,
1432            ) {
1433            }
1434        }
1435
1436        // Init state with timer, followed by next state without timer.
1437        assert_eq!(
1438            ActorModel::new((), ())
1439                .actor(TestActor)
1440                .property(Expectation::Always, "unused", |_, _| true) // force full traversal
1441                .checker()
1442                .spawn_bfs()
1443                .join()
1444                .unique_state_count(),
1445            2
1446        );
1447    }
1448
1449    #[test]
1450    fn choose_random() {
1451        #[derive(Hash, PartialEq, Eq, Debug, Clone, Ord, PartialOrd)]
1452        enum TestRandom {
1453            Choice1,
1454            Choice2,
1455            Choice3,
1456        }
1457
1458        struct TestActor;
1459        impl Actor for TestActor {
1460            type State = Option<TestRandom>;
1461            type Msg = ();
1462            type Timer = ();
1463            type Random = TestRandom;
1464            type Storage = ();
1465            fn on_start(
1466                &self,
1467                _: Id,
1468                _: &Option<Self::Storage>,
1469                o: &mut Out<Self>,
1470            ) -> Option<TestRandom> {
1471                o.choose_random(
1472                    "key1",
1473                    vec![
1474                        TestRandom::Choice1,
1475                        TestRandom::Choice2,
1476                        TestRandom::Choice3,
1477                    ],
1478                );
1479                None
1480            }
1481            fn on_msg(
1482                &self,
1483                _: Id,
1484                _: &mut Cow<Self::State>,
1485                _: Id,
1486                _: Self::Msg,
1487                _: &mut Out<Self>,
1488            ) {
1489            }
1490            fn on_random(
1491                &self,
1492                _: Id,
1493                state: &mut Cow<Self::State>,
1494                random: &Self::Random,
1495                _: &mut Out<Self>,
1496            ) {
1497                *state.to_mut() = Some(random.clone());
1498            }
1499        }
1500
1501        // Init state with a random choice, followed by 3 possible next states.
1502        assert_eq!(
1503            ActorModel::new((), ())
1504                .actor(TestActor)
1505                .property(Expectation::Always, "unused", |_, _| true) // force full traversal
1506                .checker()
1507                .spawn_bfs()
1508                .join()
1509                .unique_state_count(),
1510            4
1511        );
1512    }
1513
1514    #[test]
1515    fn overwrite_choose_random() {
1516        #[derive(Hash, PartialEq, Eq, Debug, Clone, Ord, PartialOrd)]
1517        enum TestRandom {
1518            Choice1,
1519            Choice2,
1520            Choice3,
1521        }
1522
1523        struct TestActor;
1524        impl Actor for TestActor {
1525            type State = Vec<TestRandom>;
1526            type Msg = ();
1527            type Timer = ();
1528            type Random = TestRandom;
1529            type Storage = ();
1530            fn on_start(
1531                &self,
1532                _: Id,
1533                _: &Option<Self::Storage>,
1534                o: &mut Out<Self>,
1535            ) -> Vec<TestRandom> {
1536                o.choose_random("key1", vec![TestRandom::Choice1]);
1537                o.choose_random("key2", vec![TestRandom::Choice2, TestRandom::Choice3]);
1538                Vec::new()
1539            }
1540            fn on_msg(
1541                &self,
1542                _: Id,
1543                _: &mut Cow<Self::State>,
1544                _: Id,
1545                _: Self::Msg,
1546                _: &mut Out<Self>,
1547            ) {
1548            }
1549            fn on_random(
1550                &self,
1551                _: Id,
1552                state: &mut Cow<Self::State>,
1553                random: &Self::Random,
1554                o: &mut Out<Self>,
1555            ) {
1556                if *random == TestRandom::Choice1 {
1557                    // If choice1 is chosen, set "key2" to just Choice3.
1558                    // If "key2" hasn't been taken yet, this will reduce the choice from [2,3] to
1559                    //    just [3].
1560                    // If "key2" has already been taken, this will allow the actor to choose "key2"
1561                    //    again, this time with just one option ([Choice3]).
1562                    o.choose_random("key2", vec![TestRandom::Choice3]);
1563                }
1564                state.to_mut().push(random.clone());
1565            }
1566        }
1567
1568        //      /-> key1:Choice1 -> key2:Choice3
1569        // Init --> key2:Choice2 -> key1:Choice1 -> key2:Choice3
1570        //      \-> key2:Choice3 -> key1:Choice1 -> key2:Choice3
1571        assert_eq!(
1572            ActorModel::new((), ())
1573                .actor(TestActor)
1574                .property(Expectation::Always, "unused", |_, _| true) // force full traversal
1575                .checker()
1576                .spawn_bfs()
1577                .join()
1578                .unique_state_count(),
1579            9
1580        );
1581    }
1582
1583    #[test]
1584    fn crash_test() {
1585        #[derive(Clone)]
1586        struct TestActor;
1587        impl Actor for TestActor {
1588            type Msg = ();
1589            type Timer = ();
1590            type State = ();
1591            type Storage = ();
1592            type Random = ();
1593            fn on_start(
1594                &self,
1595                _id: Id,
1596                _storage: &Option<Self::Storage>,
1597                _o: &mut Out<Self>,
1598            ) -> Self::State {
1599            }
1600        }
1601
1602        assert_eq!(
1603            ActorModel::new((), ())
1604                .actors(vec![TestActor; 3])
1605                .max_crashes(1)
1606                .property(Expectation::Always, "unused", |_, _| true) // force full traversal
1607                .checker()
1608                .spawn_bfs()
1609                .join()
1610                .unique_state_count(),
1611            // comb(3, 1) + 1 (no actor is crashed) = 3 + 1 = 4
1612            4
1613        )
1614    }
1615
1616    #[test]
1617    fn recover_test() {
1618        #[derive(Clone)]
1619        struct TestActor(usize); // carry a counter to limit the model space
1620        #[derive(Clone, Debug, PartialEq, Hash)]
1621        struct TestState {
1622            volatile: usize,
1623            non_volatile: usize,
1624        }
1625        #[derive(Clone, Debug, PartialEq, Hash)]
1626        struct TestStorage {
1627            non_volatile: usize,
1628        }
1629
1630        impl Actor for TestActor {
1631            type Msg = ();
1632            type Timer = ();
1633            type State = TestState;
1634            type Storage = TestStorage;
1635            type Random = ();
1636
1637            fn on_start(
1638                &self,
1639                _id: Id,
1640                storage: &Option<Self::Storage>,
1641                o: &mut Out<Self>,
1642            ) -> Self::State {
1643                o.set_timer((), model_timeout());
1644                if let Some(storage) = storage {
1645                    Self::State {
1646                        volatile: 0,
1647                        non_volatile: storage.non_volatile, // restore non-volatile state from `storage`
1648                    }
1649                } else {
1650                    Self::State {
1651                        volatile: 0,
1652                        non_volatile: 0, // no available `storage`
1653                    }
1654                }
1655            }
1656
1657            fn on_timeout(
1658                &self,
1659                _id: Id,
1660                state: &mut Cow<Self::State>,
1661                _timer: &Self::Timer,
1662                o: &mut Out<Self>,
1663            ) {
1664                let state = state.to_mut();
1665                state.volatile += 1;
1666                state.non_volatile += 1;
1667                o.save(TestStorage {
1668                    non_volatile: state.non_volatile,
1669                });
1670                if state.non_volatile < self.0 {
1671                    // restore the timer
1672                    o.set_timer((), model_timeout());
1673                }
1674            }
1675        }
1676
1677        let checker = ActorModel::new((), ())
1678            .actor(TestActor(3))
1679            .property(Expectation::Sometimes, "recovered", |_, state| {
1680                let actor_state = state.actor_states.first().unwrap();
1681                actor_state.non_volatile > actor_state.volatile
1682            })
1683            .max_crashes(1)
1684            .checker()
1685            .spawn_bfs()
1686            .join();
1687        checker.assert_any_discovery("recovered");
1688    }
1689}
1690
1691#[cfg(test)]
1692mod choice_test {
1693    use super::*;
1694    use crate::{Checker, Model, StateRecorder};
1695    use choice::Choice;
1696
1697    #[derive(Clone)]
1698    struct A {
1699        b: Id,
1700    }
1701    impl Actor for A {
1702        type State = u8;
1703        type Msg = ();
1704        type Timer = ();
1705        type Random = ();
1706        type Storage = ();
1707        fn on_start(&self, _: Id, _: &Option<Self::Storage>, _: &mut Out<Self>) -> Self::State {
1708            1
1709        }
1710        fn on_msg(
1711            &self,
1712            _: Id,
1713            state: &mut Cow<Self::State>,
1714            _: Id,
1715            _: Self::Msg,
1716            o: &mut Out<Self>,
1717        ) {
1718            *state.to_mut() = state.wrapping_add(1);
1719            o.send(self.b, ());
1720        }
1721    }
1722
1723    #[derive(Clone)]
1724    struct B {
1725        c: Id,
1726    }
1727    impl Actor for B {
1728        type State = char;
1729        type Msg = ();
1730        type Timer = ();
1731        type Random = ();
1732        type Storage = ();
1733        fn on_start(&self, _: Id, _: &Option<Self::Storage>, _: &mut Out<Self>) -> Self::State {
1734            'a'
1735        }
1736        fn on_msg(
1737            &self,
1738            _: Id,
1739            state: &mut Cow<Self::State>,
1740            _: Id,
1741            _: Self::Msg,
1742            o: &mut Out<Self>,
1743        ) {
1744            *state.to_mut() = (**state as u8).wrapping_add(1) as char;
1745            o.send(self.c, ());
1746        }
1747    }
1748
1749    #[derive(Clone)]
1750    struct C {
1751        a: Id,
1752    }
1753    impl Actor for C {
1754        type State = String;
1755        type Msg = ();
1756        type Timer = ();
1757        type Random = ();
1758        type Storage = ();
1759        fn on_start(&self, _: Id, _: &Option<Self::Storage>, o: &mut Out<Self>) -> Self::State {
1760            o.send(self.a, ());
1761            "I".to_string()
1762        }
1763        fn on_msg(
1764            &self,
1765            _: Id,
1766            state: &mut Cow<Self::State>,
1767            _: Id,
1768            _: Self::Msg,
1769            o: &mut Out<Self>,
1770        ) {
1771            state.to_mut().push('I');
1772            o.send(self.a, ());
1773        }
1774    }
1775
1776    #[test]
1777    fn choice_correctly_implements_actor() {
1778        use choice::choice;
1779        let sys = ActorModel::<choice![A, B, C], (), u8>::new((), 0)
1780            .actor(Choice::new(A { b: Id::from(1) }))
1781            .actor(Choice::new(B { c: Id::from(2) }).or())
1782            .actor(Choice::new(C { a: Id::from(0) }).or().or())
1783            .init_network(Network::new_unordered_nonduplicating([]))
1784            .record_msg_out(|_, out_count, _| Some(out_count + 1))
1785            .property(Expectation::Always, "true", |_, _| true)
1786            .within_boundary(|_, state| state.history < 8);
1787
1788        let (recorder, accessor) = StateRecorder::new_with_accessor();
1789
1790        sys.checker().visitor(recorder).spawn_dfs().join();
1791
1792        type StateVector = choice![u8, char, String];
1793        let states: Vec<Vec<StateVector>> = accessor()
1794            .into_iter()
1795            .map(|s| s.actor_states.into_iter().map(|a| (*a).clone()).collect())
1796            .collect();
1797        assert_eq!(
1798            states,
1799            vec![
1800                // Init.
1801                vec![
1802                    Choice::new(1),
1803                    Choice::new('a').or(),
1804                    Choice::new("I".to_string()).or().or(),
1805                ],
1806                // Then deliver to A.
1807                vec![
1808                    Choice::new(2),
1809                    Choice::new('a').or(),
1810                    Choice::new("I".to_string()).or().or(),
1811                ],
1812                // Then deliver to B.
1813                vec![
1814                    Choice::new(2),
1815                    Choice::new('b').or(),
1816                    Choice::new("I".to_string()).or().or(),
1817                ],
1818                // Then deliver to C.
1819                vec![
1820                    Choice::new(2),
1821                    Choice::new('b').or(),
1822                    Choice::new("II".to_string()).or().or(),
1823                ],
1824                // Then deliver to A again.
1825                vec![
1826                    Choice::new(3),
1827                    Choice::new('b').or(),
1828                    Choice::new("II".to_string()).or().or(),
1829                ],
1830                // Then deliver to B again.
1831                vec![
1832                    Choice::new(3),
1833                    Choice::new('c').or(),
1834                    Choice::new("II".to_string()).or().or(),
1835                ],
1836                // Then deliver to C again.
1837                vec![
1838                    Choice::new(3),
1839                    Choice::new('c').or(),
1840                    Choice::new("III".to_string()).or().or(),
1841                ],
1842            ]
1843        );
1844    }
1845}