1use std::sync::atomic::AtomicU64;
2#[cfg(feature = "full")]
3use std::{collections::HashMap, sync::{atomic::Ordering, Arc, Mutex}};
4#[cfg(feature = "full")]
5use crate::internal;
6#[cfg(feature = "full")]
7use linkme::distributed_slice;
8#[cfg(feature = "full")]
9use once_cell::sync::Lazy;
10use serde::Serialize;
11use serde_json::Value;
12#[cfg(feature = "full")]
13use serde_json::json;
14
15mod macros;
16#[doc(hidden)]
17#[cfg(feature = "full")]
18pub mod guidance;
19
20#[doc(hidden)]
22#[distributed_slice]
23#[cfg(feature = "full")]
24pub static ANTITHESIS_CATALOG: [AssertionCatalogInfo];
25
26#[doc(hidden)]
28#[distributed_slice]
29#[cfg(feature = "full")]
30pub static ANTITHESIS_GUIDANCE_CATALOG: [self::guidance::GuidanceCatalogInfo];
31
32#[cfg(feature = "full")]
33pub(crate) static INIT_CATALOG: Lazy<()> = Lazy::new(|| {
34 for info in ANTITHESIS_CATALOG.iter() {
35 let f_name: &str = info.function.as_ref();
36 assert_impl(
37 info.assert_type,
38 info.display_type,
39 info.condition,
40 info.message,
41 info.class,
42 f_name,
43 info.file,
44 info.begin_line,
45 info.begin_column,
46 false, info.must_hit,
48 info.id,
49 &json!(null),
50 None,
51 );
52 }
53 for info in ANTITHESIS_GUIDANCE_CATALOG.iter() {
54 guidance::guidance_impl(
55 info.guidance_type,
56 info.message,
57 info.id,
58 info.class,
59 #[allow(clippy::explicit_auto_deref)]
60 *Lazy::force(info.function),
61 info.file,
62 info.begin_line,
63 info.begin_column,
64 info.maximize,
65 json!(null),
66 false,
67 )
68 }
69});
70
71pub struct TrackingInfo {
72 pub pass_count: AtomicU64,
73 pub fail_count: AtomicU64,
74}
75
76impl Default for TrackingInfo {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl TrackingInfo {
83 pub const fn new() -> Self {
84 TrackingInfo {
85 pass_count: AtomicU64::new(0),
86 fail_count: AtomicU64::new(0),
87 }
88 }
89}
90
91#[derive(Copy, Clone, PartialEq, Debug, Serialize)]
92#[serde(rename_all(serialize = "lowercase"))]
93pub enum AssertType {
94 Always,
95 Sometimes,
96 Reachability,
97}
98
99#[derive(Serialize, Debug)]
100struct AntithesisLocationInfo<'a> {
101 class: &'a str,
102 function: &'a str,
103 file: &'a str,
104 begin_line: u32,
105 begin_column: u32,
106}
107
108#[doc(hidden)]
110#[derive(Debug)]
111#[cfg(feature = "full")]
112pub struct AssertionCatalogInfo {
113 pub assert_type: AssertType,
114 pub display_type: &'static str,
115 pub condition: bool,
116 pub message: &'static str,
117 pub class: &'static str,
118 pub function: &'static Lazy<&'static str>,
119 pub file: &'static str,
120 pub begin_line: u32,
121 pub begin_column: u32,
122 pub must_hit: bool,
123 pub id: &'static str,
124}
125
126#[derive(Serialize, Debug)]
127struct AssertionInfo<'a, S: Serialize> {
128 assert_type: AssertType,
129 display_type: &'a str,
130 condition: bool,
131 message: &'a str,
132 location: AntithesisLocationInfo<'a>,
133 hit: bool,
134 must_hit: bool,
135 id: &'a str,
136 details: &'a S,
137}
138
139impl<'a, S: Serialize> AssertionInfo<'a, S> {
140 #[allow(clippy::too_many_arguments)]
141 pub fn new(
142 assert_type: AssertType,
143 display_type: &'a str,
144 condition: bool,
145 message: &'a str,
146 class: &'a str,
147 function: &'a str,
148 file: &'a str,
149 begin_line: u32,
150 begin_column: u32,
151 hit: bool,
152 must_hit: bool,
153 id: &'a str,
154 details: &'a S,
155 ) -> Self {
156 let location = AntithesisLocationInfo {
157 class,
158 function,
159 file,
160 begin_line,
161 begin_column,
162 };
163
164 AssertionInfo {
165 assert_type,
166 display_type,
167 condition,
168 message,
169 location,
170 hit,
171 must_hit,
172 id,
173 details
174 }
175 }
176}
177
178#[cfg(feature = "full")]
179impl<S: Serialize> AssertionInfo<'_, S> {
180 fn track_entry(&self, info: Option<&TrackingInfo>) {
196 if !self.hit {
198 self.emit();
199 return;
200 }
201
202 let emitting = match (info, self.condition) {
205 (None, _) => true,
206 (Some(info), true) => {
207 let prior_value = info.pass_count.fetch_add(1, Ordering::SeqCst);
208 prior_value == 0
209 }
210 (Some(info), false) => {
211 let prior_value = info.fail_count.fetch_add(1, Ordering::SeqCst);
212 prior_value == 0
213 }
214 };
215 if emitting {
216 Lazy::force(&INIT_CATALOG);
217 self.emit();
218 }
219 }
220
221 fn emit(&self) {
222 let json_event = json!({ "antithesis_assert": &self });
223 internal::dispatch_output(&json_event)
224 }
225}
226
227#[cfg(not(feature = "full"))]
228impl<S: Serialize> AssertionInfo<'_, S> {
229 fn track_entry(&self, _info: Option<&TrackingInfo>) {
230 return
231 }
232}
233
234
235#[allow(clippy::too_many_arguments)]
346#[cfg(feature = "full")]
347pub fn assert_raw(
348 condition: bool,
349 message: String,
350 details: &Value,
351 class: String,
352 function: String,
353 file: String,
354 begin_line: u32,
355 begin_column: u32,
356 hit: bool,
357 must_hit: bool,
358 assert_type: AssertType,
359 display_type: String,
360 id: String,
361) {
362 static ASSERT_TRACKER: Lazy<Mutex<HashMap<String, Arc<TrackingInfo>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
363
364 let info = {
366 let mut tracker = ASSERT_TRACKER.lock().unwrap();
367 if !tracker.contains_key(&id) {
368 tracker.insert(id.clone(), Arc::new(TrackingInfo::default()));
369 }
370 tracker.get(&id).unwrap().clone()
371 };
372
373 assert_impl(
374 assert_type,
375 display_type.as_str(),
376 condition,
377 message.as_str(),
378 class.as_str(),
379 function.as_str(),
380 file.as_str(),
381 begin_line,
382 begin_column,
383 hit,
384 must_hit,
385 id.as_str(),
386 details,
387 Some(&*info),
388 )
389}
390
391#[allow(clippy::too_many_arguments)]
392#[cfg(not(feature = "full"))]
393pub fn assert_raw(
394 condition: bool,
395 message: String,
396 details: &Value,
397 class: String,
398 function: String,
399 file: String,
400 begin_line: u32,
401 begin_column: u32,
402 hit: bool,
403 must_hit: bool,
404 assert_type: AssertType,
405 display_type: String,
406 id: String,
407) {
408 assert_impl(
409 assert_type,
410 display_type.as_str(),
411 condition,
412 message.as_str(),
413 class.as_str(),
414 function.as_str(),
415 file.as_str(),
416 begin_line,
417 begin_column,
418 hit,
419 must_hit,
420 id.as_str(),
421 details,
422 None,
423 )
424}
425
426#[doc(hidden)]
427#[allow(clippy::too_many_arguments)]
428pub fn assert_impl<'a, S: Serialize>(
429 assert_type: AssertType,
430 display_type: &'a str,
431 condition: bool,
432 message: &'a str,
433 class: &'a str,
434 function: &'a str,
435 file: &'a str,
436 begin_line: u32,
437 begin_column: u32,
438 hit: bool,
439 must_hit: bool,
440 id: &'a str,
441 details: &S,
442 info: Option<&TrackingInfo>,
443) {
444 let assertion = AssertionInfo::new(
445 assert_type,
446 display_type,
447 condition,
448 message,
449 class,
450 function,
451 file,
452 begin_line,
453 begin_column,
454 hit,
455 must_hit,
456 id,
457 details,
458 );
459
460 let _ = &assertion.track_entry(info);
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
471 fn new_tracking_info() {
472 let ti = TrackingInfo::new();
473 assert_eq!(ti.pass_count.load(Ordering::SeqCst), 0);
474 assert_eq!(ti.fail_count.load(Ordering::SeqCst), 0);
475 }
476
477 #[test]
478 fn default_tracking_info() {
479 let ti: TrackingInfo = Default::default();
480 assert_eq!(ti.pass_count.load(Ordering::SeqCst), 0);
481 assert_eq!(ti.fail_count.load(Ordering::SeqCst), 0);
482 }
483
484 #[test]
489 fn new_assertion_info_always() {
490 let this_assert_type = AssertType::Always;
491 let this_display_type = "Always";
492 let this_condition = true;
493 let this_message = "Always message";
494 let this_class = "binary::always";
495 let this_function = "binary::always::always_function";
496 let this_file = "/home/user/binary/src/always_binary.rs";
497 let this_begin_line = 10;
498 let this_begin_column = 5;
499 let this_hit = true;
500 let this_must_hit = true;
501 let this_id = "ID Always message";
502 let this_details = json!({
503 "color": "always red",
504 "extent": 15,
505 });
506
507 let ai = AssertionInfo::new(
508 this_assert_type,
509 this_display_type,
510 this_condition,
511 this_message,
512 this_class,
513 this_function,
514 this_file,
515 this_begin_line,
516 this_begin_column,
517 this_hit,
518 this_must_hit,
519 this_id,
520 &this_details,
521 );
522 assert_eq!(ai.display_type, this_display_type);
523 assert_eq!(ai.condition, this_condition);
524 assert_eq!(ai.message, this_message);
525 assert_eq!(ai.location.class, this_class);
526 assert_eq!(ai.location.function, this_function);
527 assert_eq!(ai.location.file, this_file);
528 assert_eq!(ai.location.begin_line, this_begin_line);
529 assert_eq!(ai.location.begin_column, this_begin_column);
530 assert_eq!(ai.hit, this_hit);
531 assert_eq!(ai.must_hit, this_must_hit);
532 assert_eq!(ai.id, this_id);
533 assert_eq!(ai.details, &this_details);
534 }
535
536 #[test]
537 fn new_assertion_info_sometimes() {
538 let this_assert_type = AssertType::Sometimes;
539 let this_display_type = "Sometimes";
540 let this_condition = true;
541 let this_message = "Sometimes message";
542 let this_class = "binary::sometimes";
543 let this_function = "binary::sometimes::sometimes_function";
544 let this_file = "/home/user/binary/src/sometimes_binary.rs";
545 let this_begin_line = 11;
546 let this_begin_column = 6;
547 let this_hit = true;
548 let this_must_hit = true;
549 let this_id = "ID Sometimes message";
550 let this_details = json!({
551 "color": "sometimes red",
552 "extent": 17,
553 });
554
555 let ai = AssertionInfo::new(
556 this_assert_type,
557 this_display_type,
558 this_condition,
559 this_message,
560 this_class,
561 this_function,
562 this_file,
563 this_begin_line,
564 this_begin_column,
565 this_hit,
566 this_must_hit,
567 this_id,
568 &this_details,
569 );
570 assert_eq!(ai.display_type, this_display_type);
571 assert_eq!(ai.condition, this_condition);
572 assert_eq!(ai.message, this_message);
573 assert_eq!(ai.location.class, this_class);
574 assert_eq!(ai.location.function, this_function);
575 assert_eq!(ai.location.file, this_file);
576 assert_eq!(ai.location.begin_line, this_begin_line);
577 assert_eq!(ai.location.begin_column, this_begin_column);
578 assert_eq!(ai.hit, this_hit);
579 assert_eq!(ai.must_hit, this_must_hit);
580 assert_eq!(ai.id, this_id);
581 assert_eq!(ai.details, &this_details);
582 }
583
584 #[test]
585 fn new_assertion_info_reachable() {
586 let this_assert_type = AssertType::Reachability;
587 let this_display_type = "Reachable";
588 let this_condition = true;
589 let this_message = "Reachable message";
590 let this_class = "binary::reachable";
591 let this_function = "binary::reachable::reachable_function";
592 let this_file = "/home/user/binary/src/reachable_binary.rs";
593 let this_begin_line = 12;
594 let this_begin_column = 7;
595 let this_hit = true;
596 let this_must_hit = true;
597 let this_id = "ID Reachable message";
598 let this_details = json!({
599 "color": "reachable red",
600 "extent": 19,
601 });
602
603 let ai = AssertionInfo::new(
604 this_assert_type,
605 this_display_type,
606 this_condition,
607 this_message,
608 this_class,
609 this_function,
610 this_file,
611 this_begin_line,
612 this_begin_column,
613 this_hit,
614 this_must_hit,
615 this_id,
616 &this_details,
617 );
618 assert_eq!(ai.display_type, this_display_type);
619 assert_eq!(ai.condition, this_condition);
620 assert_eq!(ai.message, this_message);
621 assert_eq!(ai.location.class, this_class);
622 assert_eq!(ai.location.function, this_function);
623 assert_eq!(ai.location.file, this_file);
624 assert_eq!(ai.location.begin_line, this_begin_line);
625 assert_eq!(ai.location.begin_column, this_begin_column);
626 assert_eq!(ai.hit, this_hit);
627 assert_eq!(ai.must_hit, this_must_hit);
628 assert_eq!(ai.id, this_id);
629 assert_eq!(ai.details, &this_details);
630 }
631
632 #[test]
633 fn assert_impl_pass() {
634 let this_assert_type = AssertType::Always;
635 let this_display_type = "Always";
636 let this_condition = true;
637 let this_message = "Always message 2";
638 let this_class = "binary::always";
639 let this_function = "binary::always::always_function";
640 let this_file = "/home/user/binary/src/always_binary.rs";
641 let this_begin_line = 10;
642 let this_begin_column = 5;
643 let this_hit = true;
644 let this_must_hit = true;
645 let this_id = "ID Always message 2";
646 let this_details = json!({
647 "color": "always red",
648 "extent": 15,
649 });
650
651 let tracker = TrackingInfo::new();
652
653 let before_tracker = clone_tracker(&tracker);
654
655 assert_impl(
656 this_assert_type,
657 this_display_type,
658 this_condition,
659 this_message,
660 this_class,
661 this_function,
662 this_file,
663 this_begin_line,
664 this_begin_column,
665 this_hit,
666 this_must_hit,
667 this_id,
668 &this_details,
669 Some(&tracker),
670 );
671
672 let after_tracker: TrackingInfo = clone_tracker(&tracker);
673
674 if this_condition {
675 assert_eq!(before_tracker.pass_count.load(Ordering::SeqCst) + 1, after_tracker.pass_count.load(Ordering::SeqCst));
676 assert_eq!(before_tracker.fail_count.load(Ordering::SeqCst), after_tracker.fail_count.load(Ordering::SeqCst));
677 } else {
678 assert_eq!(before_tracker.fail_count.load(Ordering::SeqCst) + 1, after_tracker.fail_count.load(Ordering::SeqCst));
679 assert_eq!(before_tracker.pass_count.load(Ordering::SeqCst), after_tracker.pass_count.load(Ordering::SeqCst));
680 };
681 }
682
683 #[test]
684 fn assert_impl_fail() {
685 let this_assert_type = AssertType::Always;
686 let this_display_type = "Always";
687 let this_condition = false;
688 let this_message = "Always message 3";
689 let this_class = "binary::always";
690 let this_function = "binary::always::always_function";
691 let this_file = "/home/user/binary/src/always_binary.rs";
692 let this_begin_line = 10;
693 let this_begin_column = 5;
694 let this_hit = true;
695 let this_must_hit = true;
696 let this_id = "ID Always message 3";
697 let this_details = json!({
698 "color": "always red",
699 "extent": 15,
700 });
701
702 let tracker = TrackingInfo::new();
703
704 let before_tracker = clone_tracker(&tracker);
705
706 assert_impl(
707 this_assert_type,
708 this_display_type,
709 this_condition,
710 this_message,
711 this_class,
712 this_function,
713 this_file,
714 this_begin_line,
715 this_begin_column,
716 this_hit,
717 this_must_hit,
718 this_id,
719 &this_details,
720 Some(&tracker),
721 );
722
723 let after_tracker: TrackingInfo = clone_tracker(&tracker);
724
725 if this_condition {
726 assert_eq!(before_tracker.pass_count.load(Ordering::SeqCst) + 1, after_tracker.pass_count.load(Ordering::SeqCst));
727 assert_eq!(before_tracker.fail_count.load(Ordering::SeqCst), after_tracker.fail_count.load(Ordering::SeqCst));
728 } else {
729 assert_eq!(before_tracker.fail_count.load(Ordering::SeqCst) + 1, after_tracker.fail_count.load(Ordering::SeqCst));
730 assert_eq!(before_tracker.pass_count.load(Ordering::SeqCst), after_tracker.pass_count.load(Ordering::SeqCst));
731 };
732 }
733
734 fn clone_tracker(old: &TrackingInfo) -> TrackingInfo {
735 let tracking_data = TrackingInfo::new();
736 tracking_data.pass_count.store(old.pass_count.load(Ordering::SeqCst), Ordering::SeqCst);
737 tracking_data.fail_count.store(old.fail_count.load(Ordering::SeqCst), Ordering::SeqCst);
738 tracking_data
739
740 }
741}