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