use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use crate::generators::Generator;
use crate::test_case::ASSUME_FAIL_STRING;
struct ExplicitValue {
source_expr: String,
value: Option<Box<dyn Any>>,
debug_repr: String,
}
pub struct ExplicitTestCase {
values: RefCell<HashMap<String, ExplicitValue>>,
notes: RefCell<Vec<String>>,
}
impl ExplicitTestCase {
#[doc(hidden)]
pub fn new() -> Self {
ExplicitTestCase {
values: RefCell::new(HashMap::new()),
notes: RefCell::new(Vec::new()),
}
}
#[doc(hidden)]
pub fn with_value<T: Any + Debug>(self, name: &str, source_expr: &str, value: T) -> Self {
let debug_repr = format!("{:?}", value);
self.values.borrow_mut().insert(
name.to_string(),
ExplicitValue {
source_expr: source_expr.to_string(),
value: Some(Box::new(value)),
debug_repr,
},
);
self
}
pub fn draw<T: Debug + 'static>(&self, generator: impl Generator<T>) -> T {
self.__draw_named(generator, "draw", true)
}
pub fn __draw_named<T: Debug + 'static>(
&self,
_generator: impl Generator<T>,
name: &str,
_repeatable: bool,
) -> T {
let mut values = self.values.borrow_mut();
let entry = match values.get_mut(name) {
Some(e) => e,
None => {
let available: Vec<_> = values.keys().cloned().collect();
panic!(
"Explicit test case: no value provided for {:?}. Available: {:?}",
name, available
);
}
};
let boxed = match entry.value.take() {
Some(v) => v,
None => {
panic!(
"Explicit test case: value {:?} was already consumed by a previous draw",
name
);
}
};
let source = &entry.source_expr;
let debug = &entry.debug_repr;
let source_normalized: String = source.chars().filter(|c| !c.is_whitespace()).collect();
let debug_normalized: String = debug.chars().filter(|c| !c.is_whitespace()).collect();
if source_normalized == debug_normalized {
eprintln!("let {} = {};", name, source);
} else {
eprintln!("let {} = {}; // = {}", name, source, debug);
}
match boxed.downcast::<T>() {
Ok(typed) => *typed,
Err(_) => panic!(
"Explicit test case: type mismatch for {:?}. \
The value provided in #[hegel::explicit_test_case] \
does not match the type expected by draw.",
name
),
}
}
pub fn draw_silent<T>(&self, _generator: impl Generator<T>) -> T {
panic!("draw_silent is not supported in explicit test cases");
}
pub fn note(&self, message: &str) {
self.notes.borrow_mut().push(message.to_string());
}
pub fn assume(&self, condition: bool) {
if !condition {
self.reject();
}
}
pub fn reject(&self) -> ! {
panic!("{}", ASSUME_FAIL_STRING);
}
#[doc(hidden)]
pub fn start_span(&self, _label: u64) {
panic!("start_span is not supported in explicit test cases");
}
#[doc(hidden)]
pub fn stop_span(&self, _discard: bool) {
panic!("stop_span is not supported in explicit test cases");
}
#[doc(hidden)]
pub fn run<F: FnOnce(&ExplicitTestCase)>(&self, f: F) {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
f(self);
}));
match result {
Ok(()) => {
let values = self.values.borrow();
let unused: Vec<_> = values
.iter()
.filter(|(_, v)| v.value.is_some())
.map(|(k, _)| k.clone())
.collect();
if !unused.is_empty() {
panic!(
"Explicit test case: the following values were provided \
but never drawn: {:?}",
unused
);
}
}
Err(payload) => {
let notes = self.notes.borrow();
for note in notes.iter() {
eprintln!("{}", note);
}
std::panic::resume_unwind(payload);
}
}
}
}