1#![allow(clippy::wrong_self_convention)]
2
3use std::borrow::Borrow;
148use std::cmp::PartialEq;
149use std::fmt::Debug;
150
151use colours::{TERM_BOLD, TERM_RED, TERM_RESET};
152
153pub mod boolean;
154pub mod hashmap;
155pub mod hashset;
156pub mod iter;
157pub mod numeric;
158pub mod option;
159pub mod path;
160pub mod prelude;
161pub mod result;
162pub mod string;
163pub mod vec;
164
165#[cfg(not(test))]
168mod colours {
169 pub const TERM_RED: &str = "\x1B[31m";
170 pub const TERM_BOLD: &str = "\x1B[1m";
171 pub const TERM_RESET: &str = "\x1B[0m";
172}
173
174#[cfg(test)]
175mod colours {
176 pub const TERM_RED: &str = "";
177 pub const TERM_BOLD: &str = "";
178 pub const TERM_RESET: &str = "";
179}
180
181#[cfg(feature = "num")]
182extern crate num;
183
184#[macro_export]
185macro_rules! assert_that {
186 (&$subject:tt) => {
187 assert_that!($subject)
188 };
189 ($subject:tt) => {{
190 let line = line!();
191 let file = file!();
192 assert_that(&$subject).at_location(format!("{}:{}", file, line))
193 }};
194 (&$subject:expr) => {
195 assert_that!($subject)
196 };
197 ($subject:expr) => {{
198 let line = line!();
199 let file = file!();
200 assert_that(&$subject).at_location(format!("{}:{}", file, line))
201 }};
202}
203
204#[macro_export]
205macro_rules! asserting {
206 (&$description:tt) => {
207 asserting!($description)
208 };
209 ($description:tt) => {{
210 let line = line!();
211 let file = file!();
212 asserting(&$description).at_location(format!("{}:{}", file, line))
213 }};
214}
215
216pub trait DescriptiveSpec<'r> {
217 fn subject_name(&self) -> Option<&'r str>;
218 fn location(&self) -> Option<String>;
219 fn description(&self) -> Option<&'r str>;
220}
221
222#[derive(Debug)]
226pub struct AssertionFailure<'r, T: 'r> {
227 spec: &'r T,
228 expected: Option<String>,
229 actual: Option<String>,
230}
231
232#[derive(Debug)]
236pub struct SpecDescription<'r> {
237 value: &'r str,
238 location: Option<String>,
239}
240
241#[derive(Debug)]
246pub struct Spec<'s, S: 's> {
247 pub subject: &'s S,
248 pub subject_name: Option<&'s str>,
249 pub location: Option<String>,
250 pub description: Option<&'s str>,
251}
252
253pub fn assert_that<S>(subject: &S) -> Spec<S> {
257 Spec {
258 subject,
259 subject_name: None,
260 location: None,
261 description: None,
262 }
263}
264
265pub fn asserting(description: &str) -> SpecDescription {
267 SpecDescription {
268 value: description,
269 location: None,
270 }
271}
272
273impl<'r> SpecDescription<'r> {
274 pub fn at_location(self, location: String) -> Self {
275 let mut description = self;
276
277 description.location = Some(location);
278 description
279 }
280
281 pub fn that<S>(self, subject: &'r S) -> Spec<'r, S> {
283 Spec {
284 subject,
285 subject_name: None,
286 location: self.location,
287 description: Some(self.value),
288 }
289 }
290}
291
292impl<'r, T> DescriptiveSpec<'r> for Spec<'r, T> {
293 fn subject_name(&self) -> Option<&'r str> {
294 self.subject_name
295 }
296
297 fn location(&self) -> Option<String> {
298 self.location.clone()
299 }
300
301 fn description(&self) -> Option<&'r str> {
302 self.description
303 }
304}
305
306impl<'r, T: DescriptiveSpec<'r>> AssertionFailure<'r, T> {
307 pub fn from_spec(spec: &'r T) -> AssertionFailure<'r, T> {
309 AssertionFailure {
310 spec,
311 expected: None,
312 actual: None,
313 }
314 }
315
316 pub fn with_expected(&mut self, expected: String) -> &mut Self {
318 let mut assertion = self;
319 assertion.expected = Some(expected);
320
321 assertion
322 }
323
324 pub fn with_actual(&mut self, actual: String) -> &mut Self {
326 let mut assertion = self;
327 assertion.actual = Some(actual);
328
329 assertion
330 }
331
332 pub fn fail(&mut self) {
335 assert!(
336 !(self.expected.is_none() || self.actual.is_none()),
337 "invalid assertion"
338 );
339
340 let location = self.maybe_build_location();
341 let subject_name = self.maybe_build_subject_name();
342 let description = self.maybe_build_description();
343
344 panic!(
345 "{}{}\n\t{}expected: {}\n\t but was: {}{}\n{}",
346 description,
347 subject_name,
348 TERM_RED,
349 self.expected.clone().unwrap(),
350 self.actual.clone().unwrap(),
351 TERM_RESET,
352 location
353 )
354 }
355
356 fn fail_with_message(&mut self, message: String) {
359 let location = self.maybe_build_location();
360 let subject_name = self.maybe_build_subject_name();
361 let description = self.maybe_build_description();
362
363 panic!(
364 "{}{}\n\t{}{}{}\n{}",
365 description, subject_name, TERM_RED, message, TERM_RESET, location
366 )
367 }
368
369 fn maybe_build_location(&self) -> String {
370 match self.spec.location() {
371 Some(value) => format!("\n\t{}at location: {}{}\n", TERM_BOLD, value, TERM_RESET),
372 None => "".to_string(),
373 }
374 }
375
376 fn maybe_build_description(&self) -> String {
377 match self.spec.description() {
378 Some(value) => format!("\n\t{}{}:{}", TERM_BOLD, value, TERM_RESET),
379 None => "".to_string(),
380 }
381 }
382
383 fn maybe_build_subject_name(&self) -> String {
384 match self.spec.subject_name() {
385 Some(value) => format!("\n\t{}for subject [{}]{}", TERM_BOLD, value, TERM_RESET),
386 None => "".to_string(),
387 }
388 }
389}
390
391impl<'s, S> Spec<'s, S> {
392 pub fn at_location(self, location: String) -> Self {
397 let mut spec = self;
398 spec.location = Some(location);
399
400 spec
401 }
402
403 pub fn named(self, subject_name: &'s str) -> Self {
407 let mut spec = self;
408 spec.subject_name = Some(subject_name);
409
410 spec
411 }
412}
413
414impl<'s, S> Spec<'s, S>
415where
416 S: Debug + PartialEq,
417{
418 pub fn is_equal_to<E: Borrow<S>>(&mut self, expected: E) {
426 let subject = self.subject;
427 let borrowed_expected = expected.borrow();
428
429 if !subject.eq(borrowed_expected) {
430 AssertionFailure::from_spec(self)
431 .with_expected(format!("<{:?}>", borrowed_expected))
432 .with_actual(format!("<{:?}>", subject))
433 .fail();
434 }
435 }
436
437 pub fn is_not_equal_to<E: Borrow<S>>(&mut self, expected: E) {
445 let subject = self.subject;
446 let borrowed_expected = expected.borrow();
447
448 if subject.eq(borrowed_expected) {
449 AssertionFailure::from_spec(self)
450 .with_expected(format!(
451 "<{:?}> not equal to <{:?}>",
452 subject, borrowed_expected
453 ))
454 .with_actual("equal".to_string())
455 .fail();
456 }
457 }
458}
459
460impl<'s, S> Spec<'s, S>
461where
462 S: Debug,
463{
464 pub fn matches<F>(&mut self, matching_function: F) -> &mut Self
477 where
478 F: Fn(&'s S) -> bool,
479 {
480 let subject = self.subject;
481
482 if !matching_function(subject) {
483 AssertionFailure::from_spec(self)
484 .fail_with_message(format!("expectation failed for value <{:?}>", subject));
485 }
486
487 self
488 }
489
490 pub fn map<F, T>(self, mapping_function: F) -> Spec<'s, T>
503 where
504 F: Fn(&'s S) -> &'s T,
505 {
506 Spec {
507 subject: mapping_function(self.subject),
508 subject_name: self.subject_name,
509 location: self.location.clone(),
510 description: self.description,
511 }
512 }
513}
514
515#[cfg(test)]
516mod tests {
517
518 use super::prelude::*;
519
520 #[test]
521 fn should_be_able_to_use_macro_form_with_deliberate_reference() {
522 let test_vec = vec![1, 2, 3, 4, 5];
523
524 assert_that!(&test_vec).mapped_contains(|val| val * 2, &6);
525 }
526
527 #[test]
528 fn should_be_able_to_use_macro_form_without_deliberate_reference() {
529 let test_vec = vec![1, 2, 3, 4, 5];
530
531 assert_that!(test_vec).mapped_contains(|val| val * 2, &6);
532 }
533
534 #[test]
535 fn should_be_able_to_use_function_call_with_macro() {
536 struct Line {
537 x0: i32,
538 x1: i32,
539 }
540
541 impl Line {
542 fn get_delta_x(&self) -> i32 {
543 (self.x1 - self.x0).abs()
544 }
545 }
546
547 let line = Line { x0: 1, x1: 3 };
548 assert_that!(line.get_delta_x()).is_equal_to(2);
549 assert_that!(&line.get_delta_x()).is_equal_to(2);
550 }
551
552 #[test]
553 #[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>")]
554 fn should_contain_assertion_description_in_panic() {
555 asserting("test condition").that(&1).is_equal_to(&2);
556 }
557
558 #[test]
559 #[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">")]
560 fn should_contain_assertion_description_if_message_is_provided() {
561 let value = "Hello";
562 asserting("closure")
563 .that(&value)
564 .matches(|val| val.eq(&"Hi"));
565 }
566
567 #[test]
568 #[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>\
569 \n\n\tat location: src/lib.rs:")]
570 fn should_contain_file_and_line_in_panic_for_assertions() {
571 assert_that!(&1).is_equal_to(&2);
572 }
573
574 #[test]
575 #[should_panic(expected = "\n\texpectation failed for value <\"Hello\">\
576 \n\n\tat location: src/lib.rs:")]
577 fn should_contain_file_and_line_for_assertions_if_message_is_provided() {
578 let value = "Hello";
579 assert_that!(&value).matches(|val| val.eq(&"Hi"));
580 }
581
582 #[test]
583 #[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>\
584 \n\n\tat location: src/lib.rs:")]
585 fn should_contain_file_and_line_in_panic_for_descriptive_assertions() {
586 asserting!(&"test condition").that(&1).is_equal_to(&2);
587 }
588
589 #[test]
590 #[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">\
591 \n\n\tat location: src/lib.rs:")]
592 fn should_contain_file_and_line_for_descriptive_assertions_if_message_is_provided() {
593 let value = "Hello";
594 asserting!(&"closure")
595 .that(&value)
596 .matches(|val| val.eq(&"Hi"));
597 }
598
599 #[test]
600 #[should_panic(
601 expected = "\n\tfor subject [number one]\n\texpected: <2>\n\t but was: <1>\
602 \n\n\tat location: src/lib.rs:"
603 )]
604 fn should_contain_subject_name_in_panic_for_assertions() {
605 assert_that!(&1).named("number one").is_equal_to(&2);
606 }
607
608 #[test]
609 #[should_panic(
610 expected = "\n\tfor subject [a word]\n\texpectation failed for value <\"Hello\">\
611 \n\n\tat location: src/lib.rs:"
612 )]
613 fn should_contain_subject_name_in_panic_for_assertions_if_message_is_provided() {
614 let value = "Hello";
615 assert_that!(&value)
616 .named("a word")
617 .matches(|val| val.eq(&"Hi"));
618 }
619
620 #[test]
621 fn is_equal_to_should_support_multiple_borrow_forms() {
622 assert_that(&1).is_equal_to(1);
623 assert_that(&1).is_equal_to(&mut 1);
624 assert_that(&1).is_equal_to(&1);
625 }
626
627 #[test]
628 fn should_not_panic_on_equal_subjects() {
629 assert_that(&1).is_equal_to(&1);
630 }
631
632 #[test]
633 #[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>")]
634 fn should_panic_on_unequal_subjects() {
635 assert_that(&1).is_equal_to(&2);
636 }
637
638 #[test]
639 fn is_not_equal_to_should_support_multiple_borrow_forms() {
640 assert_that(&1).is_not_equal_to(2);
641 assert_that(&1).is_not_equal_to(&mut 2);
642 assert_that(&1).is_not_equal_to(&2);
643 }
644
645 #[test]
646 fn should_not_panic_on_unequal_subjects_if_expected() {
647 assert_that(&1).is_not_equal_to(&2);
648 }
649
650 #[test]
651 #[should_panic(expected = "\n\texpected: <1> not equal to <1>\n\t but was: equal")]
652 fn should_panic_on_equal_subjects_if_expected_unequal() {
653 assert_that(&1).is_not_equal_to(&1);
654 }
655
656 #[test]
657 fn should_not_panic_if_value_matches() {
658 let value = "Hello";
659 assert_that(&value).matches(|val| val.eq(&"Hello"));
660 }
661
662 #[test]
663 #[should_panic(expected = "\n\texpectation failed for value <\"Hello\">")]
664 fn should_panic_if_value_does_not_match() {
665 let value = "Hello";
666 assert_that(&value).matches(|val| val.eq(&"Hi"));
667 }
668
669 #[test]
670 fn should_permit_chained_matches_calls() {
671 let value = ("Hello", "World");
672 assert_that(&value)
673 .matches(|val| val.0.eq("Hello"))
674 .matches(|val| val.1.eq("World"));
675 }
676
677 #[test]
678 fn should_be_able_to_map_to_inner_field_of_struct_when_matching() {
679 let test_struct = TestStruct { value: 5 };
680 assert_that(&test_struct)
681 .map(|val| &val.value)
682 .is_equal_to(&5);
683 }
684
685 #[derive(Debug, PartialEq)]
686 struct TestStruct {
687 pub value: u8,
688 }
689}