1use std::{
14 collections::HashMap,
15 error,
16 fmt::{self},
17 hash,
18};
19
20use taco_display_utils::join_iterator;
21
22use crate::{
23 ActionDefinition, RuleDefinition, ThresholdAutomaton,
24 expressions::{
25 Atomic, BooleanExpression, Location, Parameter, Variable, properties::EvaluationError,
26 },
27 general_threshold_automaton::{GeneralThresholdAutomaton, Rule, UpdateExpression},
28};
29
30#[derive(Debug, Clone, PartialEq)]
35pub struct Configuration {
36 variable_assignment: HashMap<Variable, u32>,
38 location_assignment: HashMap<Location, u32>,
40}
41impl Configuration {
42 pub fn new<T, S>(variable_assignment: T, location_assignment: S) -> Self
52 where
53 T: Into<HashMap<Variable, u32>>,
54 S: Into<HashMap<Location, u32>>,
55 {
56 Configuration {
57 variable_assignment: variable_assignment.into(),
58 location_assignment: location_assignment.into(),
59 }
60 }
61
62 pub fn get_processes_in_location(&self, loc: &Location) -> u32 {
68 *self.location_assignment.get(loc).unwrap_or(&0)
69 }
70
71 pub fn get_variable_value(&self, var: &Variable) -> u32 {
76 *self.variable_assignment.get(var).unwrap_or(&0)
77 }
78
79 pub fn variable_assignment(&self) -> impl Iterator<Item = (&Variable, &u32)> {
84 self.variable_assignment.iter()
85 }
86
87 pub fn location_assignment(&self) -> impl Iterator<Item = (&Location, &u32)> {
93 self.location_assignment.iter()
94 }
95
96 pub fn apply_rule(&self, tr: &Transition) -> Option<Self> {
102 if *self.location_assignment.get(tr.rule_used.source())? == 0 {
105 return None;
106 }
107
108 let mut new_loc_assignment = self.location_assignment.clone();
109 let mut new_var_assignment = self.variable_assignment.clone();
110
111 new_loc_assignment
112 .entry(tr.rule_used.target().clone())
113 .and_modify(|e| *e += tr.number_applied);
114
115 if let Some(e) = new_loc_assignment.get_mut(tr.rule_used.source()) {
116 if let Some(sub) = e.checked_sub(tr.number_applied) {
117 *e = sub;
118 } else {
119 return None;
120 }
121 }
122
123 for act in tr.rule_used.actions() {
124 let var = act.variable();
125
126 match act.update() {
127 UpdateExpression::Inc(i) => {
128 *new_var_assignment.entry(var.clone()).or_insert(0) += tr.number_applied * i;
129 }
130 UpdateExpression::Dec(d) => {
131 *new_var_assignment.entry(var.clone()).or_insert(0) -= tr.number_applied * d;
132 }
133 UpdateExpression::Reset => _ = new_var_assignment.insert(var.clone(), 0),
134 UpdateExpression::Unchanged => continue,
135 }
136 }
137
138 Some(Self {
139 variable_assignment: new_var_assignment,
140 location_assignment: new_loc_assignment,
141 })
142 }
143
144 pub fn display_compact(&self) -> String {
147 let mut locs = self
148 .location_assignment
149 .iter()
150 .filter(|(_, n)| **n > 0)
151 .map(|(l, _)| l)
152 .collect::<Vec<_>>();
153 locs.sort_by_key(|loc| loc.name());
154 let locs = join_iterator(
155 locs.into_iter()
156 .map(|loc| format!("{} : {}", loc.name(), self.location_assignment[loc])),
157 ", ",
158 );
159
160 let mut vars = self
161 .variable_assignment
162 .iter()
163 .filter(|(_, n)| **n > 0)
164 .map(|(v, _)| v)
165 .collect::<Vec<_>>();
166 vars.sort_by_key(|var| var.name());
167 let vars = join_iterator(
168 vars.into_iter()
169 .map(|var| format!("{} : {}", var.name(), self.variable_assignment[var])),
170 ", ",
171 );
172
173 format!("locations: ({locs}), variables: ({vars})")
174 }
175}
176
177#[derive(Debug, Clone, PartialEq)]
184pub struct Transition {
185 rule_used: Rule,
187 number_applied: u32,
189}
190
191impl Transition {
192 pub fn new(rule_used: Rule, n: u32) -> Self {
196 Transition {
197 rule_used,
198 number_applied: n,
199 }
200 }
201
202 pub fn rule(&self) -> &Rule {
204 &self.rule_used
205 }
206
207 pub fn is_noop(&self) -> bool {
212 self.number_applied == 0
213 || (self.rule_used.source() == self.rule_used.target()
214 && self.rule_used.actions().all(|act| act.is_unchanged()))
215 }
216}
217
218#[derive(Debug, Clone, PartialEq)]
223pub struct Path {
224 ta: GeneralThresholdAutomaton,
226 parameter_assignment: HashMap<Parameter, u32>,
228 configurations: Vec<Configuration>,
230 transitions: Vec<Transition>,
232}
233impl Path {
234 pub fn get_parameter_value(&self, param: &Parameter) -> Option<u32> {
236 self.parameter_assignment.get(param).cloned()
237 }
238
239 pub fn parameter_values(&self) -> impl Iterator<Item = (&Parameter, &u32)> {
241 self.parameter_assignment.iter()
242 }
243
244 pub fn configurations(&self) -> impl Iterator<Item = &Configuration> {
246 self.configurations.iter()
247 }
248
249 pub fn transitions(&self) -> impl Iterator<Item = &Transition> {
251 self.transitions.iter()
252 }
253
254 pub fn display_compact(&self) -> String {
256 debug_assert!(self.configurations.len() == self.transitions.len() + 1);
257
258 let mut res = format!("Path of threshold automaton {}: \n", self.ta.name());
259
260 let param = display_assignment(self.parameter_assignment.clone());
261 res += &format!("parameters: {param}\n");
262
263 let mut it_transitions = self.transitions.iter();
264 for (n_config, config) in self.configurations.iter().enumerate() {
265 res += &format!("Configuration {n_config}: {}\n", config.display_compact());
266 if let Some(transition) = it_transitions.next() {
267 res += " |\n";
268 res += &format!(" |{transition}\n");
269 res += " V\n";
270 }
271 }
272
273 res
274 }
275}
276
277#[derive(Debug, Clone, PartialEq)]
290pub struct PathBuilder {
291 ta: GeneralThresholdAutomaton,
292 parameter_assignment: HashMap<Parameter, u32>,
293}
294
295impl PathBuilder {
296 pub fn new(ta: GeneralThresholdAutomaton) -> Self {
298 Self {
299 ta,
300 parameter_assignment: HashMap::new(),
301 }
302 }
303
304 pub fn add_parameter_assignment(
307 mut self,
308 param_evaluation: impl Into<HashMap<Parameter, u32>>,
309 ) -> Result<InitializedPathBuilder, PathBuilderError> {
310 let param_evaluation = param_evaluation.into();
311
312 self.ta.resilience_conditions().try_for_each(|cond| {
313 if !cond
314 .check_satisfied(&HashMap::new(), ¶m_evaluation)
315 .map_err(PathBuilderError::from)?
316 {
317 return Err(PathBuilderError::ResilienceConditionNotSatisfied(
318 Box::new(cond.clone()),
319 Box::new(param_evaluation.clone()),
320 ));
321 }
322
323 Ok(())
324 })?;
325
326 self.parameter_assignment = param_evaluation;
327
328 Ok(InitializedPathBuilder {
329 ta: self.ta,
330 n_processes: 0,
331 parameter_assignment: self.parameter_assignment,
332 configurations: Vec::new(),
333 transitions: Vec::new(),
334 })
335 }
336}
337
338#[derive(Debug, Clone, PartialEq)]
344pub struct InitializedPathBuilder {
345 ta: GeneralThresholdAutomaton,
347 n_processes: u32,
349 parameter_assignment: HashMap<Parameter, u32>,
351 configurations: Vec<Configuration>,
353 transitions: Vec<Transition>,
355}
356
357impl InitializedPathBuilder {
358 pub fn add_configuration(mut self, config: Configuration) -> Result<Self, PathBuilderError> {
360 self.validate_configuration(&config)?;
361 self.configurations.push(config);
362 Ok(self)
363 }
364
365 pub fn add_configurations(
367 self,
368 configs: impl IntoIterator<Item = Configuration>,
369 ) -> Result<Self, PathBuilderError> {
370 let mut builder = self;
371
372 for config in configs {
373 builder = builder.add_configuration(config)?;
374 }
375
376 Ok(builder)
377 }
378
379 pub fn add_transition(mut self, transition: Transition) -> Result<Self, PathBuilderError> {
381 if !self.ta.rules().any(|r| *r == transition.rule_used) {
383 return Err(PathBuilderError::UnknownRule(transition.rule_used.clone()));
384 }
385
386 self.transitions.push(transition);
387
388 Ok(self)
389 }
390
391 pub fn add_transitions(
393 mut self,
394 transitions: impl IntoIterator<Item = Transition>,
395 ) -> Result<Self, PathBuilderError> {
396 for transition in transitions {
397 self = self.add_transition(transition)?;
398 }
399
400 Ok(self)
401 }
402
403 pub fn last_configuration(&self) -> Option<&Configuration> {
405 self.configurations.last()
406 }
407
408 fn validate_configuration(&mut self, config: &Configuration) -> Result<(), PathBuilderError> {
410 for loc in self.ta.locations() {
412 if !config.location_assignment.contains_key(loc) {
413 return Err(PathBuilderError::MissingLocation(loc.clone()));
414 }
415 }
416
417 for var in self.ta.variables() {
419 if !config.variable_assignment.contains_key(var) {
420 return Err(PathBuilderError::MissingVariable(var.clone()));
421 }
422 }
423
424 if self.configurations.is_empty() {
426 Self::validate_initial_configuration(self, config)?;
427 self.n_processes = config.location_assignment.values().sum();
428 return Ok(());
429 }
430
431 if self.n_processes != config.location_assignment.values().sum() {
433 return Err(PathBuilderError::InconsistentNumberOfProcesses {
434 initial_n: self.n_processes,
435 config_n: config.location_assignment.values().sum(),
436 });
437 }
438
439 Ok(())
440 }
441
442 fn validate_initial_configuration(
444 &self,
445 config: &Configuration,
446 ) -> Result<(), PathBuilderError> {
447 for loc_constr in self.ta.initial_location_conditions() {
449 if !loc_constr
450 .check_satisfied(&config.location_assignment, &self.parameter_assignment)
451 .map_err(PathBuilderError::from)?
452 {
453 return Err(PathBuilderError::InitialLocationConstraintNotSatisfied(
454 Box::new(loc_constr.clone()),
455 Box::new(config.location_assignment.clone()),
456 ));
457 }
458 }
459
460 for var_constr in self.ta.initial_variable_conditions() {
462 if !var_constr
463 .check_satisfied(&config.variable_assignment, &self.parameter_assignment)
464 .map_err(PathBuilderError::from)?
465 {
466 return Err(PathBuilderError::InitialVariableConstraintNotSatisfied(
467 Box::new(var_constr.clone()),
468 Box::new(config.variable_assignment.clone()),
469 ));
470 }
471 }
472
473 Ok(())
474 }
475
476 pub fn build(mut self) -> Result<Path, PathBuilderError> {
478 if self.configurations.is_empty() {
480 return Ok(Path {
481 ta: self.ta,
482 parameter_assignment: self.parameter_assignment,
483 configurations: self.configurations,
484 transitions: self.transitions,
485 });
486 }
487 self.transitions.retain(|tr| !tr.is_noop());
489 self.configurations.dedup();
491
492 self.validate_path()?;
493
494 self.shorten_path();
495
496 Ok(Path {
497 ta: self.ta,
498 parameter_assignment: self.parameter_assignment,
499 configurations: self.configurations,
500 transitions: self.transitions,
501 })
502 }
503
504 fn shorten_path(&mut self) {
509 if self.configurations.len() <= 2 {
510 return;
511 }
512
513 let mut new_config = vec![
514 self.configurations[0].clone(),
515 self.configurations[1].clone(),
516 ];
517 let mut new_tr = vec![self.transitions[0].clone()];
518
519 for (tr, config) in self
520 .transitions
521 .iter()
522 .skip(1)
523 .zip(self.configurations.iter().skip(2))
524 {
525 let mut tr = tr.clone();
526
527 if new_tr.last().unwrap().rule() == tr.rule() {
528 tr.number_applied += new_tr.last().unwrap().number_applied;
529
530 new_tr.pop();
531 new_config.pop();
532 }
533
534 new_tr.push(tr);
535 new_config.push(config.clone());
536 }
537
538 self.configurations = new_config;
539 self.transitions = new_tr;
540
541 debug_assert!(self.validate_path().is_ok());
542 }
543
544 fn validate_path(&self) -> Result<(), PathBuilderError> {
546 if self.transitions.len() + 1 != self.configurations.len() {
548 return Err(PathBuilderError::InconsistentNumberOfConfigurations);
549 }
550
551 let mut it_configs = self.configurations.iter();
552 let mut curr_config = it_configs.next().unwrap();
553
554 for transition in self.transitions.iter() {
555 if !transition
557 .rule_used
558 .guard()
559 .check_satisfied(&curr_config.variable_assignment, &self.parameter_assignment)
560 .unwrap()
561 {
562 return Err(PathBuilderError::GuardNotSatisfied(
563 Box::new(curr_config.clone()),
564 Box::new(transition.clone()),
565 ));
566 }
567
568 if transition.number_applied > 1 {
571 let mut transition_with_reduced_counter = transition.clone();
572 transition_with_reduced_counter.number_applied -= 1;
573 let last_config = curr_config.apply_rule(&transition_with_reduced_counter);
574 if last_config.is_none() {
575 return Err(PathBuilderError::NotEnoughProcesses(
576 Box::new(curr_config.clone()),
577 Box::new(transition.clone()),
578 ));
579 }
580 let last_config = last_config.unwrap();
581 if !transition
582 .rule_used
583 .guard()
584 .check_satisfied(&last_config.variable_assignment, &self.parameter_assignment)
585 .unwrap()
586 {
587 return Err(PathBuilderError::GuardNotSatisfied(
588 Box::new(curr_config.clone()),
589 Box::new(transition.clone()),
590 ));
591 }
592 }
593
594 let next_config = it_configs.next().unwrap();
595
596 let calculated_config = curr_config.apply_rule(transition);
597 if calculated_config.is_none() {
598 return Err(PathBuilderError::NotEnoughProcesses(
599 Box::new(curr_config.clone()),
600 Box::new(transition.clone()),
601 ));
602 }
603 let calculated_config = calculated_config.unwrap();
604
605 if calculated_config != *next_config {
606 return Err(PathBuilderError::InconsistentTransition {
607 config: Box::new(curr_config.clone()),
608 tr: Box::new(transition.clone()),
609 expected_config: Box::new(next_config.clone()),
610 next_config: Box::new(calculated_config),
611 });
612 }
613
614 curr_config = next_config;
615 }
616
617 Ok(())
618 }
619}
620
621#[derive(Debug, Clone, PartialEq)]
623pub enum PathBuilderError {
624 ResilienceConditionNotSatisfied(
626 Box<BooleanExpression<Parameter>>,
627 Box<HashMap<Parameter, u32>>,
628 ),
629 MissingParameter(Parameter),
631 InitialLocationConstraintNotSatisfied(
633 Box<BooleanExpression<Location>>,
634 Box<HashMap<Location, u32>>,
635 ),
636 MissingLocation(Location),
638 InitialVariableConstraintNotSatisfied(
640 Box<BooleanExpression<Variable>>,
641 Box<HashMap<Variable, u32>>,
642 ),
643 MissingVariable(Variable),
645 InconsistentNumberOfProcesses {
647 initial_n: u32,
649 config_n: u32,
651 },
652 UnknownRule(Rule),
654 InconsistentNumberOfConfigurations,
656 InconsistentTransition {
658 config: Box<Configuration>,
660 tr: Box<Transition>,
662 expected_config: Box<Configuration>,
664 next_config: Box<Configuration>,
666 },
667 GuardNotSatisfied(Box<Configuration>, Box<Transition>),
669 NotEnoughProcesses(Box<Configuration>, Box<Transition>),
671}
672
673impl error::Error for PathBuilderError {}
674
675impl fmt::Display for PathBuilderError {
676 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
677 write!(f, "Error building path: ")?;
678 match self {
679 PathBuilderError::ResilienceConditionNotSatisfied(rc, assignment) => {
680 write!(
681 f,
682 "Resilience condition {} not satisfied with assignment {}",
683 rc,
684 display_assignment(*assignment.clone())
685 )
686 }
687 PathBuilderError::MissingParameter(p) => {
688 write!(f, "Parameter assignment is missing parameter {p}")
689 }
690 PathBuilderError::InitialLocationConstraintNotSatisfied(constr, assignment) => {
691 write!(
692 f,
693 "Initial location constraint {} not satisfied with assignment {}",
694 constr,
695 display_assignment(*assignment.clone())
696 )
697 }
698 PathBuilderError::MissingLocation(loc) => {
699 write!(f, "Location assignment is missing location {loc}")
700 }
701 PathBuilderError::InitialVariableConstraintNotSatisfied(constr, assignment) => {
702 write!(
703 f,
704 "Initial variable constraint {} not satisfied with assignment {}",
705 constr,
706 display_assignment(*assignment.clone())
707 )
708 }
709 PathBuilderError::MissingVariable(variable) => {
710 write!(f, "Variable assignment is missing variable {variable}")
711 }
712 PathBuilderError::InconsistentNumberOfProcesses {
713 initial_n,
714 config_n,
715 } => {
716 write!(
717 f,
718 "Inconsistent number of processes: expected {initial_n} but got {config_n}"
719 )
720 }
721 PathBuilderError::UnknownRule(rule) => {
722 write!(f, "Unknown rule appears in the transition: {rule}")
723 }
724 PathBuilderError::InconsistentNumberOfConfigurations => {
725 write!(
726 f,
727 "Inconsistent number of configurations and rules were supplied to the path builder"
728 )
729 }
730 PathBuilderError::InconsistentTransition {
731 config,
732 tr,
733 expected_config,
734 next_config,
735 } => {
736 write!(
737 f,
738 "Inconsistent transition: configuration {config} with transition {tr} does not lead to the expected configuration {expected_config} but to {next_config}"
739 )
740 }
741 PathBuilderError::GuardNotSatisfied(config, tr) => {
742 write!(
743 f,
744 "Tried to apply rule {} to configuration {}, but the guard is not satisfied",
745 tr.rule_used, config
746 )
747 }
748 PathBuilderError::NotEnoughProcesses(config, tr) => {
749 write!(
750 f,
751 "Tried to apply rule {} to configuration {}, but there are not enough processes in the source location",
752 tr.rule_used.id(),
753 config
754 )
755 }
756 }
757 }
758}
759
760fn display_assignment<K: fmt::Display + hash::Hash + Eq, V: fmt::Display>(
762 map: HashMap<K, V>,
763) -> String {
764 let mut keys = map.keys().collect::<Vec<_>>();
765 keys.sort_by_key(|k| k.to_string());
766 join_iterator(
767 keys.into_iter().map(|k| format!("{} : {}", k, map[k])),
768 ", ",
769 )
770}
771
772impl From<EvaluationError<Parameter>> for PathBuilderError {
773 fn from(err: EvaluationError<Parameter>) -> Self {
774 match err {
775 EvaluationError::AtomicNotFound(p) | EvaluationError::ParameterNotFound(p) => {
776 PathBuilderError::MissingParameter(p)
777 }
778 }
779 }
780}
781
782impl From<EvaluationError<Location>> for PathBuilderError {
783 fn from(err: EvaluationError<Location>) -> Self {
784 match err {
785 EvaluationError::AtomicNotFound(l) => PathBuilderError::MissingLocation(l),
786 EvaluationError::ParameterNotFound(p) => PathBuilderError::MissingParameter(p),
787 }
788 }
789}
790
791impl From<EvaluationError<Variable>> for PathBuilderError {
792 fn from(err: EvaluationError<Variable>) -> Self {
793 match err {
794 EvaluationError::AtomicNotFound(v) => PathBuilderError::MissingVariable(v),
795 EvaluationError::ParameterNotFound(p) => PathBuilderError::MissingParameter(p),
796 }
797 }
798}
799
800impl fmt::Display for Configuration {
801 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
802 let mut locs = self.location_assignment.keys().collect::<Vec<_>>();
803 locs.sort_by_key(|loc| loc.name());
804 let locs = join_iterator(
805 locs.into_iter()
806 .map(|loc| format!("{} : {}", loc.name(), self.location_assignment[loc])),
807 ", ",
808 );
809
810 let mut vars = self.variable_assignment.keys().collect::<Vec<_>>();
811 vars.sort_by_key(|var| var.name());
812 let vars = join_iterator(
813 vars.into_iter()
814 .map(|var| format!("{} : {}", var.name(), self.variable_assignment[var])),
815 ", ",
816 );
817
818 write!(f, "locations: ({locs}), variables: ({vars})")
819 }
820}
821
822impl fmt::Display for Transition {
823 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824 write!(
825 f,
826 "rule: {} applied {} times",
827 self.rule_used.id(),
828 self.number_applied
829 )
830 }
831}
832
833impl fmt::Display for Path {
834 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
835 debug_assert!(self.configurations.len() == self.transitions.len() + 1);
836
837 writeln!(f, "Path of threshold automaton {}: ", self.ta.name())?;
838
839 let param = display_assignment(self.parameter_assignment.clone());
840 writeln!(f, "parameters: {param}")?;
841
842 let mut it_transitions = self.transitions.iter();
843 for (n_config, config) in self.configurations.iter().enumerate() {
844 writeln!(f, "Configuration {n_config}: {config}")?;
845 if let Some(transition) = it_transitions.next() {
846 writeln!(f, " |",)?;
847 writeln!(f, " |{transition}")?;
848 writeln!(f, " V")?;
849 }
850 }
851
852 Ok(())
853 }
854}
855
856#[cfg(test)]
857mod test {
858 use crate::{
859 BooleanVarConstraint, LocationConstraint, ParameterConstraint,
860 expressions::{BooleanExpression, ComparisonOp, IntegerExpression, IntegerOp},
861 general_threshold_automaton::{
862 Action,
863 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
864 },
865 };
866
867 use super::*;
868
869 lazy_static::lazy_static! {
870 static ref TEST_TA: GeneralThresholdAutomaton = {
871 GeneralThresholdAutomatonBuilder::new("test_ta1")
872 .with_parameters(vec![
873 Parameter::new("n"),
874 Parameter::new("t"),
875 Parameter::new("f"),
876 ])
877 .unwrap()
878 .with_variables(vec![
879 Variable::new("var1"),
880 Variable::new("var2"),
881 Variable::new("var3"),
882 ])
883 .unwrap()
884 .with_locations(vec![
885 Location::new("loc1"),
886 Location::new("loc2"),
887 Location::new("loc3"),
888 ])
889 .unwrap()
890 .initialize()
891 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
892 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
893 ComparisonOp::Eq,
894 Box::new(IntegerExpression::Const(1)),
895 )])
896 .unwrap()
897 .with_initial_location_constraints(vec![
898 LocationConstraint::ComparisonExpression(
899 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
900 ComparisonOp::Eq,
901 Box::new(
902 IntegerExpression::Param(Parameter::new("n"))
903 - IntegerExpression::Param(Parameter::new("f")),
904 ),
905 ) & LocationConstraint::ComparisonExpression(
906 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
907 ComparisonOp::Eq,
908 Box::new(IntegerExpression::Const(0)),
909 ),
910 ])
911 .unwrap()
912 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
913 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
914 ComparisonOp::Gt,
915 Box::new(IntegerExpression::BinaryExpr(
916 Box::new(IntegerExpression::Const(3)),
917 IntegerOp::Mul,
918 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
919 )),
920 )])
921 .unwrap()
922 .with_rules(vec![
923 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
924 .with_actions(vec![Action::new(
925 Variable::new("var1"),
926 IntegerExpression::Atom(Variable::new("var1")),
927 )
928 .unwrap()])
929 .build(),
930 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
931 .with_guard(
932 BooleanVarConstraint::ComparisonExpression(
933 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
934 ComparisonOp::Eq,
935 Box::new(IntegerExpression::Const(1)),
936 ) & BooleanVarConstraint::ComparisonExpression(
937 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
938 ComparisonOp::Eq,
939 Box::new(IntegerExpression::Param(Parameter::new("n")) - IntegerExpression::Const(2)),
940 ),
941 )
942 .with_actions(vec![
943 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
944 Action::new(
945 Variable::new("var1"),
946 IntegerExpression::BinaryExpr(
947 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
948 IntegerOp::Add,
949 Box::new(IntegerExpression::Const(1)),
950 ),
951 )
952 .unwrap(),
953 ])
954 .build(),
955 RuleBuilder::new(2, Location::new("loc3"), Location::new("loc3"))
956 .with_guard(
957 BooleanVarConstraint::True,
958 )
959 .with_actions(vec![
960 Action::new(
961 Variable::new("var1"),
962 IntegerExpression::BinaryExpr(
963 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
964 IntegerOp::Add,
965 Box::new(-IntegerExpression::Const(1)),
966 ),
967 )
968 .unwrap(),
969 ])
970 .build(),
971 ])
972 .unwrap()
973 .build()
974 };
975 }
976
977 #[test]
978 fn test_configuration_getters() {
979 let config = Configuration {
980 variable_assignment: HashMap::from([
981 (Variable::new("var1"), 1),
982 (Variable::new("var2"), 2),
983 ]),
984
985 location_assignment: HashMap::from([
986 (Location::new("loc1"), 1),
987 (Location::new("loc2"), 2),
988 ]),
989 };
990
991 let got_var_assignment: HashMap<_, _> = config
992 .variable_assignment()
993 .map(|(x, y)| (x.clone(), *y))
994 .collect();
995 assert_eq!(
996 got_var_assignment,
997 HashMap::from([(Variable::new("var1"), 1), (Variable::new("var2"), 2),])
998 );
999
1000 let got_loc_assignment: HashMap<_, _> = config
1001 .location_assignment()
1002 .map(|(x, y)| (x.clone(), *y))
1003 .collect();
1004 assert_eq!(
1005 got_loc_assignment,
1006 HashMap::from([(Location::new("loc1"), 1), (Location::new("loc2"), 2),])
1007 );
1008 }
1009
1010 #[test]
1011 fn test_display_configuration() {
1012 let config = Configuration {
1013 variable_assignment: HashMap::from([
1014 (Variable::new("var1"), 1),
1015 (Variable::new("var2"), 2),
1016 ]),
1017
1018 location_assignment: HashMap::from([
1019 (Location::new("loc1"), 1),
1020 (Location::new("loc2"), 2),
1021 ]),
1022 };
1023
1024 assert_eq!(
1025 format!("{config}"),
1026 "locations: (loc1 : 1, loc2 : 2), variables: (var1 : 1, var2 : 2)"
1027 );
1028 }
1029
1030 #[test]
1031 fn test_display_compact_configuration() {
1032 let config = Configuration {
1033 variable_assignment: HashMap::from([
1034 (Variable::new("var1"), 1),
1035 (Variable::new("var2"), 2),
1036 (Variable::new("var3"), 0),
1037 ]),
1038
1039 location_assignment: HashMap::from([
1040 (Location::new("loc1"), 1),
1041 (Location::new("loc2"), 2),
1042 (Location::new("loc3"), 0),
1043 ]),
1044 };
1045
1046 assert_eq!(
1047 config.display_compact(),
1048 "locations: (loc1 : 1, loc2 : 2), variables: (var1 : 1, var2 : 2)"
1049 );
1050 }
1051
1052 #[test]
1053 fn test_display_rule() {
1054 let rule1 = RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2"))
1055 .with_guard(BooleanExpression::True)
1056 .build();
1057
1058 let transition = Transition {
1059 rule_used: rule1.clone(),
1060 number_applied: 1,
1061 };
1062
1063 assert_eq!(format!("{transition}"), "rule: 1 applied 1 times");
1064 }
1065
1066 #[test]
1067 fn test_display_path() {
1068 let ta = TEST_TA.clone();
1069
1070 let path = Path {
1071 ta: ta.clone(),
1072 parameter_assignment: [(Parameter::new("n"), 4), (Parameter::new("t"), 2)]
1073 .iter()
1074 .cloned()
1075 .collect(),
1076 configurations: vec![
1077 Configuration {
1078 variable_assignment: [(Variable::new("var1"), 1), (Variable::new("var2"), 2)]
1079 .into(),
1080 location_assignment: [(Location::new("loc1"), 3), (Location::new("loc2"), 0)]
1081 .into(),
1082 },
1083 Configuration {
1084 variable_assignment: [(Variable::new("var1"), 1), (Variable::new("var2"), 2)]
1085 .into(),
1086 location_assignment: [(Location::new("loc1"), 2), (Location::new("loc2"), 1)]
1087 .into(),
1088 },
1089 Configuration {
1090 variable_assignment: [(Variable::new("var1"), 2), (Variable::new("var2"), 2)]
1091 .into(),
1092 location_assignment: [(Location::new("loc1"), 2), (Location::new("loc3"), 1)]
1093 .into(),
1094 },
1095 ],
1096 transitions: vec![
1097 Transition {
1098 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1099 number_applied: 1,
1100 },
1101 Transition {
1102 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
1103 number_applied: 1,
1104 },
1105 ],
1106 };
1107
1108 assert_eq!(
1109 format!("{path}"),
1110 "Path of threshold automaton test_ta1: \nparameters: n : 4, t : 2\nConfiguration 0: locations: (loc1 : 3, loc2 : 0), variables: (var1 : 1, var2 : 2)\n |\n |rule: 0 applied 1 times\n V\nConfiguration 1: locations: (loc1 : 2, loc2 : 1), variables: (var1 : 1, var2 : 2)\n |\n |rule: 1 applied 1 times\n V\nConfiguration 2: locations: (loc1 : 2, loc3 : 1), variables: (var1 : 2, var2 : 2)\n"
1111 );
1112 }
1113
1114 #[test]
1115 fn test_display_compact_path() {
1116 let ta = TEST_TA.clone();
1117
1118 let path = Path {
1119 ta: ta.clone(),
1120 parameter_assignment: [(Parameter::new("n"), 5), (Parameter::new("t"), 2)].into(),
1121 configurations: vec![
1122 Configuration {
1123 variable_assignment: [
1124 (Variable::new("var0"), 0),
1125 (Variable::new("var1"), 1),
1126 (Variable::new("var2"), 2),
1127 ]
1128 .into(),
1129 location_assignment: [
1130 (Location::new("loc1"), 3),
1131 (Location::new("loc2"), 1),
1132 (Location::new("loc3"), 0),
1133 ]
1134 .into(),
1135 },
1136 Configuration {
1137 variable_assignment: [
1138 (Variable::new("var0"), 0),
1139 (Variable::new("var1"), 1),
1140 (Variable::new("var2"), 2),
1141 ]
1142 .into(),
1143 location_assignment: [
1144 (Location::new("loc1"), 1),
1145 (Location::new("loc2"), 3),
1146 (Location::new("loc3"), 0),
1147 ]
1148 .into(),
1149 },
1150 Configuration {
1151 variable_assignment: [
1152 (Variable::new("var0"), 0),
1153 (Variable::new("var1"), 2),
1154 (Variable::new("var2"), 2),
1155 ]
1156 .into(),
1157 location_assignment: [
1158 (Location::new("loc1"), 2),
1159 (Location::new("loc2"), 2),
1160 (Location::new("loc3"), 0),
1161 ]
1162 .into(),
1163 },
1164 ],
1165 transitions: vec![
1166 Transition {
1167 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1168 number_applied: 1,
1169 },
1170 Transition {
1171 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
1172 number_applied: 1,
1173 },
1174 ],
1175 };
1176
1177 assert_eq!(
1178 path.display_compact(),
1179 "Path of threshold automaton test_ta1: \nparameters: n : 5, t : 2\nConfiguration 0: locations: (loc1 : 3, loc2 : 1), variables: (var1 : 1, var2 : 2)\n |\n |rule: 0 applied 1 times\n V\nConfiguration 1: locations: (loc1 : 1, loc2 : 3), variables: (var1 : 1, var2 : 2)\n |\n |rule: 1 applied 1 times\n V\nConfiguration 2: locations: (loc1 : 2, loc2 : 2), variables: (var1 : 2, var2 : 2)\n"
1180 );
1181 }
1182
1183 #[test]
1184 fn test_display_transition() {
1185 let rule = RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2"))
1186 .with_guard(BooleanExpression::True)
1187 .build();
1188
1189 let transition = Transition {
1190 rule_used: rule.clone(),
1191 number_applied: 1,
1192 };
1193
1194 assert_eq!(format!("{transition}"), "rule: 1 applied 1 times");
1195 }
1196
1197 #[test]
1198 fn test_path_builder_valid() {
1199 let ta = TEST_TA.clone();
1200
1201 let path = PathBuilder::new(ta.clone())
1202 .add_parameter_assignment([
1203 (Parameter::new("n"), 4),
1204 (Parameter::new("t"), 2),
1205 (Parameter::new("f"), 1),
1206 ])
1207 .unwrap()
1208 .add_configuration(Configuration {
1209 variable_assignment: HashMap::from([
1210 (Variable::new("var1"), 1),
1211 (Variable::new("var2"), 2),
1212 (Variable::new("var3"), 0),
1213 ]),
1214 location_assignment: HashMap::from([
1215 (Location::new("loc1"), 3),
1216 (Location::new("loc2"), 0),
1217 (Location::new("loc3"), 0),
1218 ]),
1219 })
1220 .unwrap()
1221 .add_transition(Transition {
1222 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1223 number_applied: 1,
1224 })
1225 .unwrap()
1226 .add_configuration(Configuration {
1227 variable_assignment: HashMap::from([
1228 (Variable::new("var1"), 1),
1229 (Variable::new("var2"), 2),
1230 (Variable::new("var3"), 0),
1231 ]),
1232 location_assignment: HashMap::from([
1233 (Location::new("loc1"), 2),
1234 (Location::new("loc2"), 1),
1235 (Location::new("loc3"), 0),
1236 ]),
1237 })
1238 .unwrap()
1239 .add_transition(Transition {
1240 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
1241 number_applied: 1,
1242 })
1243 .unwrap()
1244 .add_configuration(Configuration {
1245 variable_assignment: HashMap::from([
1246 (Variable::new("var1"), 2),
1247 (Variable::new("var2"), 2),
1248 (Variable::new("var3"), 0),
1249 ]),
1250 location_assignment: HashMap::from([
1251 (Location::new("loc1"), 2),
1252 (Location::new("loc2"), 0),
1253 (Location::new("loc3"), 1),
1254 ]),
1255 })
1256 .unwrap()
1257 .add_transition(Transition {
1258 rule_used: ta.rules().find(|r| r.id() == 2).unwrap().clone(),
1259 number_applied: 1,
1260 })
1261 .unwrap()
1262 .add_configuration(Configuration {
1263 variable_assignment: HashMap::from([
1264 (Variable::new("var1"), 1),
1265 (Variable::new("var2"), 2),
1266 (Variable::new("var3"), 0),
1267 ]),
1268 location_assignment: HashMap::from([
1269 (Location::new("loc1"), 2),
1270 (Location::new("loc2"), 0),
1271 (Location::new("loc3"), 1),
1272 ]),
1273 })
1274 .unwrap()
1275 .build()
1276 .unwrap();
1277
1278 assert_eq!(path.configurations.len(), 4);
1279 assert_eq!(path.transitions.len(), 3);
1280
1281 assert_eq!(
1282 path.configurations[0].variable_assignment,
1283 HashMap::from([
1284 (Variable::new("var1"), 1),
1285 (Variable::new("var2"), 2),
1286 (Variable::new("var3"), 0),
1287 ])
1288 );
1289 assert_eq!(
1290 path.configurations[0].location_assignment,
1291 HashMap::from([
1292 (Location::new("loc1"), 3),
1293 (Location::new("loc2"), 0),
1294 (Location::new("loc3"), 0),
1295 ])
1296 );
1297
1298 assert_eq!(
1299 path.configurations[1].variable_assignment,
1300 HashMap::from([
1301 (Variable::new("var1"), 1),
1302 (Variable::new("var2"), 2),
1303 (Variable::new("var3"), 0),
1304 ])
1305 );
1306
1307 assert_eq!(
1308 path.configurations[1].location_assignment,
1309 HashMap::from([
1310 (Location::new("loc1"), 2),
1311 (Location::new("loc2"), 1),
1312 (Location::new("loc3"), 0),
1313 ])
1314 );
1315
1316 assert_eq!(
1317 path.configurations[2].variable_assignment,
1318 HashMap::from([
1319 (Variable::new("var1"), 2),
1320 (Variable::new("var2"), 2),
1321 (Variable::new("var3"), 0),
1322 ])
1323 );
1324 assert_eq!(
1325 path.configurations[2].location_assignment,
1326 HashMap::from([
1327 (Location::new("loc1"), 2),
1328 (Location::new("loc2"), 0),
1329 (Location::new("loc3"), 1),
1330 ])
1331 );
1332
1333 assert_eq!(
1334 path.configurations[3].variable_assignment,
1335 HashMap::from([
1336 (Variable::new("var1"), 1),
1337 (Variable::new("var2"), 2),
1338 (Variable::new("var3"), 0),
1339 ])
1340 );
1341 assert_eq!(
1342 path.configurations[3].location_assignment,
1343 HashMap::from([
1344 (Location::new("loc1"), 2),
1345 (Location::new("loc2"), 0),
1346 (Location::new("loc3"), 1),
1347 ])
1348 );
1349 }
1350
1351 #[test]
1352 fn test_empty_path_builder() {
1353 let ta = TEST_TA.clone();
1354
1355 let path = PathBuilder::new(ta.clone())
1356 .add_parameter_assignment([
1357 (Parameter::new("n"), 4),
1358 (Parameter::new("t"), 2),
1359 (Parameter::new("f"), 1),
1360 ])
1361 .unwrap()
1362 .build()
1363 .unwrap();
1364
1365 assert_eq!(path.configurations.len(), 0);
1366 assert_eq!(path.transitions.len(), 0);
1367 }
1368
1369 #[test]
1370 fn test_path_builder_err_inconsistent_rc() {
1371 let ta = TEST_TA.clone();
1372
1373 let path = PathBuilder::new(ta.clone()).add_parameter_assignment(HashMap::from([
1374 (Parameter::new("n"), 3),
1375 (Parameter::new("t"), 2),
1376 (Parameter::new("f"), 1),
1377 ]));
1378
1379 assert!(path.is_err());
1380 let err = path.unwrap_err();
1381 assert!(
1382 matches!(err, PathBuilderError::ResilienceConditionNotSatisfied(_, _)),
1383 "got error instead {err}"
1384 );
1385 assert!(err.to_string().contains("Resilience condition"));
1386 assert!(
1387 err.to_string().contains(
1388 &ParameterConstraint::ComparisonExpression(
1389 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1390 ComparisonOp::Gt,
1391 Box::new(IntegerExpression::BinaryExpr(
1392 Box::new(IntegerExpression::Const(3)),
1393 IntegerOp::Mul,
1394 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1395 )),
1396 )
1397 .to_string()
1398 )
1399 );
1400 }
1401
1402 #[test]
1403 fn test_path_builder_err_missing_param() {
1404 let ta = TEST_TA.clone();
1405
1406 let path = PathBuilder::new(ta.clone()).add_parameter_assignment(HashMap::from([
1408 (Parameter::new("n"), 4),
1409 (Parameter::new("t"), 2),
1410 ]));
1411
1412 assert!(path.is_err());
1413 let err = path.unwrap_err();
1414 assert!(
1415 matches!(err, PathBuilderError::MissingParameter(_)),
1416 "got error instead {err}"
1417 );
1418 assert!(err.to_string().contains("missing parameter"));
1419 assert!(err.to_string().contains(&Parameter::new("f").to_string()));
1420 }
1421
1422 #[test]
1423 fn test_path_builder_err_init_loc_constr() {
1424 let ta = TEST_TA.clone();
1425
1426 let path = PathBuilder::new(ta.clone())
1428 .add_parameter_assignment(HashMap::from([
1429 (Parameter::new("n"), 4),
1430 (Parameter::new("t"), 2),
1431 (Parameter::new("f"), 1),
1432 ]))
1433 .unwrap()
1434 .add_configuration(Configuration {
1435 variable_assignment: HashMap::from([
1436 (Variable::new("var1"), 1),
1437 (Variable::new("var2"), 2),
1438 (Variable::new("var3"), 0),
1439 ]),
1440 location_assignment: HashMap::from([
1441 (Location::new("loc1"), 1),
1442 (Location::new("loc2"), 0),
1443 (Location::new("loc3"), 0),
1444 ]),
1445 });
1446
1447 assert!(path.is_err());
1448 let err = path.unwrap_err();
1449 assert!(
1450 matches!(
1451 err,
1452 PathBuilderError::InitialLocationConstraintNotSatisfied(_, _)
1453 ),
1454 "got error instead {err}"
1455 );
1456 assert!(err.to_string().contains("Initial location constraint"));
1457 assert!(
1458 err.to_string().contains(
1459 &LocationConstraint::ComparisonExpression(
1460 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1461 ComparisonOp::Eq,
1462 Box::new(
1463 IntegerExpression::Param(Parameter::new("n"))
1464 - IntegerExpression::Param(Parameter::new("f")),
1465 ),
1466 )
1467 .to_string()
1468 )
1469 );
1470 }
1471
1472 #[test]
1473 fn test_path_builder_err_loc_missing() {
1474 let ta = TEST_TA.clone();
1475
1476 let path = PathBuilder::new(ta.clone())
1478 .add_parameter_assignment(HashMap::from([
1479 (Parameter::new("n"), 4),
1480 (Parameter::new("t"), 2),
1481 (Parameter::new("f"), 1),
1482 ]))
1483 .unwrap()
1484 .add_configuration(Configuration {
1485 variable_assignment: HashMap::from([
1486 (Variable::new("var1"), 1),
1487 (Variable::new("var2"), 2),
1488 (Variable::new("var3"), 0),
1489 ]),
1490 location_assignment: HashMap::from([
1491 (Location::new("loc1"), 2),
1492 (Location::new("loc3"), 0),
1493 ]),
1494 });
1495
1496 assert!(path.is_err());
1497 let err = path.unwrap_err();
1498 assert!(
1499 matches!(err, PathBuilderError::MissingLocation(_)),
1500 "got error instead {err}"
1501 );
1502 assert!(err.to_string().contains("missing location"));
1503 assert!(err.to_string().contains(&Location::new("loc2").to_string()));
1504 }
1505
1506 #[test]
1507 fn test_path_builder_err_init_var_constr() {
1508 let ta = TEST_TA.clone();
1509 let path = PathBuilder::new(ta.clone())
1511 .add_parameter_assignment(HashMap::from([
1512 (Parameter::new("n"), 4),
1513 (Parameter::new("t"), 2),
1514 (Parameter::new("f"), 1),
1515 ]))
1516 .unwrap()
1517 .add_configuration(Configuration {
1518 variable_assignment: HashMap::from([
1519 (Variable::new("var1"), 2),
1520 (Variable::new("var2"), 2),
1521 (Variable::new("var3"), 0),
1522 ]),
1523 location_assignment: HashMap::from([
1524 (Location::new("loc1"), 3),
1525 (Location::new("loc2"), 0),
1526 (Location::new("loc3"), 1),
1527 ]),
1528 });
1529
1530 assert!(path.is_err());
1531 let err = path.unwrap_err();
1532 assert!(
1533 matches!(
1534 err,
1535 PathBuilderError::InitialVariableConstraintNotSatisfied(_, _)
1536 ),
1537 "got error instead {err}"
1538 );
1539 assert!(err.to_string().contains("Initial variable constraint"));
1540 }
1541
1542 #[test]
1543 fn test_path_builder_err_var_missing() {
1544 let ta = TEST_TA.clone();
1545 let path = PathBuilder::new(ta.clone())
1547 .add_parameter_assignment(HashMap::from([
1548 (Parameter::new("n"), 4),
1549 (Parameter::new("t"), 2),
1550 (Parameter::new("f"), 1),
1551 ]))
1552 .unwrap()
1553 .add_configuration(Configuration {
1554 variable_assignment: HashMap::from([
1555 (Variable::new("var1"), 1),
1556 (Variable::new("var2"), 2),
1557 ]),
1558 location_assignment: HashMap::from([
1559 (Location::new("loc1"), 2),
1560 (Location::new("loc2"), 0),
1561 (Location::new("loc3"), 1),
1562 ]),
1563 });
1564
1565 assert!(path.is_err());
1566 let err = path.unwrap_err();
1567 assert!(
1568 matches!(err, PathBuilderError::MissingVariable(_)),
1569 "got error instead {err}"
1570 );
1571 assert!(err.to_string().contains("missing variable"));
1572 assert!(err.to_string().contains(&Variable::new("var3").to_string()));
1573 }
1574
1575 #[test]
1576 fn test_path_builder_err_inconsistent_n_proc() {
1577 let ta = TEST_TA.clone();
1578 let path = PathBuilder::new(ta.clone())
1580 .add_parameter_assignment(HashMap::from([
1581 (Parameter::new("n"), 4),
1582 (Parameter::new("t"), 2),
1583 (Parameter::new("f"), 1),
1584 ]))
1585 .unwrap()
1586 .add_configuration(Configuration {
1587 variable_assignment: HashMap::from([
1588 (Variable::new("var1"), 1),
1589 (Variable::new("var2"), 2),
1590 (Variable::new("var3"), 0),
1591 ]),
1592 location_assignment: HashMap::from([
1593 (Location::new("loc1"), 3),
1594 (Location::new("loc2"), 0),
1595 (Location::new("loc3"), 0),
1596 ]),
1597 })
1598 .unwrap()
1599 .add_transition(Transition {
1600 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1601 number_applied: 1,
1602 })
1603 .unwrap()
1604 .add_configuration(Configuration {
1605 variable_assignment: HashMap::from([
1606 (Variable::new("var1"), 1),
1607 (Variable::new("var2"), 2),
1608 (Variable::new("var3"), 0),
1609 ]),
1610 location_assignment: HashMap::from([
1611 (Location::new("loc1"), 2),
1612 (Location::new("loc2"), 5),
1613 (Location::new("loc3"), 0),
1614 ]),
1615 });
1616
1617 assert!(path.is_err());
1618 let err = path.unwrap_err();
1619 assert!(
1620 matches!(err, PathBuilderError::InconsistentNumberOfProcesses { .. }),
1621 "got error instead {err}"
1622 );
1623 assert!(err.to_string().contains("Inconsistent number of processes"));
1624 assert!(err.to_string().contains("expected 3 but got 7"));
1625 }
1626
1627 #[test]
1628 fn test_path_builder_err_not_enough_proc() {
1629 let ta = TEST_TA.clone();
1630 let path = PathBuilder::new(ta.clone())
1632 .add_parameter_assignment(HashMap::from([
1633 (Parameter::new("n"), 4),
1634 (Parameter::new("t"), 2),
1635 (Parameter::new("f"), 1),
1636 ]))
1637 .unwrap()
1638 .add_configuration(Configuration {
1639 variable_assignment: HashMap::from([
1640 (Variable::new("var1"), 1),
1641 (Variable::new("var2"), 2),
1642 (Variable::new("var3"), 0),
1643 ]),
1644 location_assignment: HashMap::from([
1645 (Location::new("loc1"), 3),
1646 (Location::new("loc2"), 0),
1647 (Location::new("loc3"), 0),
1648 ]),
1649 })
1650 .unwrap()
1651 .add_transition(Transition {
1652 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1653 number_applied: 4,
1654 })
1655 .unwrap()
1656 .add_configuration(Configuration {
1657 variable_assignment: HashMap::from([
1658 (Variable::new("var1"), 0),
1659 (Variable::new("var2"), 4),
1660 (Variable::new("var3"), 0),
1661 ]),
1662 location_assignment: HashMap::from([
1663 (Location::new("loc1"), 0),
1664 (Location::new("loc2"), 3),
1665 (Location::new("loc3"), 0),
1666 ]),
1667 })
1668 .unwrap()
1669 .build();
1670
1671 assert!(path.is_err());
1672 let err = path.unwrap_err();
1673 assert!(
1674 matches!(err, PathBuilderError::NotEnoughProcesses(_, _)),
1675 "got error instead {err}"
1676 );
1677 }
1678
1679 #[test]
1680 fn test_path_guard_not_sat() {
1681 let ta = TEST_TA.clone();
1682 let path = PathBuilder::new(ta.clone())
1684 .add_parameter_assignment(HashMap::from([
1685 (Parameter::new("n"), 4),
1686 (Parameter::new("t"), 2),
1687 (Parameter::new("f"), 1),
1688 ]))
1689 .unwrap()
1690 .add_configuration(Configuration {
1691 variable_assignment: HashMap::from([
1692 (Variable::new("var1"), 1),
1693 (Variable::new("var2"), 2),
1694 (Variable::new("var3"), 0),
1695 ]),
1696 location_assignment: HashMap::from([
1697 (Location::new("loc1"), 3),
1698 (Location::new("loc2"), 0),
1699 (Location::new("loc3"), 0),
1700 ]),
1701 })
1702 .unwrap()
1703 .add_transition(Transition {
1704 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1705 number_applied: 2,
1706 })
1707 .unwrap()
1708 .add_configuration(Configuration {
1709 variable_assignment: HashMap::from([
1710 (Variable::new("var1"), 1),
1711 (Variable::new("var2"), 2),
1712 (Variable::new("var3"), 0),
1713 ]),
1714 location_assignment: HashMap::from([
1715 (Location::new("loc1"), 1),
1716 (Location::new("loc2"), 2),
1717 (Location::new("loc3"), 0),
1718 ]),
1719 })
1720 .unwrap()
1721 .add_transition(Transition {
1722 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
1723 number_applied: 2,
1724 })
1725 .unwrap()
1726 .add_configuration(Configuration {
1727 variable_assignment: HashMap::from([
1728 (Variable::new("var1"), 3),
1729 (Variable::new("var2"), 2),
1730 (Variable::new("var3"), 0),
1731 ]),
1732 location_assignment: HashMap::from([
1733 (Location::new("loc1"), 1),
1734 (Location::new("loc2"), 1),
1735 (Location::new("loc3"), 1),
1736 ]),
1737 })
1738 .unwrap()
1739 .build();
1740
1741 assert!(path.is_err());
1742 let err = path.unwrap_err();
1743 assert!(
1744 matches!(err, PathBuilderError::GuardNotSatisfied(_, _)),
1745 "got error instead {err}"
1746 );
1747 }
1748
1749 #[test]
1750 fn test_builder_err_unknown_rule() {
1751 let ta = TEST_TA.clone();
1752 let path = PathBuilder::new(ta.clone())
1754 .add_parameter_assignment(HashMap::from([
1755 (Parameter::new("n"), 4),
1756 (Parameter::new("t"), 2),
1757 (Parameter::new("f"), 1),
1758 ]))
1759 .unwrap()
1760 .add_configuration(Configuration {
1761 variable_assignment: HashMap::from([
1762 (Variable::new("var1"), 1),
1763 (Variable::new("var2"), 2),
1764 (Variable::new("var3"), 0),
1765 ]),
1766 location_assignment: HashMap::from([
1767 (Location::new("loc1"), 3),
1768 (Location::new("loc2"), 0),
1769 (Location::new("loc3"), 0),
1770 ]),
1771 })
1772 .unwrap()
1773 .add_transition(Transition {
1774 rule_used: RuleBuilder::new(42, Location::new("loc1"), Location::new("loc2"))
1775 .build(),
1776 number_applied: 1,
1777 });
1778
1779 assert!(path.is_err());
1780 let err = path.unwrap_err();
1781 assert!(
1782 matches!(err, PathBuilderError::UnknownRule(_)),
1783 "got error instead {err}"
1784 );
1785 assert!(err.to_string().contains("Unknown rule"));
1786 assert!(
1787 err.to_string().contains(
1788 &RuleBuilder::new(42, Location::new("loc1"), Location::new("loc2"))
1789 .build()
1790 .to_string()
1791 )
1792 );
1793 }
1794
1795 #[test]
1796 fn test_builder_err_inconsistent_number_cfgs() {
1797 let ta = TEST_TA.clone();
1798
1799 let path = PathBuilder::new(ta.clone())
1800 .add_parameter_assignment(HashMap::from([
1801 (Parameter::new("n"), 4),
1802 (Parameter::new("t"), 2),
1803 (Parameter::new("f"), 1),
1804 ]))
1805 .unwrap()
1806 .add_configuration(Configuration {
1807 variable_assignment: HashMap::from([
1808 (Variable::new("var1"), 1),
1809 (Variable::new("var2"), 2),
1810 (Variable::new("var3"), 0),
1811 ]),
1812 location_assignment: HashMap::from([
1813 (Location::new("loc1"), 3),
1814 (Location::new("loc2"), 0),
1815 (Location::new("loc3"), 0),
1816 ]),
1817 })
1818 .unwrap()
1819 .add_configuration(Configuration {
1820 variable_assignment: HashMap::from([
1821 (Variable::new("var1"), 1),
1822 (Variable::new("var2"), 2),
1823 (Variable::new("var3"), 0),
1824 ]),
1825 location_assignment: HashMap::from([
1826 (Location::new("loc1"), 1),
1827 (Location::new("loc2"), 2),
1828 (Location::new("loc3"), 0),
1829 ]),
1830 })
1831 .unwrap()
1832 .build();
1833
1834 assert!(path.is_err());
1835 let err = path.unwrap_err();
1836 assert!(
1837 matches!(err, PathBuilderError::InconsistentNumberOfConfigurations),
1838 "got error instead {err}"
1839 );
1840 assert!(
1841 err.to_string()
1842 .contains("Inconsistent number of configurations")
1843 );
1844 }
1845
1846 #[test]
1847 fn test_builder_err_inconsistent_transition() {
1848 let ta = TEST_TA.clone();
1849
1850 let path = PathBuilder::new(ta.clone())
1851 .add_parameter_assignment(HashMap::from([
1852 (Parameter::new("n"), 4),
1853 (Parameter::new("t"), 2),
1854 (Parameter::new("f"), 1),
1855 ]))
1856 .unwrap()
1857 .add_configuration(Configuration {
1858 variable_assignment: HashMap::from([
1859 (Variable::new("var1"), 1),
1860 (Variable::new("var2"), 2),
1861 (Variable::new("var3"), 0),
1862 ]),
1863 location_assignment: HashMap::from([
1864 (Location::new("loc1"), 3),
1865 (Location::new("loc2"), 0),
1866 (Location::new("loc3"), 0),
1867 ]),
1868 })
1869 .unwrap()
1870 .add_transition(Transition {
1871 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1872 number_applied: 1,
1873 })
1874 .unwrap()
1875 .add_configuration(Configuration {
1876 variable_assignment: HashMap::from([
1877 (Variable::new("var1"), 1),
1878 (Variable::new("var2"), 2),
1879 (Variable::new("var3"), 0),
1880 ]),
1881 location_assignment: HashMap::from([
1882 (Location::new("loc1"), 2),
1883 (Location::new("loc2"), 0),
1884 (Location::new("loc3"), 1),
1885 ]),
1886 })
1887 .unwrap()
1888 .build();
1889
1890 assert!(
1891 path.is_err(),
1892 "expected error because of inconsistent transition"
1893 );
1894 let err = path.unwrap_err();
1895 assert!(
1896 matches!(err, PathBuilderError::InconsistentTransition { .. }),
1897 "got error instead {err}"
1898 );
1899 assert!(err.to_string().contains("Inconsistent transition"));
1900
1901 assert!(
1902 err.to_string().contains(
1903 &Configuration {
1904 variable_assignment: HashMap::from([
1905 (Variable::new("var1"), 1),
1906 (Variable::new("var2"), 2),
1907 (Variable::new("var3"), 0),
1908 ]),
1909 location_assignment: HashMap::from([
1910 (Location::new("loc1"), 2),
1911 (Location::new("loc2"), 0),
1912 (Location::new("loc3"), 1),
1913 ]),
1914 }
1915 .to_string()
1916 )
1917 );
1918
1919 assert!(
1920 err.to_string().contains(
1921 &Transition {
1922 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
1923 number_applied: 1,
1924 }
1925 .to_string()
1926 )
1927 );
1928
1929 assert!(
1930 err.to_string().contains(
1931 &Configuration {
1932 variable_assignment: HashMap::from([
1933 (Variable::new("var1"), 1),
1934 (Variable::new("var2"), 2),
1935 (Variable::new("var3"), 0),
1936 ]),
1937 location_assignment: HashMap::from([
1938 (Location::new("loc1"), 3),
1939 (Location::new("loc2"), 0),
1940 (Location::new("loc3"), 0),
1941 ]),
1942 }
1943 .to_string()
1944 )
1945 );
1946 }
1947
1948 #[test]
1949 fn test_builder_err_not_enough_processes() {
1950 let ta = TEST_TA.clone();
1951
1952 let path = PathBuilder::new(ta.clone())
1953 .add_parameter_assignment(HashMap::from([
1954 (Parameter::new("n"), 4),
1955 (Parameter::new("t"), 2),
1956 (Parameter::new("f"), 1),
1957 ]))
1958 .unwrap()
1959 .add_configuration(Configuration {
1960 variable_assignment: HashMap::from([
1961 (Variable::new("var1"), 1),
1962 (Variable::new("var2"), 2),
1963 (Variable::new("var3"), 0),
1964 ]),
1965 location_assignment: HashMap::from([
1966 (Location::new("loc1"), 3),
1967 (Location::new("loc2"), 0),
1968 (Location::new("loc3"), 0),
1969 ]),
1970 })
1971 .unwrap()
1972 .add_transition(Transition {
1973 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
1974 number_applied: 1,
1975 })
1976 .unwrap()
1977 .add_configuration(Configuration {
1978 variable_assignment: HashMap::from([
1979 (Variable::new("var1"), 1),
1980 (Variable::new("var2"), 2),
1981 (Variable::new("var3"), 0),
1982 ]),
1983 location_assignment: HashMap::from([
1984 (Location::new("loc1"), 2),
1985 (Location::new("loc2"), 0),
1986 (Location::new("loc3"), 1),
1987 ]),
1988 })
1989 .unwrap()
1990 .build();
1991
1992 assert!(
1993 path.is_err(),
1994 "expected error because not enough processes to apply rule"
1995 );
1996 let err = path.unwrap_err();
1997 assert!(
1998 matches!(err, PathBuilderError::NotEnoughProcesses(_, _)),
1999 "got error instead {err}"
2000 );
2001 assert!(err.to_string().contains("not enough processes"));
2002 }
2003
2004 #[test]
2005 fn test_builder_err_not_enough_processes_multi_apply() {
2006 let ta = TEST_TA.clone();
2007
2008 let path = PathBuilder::new(ta.clone())
2009 .add_parameter_assignment(HashMap::from([
2010 (Parameter::new("n"), 5),
2011 (Parameter::new("t"), 2),
2012 (Parameter::new("f"), 1),
2013 ]))
2014 .unwrap()
2015 .add_configuration(Configuration {
2016 variable_assignment: HashMap::from([
2017 (Variable::new("var1"), 1),
2018 (Variable::new("var2"), 2),
2019 (Variable::new("var3"), 0),
2020 ]),
2021 location_assignment: HashMap::from([
2022 (Location::new("loc1"), 4),
2023 (Location::new("loc2"), 0),
2024 (Location::new("loc3"), 0),
2025 ]),
2026 })
2027 .unwrap()
2028 .add_transition(Transition {
2029 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
2030 number_applied: 5,
2031 })
2032 .unwrap()
2033 .add_configuration(Configuration {
2034 variable_assignment: HashMap::from([
2035 (Variable::new("var1"), 1),
2036 (Variable::new("var2"), 2),
2037 (Variable::new("var3"), 0),
2038 ]),
2039 location_assignment: HashMap::from([
2040 (Location::new("loc1"), 0),
2041 (Location::new("loc2"), 4),
2042 (Location::new("loc3"), 0),
2043 ]),
2044 })
2045 .unwrap()
2046 .build();
2047
2048 assert!(
2049 path.is_err(),
2050 "expected error because not enough processes to apply rule"
2051 );
2052 let err = path.unwrap_err();
2053 assert!(
2054 matches!(err, PathBuilderError::NotEnoughProcesses(_, _)),
2055 "got error instead {err}"
2056 );
2057 assert!(err.to_string().contains("not enough processes"));
2058 }
2059
2060 #[test]
2061 fn test_builder_error_guard_not_satisfied() {
2062 let ta = TEST_TA.clone();
2063
2064 let path = PathBuilder::new(ta.clone())
2065 .add_parameter_assignment([
2066 (Parameter::new("n"), 4),
2067 (Parameter::new("t"), 2),
2068 (Parameter::new("f"), 1),
2069 ])
2070 .unwrap()
2071 .add_configurations(vec![
2072 Configuration {
2073 variable_assignment: HashMap::from([
2074 (Variable::new("var1"), 1),
2075 (Variable::new("var2"), 5),
2076 (Variable::new("var3"), 0),
2077 ]),
2078 location_assignment: HashMap::from([
2079 (Location::new("loc1"), 3),
2080 (Location::new("loc2"), 0),
2081 (Location::new("loc3"), 0),
2082 ]),
2083 },
2084 Configuration {
2085 variable_assignment: HashMap::from([
2086 (Variable::new("var1"), 1),
2087 (Variable::new("var2"), 5),
2088 (Variable::new("var3"), 0),
2089 ]),
2090 location_assignment: HashMap::from([
2091 (Location::new("loc1"), 2),
2092 (Location::new("loc2"), 1),
2093 (Location::new("loc3"), 0),
2094 ]),
2095 },
2096 Configuration {
2097 variable_assignment: HashMap::from([
2098 (Variable::new("var1"), 2),
2099 (Variable::new("var2"), 5),
2100 (Variable::new("var3"), 0),
2101 ]),
2102 location_assignment: HashMap::from([
2103 (Location::new("loc1"), 2),
2104 (Location::new("loc2"), 0),
2105 (Location::new("loc3"), 1),
2106 ]),
2107 },
2108 ])
2109 .unwrap()
2110 .add_transitions(vec![
2111 Transition {
2112 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
2113 number_applied: 1,
2114 },
2115 Transition {
2116 rule_used: ta.rules().find(|r| r.id() == 1).unwrap().clone(),
2117 number_applied: 1,
2118 },
2119 ])
2120 .unwrap()
2121 .build();
2122
2123 assert!(
2124 path.is_err(),
2125 "expected error because guard of rule not satisfied"
2126 );
2127 let err = path.unwrap_err();
2128 assert!(
2129 matches!(err, PathBuilderError::GuardNotSatisfied(_, _)),
2130 "got error instead {err}"
2131 );
2132 assert!(err.to_string().contains("guard is not satisfied"));
2133 assert!(
2134 err.to_string().contains(
2135 &ta.rules()
2136 .find(|r| r.id() == 1)
2137 .unwrap()
2138 .clone()
2139 .to_string()
2140 )
2141 );
2142 }
2143
2144 #[test]
2145 fn test_from_evaluation_error_location() {
2146 let err = EvaluationError::AtomicNotFound(Location::new("loc1"));
2147 let path_err: PathBuilderError = err.into();
2148 assert!(matches!(path_err, PathBuilderError::MissingLocation(_)));
2149
2150 let err: EvaluationError<Location> =
2151 EvaluationError::ParameterNotFound(Parameter::new("param1"));
2152 let path_err: PathBuilderError = err.into();
2153 assert!(matches!(path_err, PathBuilderError::MissingParameter(_)));
2154 }
2155
2156 #[test]
2157 fn test_from_evaluation_error_variable() {
2158 let err = EvaluationError::AtomicNotFound(Variable::new("var1"));
2159 let path_err: PathBuilderError = err.into();
2160 assert!(matches!(path_err, PathBuilderError::MissingVariable(_)));
2161
2162 let err: EvaluationError<Variable> =
2163 EvaluationError::ParameterNotFound(Parameter::new("param1"));
2164 let path_err: PathBuilderError = err.into();
2165 assert!(matches!(path_err, PathBuilderError::MissingParameter(_)));
2166 }
2167
2168 #[test]
2169 fn test_apply_rule() {
2170 let cfg = Configuration {
2171 variable_assignment: HashMap::from([]),
2172 location_assignment: HashMap::from([
2173 (Location::new("loc1"), 2),
2174 (Location::new("loc2"), 0),
2175 (Location::new("loc3"), 1),
2176 ]),
2177 };
2178
2179 let rule = RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2"))
2180 .with_guard(BooleanExpression::True)
2181 .build();
2182
2183 let trans = Transition {
2184 rule_used: rule,
2185 number_applied: 1,
2186 };
2187
2188 let expected_cfg = Configuration {
2189 variable_assignment: HashMap::from([]),
2190 location_assignment: HashMap::from([
2191 (Location::new("loc1"), 1),
2192 (Location::new("loc2"), 1),
2193 (Location::new("loc3"), 1),
2194 ]),
2195 };
2196
2197 assert_eq!(cfg.apply_rule(&trans), Some(expected_cfg));
2198 }
2199
2200 #[test]
2201 fn test_apply_rule_self_loop_multi() {
2202 let cfg = Configuration {
2203 variable_assignment: HashMap::from([]),
2204 location_assignment: HashMap::from([
2205 (Location::new("loc1"), 2),
2206 (Location::new("loc2"), 0),
2207 (Location::new("loc3"), 1),
2208 ]),
2209 };
2210
2211 let rule = RuleBuilder::new(1, Location::new("loc1"), Location::new("loc1"))
2212 .with_guard(BooleanExpression::True)
2213 .build();
2214
2215 let trans = Transition {
2216 rule_used: rule,
2217 number_applied: 5,
2218 };
2219
2220 let expected_cfg = Configuration {
2221 variable_assignment: HashMap::from([]),
2222 location_assignment: HashMap::from([
2223 (Location::new("loc1"), 2),
2224 (Location::new("loc2"), 0),
2225 (Location::new("loc3"), 1),
2226 ]),
2227 };
2228
2229 assert_eq!(cfg.apply_rule(&trans), Some(expected_cfg));
2230 }
2231
2232 #[test]
2233 fn test_apply_rule_self_loop_no_proc() {
2234 let cfg = Configuration {
2235 variable_assignment: HashMap::from([]),
2236 location_assignment: HashMap::from([
2237 (Location::new("loc1"), 2),
2238 (Location::new("loc2"), 0),
2239 (Location::new("loc3"), 1),
2240 ]),
2241 };
2242
2243 let rule = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc2"))
2244 .with_guard(BooleanExpression::True)
2245 .build();
2246
2247 let trans = Transition {
2248 rule_used: rule,
2249 number_applied: 1,
2250 };
2251
2252 assert_eq!(cfg.apply_rule(&trans), None);
2253 }
2254
2255 #[test]
2256 fn test_apply_rule_not_enough_processes() {
2257 let cfg = Configuration {
2258 variable_assignment: HashMap::from([]),
2259 location_assignment: HashMap::from([
2260 (Location::new("loc1"), 2),
2261 (Location::new("loc2"), 0),
2262 (Location::new("loc3"), 1),
2263 ]),
2264 };
2265
2266 let rule = RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2"))
2267 .with_guard(BooleanExpression::True)
2268 .build();
2269
2270 let trans = Transition {
2271 rule_used: rule,
2272 number_applied: 3,
2273 };
2274
2275 assert_eq!(cfg.apply_rule(&trans), None);
2276 }
2277
2278 #[test]
2279 fn test_shorten_path() {
2280 let ta = TEST_TA.clone();
2281
2282 let path = PathBuilder::new(ta.clone())
2283 .add_parameter_assignment([
2284 (Parameter::new("n"), 4),
2285 (Parameter::new("t"), 2),
2286 (Parameter::new("f"), 1),
2287 ])
2288 .unwrap()
2289 .add_configurations(vec![
2290 Configuration {
2291 variable_assignment: HashMap::from([
2292 (Variable::new("var1"), 1),
2293 (Variable::new("var2"), 0),
2294 (Variable::new("var3"), 0),
2295 ]),
2296 location_assignment: HashMap::from([
2297 (Location::new("loc1"), 3),
2298 (Location::new("loc2"), 0),
2299 (Location::new("loc3"), 0),
2300 ]),
2301 },
2302 Configuration {
2303 variable_assignment: HashMap::from([
2304 (Variable::new("var1"), 1),
2305 (Variable::new("var2"), 0),
2306 (Variable::new("var3"), 0),
2307 ]),
2308 location_assignment: HashMap::from([
2309 (Location::new("loc1"), 2),
2310 (Location::new("loc2"), 1),
2311 (Location::new("loc3"), 0),
2312 ]),
2313 },
2314 Configuration {
2315 variable_assignment: HashMap::from([
2316 (Variable::new("var1"), 1),
2317 (Variable::new("var2"), 0),
2318 (Variable::new("var3"), 0),
2319 ]),
2320 location_assignment: HashMap::from([
2321 (Location::new("loc1"), 1),
2322 (Location::new("loc2"), 2),
2323 (Location::new("loc3"), 0),
2324 ]),
2325 },
2326 ])
2327 .unwrap()
2328 .add_transitions(vec![
2329 Transition {
2330 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
2331 number_applied: 1,
2332 },
2333 Transition {
2334 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
2335 number_applied: 1,
2336 },
2337 ])
2338 .unwrap()
2339 .build();
2340
2341 assert!(
2342 path.is_ok(),
2343 "Expected path to be valid, got error {}",
2344 path.unwrap_err()
2345 );
2346
2347 let got_path = path.unwrap();
2348
2349 let expected_path = Path {
2350 ta: ta.clone(),
2351 parameter_assignment: HashMap::from([
2352 (Parameter::new("n"), 4),
2353 (Parameter::new("t"), 2),
2354 (Parameter::new("f"), 1),
2355 ]),
2356 configurations: vec![
2357 Configuration {
2358 variable_assignment: HashMap::from([
2359 (Variable::new("var1"), 1),
2360 (Variable::new("var2"), 0),
2361 (Variable::new("var3"), 0),
2362 ]),
2363 location_assignment: HashMap::from([
2364 (Location::new("loc1"), 3),
2365 (Location::new("loc2"), 0),
2366 (Location::new("loc3"), 0),
2367 ]),
2368 },
2369 Configuration {
2370 variable_assignment: HashMap::from([
2371 (Variable::new("var1"), 1),
2372 (Variable::new("var2"), 0),
2373 (Variable::new("var3"), 0),
2374 ]),
2375 location_assignment: HashMap::from([
2376 (Location::new("loc1"), 1),
2377 (Location::new("loc2"), 2),
2378 (Location::new("loc3"), 0),
2379 ]),
2380 },
2381 ],
2382 transitions: vec![Transition {
2383 rule_used: ta.rules().find(|r| r.id() == 0).unwrap().clone(),
2384 number_applied: 2,
2385 }],
2386 };
2387
2388 assert_eq!(
2389 got_path, expected_path,
2390 "Got: {}\nExpected: {}",
2391 got_path, expected_path
2392 )
2393 }
2394}