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