#![allow(clippy::wrong_self_convention)]
use std::borrow::Borrow;
use std::cmp::PartialEq;
use std::fmt::Debug;
use colours::{TERM_BOLD, TERM_RED, TERM_RESET};
pub mod boolean;
pub mod hashmap;
pub mod hashset;
pub mod iter;
pub mod numeric;
pub mod option;
pub mod path;
pub mod prelude;
pub mod result;
pub mod string;
pub mod vec;
#[cfg(not(test))]
mod colours {
pub const TERM_RED: &str = "\x1B[31m";
pub const TERM_BOLD: &str = "\x1B[1m";
pub const TERM_RESET: &str = "\x1B[0m";
}
#[cfg(test)]
mod colours {
pub const TERM_RED: &str = "";
pub const TERM_BOLD: &str = "";
pub const TERM_RESET: &str = "";
}
#[cfg(feature = "num")]
extern crate num;
#[macro_export]
macro_rules! assert_that {
(&$subject:tt) => {
assert_that!($subject)
};
($subject:tt) => {{
let line = line!();
let file = file!();
assert_that(&$subject).at_location(format!("{}:{}", file, line))
}};
(&$subject:expr) => {
assert_that!($subject)
};
($subject:expr) => {{
let line = line!();
let file = file!();
assert_that(&$subject).at_location(format!("{}:{}", file, line))
}};
}
#[macro_export]
macro_rules! asserting {
(&$description:tt) => {
asserting!($description)
};
($description:tt) => {{
let line = line!();
let file = file!();
asserting(&$description).at_location(format!("{}:{}", file, line))
}};
}
pub trait DescriptiveSpec<'r> {
fn subject_name(&self) -> Option<&'r str>;
fn location(&self) -> Option<String>;
fn description(&self) -> Option<&'r str>;
}
#[derive(Debug)]
pub struct AssertionFailure<'r, T: 'r> {
spec: &'r T,
expected: Option<String>,
actual: Option<String>,
}
#[derive(Debug)]
pub struct SpecDescription<'r> {
value: &'r str,
location: Option<String>,
}
#[derive(Debug)]
pub struct Spec<'s, S: 's> {
pub subject: &'s S,
pub subject_name: Option<&'s str>,
pub location: Option<String>,
pub description: Option<&'s str>,
}
pub fn assert_that<S>(subject: &S) -> Spec<S> {
Spec {
subject,
subject_name: None,
location: None,
description: None,
}
}
pub fn asserting(description: &str) -> SpecDescription {
SpecDescription {
value: description,
location: None,
}
}
impl<'r> SpecDescription<'r> {
pub fn at_location(self, location: String) -> Self {
let mut description = self;
description.location = Some(location);
description
}
pub fn that<S>(self, subject: &'r S) -> Spec<'r, S> {
Spec {
subject,
subject_name: None,
location: self.location,
description: Some(self.value),
}
}
}
impl<'r, T> DescriptiveSpec<'r> for Spec<'r, T> {
fn subject_name(&self) -> Option<&'r str> {
self.subject_name
}
fn location(&self) -> Option<String> {
self.location.clone()
}
fn description(&self) -> Option<&'r str> {
self.description
}
}
impl<'r, T: DescriptiveSpec<'r>> AssertionFailure<'r, T> {
pub fn from_spec(spec: &'r T) -> AssertionFailure<'r, T> {
AssertionFailure {
spec,
expected: None,
actual: None,
}
}
pub fn with_expected(&mut self, expected: String) -> &mut Self {
let mut assertion = self;
assertion.expected = Some(expected);
assertion
}
pub fn with_actual(&mut self, actual: String) -> &mut Self {
let mut assertion = self;
assertion.actual = Some(actual);
assertion
}
pub fn fail(&mut self) {
assert!(
!(self.expected.is_none() || self.actual.is_none()),
"invalid assertion"
);
let location = self.maybe_build_location();
let subject_name = self.maybe_build_subject_name();
let description = self.maybe_build_description();
panic!(
"{}{}\n\t{}expected: {}\n\t but was: {}{}\n{}",
description,
subject_name,
TERM_RED,
self.expected.clone().unwrap(),
self.actual.clone().unwrap(),
TERM_RESET,
location
)
}
fn fail_with_message(&mut self, message: String) {
let location = self.maybe_build_location();
let subject_name = self.maybe_build_subject_name();
let description = self.maybe_build_description();
panic!(
"{}{}\n\t{}{}{}\n{}",
description, subject_name, TERM_RED, message, TERM_RESET, location
)
}
fn maybe_build_location(&self) -> String {
match self.spec.location() {
Some(value) => format!("\n\t{}at location: {}{}\n", TERM_BOLD, value, TERM_RESET),
None => "".to_string(),
}
}
fn maybe_build_description(&self) -> String {
match self.spec.description() {
Some(value) => format!("\n\t{}{}:{}", TERM_BOLD, value, TERM_RESET),
None => "".to_string(),
}
}
fn maybe_build_subject_name(&self) -> String {
match self.spec.subject_name() {
Some(value) => format!("\n\t{}for subject [{}]{}", TERM_BOLD, value, TERM_RESET),
None => "".to_string(),
}
}
}
impl<'s, S> Spec<'s, S> {
pub fn at_location(self, location: String) -> Self {
let mut spec = self;
spec.location = Some(location);
spec
}
pub fn named(self, subject_name: &'s str) -> Self {
let mut spec = self;
spec.subject_name = Some(subject_name);
spec
}
}
impl<'s, S> Spec<'s, S>
where
S: Debug + PartialEq,
{
pub fn is_equal_to<E: Borrow<S>>(&mut self, expected: E) {
let subject = self.subject;
let borrowed_expected = expected.borrow();
if !subject.eq(borrowed_expected) {
AssertionFailure::from_spec(self)
.with_expected(format!("<{:?}>", borrowed_expected))
.with_actual(format!("<{:?}>", subject))
.fail();
}
}
pub fn is_not_equal_to<E: Borrow<S>>(&mut self, expected: E) {
let subject = self.subject;
let borrowed_expected = expected.borrow();
if subject.eq(borrowed_expected) {
AssertionFailure::from_spec(self)
.with_expected(format!(
"<{:?}> not equal to <{:?}>",
subject, borrowed_expected
))
.with_actual("equal".to_string())
.fail();
}
}
}
impl<'s, S> Spec<'s, S>
where
S: Debug,
{
pub fn matches<F>(&mut self, matching_function: F) -> &mut Self
where
F: Fn(&'s S) -> bool,
{
let subject = self.subject;
if !matching_function(subject) {
AssertionFailure::from_spec(self)
.fail_with_message(format!("expectation failed for value <{:?}>", subject));
}
self
}
pub fn map<F, T>(self, mapping_function: F) -> Spec<'s, T>
where
F: Fn(&'s S) -> &'s T,
{
Spec {
subject: mapping_function(self.subject),
subject_name: self.subject_name,
location: self.location.clone(),
description: self.description,
}
}
}
#[cfg(test)]
mod tests {
use super::prelude::*;
#[test]
fn should_be_able_to_use_macro_form_with_deliberate_reference() {
let test_vec = vec![1, 2, 3, 4, 5];
assert_that!(&test_vec).mapped_contains(|val| val * 2, &6);
}
#[test]
fn should_be_able_to_use_macro_form_without_deliberate_reference() {
let test_vec = vec![1, 2, 3, 4, 5];
assert_that!(test_vec).mapped_contains(|val| val * 2, &6);
}
#[test]
fn should_be_able_to_use_function_call_with_macro() {
struct Line {
x0: i32,
x1: i32,
}
impl Line {
fn get_delta_x(&self) -> i32 {
(self.x1 - self.x0).abs()
}
}
let line = Line { x0: 1, x1: 3 };
assert_that!(line.get_delta_x()).is_equal_to(2);
assert_that!(&line.get_delta_x()).is_equal_to(2);
}
#[test]
#[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>")]
fn should_contain_assertion_description_in_panic() {
asserting("test condition").that(&1).is_equal_to(&2);
}
#[test]
#[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">")]
fn should_contain_assertion_description_if_message_is_provided() {
let value = "Hello";
asserting("closure")
.that(&value)
.matches(|val| val.eq(&"Hi"));
}
#[test]
#[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>\
\n\n\tat location: src/lib.rs:")]
fn should_contain_file_and_line_in_panic_for_assertions() {
assert_that!(&1).is_equal_to(&2);
}
#[test]
#[should_panic(expected = "\n\texpectation failed for value <\"Hello\">\
\n\n\tat location: src/lib.rs:")]
fn should_contain_file_and_line_for_assertions_if_message_is_provided() {
let value = "Hello";
assert_that!(&value).matches(|val| val.eq(&"Hi"));
}
#[test]
#[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>\
\n\n\tat location: src/lib.rs:")]
fn should_contain_file_and_line_in_panic_for_descriptive_assertions() {
asserting!(&"test condition").that(&1).is_equal_to(&2);
}
#[test]
#[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">\
\n\n\tat location: src/lib.rs:")]
fn should_contain_file_and_line_for_descriptive_assertions_if_message_is_provided() {
let value = "Hello";
asserting!(&"closure")
.that(&value)
.matches(|val| val.eq(&"Hi"));
}
#[test]
#[should_panic(
expected = "\n\tfor subject [number one]\n\texpected: <2>\n\t but was: <1>\
\n\n\tat location: src/lib.rs:"
)]
fn should_contain_subject_name_in_panic_for_assertions() {
assert_that!(&1).named("number one").is_equal_to(&2);
}
#[test]
#[should_panic(
expected = "\n\tfor subject [a word]\n\texpectation failed for value <\"Hello\">\
\n\n\tat location: src/lib.rs:"
)]
fn should_contain_subject_name_in_panic_for_assertions_if_message_is_provided() {
let value = "Hello";
assert_that!(&value)
.named("a word")
.matches(|val| val.eq(&"Hi"));
}
#[test]
fn is_equal_to_should_support_multiple_borrow_forms() {
assert_that(&1).is_equal_to(1);
assert_that(&1).is_equal_to(&mut 1);
assert_that(&1).is_equal_to(&1);
}
#[test]
fn should_not_panic_on_equal_subjects() {
assert_that(&1).is_equal_to(&1);
}
#[test]
#[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>")]
fn should_panic_on_unequal_subjects() {
assert_that(&1).is_equal_to(&2);
}
#[test]
fn is_not_equal_to_should_support_multiple_borrow_forms() {
assert_that(&1).is_not_equal_to(2);
assert_that(&1).is_not_equal_to(&mut 2);
assert_that(&1).is_not_equal_to(&2);
}
#[test]
fn should_not_panic_on_unequal_subjects_if_expected() {
assert_that(&1).is_not_equal_to(&2);
}
#[test]
#[should_panic(expected = "\n\texpected: <1> not equal to <1>\n\t but was: equal")]
fn should_panic_on_equal_subjects_if_expected_unequal() {
assert_that(&1).is_not_equal_to(&1);
}
#[test]
fn should_not_panic_if_value_matches() {
let value = "Hello";
assert_that(&value).matches(|val| val.eq(&"Hello"));
}
#[test]
#[should_panic(expected = "\n\texpectation failed for value <\"Hello\">")]
fn should_panic_if_value_does_not_match() {
let value = "Hello";
assert_that(&value).matches(|val| val.eq(&"Hi"));
}
#[test]
fn should_permit_chained_matches_calls() {
let value = ("Hello", "World");
assert_that(&value)
.matches(|val| val.0.eq("Hello"))
.matches(|val| val.1.eq("World"));
}
#[test]
fn should_be_able_to_map_to_inner_field_of_struct_when_matching() {
let test_struct = TestStruct { value: 5 };
assert_that(&test_struct)
.map(|val| &val.value)
.is_equal_to(&5);
}
#[derive(Debug, PartialEq)]
struct TestStruct {
pub value: u8,
}
}