1use std::cell::RefCell;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq)]
16pub struct AssertionStats {
17 pub total_checks: usize,
19 pub successes: usize,
21}
22
23impl AssertionStats {
24 pub fn new() -> Self {
26 Self {
27 total_checks: 0,
28 successes: 0,
29 }
30 }
31
32 pub fn success_rate(&self) -> f64 {
38 if self.total_checks == 0 {
39 0.0
40 } else {
41 (self.successes as f64 / self.total_checks as f64) * 100.0
42 }
43 }
44
45 pub fn record(&mut self, success: bool) {
49 self.total_checks += 1;
50 if success {
51 self.successes += 1;
52 }
53 }
54}
55
56impl Default for AssertionStats {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62thread_local! {
63 static ASSERTION_RESULTS: RefCell<HashMap<String, AssertionStats>> = RefCell::new(HashMap::new());
69}
70
71pub fn record_assertion(name: &str, success: bool) {
81 ASSERTION_RESULTS.with(|results| {
82 let mut results = results.borrow_mut();
83 let stats = results.entry(name.to_string()).or_default();
84 stats.record(success);
85 });
86}
87
88pub fn get_assertion_results() -> HashMap<String, AssertionStats> {
95 ASSERTION_RESULTS.with(|results| results.borrow().clone())
96}
97
98pub fn reset_assertion_results() {
105 ASSERTION_RESULTS.with(|results| {
106 results.borrow_mut().clear();
107 });
108}
109
110pub fn panic_on_assertion_violations(_report: &crate::runner::SimulationReport) {
123 let results = get_assertion_results();
124 let mut violations = Vec::new();
125
126 for (name, stats) in &results {
127 if stats.total_checks > 0 && stats.success_rate() == 0.0 {
128 violations.push(format!(
129 "sometimes_assert!('{}') has 0% success rate (expected at least 1%)",
130 name
131 ));
132 }
133 }
134
135 if !violations.is_empty() {
136 println!("❌ Assertion violations found:");
137 for violation in &violations {
138 println!(" - {}", violation);
139 }
140 panic!("❌ Unexpected assertion violations detected!");
141 } else {
142 println!("✅ All assertions passed basic validation!");
143 }
144}
145
146pub fn validate_assertion_contracts() -> Vec<String> {
157 let mut violations = Vec::new();
158 let results = get_assertion_results();
159
160 for (name, stats) in &results {
161 let rate = stats.success_rate();
162 if stats.total_checks > 0 && rate == 0.0 {
163 violations.push(format!(
164 "sometimes_assert!('{}') has {:.1}% success rate (expected at least 1%)",
165 name, rate
166 ));
167 }
168 }
169
170 violations
171}
172
173#[macro_export]
187macro_rules! always_assert {
188 ($name:ident, $condition:expr, $message:expr) => {
189 let result = $condition;
190 if !result {
191 let current_seed = $crate::sim::get_current_sim_seed();
192 panic!(
193 "Always assertion '{}' failed (seed: {}): {}",
194 stringify!($name),
195 current_seed,
196 $message
197 );
198 }
199 };
200}
201
202#[macro_export]
220macro_rules! sometimes_assert {
221 ($name:ident, $condition:expr, $message:expr) => {
222 let result = $condition;
224 $crate::chaos::assertions::record_assertion(stringify!($name), result);
225 };
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_assertion_stats_new() {
234 let stats = AssertionStats::new();
235 assert_eq!(stats.total_checks, 0);
236 assert_eq!(stats.successes, 0);
237 assert_eq!(stats.success_rate(), 0.0);
238 }
239
240 #[test]
241 fn test_assertion_stats_record() {
242 let mut stats = AssertionStats::new();
243
244 stats.record(true);
245 assert_eq!(stats.total_checks, 1);
246 assert_eq!(stats.successes, 1);
247 assert_eq!(stats.success_rate(), 100.0);
248
249 stats.record(false);
250 assert_eq!(stats.total_checks, 2);
251 assert_eq!(stats.successes, 1);
252 assert_eq!(stats.success_rate(), 50.0);
253
254 stats.record(true);
255 assert_eq!(stats.total_checks, 3);
256 assert_eq!(stats.successes, 2);
257 let expected = 200.0 / 3.0;
258 assert!((stats.success_rate() - expected).abs() < 1e-10);
259 }
260
261 #[test]
262 fn test_assertion_stats_success_rate_edge_cases() {
263 let mut stats = AssertionStats::new();
264 assert_eq!(stats.success_rate(), 0.0);
265
266 stats.record(false);
267 assert_eq!(stats.success_rate(), 0.0);
268
269 stats.record(true);
270 assert_eq!(stats.success_rate(), 50.0);
271 }
272
273 #[test]
274 fn test_record_assertion_and_get_results() {
275 reset_assertion_results();
276
277 record_assertion("test1", true);
278 record_assertion("test1", false);
279 record_assertion("test2", true);
280
281 let results = get_assertion_results();
282 assert_eq!(results.len(), 2);
283
284 let test1_stats = &results["test1"];
285 assert_eq!(test1_stats.total_checks, 2);
286 assert_eq!(test1_stats.successes, 1);
287 assert_eq!(test1_stats.success_rate(), 50.0);
288
289 let test2_stats = &results["test2"];
290 assert_eq!(test2_stats.total_checks, 1);
291 assert_eq!(test2_stats.successes, 1);
292 assert_eq!(test2_stats.success_rate(), 100.0);
293 }
294
295 #[test]
296 fn test_reset_assertion_results() {
297 record_assertion("test", true);
298 assert!(!get_assertion_results().is_empty());
299
300 reset_assertion_results();
301 assert!(get_assertion_results().is_empty());
302 }
303
304 #[test]
305 fn test_always_assert_success() {
306 reset_assertion_results();
307
308 let value = 42;
309 always_assert!(value_is_42, value == 42, "Value should be 42");
310
311 let results = get_assertion_results();
314 assert!(
315 results.is_empty(),
316 "always_assert! should not be tracked when successful"
317 );
318 }
319
320 #[test]
321 #[should_panic(
322 expected = "Always assertion 'impossible' failed (seed: 0): This should never happen"
323 )]
324 fn test_always_assert_failure() {
325 let value = 42;
326 always_assert!(impossible, value == 0, "This should never happen");
327 }
328
329 #[test]
330 fn test_sometimes_assert() {
331 reset_assertion_results();
332
333 let fast_time = 50;
334 let slow_time = 150;
335 let threshold = 100;
336
337 sometimes_assert!(
338 fast_operation,
339 fast_time < threshold,
340 "Operation should be fast"
341 );
342 sometimes_assert!(
343 fast_operation,
344 slow_time < threshold,
345 "Operation should be fast"
346 );
347
348 let results = get_assertion_results();
349 let stats = &results["fast_operation"];
350 assert_eq!(stats.total_checks, 2);
351 assert_eq!(stats.successes, 1);
352 assert_eq!(stats.success_rate(), 50.0);
353 }
354
355 #[test]
356 fn test_assertion_isolation_between_tests() {
357 reset_assertion_results();
359
360 record_assertion("isolation_test", true);
361 let results = get_assertion_results();
362 assert_eq!(results["isolation_test"].total_checks, 1);
363
364 }
366
367 #[test]
368 fn test_multiple_assertions_same_name() {
369 reset_assertion_results();
370
371 sometimes_assert!(reliability, true, "System should be reliable");
372 sometimes_assert!(reliability, false, "System should be reliable");
373 sometimes_assert!(reliability, true, "System should be reliable");
374 sometimes_assert!(reliability, true, "System should be reliable");
375
376 let results = get_assertion_results();
377 let stats = &results["reliability"];
378 assert_eq!(stats.total_checks, 4);
379 assert_eq!(stats.successes, 3);
380 assert_eq!(stats.success_rate(), 75.0);
381 }
382
383 #[test]
384 fn test_complex_assertion_conditions() {
385 reset_assertion_results();
386
387 let items = [1, 2, 3, 4, 5];
388 let sum: i32 = items.iter().sum();
389
390 sometimes_assert!(
391 sum_in_range,
392 (10..=20).contains(&sum),
393 "Sum should be in reasonable range"
394 );
395
396 always_assert!(
397 not_empty,
398 !items.is_empty(),
399 "Items list should not be empty"
400 );
401
402 let results = get_assertion_results();
403 assert_eq!(results.len(), 1, "Only sometimes_assert should be tracked");
405 assert_eq!(results["sum_in_range"].success_rate(), 100.0);
406 assert!(
408 !results.contains_key("not_empty"),
409 "always_assert should not be tracked"
410 );
411 }
412
413 #[test]
414 fn test_sometimes_assert_macro() {
415 reset_assertion_results();
416
417 sometimes_assert!(macro_test, true, "Test assertion");
419 sometimes_assert!(macro_test, false, "Test assertion");
420
421 let results = get_assertion_results();
422 assert!(results.contains_key("macro_test"));
423 assert_eq!(results["macro_test"].total_checks, 2);
424 assert_eq!(results["macro_test"].successes, 1);
425
426 let violations = validate_assertion_contracts();
428 assert!(violations.is_empty());
429 }
430}