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;
7use crate::test_case::ASSUME_FAIL_STRING;
8
9struct ExplicitValue {
10 source_expr: String,
11 value: Option<Box<dyn Any>>,
12 debug_repr: String,
13}
14
15pub struct ExplicitTestCase {
30 values: RefCell<HashMap<String, ExplicitValue>>,
31 notes: RefCell<Vec<String>>,
32}
33
34impl ExplicitTestCase {
35 #[doc(hidden)]
36 pub fn new() -> Self {
37 ExplicitTestCase {
38 values: RefCell::new(HashMap::new()),
39 notes: RefCell::new(Vec::new()),
40 }
41 }
42
43 #[doc(hidden)]
44 pub fn with_value<T: Any + Debug>(self, name: &str, source_expr: &str, value: T) -> Self {
45 let debug_repr = format!("{:?}", value);
46 self.values.borrow_mut().insert(
47 name.to_string(),
48 ExplicitValue {
49 source_expr: source_expr.to_string(),
50 value: Some(Box::new(value)),
51 debug_repr,
52 },
53 );
54 self
55 }
56
57 pub fn draw<T: Debug + 'static>(&self, generator: impl Generator<T>) -> T {
58 self.__draw_named(generator, "draw", true)
59 }
60
61 pub fn __draw_named<T: Debug + 'static>(
62 &self,
63 _generator: impl Generator<T>,
64 name: &str,
65 _repeatable: bool,
66 ) -> T {
67 let mut values = self.values.borrow_mut();
68 let entry = match values.get_mut(name) {
69 Some(e) => e,
70 None => {
71 let available: Vec<_> = values.keys().cloned().collect();
72 panic!(
73 "Explicit test case: no value provided for {:?}. Available: {:?}",
74 name, available
75 );
76 }
77 };
78
79 let boxed = match entry.value.take() {
80 Some(v) => v,
81 None => {
82 panic!(
83 "Explicit test case: value {:?} was already consumed by a previous draw",
84 name
85 );
86 }
87 };
88
89 let source = &entry.source_expr;
90 let debug = &entry.debug_repr;
91
92 let source_normalized: String = source.chars().filter(|c| !c.is_whitespace()).collect();
95 let debug_normalized: String = debug.chars().filter(|c| !c.is_whitespace()).collect();
96
97 if source_normalized == debug_normalized {
98 eprintln!("let {} = {};", name, source);
99 } else {
100 eprintln!("let {} = {}; // = {}", name, source, debug);
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!("{}", ASSUME_FAIL_STRING);
130 }
131
132 pub fn target(&self, _score: f64) {
133 }
136
137 pub fn target_labelled(&self, _score: f64, _label: impl Into<String>) {
138 }
140
141 #[doc(hidden)]
142 pub fn start_span(&self, _label: u64) {
143 panic!("start_span is not supported in explicit test cases");
144 }
145
146 #[doc(hidden)]
147 pub fn stop_span(&self, _discard: bool) {
148 panic!("stop_span is not supported in explicit test cases");
149 }
150
151 #[doc(hidden)]
152 pub fn run<F: FnOnce(&ExplicitTestCase)>(&self, f: F) {
153 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
154 f(self);
155 }));
156
157 match result {
158 Ok(()) => {
159 let values = self.values.borrow();
160 let unused: Vec<_> = values
161 .iter()
162 .filter(|(_, v)| v.value.is_some())
163 .map(|(k, _)| k.clone())
164 .collect();
165 if !unused.is_empty() {
166 panic!(
167 "Explicit test case: the following values were provided \
168 but never drawn: {:?}",
169 unused
170 );
171 }
172 }
173 Err(payload) => {
174 let notes = self.notes.borrow();
175 for note in notes.iter() {
176 eprintln!("{}", note);
177 }
178 std::panic::resume_unwind(payload);
179 }
180 }
181 }
182}