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 on_assertion_bool(
126 msg: impl AsRef<str>,
127 condition: bool,
128 kind: moonpool_explorer::AssertKind,
129 must_hit: bool,
130) {
131 moonpool_explorer::assertion_bool(kind, must_hit, condition, msg.as_ref());
132}
133
134pub fn on_assertion_numeric(
139 msg: impl AsRef<str>,
140 value: i64,
141 cmp: moonpool_explorer::AssertCmp,
142 threshold: i64,
143 kind: moonpool_explorer::AssertKind,
144 maximize: bool,
145) {
146 moonpool_explorer::assertion_numeric(kind, cmp, maximize, value, threshold, msg.as_ref());
147}
148
149pub fn on_assertion_sometimes_all(msg: impl AsRef<str>, named_bools: &[(&str, bool)]) {
153 moonpool_explorer::assertion_sometimes_all(msg.as_ref(), named_bools);
154}
155
156pub fn on_sometimes_each(msg: &str, keys: &[(&str, i64)], quality: &[(&str, i64)]) {
161 moonpool_explorer::assertion_sometimes_each(msg, keys, quality);
162}
163
164pub fn get_assertion_results() -> HashMap<String, AssertionStats> {
173 let slots = moonpool_explorer::assertion_read_all();
174 let mut results = HashMap::new();
175
176 for slot in &slots {
177 let total = slot.pass_count.saturating_add(slot.fail_count) as usize;
178 if total == 0 {
179 continue;
180 }
181 results.insert(
182 slot.msg.clone(),
183 AssertionStats {
184 total_checks: total,
185 successes: slot.pass_count as usize,
186 },
187 );
188 }
189
190 results
191}
192
193pub fn skip_next_assertion_reset() {
199 SKIP_NEXT_ASSERTION_RESET.with(|c| c.set(true));
200}
201
202pub fn reset_assertion_results() {
208 let skip = SKIP_NEXT_ASSERTION_RESET.with(|c| {
209 let v = c.get();
210 c.set(false); v
212 });
213 if !skip {
214 moonpool_explorer::reset_assertions();
215 }
216}
217
218pub fn panic_on_assertion_violations(report: &crate::runner::SimulationReport) {
228 if !report.assertion_violations.is_empty() {
229 eprintln!("Assertion violations found:");
230 for violation in &report.assertion_violations {
231 eprintln!(" - {}", violation);
232 }
233 panic!("Unexpected assertion violations detected!");
234 }
235}
236
237pub fn validate_assertion_contracts() -> (Vec<String>, Vec<String>) {
246 let mut always_violations = Vec::new();
247 let mut coverage_violations = Vec::new();
248 let slots = moonpool_explorer::assertion_read_all();
249
250 for slot in &slots {
251 let total = slot.pass_count.saturating_add(slot.fail_count);
252 let kind = moonpool_explorer::AssertKind::from_u8(slot.kind);
253
254 match kind {
255 Some(moonpool_explorer::AssertKind::Always) => {
256 if slot.fail_count > 0 {
257 always_violations.push(format!(
258 "assert_always!('{}') failed {} times out of {}",
259 slot.msg, slot.fail_count, total
260 ));
261 }
262 if slot.must_hit != 0 && total == 0 {
263 always_violations
264 .push(format!("assert_always!('{}') was never reached", slot.msg));
265 }
266 }
267 Some(moonpool_explorer::AssertKind::AlwaysOrUnreachable) => {
268 if slot.fail_count > 0 {
269 always_violations.push(format!(
270 "assert_always_or_unreachable!('{}') failed {} times out of {}",
271 slot.msg, slot.fail_count, total
272 ));
273 }
274 }
275 Some(moonpool_explorer::AssertKind::Sometimes) => {
276 if total > 0 && slot.pass_count == 0 {
277 coverage_violations.push(format!(
278 "assert_sometimes!('{}') has 0% success rate ({} checks)",
279 slot.msg, total
280 ));
281 }
282 }
283 Some(moonpool_explorer::AssertKind::Reachable) => {
284 if slot.pass_count == 0 {
285 coverage_violations.push(format!(
286 "assert_reachable!('{}') was never reached",
287 slot.msg
288 ));
289 }
290 }
291 Some(moonpool_explorer::AssertKind::Unreachable) => {
292 if slot.pass_count > 0 {
293 always_violations.push(format!(
294 "assert_unreachable!('{}') was reached {} times",
295 slot.msg, slot.pass_count
296 ));
297 }
298 }
299 Some(moonpool_explorer::AssertKind::NumericAlways) => {
300 if slot.fail_count > 0 {
301 always_violations.push(format!(
302 "numeric assert_always ('{}') failed {} times out of {}",
303 slot.msg, slot.fail_count, total
304 ));
305 }
306 }
307 Some(moonpool_explorer::AssertKind::NumericSometimes) => {
308 if total > 0 && slot.pass_count == 0 {
309 coverage_violations.push(format!(
310 "numeric assert_sometimes ('{}') has 0% success rate ({} checks)",
311 slot.msg, total
312 ));
313 }
314 }
315 Some(moonpool_explorer::AssertKind::BooleanSometimesAll) | None => {
316 }
319 }
320 }
321
322 (always_violations, coverage_violations)
323}
324
325#[macro_export]
336macro_rules! assert_always {
337 ($condition:expr, $message:expr) => {
338 let __msg = $message;
339 let cond = $condition;
340 $crate::chaos::assertions::on_assertion_bool(
341 &__msg,
342 cond,
343 $crate::chaos::assertions::_re_export::AssertKind::Always,
344 true,
345 );
346 if !cond {
347 let seed = $crate::sim::get_current_sim_seed();
348 tracing::error!("[ALWAYS FAILED] seed={} — {}", seed, __msg);
349 $crate::chaos::assertions::record_always_violation();
350 }
351 };
352}
353
354#[macro_export]
359macro_rules! assert_always_or_unreachable {
360 ($condition:expr, $message:expr) => {
361 let __msg = $message;
362 let cond = $condition;
363 $crate::chaos::assertions::on_assertion_bool(
364 &__msg,
365 cond,
366 $crate::chaos::assertions::_re_export::AssertKind::AlwaysOrUnreachable,
367 false,
368 );
369 if !cond {
370 let seed = $crate::sim::get_current_sim_seed();
371 tracing::error!("[ALWAYS_OR_UNREACHABLE FAILED] seed={} — {}", seed, __msg);
372 $crate::chaos::assertions::record_always_violation();
373 }
374 };
375}
376
377#[macro_export]
381macro_rules! assert_sometimes {
382 ($condition:expr, $message:expr) => {
383 $crate::chaos::assertions::on_assertion_bool(
384 &$message,
385 $condition,
386 $crate::chaos::assertions::_re_export::AssertKind::Sometimes,
387 true,
388 );
389 };
390}
391
392#[macro_export]
396macro_rules! assert_reachable {
397 ($message:expr) => {
398 $crate::chaos::assertions::on_assertion_bool(
399 &$message,
400 true,
401 $crate::chaos::assertions::_re_export::AssertKind::Reachable,
402 true,
403 );
404 };
405}
406
407#[macro_export]
412macro_rules! assert_unreachable {
413 ($message:expr) => {
414 let __msg = $message;
415 $crate::chaos::assertions::on_assertion_bool(
416 &__msg,
417 true,
418 $crate::chaos::assertions::_re_export::AssertKind::Unreachable,
419 false,
420 );
421 let seed = $crate::sim::get_current_sim_seed();
422 tracing::error!("[UNREACHABLE REACHED] seed={} — {}", seed, __msg);
423 $crate::chaos::assertions::record_always_violation();
424 };
425}
426
427#[macro_export]
431macro_rules! assert_always_greater_than {
432 ($val:expr, $thresh:expr, $message:expr) => {
433 let __msg = $message;
434 let __v = $val as i64;
435 let __t = $thresh as i64;
436 $crate::chaos::assertions::on_assertion_numeric(
437 &__msg,
438 __v,
439 $crate::chaos::assertions::_re_export::AssertCmp::Gt,
440 __t,
441 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
442 false,
443 );
444 if !(__v > __t) {
445 let seed = $crate::sim::get_current_sim_seed();
446 tracing::error!(
447 "[NUMERIC ALWAYS FAILED] seed={} — {} (val={}, thresh={})",
448 seed,
449 __msg,
450 __v,
451 __t
452 );
453 $crate::chaos::assertions::record_always_violation();
454 }
455 };
456}
457
458#[macro_export]
462macro_rules! assert_always_greater_than_or_equal_to {
463 ($val:expr, $thresh:expr, $message:expr) => {
464 let __msg = $message;
465 let __v = $val as i64;
466 let __t = $thresh as i64;
467 $crate::chaos::assertions::on_assertion_numeric(
468 &__msg,
469 __v,
470 $crate::chaos::assertions::_re_export::AssertCmp::Ge,
471 __t,
472 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
473 false,
474 );
475 if !(__v >= __t) {
476 let seed = $crate::sim::get_current_sim_seed();
477 tracing::error!(
478 "[NUMERIC ALWAYS FAILED] seed={} — {} (val={}, thresh={})",
479 seed,
480 __msg,
481 __v,
482 __t
483 );
484 $crate::chaos::assertions::record_always_violation();
485 }
486 };
487}
488
489#[macro_export]
493macro_rules! assert_always_less_than {
494 ($val:expr, $thresh:expr, $message:expr) => {
495 let __msg = $message;
496 let __v = $val as i64;
497 let __t = $thresh as i64;
498 $crate::chaos::assertions::on_assertion_numeric(
499 &__msg,
500 __v,
501 $crate::chaos::assertions::_re_export::AssertCmp::Lt,
502 __t,
503 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
504 true,
505 );
506 if !(__v < __t) {
507 let seed = $crate::sim::get_current_sim_seed();
508 tracing::error!(
509 "[NUMERIC ALWAYS FAILED] seed={} — {} (val={}, thresh={})",
510 seed,
511 __msg,
512 __v,
513 __t
514 );
515 $crate::chaos::assertions::record_always_violation();
516 }
517 };
518}
519
520#[macro_export]
524macro_rules! assert_always_less_than_or_equal_to {
525 ($val:expr, $thresh:expr, $message:expr) => {
526 let __msg = $message;
527 let __v = $val as i64;
528 let __t = $thresh as i64;
529 $crate::chaos::assertions::on_assertion_numeric(
530 &__msg,
531 __v,
532 $crate::chaos::assertions::_re_export::AssertCmp::Le,
533 __t,
534 $crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
535 true,
536 );
537 if !(__v <= __t) {
538 let seed = $crate::sim::get_current_sim_seed();
539 tracing::error!(
540 "[NUMERIC ALWAYS FAILED] seed={} — {} (val={}, thresh={})",
541 seed,
542 __msg,
543 __v,
544 __t
545 );
546 $crate::chaos::assertions::record_always_violation();
547 }
548 };
549}
550
551#[macro_export]
553macro_rules! assert_sometimes_greater_than {
554 ($val:expr, $thresh:expr, $message:expr) => {
555 $crate::chaos::assertions::on_assertion_numeric(
556 &$message,
557 $val as i64,
558 $crate::chaos::assertions::_re_export::AssertCmp::Gt,
559 $thresh as i64,
560 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
561 true,
562 );
563 };
564}
565
566#[macro_export]
568macro_rules! assert_sometimes_greater_than_or_equal_to {
569 ($val:expr, $thresh:expr, $message:expr) => {
570 $crate::chaos::assertions::on_assertion_numeric(
571 &$message,
572 $val as i64,
573 $crate::chaos::assertions::_re_export::AssertCmp::Ge,
574 $thresh as i64,
575 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
576 true,
577 );
578 };
579}
580
581#[macro_export]
583macro_rules! assert_sometimes_less_than {
584 ($val:expr, $thresh:expr, $message:expr) => {
585 $crate::chaos::assertions::on_assertion_numeric(
586 &$message,
587 $val as i64,
588 $crate::chaos::assertions::_re_export::AssertCmp::Lt,
589 $thresh as i64,
590 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
591 false,
592 );
593 };
594}
595
596#[macro_export]
598macro_rules! assert_sometimes_less_than_or_equal_to {
599 ($val:expr, $thresh:expr, $message:expr) => {
600 $crate::chaos::assertions::on_assertion_numeric(
601 &$message,
602 $val as i64,
603 $crate::chaos::assertions::_re_export::AssertCmp::Le,
604 $thresh as i64,
605 $crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
606 false,
607 );
608 };
609}
610
611#[macro_export]
626macro_rules! assert_sometimes_all {
627 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
628 $crate::chaos::assertions::on_assertion_sometimes_all($msg, &[ $(($name, $val)),+ ])
629 };
630}
631
632#[macro_export]
648macro_rules! assert_sometimes_each {
649 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
650 $crate::chaos::assertions::on_sometimes_each($msg, &[ $(($name, $val as i64)),+ ], &[])
651 };
652 ($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ], [ $(($qname:expr, $qval:expr)),+ $(,)? ]) => {
653 $crate::chaos::assertions::on_sometimes_each(
654 $msg,
655 &[ $(($name, $val as i64)),+ ],
656 &[ $(($qname, $qval as i64)),+ ],
657 )
658 };
659}
660
661pub mod _re_export {
663 pub use moonpool_explorer::{AssertCmp, AssertKind};
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669
670 #[test]
671 fn test_assertion_stats_new() {
672 let stats = AssertionStats::new();
673 assert_eq!(stats.total_checks, 0);
674 assert_eq!(stats.successes, 0);
675 assert_eq!(stats.success_rate(), 0.0);
676 }
677
678 #[test]
679 fn test_assertion_stats_record() {
680 let mut stats = AssertionStats::new();
681
682 stats.record(true);
683 assert_eq!(stats.total_checks, 1);
684 assert_eq!(stats.successes, 1);
685 assert_eq!(stats.success_rate(), 100.0);
686
687 stats.record(false);
688 assert_eq!(stats.total_checks, 2);
689 assert_eq!(stats.successes, 1);
690 assert_eq!(stats.success_rate(), 50.0);
691
692 stats.record(true);
693 assert_eq!(stats.total_checks, 3);
694 assert_eq!(stats.successes, 2);
695 let expected = 200.0 / 3.0;
696 assert!((stats.success_rate() - expected).abs() < 1e-10);
697 }
698
699 #[test]
700 fn test_assertion_stats_success_rate_edge_cases() {
701 let mut stats = AssertionStats::new();
702 assert_eq!(stats.success_rate(), 0.0);
703
704 stats.record(false);
705 assert_eq!(stats.success_rate(), 0.0);
706
707 stats.record(true);
708 assert_eq!(stats.success_rate(), 50.0);
709 }
710
711 #[test]
712 fn test_get_assertion_results_empty() {
713 let results = get_assertion_results();
716 let _ = results;
719 }
720
721 #[test]
722 fn test_validate_contracts_empty() {
723 let violations = validate_assertion_contracts();
725 let _ = violations;
727 }
728}