1use 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#[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 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
46pub enum ActorModelAction<Msg, Timer, Random> {
47 Deliver { src: Id, dst: Id, msg: Msg },
49 Drop(Envelope<Msg>),
51 Timeout(Id, Timer),
53 Crash(Id),
55 Recover(Id),
57 SelectRandom {
59 actor: Id,
60 key: String,
61 random: Random,
62 },
63}
64
65#[derive(Copy, Clone, PartialEq)]
69pub enum LossyNetwork {
70 Yes,
71 No,
72}
73
74pub fn model_timeout() -> Range<Duration> {
78 Duration::from_micros(0)..Duration::from_micros(0)
79}
80
81pub 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 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 pub fn actor(mut self, actor: A) -> Self {
115 self.actors.push(actor);
116 self
117 }
118
119 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 pub fn init_network(mut self, init_network: Network<A::Msg>) -> Self {
129 self.init_network = init_network;
130 self
131 }
132
133 pub fn lossy_network(mut self, lossy_network: LossyNetwork) -> Self {
135 self.lossy_network = lossy_network;
136 self
137 }
138
139 pub fn max_crashes(mut self, max_crashes: usize) -> Self {
141 self.max_crashes = max_crashes;
142 self
143 }
144
145 #[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 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 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 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 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 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 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 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; for env in state.network.iter_deliverable() {
280 if self.lossy_network == LossyNetwork::Yes {
282 actions.push(ActorModelAction::Drop(env.to_cloned_msg()));
283 }
284
285 if usize::from(env.dst) < self.actors.len() {
287 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 } 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 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 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 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 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 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 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 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 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 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 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 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 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 let (mut svg_w, svg_h) = plot(actor_count, path.len());
689 svg_w += 300; 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 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 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 let mut send_time = HashMap::new();
726 for (time, (state, action)) in path.clone().into_iter().enumerate() {
727 let time = time + 1; 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 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 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 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 for (time, (_state, action)) in path.into_iter().enumerate() {
816 let time = time + 1; 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 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), 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); #[rustfmt::skip]
927 assert_eq!(HashSet::<_>::from_iter(state_space), HashSet::from_iter(vec![
928 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 states_and_network(
951 vec![0, 0],
952 Vec::new(),
953 None),
954
955 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 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 );
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 );
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 );
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 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 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 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) .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 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 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 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 fn enumerate_action_sequences(
1300 lossy: LossyNetwork,
1301 init_network: Network<()>,
1302 ) -> HashSet<Vec<ActorModelAction<(), (), ()>>> {
1303 struct A;
1306 impl Actor for A {
1307 type Msg = ();
1308 type State = usize; 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 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 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 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()])); assert!(ordered_lossy.contains(&vec![drop.clone(), drop.clone()]));
1380
1381 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()])); 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 assert_eq!(
1438 ActorModel::new((), ())
1439 .actor(TestActor)
1440 .property(Expectation::Always, "unused", |_, _| true) .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 assert_eq!(
1503 ActorModel::new((), ())
1504 .actor(TestActor)
1505 .property(Expectation::Always, "unused", |_, _| true) .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 o.choose_random("key2", vec![TestRandom::Choice3]);
1563 }
1564 state.to_mut().push(random.clone());
1565 }
1566 }
1567
1568 assert_eq!(
1572 ActorModel::new((), ())
1573 .actor(TestActor)
1574 .property(Expectation::Always, "unused", |_, _| true) .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) .checker()
1608 .spawn_bfs()
1609 .join()
1610 .unique_state_count(),
1611 4
1613 )
1614 }
1615
1616 #[test]
1617 fn recover_test() {
1618 #[derive(Clone)]
1619 struct TestActor(usize); #[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, }
1649 } else {
1650 Self::State {
1651 volatile: 0,
1652 non_volatile: 0, }
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 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 vec![
1802 Choice::new(1),
1803 Choice::new('a').or(),
1804 Choice::new("I".to_string()).or().or(),
1805 ],
1806 vec![
1808 Choice::new(2),
1809 Choice::new('a').or(),
1810 Choice::new("I".to_string()).or().or(),
1811 ],
1812 vec![
1814 Choice::new(2),
1815 Choice::new('b').or(),
1816 Choice::new("I".to_string()).or().or(),
1817 ],
1818 vec![
1820 Choice::new(2),
1821 Choice::new('b').or(),
1822 Choice::new("II".to_string()).or().or(),
1823 ],
1824 vec![
1826 Choice::new(3),
1827 Choice::new('b').or(),
1828 Choice::new("II".to_string()).or().or(),
1829 ],
1830 vec![
1832 Choice::new(3),
1833 Choice::new('c').or(),
1834 Choice::new("II".to_string()).or().or(),
1835 ],
1836 vec![
1838 Choice::new(3),
1839 Choice::new('c').or(),
1840 Choice::new("III".to_string()).or().or(),
1841 ],
1842 ]
1843 );
1844 }
1845}