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 #[doc(hidden)]
133 pub fn start_span(&self, _label: u64) {
134 panic!("start_span is not supported in explicit test cases");
135 }
136
137 #[doc(hidden)]
138 pub fn stop_span(&self, _discard: bool) {
139 panic!("stop_span is not supported in explicit test cases");
140 }
141
142 #[doc(hidden)]
143 pub fn run<F: FnOnce(&ExplicitTestCase)>(&self, f: F) {
144 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
145 f(self);
146 }));
147
148 match result {
149 Ok(()) => {
150 let values = self.values.borrow();
151 let unused: Vec<_> = values
152 .iter()
153 .filter(|(_, v)| v.value.is_some())
154 .map(|(k, _)| k.clone())
155 .collect();
156 if !unused.is_empty() {
157 panic!(
158 "Explicit test case: the following values were provided \
159 but never drawn: {:?}",
160 unused
161 );
162 }
163 }
164 Err(payload) => {
165 let notes = self.notes.borrow();
166 for note in notes.iter() {
167 eprintln!("{}", note);
168 }
169 std::panic::resume_unwind(payload);
170 }
171 }
172 }
173}