1use std::cell::Cell;
34use std::collections::HashMap;
35
36thread_local! {
41 static ALWAYS_VIOLATION_COUNT: Cell<u64> = const { Cell::new(0) };
42 static SKIP_NEXT_ASSERTION_RESET: Cell<bool> = const { Cell::new(false) };
47}
48
49pub fn record_always_violation() {
54 ALWAYS_VIOLATION_COUNT.with(|c| c.set(c.get() + 1));
55}
56
57pub fn reset_always_violations() {
59 ALWAYS_VIOLATION_COUNT.with(|c| c.set(0));
60}
61
62pub fn has_always_violations() -> bool {
64 ALWAYS_VIOLATION_COUNT.with(|c| c.get() > 0)
65}
66
67#[derive(Debug, Clone, PartialEq)]
73pub struct AssertionStats {
74 pub total_checks: usize,
76 pub successes: usize,
78}
79
80impl AssertionStats {
81 pub fn new() -> Self {
83 Self {
84 total_checks: 0,
85 successes: 0,
86 }
87 }
88
89 pub fn success_rate(&self) -> f64 {
93 if self.total_checks == 0 {
94 0.0
95 } else {
96 (self.successes as f64 / self.total_checks as f64) * 100.0
97 }
98 }
99
100 pub fn record(&mut self, success: bool) {
104 self.total_checks += 1;
105 if success {
106 self.successes += 1;
107 }
108 }
109}
110
111impl Default for AssertionStats {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117pub fn format_details(pairs: &[(&str, &dyn std::fmt::Display)]) -> String {
126 pairs
127 .iter()
128 .map(|(k, v)| format!("{}={}", k, v))
129 .collect::<Vec<_>>()
130 .join(", ")
131}
132
133pub fn on_assertion_bool(
142 msg: impl AsRef<str>,
143 condition: bool,
144 kind: moonpool_explorer::AssertKind,
145 must_hit: bool,
146) {
147 moonpool_explorer::assertion_bool(kind, must_hit, condition, msg.as_ref());
148}
149
150pub fn on_assertion_numeric(
155 msg: impl AsRef<str>,
156 value: i64,
157 cmp: moonpool_explorer::AssertCmp,
158 threshold: i64,
159 kind: moonpool_explorer::AssertKind,
160 maximize: bool,
161) {
162 moonpool_explorer::assertion_numeric(kind, cmp, maximize, value, threshold, msg.as_ref());
163}
164
165pub fn on_assertion_sometimes_all(msg: impl AsRef<str>, named_bools: &[(&str, bool)]) {
169 moonpool_explorer::assertion_sometimes_all(msg.as_ref(), named_bools);
170}
171
172pub fn on_sometimes_each(msg: &str, keys: &[(&str, i64)], quality: &[(&str, i64)]) {
177 moonpool_explorer::assertion_sometimes_each(msg, keys, quality);
178}
179
180pub fn get_assertion_results() -> HashMap<String, AssertionStats> {
189 let slots = moonpool_explorer::assertion_read_all();
190 let mut results = HashMap::new();
191
192 for slot in &slots {
193 let total = slot.pass_count.saturating_add(slot.fail_count) as usize;
194 if total == 0 {
195 continue;
196 }
197 results.insert(
198 slot.msg.clone(),
199 AssertionStats {
200 total_checks: total,
201 successes: slot.pass_count as usize,
202 },
203 );
204 }
205
206 results
207}
208
209pub fn skip_next_assertion_reset() {
215 SKIP_NEXT_ASSERTION_RESET.with(|c| c.set(true));
216}
217
218pub fn reset_assertion_results() {
224 let skip = SKIP_NEXT_ASSERTION_RESET.with(|c| {
225 let v = c.get();
226 c.set(false); v
228 });
229 if !skip {
230 moonpool_explorer::reset_assertions();
231 }
232}
233
234pub fn panic_on_assertion_violations(report: &crate::runner::SimulationReport) {
244 if !report.assertion_violations.is_empty() {
245 eprintln!("Assertion violations found:");
246 for violation in &report.assertion_violations {
247 eprintln!(" - {}", violation);
248 }
249 panic!("Unexpected assertion violations detected!");
250 }
251}
252
253pub fn validate_assertion_contracts() -> (Vec<String>, Vec<String>) {
262 let mut always_violations = Vec::new();
263 let mut coverage_violations = Vec::new();
264 let slots = moonpool_explorer::assertion_read_all();
265
266 for slot in &slots {
267 let total = slot.pass_count.saturating_add(slot.fail_count);
268 let kind = moonpool_explorer::AssertKind::from_u8(slot.kind);
269
270 match kind {
271 Some(moonpool_explorer::AssertKind::Always) => {
272 if slot.fail_count > 0 {
273 always_violations.push(format!(
274 "assert_always!('{}') failed {} times out of {}",
275 slot.msg, slot.fail_count, total
276 ));
277 }
278 if slot.must_hit != 0 && total == 0 {
279 always_violations
280 .push(format!("assert_always!('{}') was never reached", slot.msg));
281 }
282 }
283 Some(moonpool_explorer::AssertKind::AlwaysOrUnreachable) => {
284 if slot.fail_count > 0 {
285 always_violations.push(format!(
286 "assert_always_or_unreachable!('{}') failed {} times out of {}",
287 slot.msg, slot.fail_count, total
288 ));
289 }
290 }
291 Some(moonpool_explorer::AssertKind::Sometimes) => {
292 if total > 0 && slot.pass_count == 0 {
293 coverage_violations.push(format!(
294 "assert_sometimes!('{}') has 0% success rate ({} checks)",
295 slot.msg, total
296 ));
297 }
298 }
299 Some(moonpool_explorer::AssertKind::Reachable) => {
300 if slot.pass_count == 0 {
301 coverage_violations.push(format!(
302 "assert_reachable!('{}') was never reached",
303 slot.msg
304 ));
305 }
306 }
307 Some(moonpool_explorer::AssertKind::Unreachable) => {
308 if slot.pass_count > 0 {
309 always_violations.push(format!(
310 "assert_unreachable!('{}') was reached {} times",
311 slot.msg, slot.pass_count
312 ));
313 }
314 }
315 Some(moonpool_explorer::AssertKind::NumericAlways) => {
316 if slot.fail_count > 0 {
317 always_violations.push(format!(
318 "numeric assert_always ('{}') failed {} times out of {}",
319 slot.msg, slot.fail_count, total
320 ));
321 }
322 }
323 Some(moonpool_explorer::AssertKind::NumericSometimes) => {
324 if total > 0 && slot.pass_count == 0 {
325 coverage_violations.push(format!(
326 "numeric assert_sometimes ('{}') has 0% success rate ({} checks)",
327 slot.msg, total
328 ));
329 }
330 }
331 Some(moonpool_explorer::AssertKind::BooleanSometimesAll) | None => {
332 }
335 }
336 }
337
338 (always_violations, coverage_violations)
339}
340
341#[macro_export]
352macro_rules! assert_always {
353 ($condition:expr, $message:expr) => {
354 let __msg = $message;
355 let cond = $condition;
356 $crate::chaos::assertions::on_assertion_bool(
357 &__msg,
358 cond,
359 $crate::chaos::assertions::_re_export::AssertKind::Always,
360 true,
361 );
362 if !cond {
363 $crate::chaos::assertions::record_always_violation();
364 }
365 };
366 ($condition:expr, $message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
367 let __msg = $message;
368 let cond = $condition;
369 $crate::chaos::assertions::on_assertion_bool(
370 &__msg,
371 cond,
372 $crate::chaos::assertions::_re_export::AssertKind::Always,
373 true,
374 );
375 if !cond {
376 $crate::chaos::assertions::record_always_violation();
377 eprintln!(
378 "[ASSERTION FAILED] {} (seed={}) | {}",
379 __msg,
380 $crate::get_current_sim_seed(),
381 $crate::chaos::assertions::format_details(
382 &[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
383 )
384 );
385 }
386 };
387}
388
389#[macro_export]
394macro_rules! assert_always_or_unreachable {
395 ($condition:expr, $message:expr) => {
396 let __msg = $message;
397 let cond = $condition;
398 $crate::chaos::assertions::on_assertion_bool(
399 &__msg,
400 cond,
401 $crate::chaos::assertions::_re_export::AssertKind::AlwaysOrUnreachable,
402 false,
403 );
404 if !cond {
405 $crate::chaos::assertions::record_always_violation();
406 }
407 };
408 ($condition:expr, $message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
409 let __msg = $message;
410 let cond = $condition;
411 $crate::chaos::assertions::on_assertion_bool(
412 &__msg,
413 cond,
414 $crate::chaos::assertions::_re_export::AssertKind::AlwaysOrUnreachable,
415 false,
416 );
417 if !cond {
418 $crate::chaos::assertions::record_always_violation();
419 eprintln!(
420 "[ASSERTION FAILED] {} (seed={}) | {}",
421 __msg,
422 $crate::get_current_sim_seed(),
423 $crate::chaos::assertions::format_details(
424 &[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
425 )
426 );
427 }
428 };
429}
430
431#[macro_export]
435macro_rules! assert_sometimes {
436 ($condition:expr, $message:expr) => {
437 $crate::chaos::assertions::on_assertion_bool(
438 &$message,
439 $condition,
440 $crate::chaos::assertions::_re_export::AssertKind::Sometimes,
441 true,
442 );
443 };
444}
445
446#[macro_export]
450macro_rules! assert_reachable {
451 ($message:expr) => {
452 $crate::chaos::assertions::on_assertion_bool(
453 &$message,
454 true,
455 $crate::chaos::assertions::_re_export::AssertKind::Reachable,
456 true,
457 );
458 };
459}
460
461#[macro_export]
466macro_rules! assert_unreachable {
467 ($message:expr) => {
468 let __msg = $message;
469 $crate::chaos::assertions::on_assertion_bool(
470 &__msg,
471 true,
472 $crate::chaos::assertions::_re_export::AssertKind::Unreachable,
473 false,
474 );
475 $crate::chaos::assertions::record_always_violation();
476 };
477 ($message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
478 let __msg = $message;
479 $crate::chaos::assertions::on_assertion_bool(
480 &__msg,
481 true,
482 $crate::chaos::assertions::_re_export::AssertKind::Unreachable,
483 false,
484 );
485 $crate::chaos::assertions::record_always_violation();
486 eprintln!(
487 "[ASSERTION FAILED] {} | {}",
488 __msg,
489 $crate::chaos::assertions::format_details(
490 &[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
491 )
492 );
493 };
494}
495
496#[macro_export]
500macro_rules! assert_always_greater_than {
501 ($val:expr, $thresh:expr, $message:expr) => {
502 let __msg = $message;
503 let __v = $val as i64;
504 let __t = $thresh as i64;
505 $crate::chaos::assertions::on_assertion_numeric(
506 &__msg,
507 __v,
508 $crate::chaos::assertions::_re_export::AssertCmp::Gt,
509 __t,
510 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
511 false,
512 );
513 if !(__v > __t) {
514 $crate::chaos::assertions::record_always_violation();
515 }
516 };
517 ($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
518 let __msg = $message;
519 let __v = $val as i64;
520 let __t = $thresh as i64;
521 $crate::chaos::assertions::on_assertion_numeric(
522 &__msg,
523 __v,
524 $crate::chaos::assertions::_re_export::AssertCmp::Gt,
525 __t,
526 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
527 false,
528 );
529 if !(__v > __t) {
530 $crate::chaos::assertions::record_always_violation();
531 eprintln!(
532 "[ASSERTION FAILED] {} ({}>{} failed, seed={}) | {}",
533 __msg, __v, __t,
534 $crate::get_current_sim_seed(),
535 $crate::chaos::assertions::format_details(
536 &[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
537 )
538 );
539 }
540 };
541}
542
543#[macro_export]
547macro_rules! assert_always_greater_than_or_equal_to {
548 ($val:expr, $thresh:expr, $message:expr) => {
549 let __msg = $message;
550 let __v = $val as i64;
551 let __t = $thresh as i64;
552 $crate::chaos::assertions::on_assertion_numeric(
553 &__msg,
554 __v,
555 $crate::chaos::assertions::_re_export::AssertCmp::Ge,
556 __t,
557 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
558 false,
559 );
560 if !(__v >= __t) {
561 $crate::chaos::assertions::record_always_violation();
562 }
563 };
564 ($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
565 let __msg = $message;
566 let __v = $val as i64;
567 let __t = $thresh as i64;
568 $crate::chaos::assertions::on_assertion_numeric(
569 &__msg,
570 __v,
571 $crate::chaos::assertions::_re_export::AssertCmp::Ge,
572 __t,
573 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
574 false,
575 );
576 if !(__v >= __t) {
577 $crate::chaos::assertions::record_always_violation();
578 eprintln!(
579 "[ASSERTION FAILED] {} ({}>={} failed, seed={}) | {}",
580 __msg, __v, __t,
581 $crate::get_current_sim_seed(),
582 $crate::chaos::assertions::format_details(
583 &[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
584 )
585 );
586 }
587 };
588}
589
590#[macro_export]
594macro_rules! assert_always_less_than {
595 ($val:expr, $thresh:expr, $message:expr) => {
596 let __msg = $message;
597 let __v = $val as i64;
598 let __t = $thresh as i64;
599 $crate::chaos::assertions::on_assertion_numeric(
600 &__msg,
601 __v,
602 $crate::chaos::assertions::_re_export::AssertCmp::Lt,
603 __t,
604 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
605 true,
606 );
607 if !(__v < __t) {
608 $crate::chaos::assertions::record_always_violation();
609 }
610 };
611 ($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
612 let __msg = $message;
613 let __v = $val as i64;
614 let __t = $thresh as i64;
615 $crate::chaos::assertions::on_assertion_numeric(
616 &__msg,
617 __v,
618 $crate::chaos::assertions::_re_export::AssertCmp::Lt,
619 __t,
620 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
621 true,
622 );
623 if !(__v < __t) {
624 $crate::chaos::assertions::record_always_violation();
625 eprintln!(
626 "[ASSERTION FAILED] {} ({}<{} failed, seed={}) | {}",
627 __msg, __v, __t,
628 $crate::get_current_sim_seed(),
629 $crate::chaos::assertions::format_details(
630 &[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
631 )
632 );
633 }
634 };
635}
636
637#[macro_export]
641macro_rules! assert_always_less_than_or_equal_to {
642 ($val:expr, $thresh:expr, $message:expr) => {
643 let __msg = $message;
644 let __v = $val as i64;
645 let __t = $thresh as i64;
646 $crate::chaos::assertions::on_assertion_numeric(
647 &__msg,
648 __v,
649 $crate::chaos::assertions::_re_export::AssertCmp::Le,
650 __t,
651 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
652 true,
653 );
654 if !(__v <= __t) {
655 $crate::chaos::assertions::record_always_violation();
656 }
657 };
658 ($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
659 let __msg = $message;
660 let __v = $val as i64;
661 let __t = $thresh as i64;
662 $crate::chaos::assertions::on_assertion_numeric(
663 &__msg,
664 __v,
665 $crate::chaos::assertions::_re_export::AssertCmp::Le,
666 __t,
667 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
668 true,
669 );
670 if !(__v <= __t) {
671 $crate::chaos::assertions::record_always_violation();
672 eprintln!(
673 "[ASSERTION FAILED] {} ({}<={} failed, seed={}) | {}",
674 __msg, __v, __t,
675 $crate::get_current_sim_seed(),
676 $crate::chaos::assertions::format_details(
677 &[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
678 )
679 );
680 }
681 };
682}
683
684#[macro_export]
686macro_rules! assert_sometimes_greater_than {
687 ($val:expr, $thresh:expr, $message:expr) => {
688 $crate::chaos::assertions::on_assertion_numeric(
689 &$message,
690 $val as i64,
691 $crate::chaos::assertions::_re_export::AssertCmp::Gt,
692 $thresh as i64,
693 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
694 true,
695 );
696 };
697}
698
699#[macro_export]
701macro_rules! assert_sometimes_greater_than_or_equal_to {
702 ($val:expr, $thresh:expr, $message:expr) => {
703 $crate::chaos::assertions::on_assertion_numeric(
704 &$message,
705 $val as i64,
706 $crate::chaos::assertions::_re_export::AssertCmp::Ge,
707 $thresh as i64,
708 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
709 true,
710 );
711 };
712}
713
714#[macro_export]
716macro_rules! assert_sometimes_less_than {
717 ($val:expr, $thresh:expr, $message:expr) => {
718 $crate::chaos::assertions::on_assertion_numeric(
719 &$message,
720 $val as i64,
721 $crate::chaos::assertions::_re_export::AssertCmp::Lt,
722 $thresh as i64,
723 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
724 false,
725 );
726 };
727}
728
729#[macro_export]
731macro_rules! assert_sometimes_less_than_or_equal_to {
732 ($val:expr, $thresh:expr, $message:expr) => {
733 $crate::chaos::assertions::on_assertion_numeric(
734 &$message,
735 $val as i64,
736 $crate::chaos::assertions::_re_export::AssertCmp::Le,
737 $thresh as i64,
738 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
739 false,
740 );
741 };
742}
743
744#[macro_export]
759macro_rules! assert_sometimes_all {
760 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
761 $crate::chaos::assertions::on_assertion_sometimes_all($msg, &[ $(($name, $val)),+ ])
762 };
763}
764
765#[macro_export]
781macro_rules! assert_sometimes_each {
782 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
783 $crate::chaos::assertions::on_sometimes_each($msg, &[ $(($name, $val as i64)),+ ], &[])
784 };
785 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ], [ $(($qname:expr, $qval:expr)),+ $(,)? ]) => {
786 $crate::chaos::assertions::on_sometimes_each(
787 $msg,
788 &[ $(($name, $val as i64)),+ ],
789 &[ $(($qname, $qval as i64)),+ ],
790 )
791 };
792}
793
794pub mod _re_export {
796 pub use moonpool_explorer::{AssertCmp, AssertKind};
797}
798
799#[cfg(test)]
800mod tests {
801 use super::*;
802
803 #[test]
804 fn test_assertion_stats_new() {
805 let stats = AssertionStats::new();
806 assert_eq!(stats.total_checks, 0);
807 assert_eq!(stats.successes, 0);
808 assert_eq!(stats.success_rate(), 0.0);
809 }
810
811 #[test]
812 fn test_assertion_stats_record() {
813 let mut stats = AssertionStats::new();
814
815 stats.record(true);
816 assert_eq!(stats.total_checks, 1);
817 assert_eq!(stats.successes, 1);
818 assert_eq!(stats.success_rate(), 100.0);
819
820 stats.record(false);
821 assert_eq!(stats.total_checks, 2);
822 assert_eq!(stats.successes, 1);
823 assert_eq!(stats.success_rate(), 50.0);
824
825 stats.record(true);
826 assert_eq!(stats.total_checks, 3);
827 assert_eq!(stats.successes, 2);
828 let expected = 200.0 / 3.0;
829 assert!((stats.success_rate() - expected).abs() < 1e-10);
830 }
831
832 #[test]
833 fn test_assertion_stats_success_rate_edge_cases() {
834 let mut stats = AssertionStats::new();
835 assert_eq!(stats.success_rate(), 0.0);
836
837 stats.record(false);
838 assert_eq!(stats.success_rate(), 0.0);
839
840 stats.record(true);
841 assert_eq!(stats.success_rate(), 50.0);
842 }
843
844 #[test]
845 fn test_get_assertion_results_empty() {
846 let results = get_assertion_results();
849 let _ = results;
852 }
853
854 #[test]
855 fn test_validate_contracts_empty() {
856 let violations = validate_assertion_contracts();
858 let _ = violations;
860 }
861}