use block::{Block, Example};
use header::{ContextHeader, ContextLabel, ExampleHeader, ExampleLabel};
use report::ExampleResult;
pub struct Context<T> {
pub(crate) header: Option<ContextHeader>,
pub(crate) blocks: Vec<Block<T>>,
pub(crate) before_all: Vec<Box<dyn Fn(&mut T)>>,
pub(crate) before_each: Vec<Box<dyn Fn(&mut T)>>,
pub(crate) after_all: Vec<Box<dyn Fn(&mut T)>>,
pub(crate) after_each: Vec<Box<dyn Fn(&mut T)>>,
}
impl<T> Context<T> {
pub(crate) fn new(header: Option<ContextHeader>) -> Self {
Context {
header,
blocks: vec![],
before_all: vec![],
before_each: vec![],
after_all: vec![],
after_each: vec![],
}
}
pub fn num_blocks(&self) -> usize {
self.blocks.len()
}
pub fn num_examples(&self) -> usize {
self.blocks.iter().map(|b| b.num_examples()).sum()
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
}
unsafe impl<T> Send for Context<T> where T: Send {}
unsafe impl<T> Sync for Context<T> where T: Sync {}
impl<T> Context<T>
where
T: Clone,
{
pub fn context<F>(&mut self, name: &'static str, body: F)
where
F: FnOnce(&mut Context<T>),
T: ::std::fmt::Debug,
{
let header = ContextHeader {
label: ContextLabel::Context,
name,
};
self.context_internal(Some(header), body)
}
pub fn specify<F>(&mut self, name: &'static str, body: F)
where
F: FnOnce(&mut Context<T>),
T: ::std::fmt::Debug,
{
let header = ContextHeader {
label: ContextLabel::Specify,
name,
};
self.context_internal(Some(header), body)
}
pub fn when<F>(&mut self, name: &'static str, body: F)
where
F: FnOnce(&mut Context<T>),
T: ::std::fmt::Debug,
{
let header = ContextHeader {
label: ContextLabel::When,
name,
};
self.context_internal(Some(header), body)
}
pub fn scope<F>(&mut self, body: F)
where
F: FnOnce(&mut Context<T>),
T: ::std::fmt::Debug,
{
self.context_internal(None, body)
}
fn context_internal<F>(&mut self, header: Option<ContextHeader>, body: F)
where
F: FnOnce(&mut Context<T>),
T: ::std::fmt::Debug,
{
let mut child = Context::new(header);
body(&mut child);
self.blocks.push(Block::Context(child))
}
pub fn example<F, U>(&mut self, name: &'static str, body: F)
where
F: 'static + Fn(&T) -> U,
U: Into<ExampleResult>,
{
let header = ExampleHeader::new(ExampleLabel::Example, name);
self.example_internal(header, body)
}
pub fn it<F, U>(&mut self, name: &'static str, body: F)
where
F: 'static + Fn(&T) -> U,
U: Into<ExampleResult>,
{
let header = ExampleHeader::new(ExampleLabel::It, name);
self.example_internal(header, body)
}
pub fn then<F, U>(&mut self, name: &'static str, body: F)
where
F: 'static + Fn(&T) -> U,
U: Into<ExampleResult>,
{
let header = ExampleHeader::new(ExampleLabel::Then, name);
self.example_internal(header, body)
}
fn example_internal<F, U>(&mut self, header: ExampleHeader, body: F)
where
F: 'static + Fn(&T) -> U,
U: Into<ExampleResult>,
{
use std::panic::{catch_unwind, AssertUnwindSafe};
let example = Example::new(header, move |environment| {
let result = catch_unwind(AssertUnwindSafe(|| body(&environment).into()));
match result {
Ok(result) => result,
Err(error) => {
use std::borrow::Cow;
let error_as_str = error.downcast_ref::<&str>().map(|s| Cow::from(*s));
let error_as_string =
error.downcast_ref::<String>().map(|s| Cow::from(s.clone()));
let message = error_as_str
.or(error_as_string)
.map(|cow| format!("thread panicked at '{:?}'.", cow.to_string()));
ExampleResult::Failure(message)
}
}
});
self.blocks.push(Block::Example(example))
}
pub fn before_all<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.before_all.push(Box::new(body))
}
pub fn before<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.before_all(body)
}
pub fn before_each<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.before_each.push(Box::new(body))
}
pub fn after_all<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.after_all.push(Box::new(body))
}
pub fn after<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.after_all(body)
}
pub fn after_each<F>(&mut self, body: F)
where
F: 'static + Fn(&mut T),
{
self.after_each.push(Box::new(body))
}
}
#[cfg(test)]
impl<T> Default for Context<T> {
fn default() -> Self {
Context::new(None)
}
}
#[cfg(test)]
mod tests {
use block::{describe, given, suite};
macro_rules! test_suite_alias {
($suite: ident) => {
$suite("suite (or alias)", (), |_| {});
};
}
#[test]
fn it_has_root_functions() {
test_suite_alias!(suite);
test_suite_alias!(describe);
test_suite_alias!(given);
}
macro_rules! test_context_alias {
($suite: ident, $context: ident) => {
$suite("suite (or alias)", (), |ctx| {
ctx.$context("context (or alias)", |_| {})
});
};
}
#[test]
fn it_has_contextual_function_context() {
test_context_alias!(suite, context);
test_context_alias!(describe, context);
test_context_alias!(given, context);
}
#[test]
fn it_has_contexual_function_specify() {
test_context_alias!(suite, specify);
test_context_alias!(describe, specify);
test_context_alias!(given, specify);
}
#[test]
fn it_has_contexual_function_when() {
test_context_alias!(suite, when);
test_context_alias!(describe, when);
test_context_alias!(given, when);
}
macro_rules! test_example_alias {
($suite: ident, $context: ident, $example: ident) => {
$suite("suite (or alias)", (), |ctx| {
ctx.$context("context (or alias)", |ctx| {
ctx.$example("example (or alias)", |_| {});
});
});
};
}
#[test]
fn it_has_check_function_example() {
test_example_alias!(suite, context, example);
test_example_alias!(suite, specify, example);
test_example_alias!(suite, when, example);
test_example_alias!(describe, context, example);
test_example_alias!(describe, specify, example);
test_example_alias!(describe, when, example);
test_example_alias!(given, context, example);
test_example_alias!(given, specify, example);
test_example_alias!(given, when, example);
}
#[test]
fn it_has_check_function_it() {
test_example_alias!(suite, context, it);
test_example_alias!(suite, specify, it);
test_example_alias!(suite, when, it);
test_example_alias!(describe, context, it);
test_example_alias!(describe, specify, it);
test_example_alias!(describe, when, it);
test_example_alias!(given, context, it);
test_example_alias!(given, specify, it);
test_example_alias!(given, when, it);
}
#[test]
fn it_has_check_function_then() {
test_example_alias!(suite, context, then);
test_example_alias!(suite, specify, then);
test_example_alias!(suite, when, then);
test_example_alias!(describe, context, then);
test_example_alias!(describe, specify, then);
test_example_alias!(describe, when, then);
test_example_alias!(given, context, then);
test_example_alias!(given, specify, then);
test_example_alias!(given, when, then);
}
}