1#![allow(clippy::wrong_self_convention)]
2
3use std::borrow::Borrow;
140use std::cmp::PartialEq;
141use std::fmt::Debug;
142
143use colours::{TERM_BOLD, TERM_RED, TERM_RESET};
144
145pub mod boolean;
146pub mod hashmap;
147pub mod hashset;
148pub mod iter;
149pub mod numeric;
150pub mod option;
151pub mod path;
152pub mod prelude;
153pub mod result;
154pub mod string;
155pub mod vec;
156
157#[cfg(not(test))]
160mod colours {
161 pub const TERM_RED: &str = "\x1B[31m";
162 pub const TERM_BOLD: &str = "\x1B[1m";
163 pub const TERM_RESET: &str = "\x1B[0m";
164}
165
166#[cfg(test)]
167mod colours {
168 pub const TERM_RED: &str = "";
169 pub const TERM_BOLD: &str = "";
170 pub const TERM_RESET: &str = "";
171}
172
173#[cfg(feature = "num")]
174extern crate num;
175
176#[macro_export]
178macro_rules! assert_that {
179 (&$subject:tt) => {
180 assert_that!($subject)
181 };
182 ($subject:tt) => {{
183 assert_that(&$subject)
184 }};
185 (&$subject:expr) => {
186 assert_that!($subject)
187 };
188 ($subject:expr) => {{
189 assert_that(&$subject)
190 }};
191}
192
193#[macro_export]
195macro_rules! asserting {
196 (&$description:tt) => {
197 asserting!($description)
198 };
199 ($description:tt) => {{
200 asserting(&$description)
201 }};
202}
203
204pub trait DescriptiveSpec<'r> {
205 fn subject_name(&self) -> Option<&'r str>;
206 fn location(&self) -> Option<String>;
207 fn description(&self) -> Option<&'r str>;
208}
209
210#[derive(Debug)]
214pub struct AssertionFailure<'r, T: 'r> {
215 spec: &'r T,
216 expected: Option<String>,
217 actual: Option<String>,
218}
219
220#[derive(Debug)]
224pub struct SpecDescription<'r> {
225 value: &'r str,
226 location: Option<String>,
227}
228
229#[derive(Debug)]
234pub struct Spec<'s, S: 's> {
235 pub subject: &'s S,
236 pub subject_name: Option<&'s str>,
237 pub location: Option<String>,
238 pub description: Option<&'s str>,
239}
240
241pub fn assert_that<S>(subject: &S) -> Spec<S> {
245 Spec {
246 subject,
247 subject_name: None,
248 location: None,
249 description: None,
250 }
251}
252
253pub fn asserting(description: &str) -> SpecDescription {
255 SpecDescription {
256 value: description,
257 location: None,
258 }
259}
260
261impl<'r> SpecDescription<'r> {
262 pub fn at_location(mut self, location: String) -> Self {
263 self.location = Some(location);
264 self
265 }
266
267 pub fn that<S>(self, subject: &'r S) -> Spec<'r, S> {
269 Spec {
270 subject,
271 subject_name: None,
272 location: self.location,
273 description: Some(self.value),
274 }
275 }
276}
277
278impl<'r, T> DescriptiveSpec<'r> for Spec<'r, T> {
279 fn subject_name(&self) -> Option<&'r str> {
280 self.subject_name
281 }
282
283 fn location(&self) -> Option<String> {
284 self.location.clone()
285 }
286
287 fn description(&self) -> Option<&'r str> {
288 self.description
289 }
290}
291
292impl<'r, T: DescriptiveSpec<'r>> AssertionFailure<'r, T> {
293 pub fn from_spec(spec: &'r T) -> AssertionFailure<'r, T> {
295 AssertionFailure {
296 spec,
297 expected: None,
298 actual: None,
299 }
300 }
301
302 pub fn with_expected(&mut self, expected: String) -> &mut Self {
304 self.expected = Some(expected);
305
306 self
307 }
308
309 pub fn with_actual(&mut self, actual: String) -> &mut Self {
311 self.actual = Some(actual);
312
313 self
314 }
315
316 #[track_caller]
319 pub fn fail(&mut self) {
320 assert!(
321 !(self.expected.is_none() || self.actual.is_none()),
322 "invalid assertion"
323 );
324
325 let location = self.maybe_build_location();
326 let subject_name = self.maybe_build_subject_name();
327 let description = self.maybe_build_description();
328
329 panic!(
330 "{}{}\n\t{}expected: {}\n\t but was: {}{}\n{}",
331 description,
332 subject_name,
333 TERM_RED,
334 self.expected.clone().unwrap(),
335 self.actual.clone().unwrap(),
336 TERM_RESET,
337 location
338 )
339 }
340
341 #[track_caller]
344 fn fail_with_message(&mut self, message: String) {
345 let location = self.maybe_build_location();
346 let subject_name = self.maybe_build_subject_name();
347 let description = self.maybe_build_description();
348
349 panic!(
350 "{}{}\n\t{}{}{}\n{}",
351 description, subject_name, TERM_RED, message, TERM_RESET, location
352 )
353 }
354
355 fn maybe_build_location(&self) -> String {
356 match self.spec.location() {
357 Some(value) => format!("\n\t{}at location: {}{}\n", TERM_BOLD, value, TERM_RESET),
358 None => "".to_string(),
359 }
360 }
361
362 fn maybe_build_description(&self) -> String {
363 match self.spec.description() {
364 Some(value) => format!("\n\t{}{}:{}", TERM_BOLD, value, TERM_RESET),
365 None => "".to_string(),
366 }
367 }
368
369 fn maybe_build_subject_name(&self) -> String {
370 match self.spec.subject_name() {
371 Some(value) => format!("\n\t{}for subject [{}]{}", TERM_BOLD, value, TERM_RESET),
372 None => "".to_string(),
373 }
374 }
375}
376
377impl<'s, S> Spec<'s, S> {
378 pub fn at_location(mut self, location: String) -> Self {
383 self.location = Some(location);
384
385 self
386 }
387
388 pub fn named(mut self, subject_name: &'s str) -> Self {
392 self.subject_name = Some(subject_name);
393
394 self
395 }
396}
397
398impl<S> Spec<'_, S>
399where
400 S: Debug + PartialEq,
401{
402 #[track_caller]
410 pub fn is_equal_to<E: Borrow<S>>(&mut self, expected: E) {
411 let subject = self.subject;
412 let borrowed_expected = expected.borrow();
413
414 if !subject.eq(borrowed_expected) {
415 AssertionFailure::from_spec(self)
416 .with_expected(format!("<{:?}>", borrowed_expected))
417 .with_actual(format!("<{:?}>", subject))
418 .fail();
419 }
420 }
421
422 #[track_caller]
430 pub fn is_not_equal_to<E: Borrow<S>>(&mut self, expected: E) {
431 let subject = self.subject;
432 let borrowed_expected = expected.borrow();
433
434 if subject.eq(borrowed_expected) {
435 AssertionFailure::from_spec(self)
436 .with_expected(format!(
437 "<{:?}> not equal to <{:?}>",
438 subject, borrowed_expected
439 ))
440 .with_actual("equal".to_string())
441 .fail();
442 }
443 }
444}
445
446impl<'s, S> Spec<'s, S>
447where
448 S: Debug,
449{
450 #[track_caller]
463 pub fn matches<F>(&mut self, matching_function: F) -> &mut Self
464 where
465 F: Fn(&'s S) -> bool,
466 {
467 let subject = self.subject;
468
469 if !matching_function(subject) {
470 AssertionFailure::from_spec(self)
471 .fail_with_message(format!("expectation failed for value <{:?}>", subject));
472 }
473
474 self
475 }
476
477 #[track_caller]
490 pub fn map<F, T>(self, mapping_function: F) -> Spec<'s, T>
491 where
492 F: Fn(&'s S) -> &'s T,
493 {
494 Spec {
495 subject: mapping_function(self.subject),
496 subject_name: self.subject_name,
497 location: self.location.clone(),
498 description: self.description,
499 }
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 #![allow(clippy::needless_borrows_for_generic_args)]
506 use super::prelude::*;
507
508 #[test]
509 fn should_be_able_to_use_macro_form_with_deliberate_reference() {
510 let test_vec = vec![1, 2, 3, 4, 5];
511
512 assert_that!(&test_vec).mapped_contains(|val| val * 2, &6);
513 }
514
515 #[test]
516 fn should_be_able_to_use_macro_form_without_deliberate_reference() {
517 let test_vec = vec![1, 2, 3, 4, 5];
518
519 assert_that!(test_vec).mapped_contains(|val| val * 2, &6);
520 }
521
522 #[test]
523 fn should_be_able_to_use_function_call_with_macro() {
524 struct Line {
525 x0: i32,
526 x1: i32,
527 }
528
529 impl Line {
530 fn get_delta_x(&self) -> i32 {
531 (self.x1 - self.x0).abs()
532 }
533 }
534
535 let line = Line { x0: 1, x1: 3 };
536 assert_that!(line.get_delta_x()).is_equal_to(2);
537 assert_that!(&line.get_delta_x()).is_equal_to(2);
538 }
539
540 #[test]
541 #[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>")]
542 fn should_contain_assertion_description_in_panic() {
543 asserting("test condition").that(&1).is_equal_to(&2);
544 }
545
546 #[test]
547 #[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">")]
548 fn should_contain_assertion_description_if_message_is_provided() {
549 let value = "Hello";
550 asserting("closure")
551 .that(&value)
552 .matches(|val| val.eq(&"Hi"));
553 }
554
555 #[test]
556 fn is_equal_to_should_support_multiple_borrow_forms() {
557 assert_that(&1).is_equal_to(1);
558 assert_that(&1).is_equal_to(&mut 1);
559 assert_that(&1).is_equal_to(&1);
560 }
561
562 #[test]
563 fn should_not_panic_on_equal_subjects() {
564 assert_that(&1).is_equal_to(&1);
565 }
566
567 #[test]
568 #[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>")]
569 fn should_panic_on_unequal_subjects() {
570 assert_that(&1).is_equal_to(&2);
571 }
572
573 #[test]
574 fn is_not_equal_to_should_support_multiple_borrow_forms() {
575 assert_that(&1).is_not_equal_to(2);
576 assert_that(&1).is_not_equal_to(&mut 2);
577 assert_that(&1).is_not_equal_to(&2);
578 }
579
580 #[test]
581 fn should_not_panic_on_unequal_subjects_if_expected() {
582 assert_that(&1).is_not_equal_to(&2);
583 }
584
585 #[test]
586 #[should_panic(expected = "\n\texpected: <1> not equal to <1>\n\t but was: equal")]
587 fn should_panic_on_equal_subjects_if_expected_unequal() {
588 assert_that(&1).is_not_equal_to(&1);
589 }
590
591 #[test]
592 fn should_not_panic_if_value_matches() {
593 let value = "Hello";
594 assert_that(&value).matches(|val| val.eq(&"Hello"));
595 }
596
597 #[test]
598 #[should_panic(expected = "\n\texpectation failed for value <\"Hello\">")]
599 fn should_panic_if_value_does_not_match() {
600 let value = "Hello";
601 assert_that(&value).matches(|val| val.eq(&"Hi"));
602 }
603
604 #[test]
605 fn should_permit_chained_matches_calls() {
606 let value = ("Hello", "World");
607 assert_that(&value)
608 .matches(|val| val.0.eq("Hello"))
609 .matches(|val| val.1.eq("World"));
610 }
611
612 #[test]
613 fn should_be_able_to_map_to_inner_field_of_struct_when_matching() {
614 let test_struct = TestStruct { value: 5 };
615 assert_that(&test_struct)
616 .map(|val| &val.value)
617 .is_equal_to(&5);
618 }
619
620 #[derive(Debug, PartialEq)]
621 struct TestStruct {
622 pub value: u8,
623 }
624}