use std::cell::Cell;
use std::collections::HashMap;
thread_local! {
static ALWAYS_VIOLATION_COUNT: Cell<u64> = const { Cell::new(0) };
static SKIP_NEXT_ASSERTION_RESET: Cell<bool> = const { Cell::new(false) };
}
pub fn record_always_violation() {
ALWAYS_VIOLATION_COUNT.with(|c| c.set(c.get() + 1));
}
pub fn reset_always_violations() {
ALWAYS_VIOLATION_COUNT.with(|c| c.set(0));
}
pub fn has_always_violations() -> bool {
ALWAYS_VIOLATION_COUNT.with(|c| c.get() > 0)
}
#[derive(Debug, Clone, PartialEq)]
pub struct AssertionStats {
pub total_checks: usize,
pub successes: usize,
}
impl AssertionStats {
pub fn new() -> Self {
Self {
total_checks: 0,
successes: 0,
}
}
pub fn success_rate(&self) -> f64 {
if self.total_checks == 0 {
0.0
} else {
(self.successes as f64 / self.total_checks as f64) * 100.0
}
}
pub fn record(&mut self, success: bool) {
self.total_checks += 1;
if success {
self.successes += 1;
}
}
}
impl Default for AssertionStats {
fn default() -> Self {
Self::new()
}
}
pub fn format_details(pairs: &[(&str, &dyn std::fmt::Display)]) -> String {
pairs
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(", ")
}
pub fn on_assertion_bool(
msg: impl AsRef<str>,
condition: bool,
kind: moonpool_explorer::AssertKind,
must_hit: bool,
) {
moonpool_explorer::assertion_bool(kind, must_hit, condition, msg.as_ref());
}
pub fn on_assertion_numeric(
msg: impl AsRef<str>,
value: i64,
cmp: moonpool_explorer::AssertCmp,
threshold: i64,
kind: moonpool_explorer::AssertKind,
maximize: bool,
) {
moonpool_explorer::assertion_numeric(kind, cmp, maximize, value, threshold, msg.as_ref());
}
pub fn on_assertion_sometimes_all(msg: impl AsRef<str>, named_bools: &[(&str, bool)]) {
moonpool_explorer::assertion_sometimes_all(msg.as_ref(), named_bools);
}
pub fn on_sometimes_each(msg: &str, keys: &[(&str, i64)], quality: &[(&str, i64)]) {
moonpool_explorer::assertion_sometimes_each(msg, keys, quality);
}
pub fn assertion_results() -> HashMap<String, AssertionStats> {
let slots = moonpool_explorer::assertion_read_all();
let mut results = HashMap::new();
for slot in &slots {
let total = slot.pass_count.saturating_add(slot.fail_count) as usize;
if total == 0 {
continue;
}
results.insert(
slot.msg.clone(),
AssertionStats {
total_checks: total,
successes: slot.pass_count as usize,
},
);
}
results
}
pub fn skip_next_assertion_reset() {
SKIP_NEXT_ASSERTION_RESET.with(|c| c.set(true));
}
pub fn reset_assertion_results() {
let skip = SKIP_NEXT_ASSERTION_RESET.with(|c| {
let v = c.get();
c.set(false); v
});
if !skip {
moonpool_explorer::reset_assertions();
}
}
pub fn panic_on_assertion_violations(report: &crate::runner::SimulationReport) {
if !report.assertion_violations.is_empty() {
eprintln!("Assertion violations found:");
for violation in &report.assertion_violations {
eprintln!(" - {}", violation);
}
panic!("Unexpected assertion violations detected!");
}
}
pub fn validate_assertion_contracts() -> (Vec<String>, Vec<String>) {
let mut always_violations = Vec::new();
let mut coverage_violations = Vec::new();
let slots = moonpool_explorer::assertion_read_all();
for slot in &slots {
let total = slot.pass_count.saturating_add(slot.fail_count);
let kind = moonpool_explorer::AssertKind::from_u8(slot.kind);
match kind {
Some(moonpool_explorer::AssertKind::Always) => {
if slot.fail_count > 0 {
always_violations.push(format!(
"assert_always!('{}') failed {} times out of {}",
slot.msg, slot.fail_count, total
));
}
if slot.must_hit != 0 && total == 0 {
always_violations
.push(format!("assert_always!('{}') was never reached", slot.msg));
}
}
Some(moonpool_explorer::AssertKind::AlwaysOrUnreachable) => {
if slot.fail_count > 0 {
always_violations.push(format!(
"assert_always_or_unreachable!('{}') failed {} times out of {}",
slot.msg, slot.fail_count, total
));
}
}
Some(moonpool_explorer::AssertKind::Sometimes) => {
if total > 0 && slot.pass_count == 0 {
coverage_violations.push(format!(
"assert_sometimes!('{}') has 0% success rate ({} checks)",
slot.msg, total
));
}
}
Some(moonpool_explorer::AssertKind::Reachable) => {
if slot.pass_count == 0 {
coverage_violations.push(format!(
"assert_reachable!('{}') was never reached",
slot.msg
));
}
}
Some(moonpool_explorer::AssertKind::Unreachable) => {
if slot.pass_count > 0 {
always_violations.push(format!(
"assert_unreachable!('{}') was reached {} times",
slot.msg, slot.pass_count
));
}
}
Some(moonpool_explorer::AssertKind::NumericAlways) => {
if slot.fail_count > 0 {
always_violations.push(format!(
"numeric assert_always ('{}') failed {} times out of {}",
slot.msg, slot.fail_count, total
));
}
}
Some(moonpool_explorer::AssertKind::NumericSometimes) => {
if total > 0 && slot.pass_count == 0 {
coverage_violations.push(format!(
"numeric assert_sometimes ('{}') has 0% success rate ({} checks)",
slot.msg, total
));
}
}
Some(moonpool_explorer::AssertKind::BooleanSometimesAll) | None => {
}
}
}
(always_violations, coverage_violations)
}
#[macro_export]
macro_rules! assert_always {
($condition:expr, $message:expr) => {
let __msg = $message;
let cond = $condition;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
cond,
$crate::chaos::assertions::_re_export::AssertKind::Always,
true,
);
if !cond {
$crate::chaos::assertions::record_always_violation();
}
};
($condition:expr, $message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
let __msg = $message;
let cond = $condition;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
cond,
$crate::chaos::assertions::_re_export::AssertKind::Always,
true,
);
if !cond {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} (seed={}) | {}",
__msg,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_always_or_unreachable {
($condition:expr, $message:expr) => {
let __msg = $message;
let cond = $condition;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
cond,
$crate::chaos::assertions::_re_export::AssertKind::AlwaysOrUnreachable,
false,
);
if !cond {
$crate::chaos::assertions::record_always_violation();
}
};
($condition:expr, $message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
let __msg = $message;
let cond = $condition;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
cond,
$crate::chaos::assertions::_re_export::AssertKind::AlwaysOrUnreachable,
false,
);
if !cond {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} (seed={}) | {}",
__msg,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_sometimes {
($condition:expr, $message:expr) => {
$crate::chaos::assertions::on_assertion_bool(
&$message,
$condition,
$crate::chaos::assertions::_re_export::AssertKind::Sometimes,
true,
);
};
}
#[macro_export]
macro_rules! assert_reachable {
($message:expr) => {
$crate::chaos::assertions::on_assertion_bool(
&$message,
true,
$crate::chaos::assertions::_re_export::AssertKind::Reachable,
true,
);
};
}
#[macro_export]
macro_rules! assert_unreachable {
($message:expr) => {
let __msg = $message;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
true,
$crate::chaos::assertions::_re_export::AssertKind::Unreachable,
false,
);
$crate::chaos::assertions::record_always_violation();
};
($message:expr, { $($key:expr => $val:expr),+ $(,)? }) => {
let __msg = $message;
$crate::chaos::assertions::on_assertion_bool(
&__msg,
true,
$crate::chaos::assertions::_re_export::AssertKind::Unreachable,
false,
);
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} | {}",
__msg,
$crate::chaos::assertions::format_details(
&[ $(($key, &$val as &dyn std::fmt::Display)),+ ]
)
);
};
}
#[macro_export]
macro_rules! assert_always_greater_than {
($val:expr, $thresh:expr, $message:expr) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Gt,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
false,
);
if !(__v > __t) {
$crate::chaos::assertions::record_always_violation();
}
};
($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Gt,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
false,
);
if !(__v > __t) {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} ({}>{} failed, seed={}) | {}",
__msg, __v, __t,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_always_greater_than_or_equal_to {
($val:expr, $thresh:expr, $message:expr) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Ge,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
false,
);
if !(__v >= __t) {
$crate::chaos::assertions::record_always_violation();
}
};
($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Ge,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
false,
);
if !(__v >= __t) {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} ({}>={} failed, seed={}) | {}",
__msg, __v, __t,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_always_less_than {
($val:expr, $thresh:expr, $message:expr) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Lt,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
true,
);
if !(__v < __t) {
$crate::chaos::assertions::record_always_violation();
}
};
($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Lt,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
true,
);
if !(__v < __t) {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} ({}<{} failed, seed={}) | {}",
__msg, __v, __t,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_always_less_than_or_equal_to {
($val:expr, $thresh:expr, $message:expr) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Le,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
true,
);
if !(__v <= __t) {
$crate::chaos::assertions::record_always_violation();
}
};
($val:expr, $thresh:expr, $message:expr, { $($key:expr => $dval:expr),+ $(,)? }) => {
let __msg = $message;
let __v = $val as i64;
let __t = $thresh as i64;
$crate::chaos::assertions::on_assertion_numeric(
&__msg,
__v,
$crate::chaos::assertions::_re_export::AssertCmp::Le,
__t,
$crate::chaos::assertions::_re_export::AssertKind::NumericAlways,
true,
);
if !(__v <= __t) {
$crate::chaos::assertions::record_always_violation();
eprintln!(
"[ASSERTION FAILED] {} ({}<={} failed, seed={}) | {}",
__msg, __v, __t,
$crate::current_sim_seed(),
$crate::chaos::assertions::format_details(
&[ $(($key, &$dval as &dyn std::fmt::Display)),+ ]
)
);
}
};
}
#[macro_export]
macro_rules! assert_sometimes_greater_than {
($val:expr, $thresh:expr, $message:expr) => {
$crate::chaos::assertions::on_assertion_numeric(
&$message,
$val as i64,
$crate::chaos::assertions::_re_export::AssertCmp::Gt,
$thresh as i64,
$crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
true,
);
};
}
#[macro_export]
macro_rules! assert_sometimes_greater_than_or_equal_to {
($val:expr, $thresh:expr, $message:expr) => {
$crate::chaos::assertions::on_assertion_numeric(
&$message,
$val as i64,
$crate::chaos::assertions::_re_export::AssertCmp::Ge,
$thresh as i64,
$crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
true,
);
};
}
#[macro_export]
macro_rules! assert_sometimes_less_than {
($val:expr, $thresh:expr, $message:expr) => {
$crate::chaos::assertions::on_assertion_numeric(
&$message,
$val as i64,
$crate::chaos::assertions::_re_export::AssertCmp::Lt,
$thresh as i64,
$crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
false,
);
};
}
#[macro_export]
macro_rules! assert_sometimes_less_than_or_equal_to {
($val:expr, $thresh:expr, $message:expr) => {
$crate::chaos::assertions::on_assertion_numeric(
&$message,
$val as i64,
$crate::chaos::assertions::_re_export::AssertCmp::Le,
$thresh as i64,
$crate::chaos::assertions::_re_export::AssertKind::NumericSometimes,
false,
);
};
}
#[macro_export]
macro_rules! assert_sometimes_all {
($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
$crate::chaos::assertions::on_assertion_sometimes_all($msg, &[ $(($name, $val)),+ ])
};
}
#[macro_export]
macro_rules! assert_sometimes_each {
($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ]) => {
$crate::chaos::assertions::on_sometimes_each($msg, &[ $(($name, $val as i64)),+ ], &[])
};
($msg:expr, [ $(($name:expr, $val:expr)),+ $(,)? ], [ $(($qname:expr, $qval:expr)),+ $(,)? ]) => {
$crate::chaos::assertions::on_sometimes_each(
$msg,
&[ $(($name, $val as i64)),+ ],
&[ $(($qname, $qval as i64)),+ ],
)
};
}
pub mod _re_export {
pub use moonpool_explorer::{AssertCmp, AssertKind};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assertion_stats_new() {
let stats = AssertionStats::new();
assert_eq!(stats.total_checks, 0);
assert_eq!(stats.successes, 0);
assert_eq!(stats.success_rate(), 0.0);
}
#[test]
fn test_assertion_stats_record() {
let mut stats = AssertionStats::new();
stats.record(true);
assert_eq!(stats.total_checks, 1);
assert_eq!(stats.successes, 1);
assert_eq!(stats.success_rate(), 100.0);
stats.record(false);
assert_eq!(stats.total_checks, 2);
assert_eq!(stats.successes, 1);
assert_eq!(stats.success_rate(), 50.0);
stats.record(true);
assert_eq!(stats.total_checks, 3);
assert_eq!(stats.successes, 2);
let expected = 200.0 / 3.0;
assert!((stats.success_rate() - expected).abs() < 1e-10);
}
#[test]
fn test_assertion_stats_success_rate_edge_cases() {
let mut stats = AssertionStats::new();
assert_eq!(stats.success_rate(), 0.0);
stats.record(false);
assert_eq!(stats.success_rate(), 0.0);
stats.record(true);
assert_eq!(stats.success_rate(), 50.0);
}
#[test]
fn test_assertion_results_empty() {
let results = assertion_results();
let _ = results;
}
#[test]
fn test_validate_contracts_empty() {
let violations = validate_assertion_contracts();
let _ = violations;
}
}