1use crate::{
16 description::Description,
17 matcher::{Matcher, MatcherBase, MatcherResult},
18 matcher_support::{
19 edit_distance,
20 summarize_diff::{create_diff, create_diff_reversed},
21 },
22 matchers::eq_matcher::EqMatcher,
23};
24use std::borrow::Cow;
25use std::fmt::Debug;
26use std::ops::Deref;
27
28pub fn contains_substring<T>(expected: T) -> StrMatcher<T> {
63 StrMatcher {
64 configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
65 expected,
66 }
67}
68
69pub fn starts_with<T>(expected: T) -> StrMatcher<T> {
102 StrMatcher {
103 configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
104 expected,
105 }
106}
107
108pub fn ends_with<T>(expected: T) -> StrMatcher<T> {
141 StrMatcher {
142 configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
143 expected,
144 }
145}
146
147pub trait StrMatcherConfigurator<ExpectedT> {
153 fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT>;
172
173 fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT>;
192
193 fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT>;
216
217 fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT>;
238
239 fn ignoring_unicode_case(self) -> StrMatcher<ExpectedT>;
257
258 fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT>;
292}
293
294#[derive(MatcherBase)]
304pub struct StrMatcher<ExpectedT> {
305 expected: ExpectedT,
306 configuration: Configuration,
307}
308
309impl<ExpectedT, ActualT> Matcher<ActualT> for StrMatcher<ExpectedT>
310where
311 ExpectedT: Deref<Target = str> + Debug,
312 ActualT: AsRef<str> + Debug + Copy,
313{
314 fn matches(&self, actual: ActualT) -> MatcherResult {
315 self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
316 }
317
318 fn describe(&self, matcher_result: MatcherResult) -> Description {
319 self.configuration.describe(matcher_result, self.expected.deref())
320 }
321
322 fn explain_match(&self, actual: ActualT) -> Description {
323 self.configuration.explain_match(self.expected.deref(), actual.as_ref())
324 }
325}
326
327impl<ExpectedT, MatcherT: Into<StrMatcher<ExpectedT>>> StrMatcherConfigurator<ExpectedT>
328 for MatcherT
329{
330 fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT> {
331 let existing = self.into();
332 StrMatcher {
333 configuration: existing.configuration.ignoring_leading_whitespace(),
334 ..existing
335 }
336 }
337
338 fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT> {
339 let existing = self.into();
340 StrMatcher {
341 configuration: existing.configuration.ignoring_trailing_whitespace(),
342 ..existing
343 }
344 }
345
346 fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT> {
347 let existing = self.into();
348 StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
349 }
350
351 fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT> {
352 let existing = self.into();
353 StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
354 }
355
356 fn ignoring_unicode_case(self) -> StrMatcher<ExpectedT> {
357 let existing = self.into();
358 StrMatcher { configuration: existing.configuration.ignoring_unicode_case(), ..existing }
359 }
360
361 fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT> {
362 let existing = self.into();
363 if !matches!(existing.configuration.mode, MatchMode::Contains) {
364 panic!("The times() configurator is only meaningful with contains_substring().");
365 }
366 StrMatcher { configuration: existing.configuration.times(times), ..existing }
367 }
368}
369
370impl<T: Deref<Target = str>> From<EqMatcher<T>> for StrMatcher<T> {
371 fn from(value: EqMatcher<T>) -> Self {
372 Self::with_default_config(value.expected)
373 }
374}
375
376impl<T> StrMatcher<T> {
377 fn with_default_config(expected: T) -> Self {
382 Self { expected, configuration: Default::default() }
383 }
384}
385
386struct Configuration {
393 mode: MatchMode,
394 ignore_leading_whitespace: bool,
395 ignore_trailing_whitespace: bool,
396 case_policy: CasePolicy,
397 times: Option<Box<dyn Matcher<usize>>>,
398}
399
400#[derive(Clone)]
401enum MatchMode {
402 Equals,
403 Contains,
404 StartsWith,
405 EndsWith,
406}
407
408impl MatchMode {
409 fn to_diff_mode(&self) -> edit_distance::Mode {
410 match self {
411 MatchMode::StartsWith | MatchMode::EndsWith => edit_distance::Mode::Prefix,
412 MatchMode::Contains => edit_distance::Mode::Contains,
413 MatchMode::Equals => edit_distance::Mode::Exact,
414 }
415 }
416}
417
418#[derive(Clone)]
419enum CasePolicy {
420 Respect,
421 IgnoreAscii,
422 IgnoreUnicode,
423}
424
425impl Configuration {
426 fn do_strings_match(&self, expected: &str, actual: &str) -> bool {
429 let (expected, actual) =
430 match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
431 (true, true) => (expected.trim(), actual.trim()),
432 (true, false) => (expected.trim_start(), actual.trim_start()),
433 (false, true) => (expected.trim_end(), actual.trim_end()),
434 (false, false) => (expected, actual),
435 };
436 match self.mode {
437 MatchMode::Equals => match self.case_policy {
438 CasePolicy::Respect => expected == actual,
439 CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual),
440 CasePolicy::IgnoreUnicode => expected.to_lowercase() == actual.to_lowercase(),
441 },
442 MatchMode::Contains => match self.case_policy {
443 CasePolicy::Respect => self.does_containment_match(actual, expected),
444 CasePolicy::IgnoreAscii => self.does_containment_match(
445 actual.to_ascii_lowercase().as_str(),
446 expected.to_ascii_lowercase().as_str(),
447 ),
448 CasePolicy::IgnoreUnicode => self.does_containment_match(
449 actual.to_lowercase().as_str(),
450 expected.to_lowercase().as_str(),
451 ),
452 },
453 MatchMode::StartsWith => match self.case_policy {
454 CasePolicy::Respect => actual.starts_with(expected),
455 CasePolicy::IgnoreAscii => {
456 actual.len() >= expected.len()
457 && actual[..expected.len()].eq_ignore_ascii_case(expected)
458 }
459 CasePolicy::IgnoreUnicode => {
460 actual.to_lowercase().starts_with(&expected.to_lowercase())
461 }
462 },
463 MatchMode::EndsWith => match self.case_policy {
464 CasePolicy::Respect => actual.ends_with(expected),
465 CasePolicy::IgnoreAscii => {
466 actual.len() >= expected.len()
467 && actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected)
468 }
469 CasePolicy::IgnoreUnicode => {
470 actual.to_lowercase().ends_with(&expected.to_lowercase())
471 }
472 },
473 }
474 }
475
476 fn does_containment_match(&self, actual: &str, expected: &str) -> bool {
479 if let Some(times) = self.times.as_ref() {
480 matches!(times.matches(actual.split(expected).count() - 1), MatcherResult::Match)
484 } else {
485 actual.contains(expected)
486 }
487 }
488
489 fn describe(&self, matcher_result: MatcherResult, expected: &str) -> Description {
491 let mut addenda: Vec<Cow<'static, str>> = Vec::with_capacity(3);
492 match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
493 (true, true) => addenda.push("ignoring leading and trailing whitespace".into()),
494 (true, false) => addenda.push("ignoring leading whitespace".into()),
495 (false, true) => addenda.push("ignoring trailing whitespace".into()),
496 (false, false) => {}
497 }
498 match self.case_policy {
499 CasePolicy::Respect => {}
500 CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()),
501 CasePolicy::IgnoreUnicode => addenda.push("ignoring Unicode case".into()),
502 }
503 if let Some(times) = self.times.as_ref() {
504 addenda.push(format!("count {}", times.describe(matcher_result)).into());
505 }
506 let extra =
507 if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() };
508 let match_mode_description = match self.mode {
509 MatchMode::Equals => match matcher_result {
510 MatcherResult::Match => "is equal to",
511 MatcherResult::NoMatch => "isn't equal to",
512 },
513 MatchMode::Contains => match matcher_result {
514 MatcherResult::Match => "contains a substring",
515 MatcherResult::NoMatch => "does not contain a substring",
516 },
517 MatchMode::StartsWith => match matcher_result {
518 MatcherResult::Match => "starts with prefix",
519 MatcherResult::NoMatch => "does not start with",
520 },
521 MatchMode::EndsWith => match matcher_result {
522 MatcherResult::Match => "ends with suffix",
523 MatcherResult::NoMatch => "does not end with",
524 },
525 };
526 format!("{match_mode_description} {expected:?}{extra}").into()
527 }
528
529 fn explain_match(&self, expected: &str, actual: &str) -> Description {
530 let default_explanation = format!(
531 "which {}",
532 self.describe(self.do_strings_match(expected, actual).into(), expected)
533 )
534 .into();
535 if !expected.contains('\n') || !actual.contains('\n') {
536 return default_explanation;
537 }
538
539 if self.ignore_leading_whitespace {
540 return default_explanation;
542 }
543
544 if self.ignore_trailing_whitespace {
545 return default_explanation;
547 }
548
549 if self.times.is_some() {
550 return default_explanation;
552 }
553 if matches!(self.case_policy, CasePolicy::IgnoreAscii) {
554 return default_explanation;
556 }
557 if matches!(self.case_policy, CasePolicy::IgnoreUnicode) {
558 return default_explanation;
560 }
561 if self.do_strings_match(expected, actual) {
562 return default_explanation;
566 }
567
568 let diff = match self.mode {
569 MatchMode::Equals | MatchMode::StartsWith | MatchMode::Contains => {
570 create_diff(actual, expected, self.mode.to_diff_mode())
574 }
575 MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
576 };
577
578 if diff.is_empty() {
579 format!("{default_explanation}").into()
580 } else {
581 format!("{default_explanation}\n\n{diff}").into()
582 }
583 }
584
585 fn ignoring_leading_whitespace(self) -> Self {
586 Self { ignore_leading_whitespace: true, ..self }
587 }
588
589 fn ignoring_trailing_whitespace(self) -> Self {
590 Self { ignore_trailing_whitespace: true, ..self }
591 }
592
593 fn ignoring_outer_whitespace(self) -> Self {
594 Self { ignore_leading_whitespace: true, ignore_trailing_whitespace: true, ..self }
595 }
596
597 fn ignoring_ascii_case(self) -> Self {
598 Self { case_policy: CasePolicy::IgnoreAscii, ..self }
599 }
600
601 fn ignoring_unicode_case(self) -> Self {
602 Self { case_policy: CasePolicy::IgnoreUnicode, ..self }
603 }
604
605 fn times(self, times: impl Matcher<usize> + 'static) -> Self {
606 Self { times: Some(Box::new(times)), ..self }
607 }
608}
609
610impl Default for Configuration {
611 fn default() -> Self {
612 Self {
613 mode: MatchMode::Equals,
614 ignore_leading_whitespace: false,
615 ignore_trailing_whitespace: false,
616 case_policy: CasePolicy::Respect,
617 times: None,
618 }
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use crate as googletest;
625 use crate::matcher::MatcherResult;
626 use crate::prelude::*;
627 use indoc::indoc;
628
629 #[test]
630 fn matches_string_reference_with_equal_string_reference() -> googletest::Result<()> {
631 let matcher = StrMatcher::with_default_config("A string");
632 verify_that!("A string", matcher)
633 }
634
635 #[test]
636 fn does_not_match_string_reference_with_non_equal_string_reference() -> googletest::Result<()> {
637 let matcher = StrMatcher::with_default_config("Another string");
638 verify_that!("A string", not(matcher))
639 }
640
641 #[test]
642 fn matches_owned_string_with_string_reference() -> googletest::Result<()> {
643 let matcher = StrMatcher::with_default_config("A string");
644 let value = "A string".to_string();
645 verify_that!(value, matcher)
646 }
647
648 #[test]
649 fn matches_owned_string_reference_with_string_reference() -> googletest::Result<()> {
650 let matcher = StrMatcher::with_default_config("A string");
651 let value = "A string".to_string();
652 verify_that!(&value, matcher)
653 }
654
655 #[test]
656 fn ignores_leading_whitespace_in_expected_when_requested() -> googletest::Result<()> {
657 let matcher = StrMatcher::with_default_config(" \n\tA string");
658 verify_that!("A string", matcher.ignoring_leading_whitespace())
659 }
660
661 #[test]
662 fn ignores_leading_whitespace_in_actual_when_requested() -> googletest::Result<()> {
663 let matcher = StrMatcher::with_default_config("A string");
664 verify_that!(" \n\tA string", matcher.ignoring_leading_whitespace())
665 }
666
667 #[test]
668 fn does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace(
669 ) -> googletest::Result<()> {
670 let matcher = StrMatcher::with_default_config(" \n\tAnother string");
671 verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
672 }
673
674 #[test]
675 fn remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace(
676 ) -> googletest::Result<()> {
677 let matcher = StrMatcher::with_default_config("A string \n\t");
678 verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
679 }
680
681 #[test]
682 fn ignores_trailing_whitespace_in_expected_when_requested() -> googletest::Result<()> {
683 let matcher = StrMatcher::with_default_config("A string \n\t");
684 verify_that!("A string", matcher.ignoring_trailing_whitespace())
685 }
686
687 #[test]
688 fn ignores_trailing_whitespace_in_actual_when_requested() -> googletest::Result<()> {
689 let matcher = StrMatcher::with_default_config("A string");
690 verify_that!("A string \n\t", matcher.ignoring_trailing_whitespace())
691 }
692
693 #[test]
694 fn does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace(
695 ) -> googletest::Result<()> {
696 let matcher = StrMatcher::with_default_config("Another string \n\t");
697 verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
698 }
699
700 #[test]
701 fn remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace(
702 ) -> googletest::Result<()> {
703 let matcher = StrMatcher::with_default_config(" \n\tA string");
704 verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
705 }
706
707 #[test]
708 fn ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> googletest::Result<()>
709 {
710 let matcher = StrMatcher::with_default_config(" \n\tA string \n\t");
711 verify_that!("A string", matcher.ignoring_outer_whitespace())
712 }
713
714 #[test]
715 fn ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> googletest::Result<()>
716 {
717 let matcher = StrMatcher::with_default_config("A string");
718 verify_that!(" \n\tA string \n\t", matcher.ignoring_outer_whitespace())
719 }
720
721 #[test]
722 fn respects_ascii_case_by_default() -> googletest::Result<()> {
723 let matcher = StrMatcher::with_default_config("A string");
724 verify_that!("A STRING", not(matcher))
725 }
726
727 #[test]
728 fn ignores_ascii_case_when_requested() -> googletest::Result<()> {
729 let matcher = StrMatcher::with_default_config("A string");
730 verify_that!("A STRING", matcher.ignoring_ascii_case())
731 }
732
733 #[test]
734 fn ignores_unicode_case_when_requested() -> googletest::Result<()> {
735 let matcher = StrMatcher::with_default_config("ὈΔΥΣΣΕΎΣ");
736 verify_that!("ὀδυσσεύς", matcher.ignoring_unicode_case())
737 }
738
739 #[test]
740 fn allows_ignoring_leading_whitespace_from_eq() -> googletest::Result<()> {
741 verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace())
742 }
743
744 #[test]
745 fn allows_ignoring_trailing_whitespace_from_eq() -> googletest::Result<()> {
746 verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace())
747 }
748
749 #[test]
750 fn allows_ignoring_outer_whitespace_from_eq() -> googletest::Result<()> {
751 verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace())
752 }
753
754 #[test]
755 fn allows_ignoring_ascii_case_from_eq() -> googletest::Result<()> {
756 verify_that!("A string", eq("A STRING").ignoring_ascii_case())
757 }
758
759 #[test]
760 fn allows_ignoring_unicode_case_from_eq() -> googletest::Result<()> {
761 verify_that!("ὈΔΥΣΣΕΎΣ", eq("ὀδυσσεύς").ignoring_unicode_case())
762 }
763
764 #[test]
765 fn unicode_case_sensitive_from_eq() -> googletest::Result<()> {
766 verify_that!("ὈΔΥΣΣΕΎΣ", not(eq("ὀδυσσεύς")))
767 }
768
769 #[test]
770 fn matches_string_containing_expected_value_in_contains_mode() -> googletest::Result<()> {
771 verify_that!("Some string", contains_substring("str"))
772 }
773
774 #[test]
775 fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case(
776 ) -> googletest::Result<()> {
777 verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
778 }
779
780 #[test]
781 fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_unicode_case(
782 ) -> googletest::Result<()> {
783 verify_that!("Some σpsilon", contains_substring("Σps").ignoring_unicode_case())
784 }
785
786 #[test]
787 fn contains_substring_matches_correct_number_of_substrings() -> googletest::Result<()> {
788 verify_that!("Some string", contains_substring("str").times(eq(1)))
789 }
790
791 #[test]
792 fn contains_substring_does_not_match_incorrect_number_of_substrings() -> googletest::Result<()>
793 {
794 verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1))))
795 }
796
797 #[test]
798 fn contains_substring_does_not_match_when_substrings_overlap() -> googletest::Result<()> {
799 verify_that!("ababab", not(contains_substring("abab").times(eq(2))))
800 }
801
802 #[test]
803 fn starts_with_matches_string_reference_with_prefix() -> googletest::Result<()> {
804 verify_that!("Some value", starts_with("Some"))
805 }
806
807 #[test]
808 fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case(
809 ) -> googletest::Result<()> {
810 verify_that!("Some value", starts_with("SOME").ignoring_ascii_case())
811 }
812
813 #[test]
814 fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> googletest::Result<()> {
815 verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case()))
816 }
817
818 #[test]
819 fn starts_with_does_not_match_short_string_ignoring_ascii_case() -> googletest::Result<()> {
820 verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case()))
821 }
822
823 #[test]
824 fn starts_with_matches_string_reference_with_prefix_ignoring_unicode_case(
825 ) -> googletest::Result<()> {
826 verify_that!("비밀 santa", starts_with("비밀").ignoring_unicode_case())
827 }
828
829 #[test]
830 fn starts_with_does_not_match_wrong_prefix_ignoring_unicode_case() -> googletest::Result<()> {
831 verify_that!("secret santa", not(starts_with("비밀").ignoring_unicode_case()))
832 }
833
834 #[test]
835 fn starts_with_does_not_match_short_string_ignoring_unicode_case() -> googletest::Result<()> {
836 verify_that!("비밀", not(starts_with("秘密").ignoring_unicode_case()))
837 }
838
839 #[test]
840 fn starts_with_does_not_match_string_without_prefix() -> googletest::Result<()> {
841 verify_that!("Some value", not(starts_with("Another")))
842 }
843
844 #[test]
845 fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> googletest::Result<()>
846 {
847 verify_that!("Some value", not(starts_with("value")))
848 }
849
850 #[test]
851 fn ends_with_matches_string_reference_with_suffix() -> googletest::Result<()> {
852 verify_that!("Some value", ends_with("value"))
853 }
854
855 #[test]
856 fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> googletest::Result<()>
857 {
858 verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case())
859 }
860
861 #[test]
862 fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> googletest::Result<()> {
863 verify_that!("Some value", not(ends_with("OTHER").ignoring_ascii_case()))
864 }
865
866 #[test]
867 fn ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> googletest::Result<()> {
868 verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case()))
869 }
870
871 #[test]
872 fn ends_with_does_not_match_string_without_suffix() -> googletest::Result<()> {
873 verify_that!("Some value", not(ends_with("other value")))
874 }
875
876 #[test]
877 fn ends_with_does_not_match_string_with_substring_not_at_end() -> googletest::Result<()> {
878 verify_that!("Some value", not(ends_with("Some")))
879 }
880
881 #[test]
882 fn ends_with_matches_string_reference_with_suffix_ignoring_unicode_case(
883 ) -> googletest::Result<()> {
884 verify_that!("santa 비밀", ends_with("비밀").ignoring_unicode_case())
885 }
886
887 #[test]
888 fn ends_with_does_not_match_wrong_suffix_ignoring_unicode_case() -> googletest::Result<()> {
889 verify_that!("secret santa", not(ends_with("비밀").ignoring_unicode_case()))
890 }
891
892 #[test]
893 fn ends_with_does_not_match_short_string_ignoring_unicode_case() -> googletest::Result<()> {
894 verify_that!("비밀", not(ends_with("秘密").ignoring_unicode_case()))
895 }
896
897 #[test]
898 fn describes_itself_for_matching_result() -> googletest::Result<()> {
899 let matcher = StrMatcher::with_default_config("A string");
900 verify_that!(
901 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
902 displays_as(eq("is equal to \"A string\""))
903 )
904 }
905
906 #[test]
907 fn describes_itself_for_non_matching_result() -> googletest::Result<()> {
908 let matcher = StrMatcher::with_default_config("A string");
909 verify_that!(
910 Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
911 displays_as(eq("isn't equal to \"A string\""))
912 )
913 }
914
915 #[test]
916 fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> googletest::Result<()>
917 {
918 let matcher = StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
919 verify_that!(
920 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
921 displays_as(eq("is equal to \"A string\" (ignoring leading whitespace)"))
922 )
923 }
924
925 #[test]
926 fn describes_itself_for_non_matching_result_ignoring_leading_whitespace(
927 ) -> googletest::Result<()> {
928 let matcher = StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
929 verify_that!(
930 Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
931 displays_as(eq("isn't equal to \"A string\" (ignoring leading whitespace)"))
932 )
933 }
934
935 #[test]
936 fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> googletest::Result<()>
937 {
938 let matcher = StrMatcher::with_default_config("A string").ignoring_trailing_whitespace();
939 verify_that!(
940 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
941 displays_as(eq("is equal to \"A string\" (ignoring trailing whitespace)"))
942 )
943 }
944
945 #[test]
946 fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace(
947 ) -> googletest::Result<()> {
948 let matcher = StrMatcher::with_default_config("A string").ignoring_outer_whitespace();
949 verify_that!(
950 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
951 displays_as(eq("is equal to \"A string\" (ignoring leading and trailing whitespace)"))
952 )
953 }
954
955 #[test]
956 fn describes_itself_for_matching_result_ignoring_ascii_case() -> googletest::Result<()> {
957 let matcher = StrMatcher::with_default_config("A string").ignoring_ascii_case();
958 verify_that!(
959 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
960 displays_as(eq("is equal to \"A string\" (ignoring ASCII case)"))
961 )
962 }
963
964 #[test]
965 fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace(
966 ) -> googletest::Result<()> {
967 let matcher = StrMatcher::with_default_config("A string")
968 .ignoring_leading_whitespace()
969 .ignoring_ascii_case();
970 verify_that!(
971 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
972 displays_as(eq(
973 "is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)"
974 ))
975 )
976 }
977
978 #[test]
979 fn describes_itself_for_matching_result_in_contains_mode() -> googletest::Result<()> {
980 let matcher = contains_substring("A string");
981 verify_that!(
982 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
983 displays_as(eq("contains a substring \"A string\""))
984 )
985 }
986
987 #[test]
988 fn describes_itself_for_non_matching_result_in_contains_mode() -> googletest::Result<()> {
989 let matcher = contains_substring("A string");
990 verify_that!(
991 Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
992 displays_as(eq("does not contain a substring \"A string\""))
993 )
994 }
995
996 #[test]
997 fn describes_itself_with_count_number() -> googletest::Result<()> {
998 let matcher = contains_substring("A string").times(gt(2));
999 verify_that!(
1000 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
1001 displays_as(eq("contains a substring \"A string\" (count is greater than 2)"))
1002 )
1003 }
1004
1005 #[test]
1006 fn describes_itself_for_matching_result_in_starts_with_mode() -> googletest::Result<()> {
1007 let matcher = starts_with("A string");
1008 verify_that!(
1009 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
1010 displays_as(eq("starts with prefix \"A string\""))
1011 )
1012 }
1013
1014 #[test]
1015 fn describes_itself_for_non_matching_result_in_starts_with_mode() -> googletest::Result<()> {
1016 let matcher = starts_with("A string");
1017 verify_that!(
1018 Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
1019 displays_as(eq("does not start with \"A string\""))
1020 )
1021 }
1022
1023 #[test]
1024 fn describes_itself_for_matching_result_in_ends_with_mode() -> googletest::Result<()> {
1025 let matcher = ends_with("A string");
1026 verify_that!(
1027 Matcher::<&str>::describe(&matcher, MatcherResult::Match),
1028 displays_as(eq("ends with suffix \"A string\""))
1029 )
1030 }
1031
1032 #[test]
1033 fn describes_itself_for_non_matching_result_in_ends_with_mode() -> googletest::Result<()> {
1034 let matcher = ends_with("A string");
1035 verify_that!(
1036 Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
1037 displays_as(eq("does not end with \"A string\""))
1038 )
1039 }
1040
1041 #[test]
1042 fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> googletest::Result<()>
1043 {
1044 let result = verify_that!(
1045 indoc!(
1046 "
1047 First line
1048 Second line
1049 Third line
1050 "
1051 ),
1052 starts_with(indoc!(
1053 "
1054 First line
1055 Second lines
1056 Third line
1057 "
1058 ))
1059 );
1060
1061 verify_that!(
1062 result,
1063 err(displays_as(contains_substring(
1064 "\
1065 First line
1066 -Second line
1067 +Second lines
1068 Third line"
1069 )))
1070 )
1071 }
1072
1073 #[test]
1074 fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string(
1075 ) -> googletest::Result<()> {
1076 let result = verify_that!(
1077 indoc!(
1078 "
1079 First line
1080 Second line
1081 Third line
1082 Fourth line
1083 "
1084 ),
1085 starts_with(indoc!(
1086 "
1087 First line
1088 Second lines
1089 Third line
1090 "
1091 ))
1092 );
1093
1094 verify_that!(
1095 result,
1096 err(displays_as(contains_substring(
1097 "
1098 First line
1099 -Second line
1100 +Second lines
1101 Third line
1102 <---- remaining lines omitted ---->"
1103 )))
1104 )
1105 }
1106
1107 #[test]
1108 fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line(
1109 ) -> googletest::Result<()> {
1110 let result = verify_that!(
1111 indoc!(
1112 "
1113 First line
1114 Second line
1115 Third line
1116 "
1117 ),
1118 starts_with(indoc!(
1119 "
1120 First line
1121 Second lines
1122 "
1123 ))
1124 );
1125
1126 verify_that!(
1127 result,
1128 err(displays_as(contains_substring(
1129 "\
1130 First line
1131 -Second line
1132 +Second lines
1133 <---- remaining lines omitted ---->"
1134 )))
1135 )
1136 }
1137
1138 #[test]
1139 fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string(
1140 ) -> googletest::Result<()> {
1141 let result = verify_that!(
1142 indoc!(
1143 "
1144 First line
1145 Second line
1146 Third line
1147 Fourth line
1148 "
1149 ),
1150 ends_with(indoc!(
1151 "
1152 Second line
1153 Third lines
1154 Fourth line
1155 "
1156 ))
1157 );
1158
1159 verify_that!(
1160 result,
1161 err(displays_as(contains_substring(
1162 "
1163 Difference(-actual / +expected):
1164 <---- remaining lines omitted ---->
1165 Second line
1166 -Third line
1167 +Third lines
1168 Fourth line"
1169 )))
1170 )
1171 }
1172
1173 #[test]
1174 fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string(
1175 ) -> googletest::Result<()> {
1176 let result = verify_that!(
1177 indoc!(
1178 "
1179 First line
1180 Second line
1181 Third line
1182 Fourth line
1183 Fifth line
1184 "
1185 ),
1186 contains_substring(indoc!(
1187 "
1188 Second line
1189 Third lines
1190 Fourth line
1191 "
1192 ))
1193 );
1194
1195 verify_that!(
1196 result,
1197 err(displays_as(contains_substring(
1198 "
1199 Difference(-actual / +expected):
1200 <---- remaining lines omitted ---->
1201 Second line
1202 -Third line
1203 +Third lines
1204 Fourth line
1205 <---- remaining lines omitted ---->"
1206 )))
1207 )
1208 }
1209
1210 #[test]
1211 fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete(
1212 ) -> googletest::Result<()> {
1213 let result = verify_that!(
1214 indoc!(
1215 "
1216 First line
1217 Second line
1218 Third line
1219 Fourth line
1220 Fifth line
1221 "
1222 ),
1223 contains_substring(indoc!(
1224 "
1225 line
1226 Third line
1227 Foorth line
1228 Fifth"
1229 ))
1230 );
1231
1232 verify_that!(
1233 result,
1234 err(displays_as(contains_substring(
1235 "
1236 Difference(-actual / +expected):
1237 <---- remaining lines omitted ---->
1238 -Second line
1239 +line
1240 Third line
1241 -Fourth line
1242 +Foorth line
1243 -Fifth line
1244 +Fifth
1245 <---- remaining lines omitted ---->"
1246 )))
1247 )
1248 }
1249
1250 #[test]
1251 fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string(
1252 ) -> googletest::Result<()> {
1253 let result = verify_that!(
1254 indoc!(
1255 "
1256 First line
1257 Second line
1258 Third line
1259 Fourth line
1260 "
1261 ),
1262 eq(indoc!(
1263 "
1264 First line
1265 Second lines
1266 Third line
1267 "
1268 ))
1269 );
1270
1271 verify_that!(
1272 result,
1273 err(displays_as(contains_substring(
1274 "\
1275 First line
1276 -Second line
1277 +Second lines
1278 Third line
1279 -Fourth line"
1280 )))
1281 )
1282 }
1283
1284 #[test]
1285 fn match_explanation_does_not_show_diff_if_actual_value_is_single_line(
1286 ) -> googletest::Result<()> {
1287 let result = verify_that!(
1288 "First line",
1289 starts_with(indoc!(
1290 "
1291 Second line
1292 Third line
1293 "
1294 ))
1295 );
1296
1297 verify_that!(
1298 result,
1299 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
1300 )
1301 }
1302
1303 #[test]
1304 fn match_explanation_does_not_show_diff_if_expected_value_is_single_line(
1305 ) -> googletest::Result<()> {
1306 let result = verify_that!(
1307 indoc!(
1308 "
1309 First line
1310 Second line
1311 Third line
1312 "
1313 ),
1314 starts_with("Second line")
1315 );
1316
1317 verify_that!(
1318 result,
1319 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
1320 )
1321 }
1322}