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