hegel/
explicit_test_case.rs1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::fmt::Debug;
5
6use crate::generators::Generator;
7
8struct ExplicitValue {
9 source_expr: String,
10 value: Option<Box<dyn Any>>,
11 debug_repr: String,
12}
13
14pub struct ExplicitTestCase {
29 values: RefCell<HashMap<String, ExplicitValue>>,
30 notes: RefCell<Vec<String>>,
31}
32
33impl ExplicitTestCase {
34 #[doc(hidden)]
35 pub fn new() -> Self {
36 ExplicitTestCase {
37 values: RefCell::new(HashMap::new()),
38 notes: RefCell::new(Vec::new()),
39 }
40 }
41
42 #[doc(hidden)]
43 pub fn with_value<T: Any + Debug>(self, name: &str, source_expr: &str, value: T) -> Self {
44 let debug_repr = format!("{:?}", value);
45 self.values.borrow_mut().insert(
46 name.to_string(),
47 ExplicitValue {
48 source_expr: source_expr.to_string(),
49 value: Some(Box::new(value)),
50 debug_repr,
51 },
52 );
53 self
54 }
55
56 pub fn draw<T: Debug + 'static>(&self, generator: impl Generator<T>) -> T {
57 self.__draw_named(generator, "draw", true)
58 }
59
60 pub fn __draw_named<T: Debug + 'static>(
61 &self,
62 _generator: impl Generator<T>,
63 name: &str,
64 _repeatable: bool,
65 ) -> T {
66 let mut values = self.values.borrow_mut();
67 let entry = match values.get_mut(name) {
68 Some(e) => e,
69 None => {
70 let available: Vec<_> = values.keys().cloned().collect();
71 panic!(
72 "Explicit test case: no value provided for {:?}. Available: {:?}",
73 name, available
74 );
75 }
76 };
77
78 let boxed = match entry.value.take() {
79 Some(v) => v,
80 None => {
81 panic!(
82 "Explicit test case: value {:?} was already consumed by a previous draw",
83 name
84 );
85 }
86 };
87
88 let source = &entry.source_expr;
89 let debug = &entry.debug_repr;
90
91 let source_normalized: String = source.chars().filter(|c| !c.is_whitespace()).collect();
92 let debug_normalized: String = debug.chars().filter(|c| !c.is_whitespace()).collect();
93
94 if source_normalized == debug_normalized {
95 crate::test_case::emit_verbose_line(&format!("let {} = {};", name, source));
96 } else {
97 crate::test_case::emit_verbose_line(&format!(
98 "let {} = {}; // = {}",
99 name, source, debug
100 ));
101 }
102
103 match boxed.downcast::<T>() {
104 Ok(typed) => *typed,
105 Err(_) => panic!(
106 "Explicit test case: type mismatch for {:?}. \
107 The value provided in #[hegel::explicit_test_case] \
108 does not match the type expected by draw.",
109 name
110 ),
111 }
112 }
113
114 pub fn draw_silent<T>(&self, _generator: impl Generator<T>) -> T {
115 panic!("draw_silent is not supported in explicit test cases");
116 }
117
118 pub fn note(&self, message: &str) {
119 self.notes.borrow_mut().push(message.to_string());
120 }
121
122 pub fn assume(&self, condition: bool) {
123 if !condition {
124 self.reject();
125 }
126 }
127
128 pub fn reject(&self) -> ! {
129 panic!("Explicit test case: assumption violated (tc.assume / tc.reject)");
130 }
131
132 pub fn target(&self, _score: f64) {}
133
134 pub fn target_labelled(&self, _score: f64, _label: impl Into<String>) {}
135
136 #[doc(hidden)]
137 pub fn start_span(&self, _label: u64) {
138 panic!("start_span is not supported in explicit test cases");
139 }
140
141 #[doc(hidden)]
142 pub fn stop_span(&self, _discard: bool) {
143 panic!("stop_span is not supported in explicit test cases");
144 }
145
146 #[doc(hidden)]
147 pub fn run<F: FnOnce(&ExplicitTestCase)>(&self, f: F) {
148 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
149 f(self);
150 }));
151
152 match result {
153 Ok(()) => {
154 let values = self.values.borrow();
155 let unused: Vec<_> = values
156 .iter()
157 .filter(|(_, v)| v.value.is_some())
158 .map(|(k, _)| k.clone())
159 .collect();
160 if !unused.is_empty() {
161 panic!(
162 "Explicit test case: the following values were provided \
163 but never drawn: {:?}",
164 unused
165 );
166 }
167 }
168 Err(payload) => {
169 let notes = self.notes.borrow();
170 for note in notes.iter() {
171 eprintln!("{}", note);
172 }
173 std::panic::resume_unwind(payload);
174 }
175 }
176 }
177}