1use core::fmt;
16use std::error;
17
18use log::info;
19use smt_encoding::{ContextMgrError, EContextMgr};
20use taco_model_checker::{
21 ModelChecker, ModelCheckerResult, TargetSpec,
22 reachability_specification::{DisjunctionTargetConfig, ReachabilityProperty},
23};
24use taco_smt_encoder::SMTSolverBuilder;
25use taco_threshold_automaton::{
26 ThresholdAutomaton, general_threshold_automaton::GeneralThresholdAutomaton,
27};
28
29#[cfg(feature = "parallel")]
30use futures::StreamExt;
31#[cfg(feature = "parallel")]
32use futures::stream::FuturesUnordered;
33#[cfg(feature = "parallel")]
34use std::sync::{Arc, atomic::AtomicUsize};
35#[cfg(feature = "parallel")]
36use std::{collections::HashMap, sync::atomic::Ordering};
37#[cfg(feature = "parallel")]
38use taco_threshold_automaton::path::Path;
39#[cfg(feature = "parallel")]
40use tokio::runtime::Builder;
41#[cfg(feature = "parallel")]
42use tokio::task::JoinHandle;
43
44pub mod smt_encoding;
45
46#[derive(Debug, Clone, PartialEq, Default)]
48pub struct SMTModelCheckerOptions {
49 #[cfg(feature = "parallel")]
51 parallel: bool,
52}
53
54impl SMTModelCheckerOptions {
55 #[cfg(feature = "parallel")]
56 pub fn new_parallel() -> Self {
57 Self { parallel: true }
58 }
59
60 #[cfg(feature = "parallel")]
61 pub fn new(parallel: bool) -> Self {
62 Self { parallel }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum SMTModelCheckerInitializationError {
69 ResetsOrDecrements,
71}
72
73impl fmt::Display for SMTModelCheckerInitializationError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 SMTModelCheckerInitializationError::ResetsOrDecrements => {
77 write!(f, "Threshold automaton has decrements or resets")
78 }
79 }
80 }
81}
82
83impl error::Error for SMTModelCheckerInitializationError {}
84
85#[derive(Debug, Clone)]
86pub struct SMTModelChecker {
87 _opts: SMTModelCheckerOptions,
88 solver_builder: SMTSolverBuilder,
89 ta_spec: Vec<(DisjunctionTargetConfig, Vec<GeneralThresholdAutomaton>)>,
90}
91
92impl ModelChecker for SMTModelChecker {
93 type ModelCheckerContext = SMTSolverBuilder;
94
95 type ModelCheckerOptions = SMTModelCheckerOptions;
96
97 type SpecType = ReachabilityProperty;
98
99 type ThresholdAutomatonType = GeneralThresholdAutomaton;
100
101 type InitializationError = SMTModelCheckerInitializationError;
102
103 type ModelCheckingError = ContextMgrError;
104
105 fn initialize(
106 opts: Self::ModelCheckerOptions,
107 ta_spec: Vec<(DisjunctionTargetConfig, Vec<Self::ThresholdAutomatonType>)>,
108 ctx: Self::ModelCheckerContext,
109 ) -> Result<Self, Self::InitializationError> {
110 if ta_spec
111 .iter()
112 .any(|(_, tas)| tas.iter().any(|ta| ta.has_decrements_or_resets()))
113 {
114 return Err(SMTModelCheckerInitializationError::ResetsOrDecrements);
115 }
116
117 Ok(Self {
118 _opts: opts,
119 solver_builder: ctx,
120 ta_spec,
121 })
122 }
123
124 fn verify(
125 self,
126 abort_on_violation: bool,
127 ) -> Result<taco_model_checker::ModelCheckerResult, Self::ModelCheckingError> {
128 #[cfg(feature = "parallel")]
129 if self._opts.parallel {
130 return self.model_check_concurrent(abort_on_violation);
131 }
132
133 self.model_check_non_concurrent(abort_on_violation)
134 }
135}
136
137impl SMTModelChecker {
138 fn model_check_non_concurrent(
140 self,
141 abort_on_violation: bool,
142 ) -> Result<ModelCheckerResult, ContextMgrError> {
143 let mut unsafe_prop = Vec::new();
144
145 for (target, tas_to_check) in self.ta_spec.into_iter() {
146 info!(
147 "Starting to check property '{}', which requires {} model checker run(s).",
148 target.name(),
149 tas_to_check.len()
150 );
151 let mut found_counter_ex = false;
152 for ta in tas_to_check.into_iter() {
153 let target_var_constr = target
154 .get_variable_constraint()
155 .into_iter()
156 .collect::<Vec<_>>();
157
158 let ctx_mgr = EContextMgr::new(ta, &target_var_constr, &self.solver_builder)?;
159 let res = ctx_mgr.check_spec(&target);
160 if let Some(p) = res {
161 info!("Property {} is not satisfied!", target.name());
162
163 unsafe_prop.push((target.name().to_string(), Box::new(p)));
164
165 if abort_on_violation {
166 return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
167 }
168
169 found_counter_ex = true;
170 break;
171 }
172 }
173
174 if !found_counter_ex {
175 info!(
176 "Finished verifying property '{}'. The property holds!",
177 target.name()
178 );
179 }
180 }
181
182 if !unsafe_prop.is_empty() {
183 return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
184 }
185
186 Ok(ModelCheckerResult::SAFE)
187 }
188
189 #[cfg(feature = "parallel")]
190 fn model_check_concurrent(
199 self,
200 abort_on_violation: bool,
201 ) -> Result<ModelCheckerResult, ContextMgrError> {
202 use std::collections::HashMap;
203
204 let runtime = Builder::new_multi_thread()
205 .thread_name("taco-smt-mc")
206 .build()
207 .unwrap();
208
209 let mut completed_map = HashMap::new();
210
211 let futures = FuturesUnordered::new();
212 for (target, tas_to_check) in self.ta_spec.into_iter() {
213 info!(
214 "Queuing checks for property '{}', which requires {} model checker run(s).",
215 target.name(),
216 tas_to_check.len()
217 );
218
219 let completed_safe_counter = Arc::new(AtomicUsize::new(0));
220 completed_map.insert(
221 target.name().to_string(),
222 (tas_to_check.len(), completed_safe_counter.clone()),
223 );
224
225 for ta in tas_to_check.into_iter() {
226 let ft = runtime.spawn(Self::check_single_target(
227 ta,
228 target.clone(),
229 self.solver_builder.clone(),
230 completed_safe_counter.clone(),
231 ));
232 futures.push(ft.into_future());
233 }
234 }
235
236 let res = runtime.spawn(Self::wait_for_mc_results(
237 futures,
238 abort_on_violation,
239 completed_map,
240 ));
241 runtime
242 .block_on(res)
243 .expect("Task panicked or runtime error")
244 }
245
246 #[cfg(feature = "parallel")]
247 async fn wait_for_mc_results(
252 mut futures: FuturesUnordered<JoinHandle<Result<ModelCheckerResult, ContextMgrError>>>,
253 abort_on_violation: bool,
254 completed_map: HashMap<String, (usize, Arc<AtomicUsize>)>,
255 ) -> Result<ModelCheckerResult, ContextMgrError> {
256 let mut found_violations: Vec<(String, Box<Path>)> = Vec::new();
257
258 while let Some(result) = futures.next().await {
259 let res = result.expect("Task panicked or runtime error")?;
260
261 if let ModelCheckerResult::UNSAFE(mut violations) = res {
262 if abort_on_violation {
263 futures.into_iter().for_each(|f| {
264 f.abort();
265 });
266 return Ok(ModelCheckerResult::UNSAFE(violations));
267 }
268 found_violations.append(&mut violations);
269 }
270 }
271
272 for (property_name, (expected_safe, terminated_safe)) in completed_map {
273 if expected_safe == terminated_safe.load(Ordering::SeqCst) {
274 info!("Finished verifying property '{property_name}'. The property holds!")
275 }
276 }
277
278 if !found_violations.is_empty() {
279 return Ok(ModelCheckerResult::UNSAFE(found_violations));
280 }
281
282 Ok(ModelCheckerResult::SAFE)
283 }
284
285 #[cfg(feature = "parallel")]
286 async fn check_single_target(
288 ta: GeneralThresholdAutomaton,
289 target: DisjunctionTargetConfig,
290 solver_builder: SMTSolverBuilder,
291 safe_counter: Arc<AtomicUsize>,
292 ) -> Result<ModelCheckerResult, ContextMgrError> {
293 let target_var_constr = target
294 .get_variable_constraint()
295 .into_iter()
296 .collect::<Vec<_>>();
297
298 let ctx_mgr = EContextMgr::new(ta, &target_var_constr, &solver_builder)?;
299 let res = ctx_mgr.check_spec(&target);
300
301 if let Some(p) = res {
302 info!("Property {} is not satisfied!", target.name());
303
304 return Ok(ModelCheckerResult::UNSAFE(vec![(
305 target.name().to_string(),
306 Box::new(p),
307 )]));
308 }
309
310 safe_counter.fetch_add(1, Ordering::SeqCst);
311 Ok(ModelCheckerResult::SAFE)
312 }
313}
314
315impl Default for SMTModelChecker {
316 fn default() -> Self {
317 Self::new_mc()
318 }
319}
320
321impl SMTModelChecker {
322 pub fn new_mc() -> Self {
323 Self {
324 _opts: SMTModelCheckerOptions::default(),
325 solver_builder: SMTSolverBuilder::default(),
326 ta_spec: Vec::new(),
327 }
328 }
329
330 pub fn new_mc_with_opts(opts: SMTModelCheckerOptions) -> Self {
331 Self {
332 _opts: opts,
333 solver_builder: SMTSolverBuilder::default(),
334 ta_spec: Vec::new(),
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341
342 use super::*;
343 use taco_model_checker::{ModelChecker, reachability_specification::TargetConfig};
344 use taco_parser::{ParseTA, ParseTAWithLTL, bymc::ByMCParser};
345 use taco_smt_encoder::SMTSolverBuilderCfg;
346 use taco_threshold_automaton::{
347 ModifiableThresholdAutomaton,
348 expressions::{
349 BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
350 },
351 general_threshold_automaton::{Action, UpdateExpression, builder::RuleBuilder},
352 path::{Configuration, PathBuilder, Transition},
353 };
354
355 #[test]
356 fn test_can_initialize_true() {
357 let test_spec = "
358 skel test_ta1 {
359 shared var1, var2, var3;
360 parameters n, f;
361
362 assumptions (1) {
363 n > 3 * f;
364 n == 1;
365 }
366
367 locations (2) {
368 loc1 : [0];
369 loc2 : [1];
370 loc3 : [2];
371 }
372
373 inits (1) {
374 loc1 == n - f;
375 loc2 == 0;
376 loc3 == 0;
377 var1 == 0;
378 var2 == 0;
379 var3 == 0;
380 }
381
382 rules (4) {
383 0: loc1 -> loc2
384 when(true)
385 do {};
386 }
387 }
388 ";
389
390 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
391 let spec = DisjunctionTargetConfig::new_from_targets(
392 "test".into(),
393 [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
394 );
395
396 let mc = SMTModelChecker::initialize(
397 SMTModelCheckerOptions::default(),
398 vec![(spec, vec![ta])],
399 SMTSolverBuilder::default(),
400 );
401 assert!(mc.is_ok());
402 }
403
404 #[test]
405 fn test_can_initialize_false() {
406 let test_spec = "
407 skel test_ta1 {
408 shared var1, var2, var3;
409 parameters n, f;
410
411 assumptions (1) {
412 n > 3 * f;
413 n == 1;
414 }
415
416 locations (2) {
417 loc1 : [0];
418 loc2 : [1];
419 loc3 : [2];
420 }
421
422 inits (1) {
423 loc1 == n - f;
424 loc2 == 0;
425 loc3 == 0;
426 var1 == 0;
427 var2 == 0;
428 var3 == 0;
429 }
430
431 rules (4) {
432 0: loc1 -> loc2
433 when(true)
434 do { var1' == 0; };
435 }
436 }
437 ";
438
439 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
440 let spec = DisjunctionTargetConfig::new_from_targets(
441 "test".into(),
442 [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
443 );
444
445 let mc = SMTModelChecker::initialize(
446 SMTModelCheckerOptions::default(),
447 vec![(spec, vec![ta])],
448 SMTSolverBuilder::default(),
449 );
450
451 assert!(mc.is_err());
452 }
453
454 #[test]
455 fn test_smt_model_checker_simple_unsafe() {
456 let test_spec = "
457 skel test_ta1 {
458 shared var1, var2, var3;
459 parameters n, f;
460
461 assumptions (1) {
462 n > 3 * f;
463 n == 1;
464 }
465
466 locations (2) {
467 loc1 : [0];
468 loc2 : [1];
469 loc3 : [2];
470 }
471
472 inits (1) {
473 loc1 == n - f;
474 loc2 == 0;
475 loc3 == 0;
476 var1 == 0;
477 var2 == 0;
478 var3 == 0;
479 }
480
481 rules (4) {
482 0: loc1 -> loc2
483 when(true)
484 do {};
485 }
486
487 specifications (1) {
488 test1:
489 [](loc1 == 0)
490 }
491 }
492 ";
493
494 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
495 let spec = DisjunctionTargetConfig::new_from_targets(
496 "test".into(),
497 [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
498 );
499
500 let mc = SMTModelChecker::initialize(
501 SMTModelCheckerOptions::default(),
502 vec![(spec, vec![ta])],
503 SMTSolverBuilder::default(),
504 )
505 .unwrap();
506 let res = mc.verify(true);
507 assert!(res.is_ok());
508 assert!(matches!(res.unwrap(), ModelCheckerResult::UNSAFE(_)));
509 }
510
511 #[cfg(feature = "parallel")]
512 #[test]
513 fn test_smt_model_checker_simple_unsafe_parallel() {
514 let test_spec = "
515 skel test_ta1 {
516 shared var1, var2, var3;
517 parameters n, f;
518
519 assumptions (1) {
520 n > 3 * f;
521 n == 1;
522 }
523
524 locations (2) {
525 loc1 : [0];
526 loc2 : [1];
527 loc3 : [2];
528 }
529
530 inits (1) {
531 loc1 == n - f;
532 loc2 == 0;
533 loc3 == 0;
534 var1 == 0;
535 var2 == 0;
536 var3 == 0;
537 }
538
539 rules (4) {
540 0: loc1 -> loc2
541 when(true)
542 do {};
543 }
544
545 specifications (1) {
546 test1:
547 [](loc1 == 0)
548 }
549 }
550 ";
551
552 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
553 let spec = DisjunctionTargetConfig::new_from_targets(
554 "test".into(),
555 [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
556 );
557
558 let mc = SMTModelChecker::initialize(
559 SMTModelCheckerOptions::new_parallel(),
560 vec![(spec, vec![ta])],
561 SMTSolverBuilder::default(),
562 )
563 .unwrap();
564 let res = mc.verify(true);
565 assert!(res.is_ok());
566 assert!(matches!(res.unwrap(), ModelCheckerResult::UNSAFE(_)));
567 }
568
569 #[test]
570 fn test_smt_model_checker_simple_safe() {
571 let test_spec = "
572 skel test_ta1 {
573 shared var1, var2, var3;
574 parameters n, f;
575
576 assumptions (1) {
577 n > 3 * f;
578 n == 1;
579 }
580
581 locations (2) {
582 loc1 : [0];
583 loc2 : [1];
584 loc3 : [2];
585 }
586
587 inits (1) {
588 loc1 == n - f;
589 loc2 == 0;
590 loc3 == 0;
591 var1 == 0;
592 var2 == 0;
593 var3 == 0;
594 }
595
596 rules (4) {
597 0: loc1 -> loc2
598 when(true)
599 do {};
600 }
601
602 specifications (1) {
603 test1:
604 [](loc3 == 0)
605 }
606 }
607 ";
608
609 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
610 let spec = DisjunctionTargetConfig::new_from_targets(
611 "test".into(),
612 [TargetConfig::new_cover([Location::new("loc3")]).unwrap()],
613 );
614
615 let mc = SMTModelChecker::initialize(
616 SMTModelCheckerOptions::default(),
617 vec![(spec, vec![ta])],
618 SMTSolverBuilder::default(),
619 )
620 .unwrap();
621 let res = mc.verify(false);
622 assert!(res.is_ok());
623 assert!(matches!(res.unwrap(), ModelCheckerResult::SAFE));
624 }
625
626 #[cfg(feature = "parallel")]
627 #[test]
628 fn test_smt_model_checker_simple_safe_parallel() {
629 let test_spec = "
630 skel test_ta1 {
631 shared var1, var2, var3;
632 parameters n, f;
633
634 assumptions (1) {
635 n > 3 * f;
636 n == 1;
637 }
638
639 locations (2) {
640 loc1 : [0];
641 loc2 : [1];
642 loc3 : [2];
643 }
644
645 inits (1) {
646 loc1 == n - f;
647 loc2 == 0;
648 loc3 == 0;
649 var1 == 0;
650 var2 == 0;
651 var3 == 0;
652 }
653
654 rules (4) {
655 0: loc1 -> loc2
656 when(true)
657 do {};
658 }
659
660 specifications (1) {
661 test1:
662 [](loc3 == 0)
663 }
664 }
665 ";
666
667 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
668 let spec = DisjunctionTargetConfig::new_from_targets(
669 "test".into(),
670 [TargetConfig::new_cover([Location::new("loc3")]).unwrap()],
671 );
672
673 let mc = SMTModelChecker::initialize(
674 SMTModelCheckerOptions::new_parallel(),
675 vec![(spec, vec![ta])],
676 SMTSolverBuilder::default(),
677 )
678 .unwrap();
679 let res = mc.verify(false);
680 assert!(res.is_ok());
681 assert!(matches!(res.unwrap(), ModelCheckerResult::SAFE));
682 }
683
684 #[test]
685 fn test_smt_model_checker_simple_safe_new_mc() {
686 let test_spec = "
687 skel test_ta1 {
688 shared var1, var2, var3;
689 parameters n, f;
690
691 assumptions (1) {
692 n > 3 * f;
693 n == 1;
694 }
695
696 locations (2) {
697 loc1 : [0];
698 loc2 : [1];
699 loc3 : [2];
700 }
701
702 inits (1) {
703 loc1 == n - f;
704 loc2 == 0;
705 loc3 == 0;
706 var1 == 0;
707 var2 == 0;
708 var3 == 0;
709 }
710
711 rules (4) {
712 0: loc1 -> loc2
713 when(true)
714 do {};
715 }
716
717 specifications (1) {
718 test1:
719 [](loc3 == 0)
720 }
721 }
722 ";
723
724 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
725
726 let mc = SMTModelChecker::new(
727 Some(SMTSolverBuilderCfg::new_z3()),
728 SMTModelCheckerOptions::default(),
729 Vec::new(),
730 ta,
731 spec.expressions().iter().cloned(),
732 )
733 .expect("Failed to create SMT model checker");
734
735 let res = mc.verify(false).expect("Failed to model check");
736 assert!(matches!(res, ModelCheckerResult::SAFE));
737 }
738
739 #[cfg(feature = "parallel")]
740 #[test]
741 fn test_smt_model_checker_simple_safe_new_mc_parallel() {
742 let test_spec = "
743 skel test_ta1 {
744 shared var1, var2, var3;
745 parameters n, f;
746
747 assumptions (1) {
748 n > 3 * f;
749 n == 1;
750 }
751
752 locations (2) {
753 loc1 : [0];
754 loc2 : [1];
755 loc3 : [2];
756 }
757
758 inits (1) {
759 loc1 == n - f;
760 loc2 == 0;
761 loc3 == 0;
762 var1 == 0;
763 var2 == 0;
764 var3 == 0;
765 }
766
767 rules (4) {
768 0: loc1 -> loc2
769 when(true)
770 do {};
771 }
772
773 specifications (1) {
774 test1:
775 [](loc3 == 0)
776 }
777 }
778 ";
779
780 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
781
782 let mc = SMTModelChecker::new(
783 Some(SMTSolverBuilderCfg::new_z3()),
784 SMTModelCheckerOptions::new_parallel(),
785 Vec::new(),
786 ta,
787 spec.expressions().iter().cloned(),
788 )
789 .expect("Failed to create SMT model checker");
790
791 let res = mc.verify(false).expect("Failed to model check");
792 assert!(matches!(res, ModelCheckerResult::SAFE));
793 }
794
795 #[test]
796 fn test_display_initialization_error() {
797 let error = SMTModelCheckerInitializationError::ResetsOrDecrements;
798 assert_eq!(
799 format!("{error}"),
800 "Threshold automaton has decrements or resets"
801 );
802 }
803
804 #[test]
805 fn test_full_model_checker_reach_location_three_rules_one_guard_self_loop_with_var_constr() {
806 let test_spec = "
807 skel test_ta1 {
808 shared var1, var2, var3;
809 parameters n, f;
810
811 assumptions (1) {
812 n > 3 * f;
813 n == 1;
814 f == 0;
815 }
816
817 locations (2) {
818 loc1 : [0];
819 loc2 : [1];
820 loc3 : [2];
821 }
822
823 inits (1) {
824 loc1 == n - f;
825 loc2 == 0;
826 loc3 == 0;
827 var1 == 0;
828 var2 == 0;
829 var3 == 0;
830 }
831
832 rules (4) {
833 0: loc1 -> loc1
834 when(true)
835 do {var1' == var1 + 1};
836 1: loc1 -> loc2
837 when(true)
838 do {};
839 2: loc2 -> loc3
840 when(var1 > 3 && var2 <= 0 && var1 < 6 )
841 do {};
842
843 }
844
845 specifications (1) {
846 test1:
847 []((loc3 == 0) || (var1 < 5))
848 }
849 }
850 ";
851
852 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
853
854 let mc = SMTModelChecker::new(
855 Some(SMTSolverBuilderCfg::new_z3()),
856 SMTModelCheckerOptions::new(false),
857 Vec::new(),
858 ta.clone(),
859 spec.into_iter(),
860 );
861 let mc = mc.unwrap();
862 let res = mc.verify(true).unwrap();
863
864 let mut spec_ta = ta.clone();
866 spec_ta.set_name("test_ta1-test1".into());
867
868 let path = PathBuilder::new(spec_ta)
871 .add_parameter_assignment(HashMap::from([
872 (Parameter::new("n"), 1),
873 (Parameter::new("f"), 0),
874 ]))
875 .unwrap()
876 .add_configuration(Configuration::new(
877 HashMap::from([
878 (Variable::new("var1"), 0),
879 (Variable::new("var2"), 0),
880 (Variable::new("var3"), 0),
881 ]),
882 HashMap::from([
883 (Location::new("loc1"), 1),
884 (Location::new("loc2"), 0),
885 (Location::new("loc3"), 0),
886 ]),
887 ))
888 .unwrap()
889 .add_transition(Transition::new(
890 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
891 .with_action(Action::new_with_update(
892 Variable::new("var1"),
893 UpdateExpression::Inc(1),
894 ))
895 .build(),
896 5,
897 ))
898 .unwrap()
899 .add_configuration(Configuration::new(
900 HashMap::from([
901 (Variable::new("var1"), 5),
902 (Variable::new("var2"), 0),
903 (Variable::new("var3"), 0),
904 ]),
905 HashMap::from([
906 (Location::new("loc1"), 1),
907 (Location::new("loc2"), 0),
908 (Location::new("loc3"), 0),
909 ]),
910 ))
911 .unwrap()
912 .add_transition(Transition::new(
913 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
914 1,
915 ))
916 .unwrap()
917 .add_configuration(Configuration::new(
918 HashMap::from([
919 (Variable::new("var1"), 5),
920 (Variable::new("var2"), 0),
921 (Variable::new("var3"), 0),
922 ]),
923 HashMap::from([
924 (Location::new("loc1"), 0),
925 (Location::new("loc2"), 1),
926 (Location::new("loc3"), 0),
927 ]),
928 ))
929 .unwrap()
930 .add_transition(Transition::new(
931 RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
932 .with_guard(
933 BooleanExpression::ComparisonExpression(
934 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
935 ComparisonOp::Gt,
936 Box::new(IntegerExpression::Const(3)),
937 ) & BooleanExpression::ComparisonExpression(
938 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
939 ComparisonOp::Leq,
940 Box::new(IntegerExpression::Const(0)),
941 ) & BooleanExpression::ComparisonExpression(
942 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
943 ComparisonOp::Lt,
944 Box::new(IntegerExpression::Const(6)),
945 ),
946 )
947 .build(),
948 1,
949 ))
950 .unwrap()
951 .add_configuration(Configuration::new(
952 HashMap::from([
953 (Variable::new("var1"), 5),
954 (Variable::new("var2"), 0),
955 (Variable::new("var3"), 0),
956 ]),
957 HashMap::from([
958 (Location::new("loc1"), 0),
959 (Location::new("loc2"), 0),
960 (Location::new("loc3"), 1),
961 ]),
962 ))
963 .unwrap()
964 .build()
965 .unwrap();
966
967 let res = match res {
968 ModelCheckerResult::SAFE => unreachable!("checked above"),
969 ModelCheckerResult::UNSAFE(v) => {
970 assert_eq!(v.len(), 1);
971 *v[0].1.clone()
972 }
973 ModelCheckerResult::UNKNOWN(_) => todo!(),
974 };
975
976 assert_eq!(
977 res,
978 path.clone(),
979 "Got:\n{}\n\nExpected:\n{}\n\n",
980 res,
981 path
982 );
983 }
984}