From c477238842590adabf0f669c71ecc0c33c614ff7 Mon Sep 17 00:00:00 2001 From: XAMeLeOH <14215665+XAMeLeOH@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:19:21 +0100 Subject: [PATCH 1/2] Extended support for StrEqualityMatcher class and StrEq/StrNe/StrCaseEq/StrCaseNe functions for strings based on the wide characters Extended StringLike to support wide character types Added more tests Restricted possible char types --- googlemock/include/gmock/gmock-matchers.h | 151 ++++--- .../test/gmock-matchers-comparisons_test.cc | 373 ++++++++++++++++++ googletest/include/gtest/gtest-matchers.h | 60 ++- 3 files changed, 497 insertions(+), 87 deletions(-) diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 60ddeabc8..1c15e8242 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -905,16 +905,34 @@ bool CaseInsensitiveStringEquals(const StringType& s1, const StringType& s2) { return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); } +// Case insensitive comparison support based on the CharT + +template +struct ci_matcher_supported : std::false_type {}; + +template<> +struct ci_matcher_supported : std::true_type {}; + +template<> +struct ci_matcher_supported : std::true_type {}; + // String matchers. // Implements equality-based string matchers like StrEq, StrCaseNe, and etc. -template +// StrCaseEq and StrCaseNe are only supported for char and wchar_t strings. +template + >, + int> = 0 + > class [[nodiscard]] StrEqualityMatcher { public: - StrEqualityMatcher(StringType str, bool expect_eq, bool case_sensitive) + StrEqualityMatcher(StringType str, bool expect_eq) : string_(std::move(str)), - expect_eq_(expect_eq), - case_sensitive_(case_sensitive) {} + expect_eq_(expect_eq) {} #if GTEST_INTERNAL_HAS_STRING_VIEW bool MatchAndExplain(const internal::StringView& s, @@ -947,8 +965,7 @@ class [[nodiscard]] StrEqualityMatcher { bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType s2(s); - const bool eq = case_sensitive_ ? s2 == string_ - : CaseInsensitiveStringEquals(s2, string_); + const bool eq = MatchStrings(s2, ci_matcher_supported{}); return expect_eq_ == eq; } @@ -961,10 +978,21 @@ class [[nodiscard]] StrEqualityMatcher { } private: + template + bool MatchStrings(const S& s2, std::true_type) const { + return CaseSensitive::value ? s2 == string_ + : CaseInsensitiveStringEquals(s2, string_); + } + + template + bool MatchStrings(const S& s2, std::false_type) const { + return s2 == string_; + } + void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { *os << (expect_eq ? "is " : "isn't "); *os << "equal to "; - if (!case_sensitive_) { + if (!CaseSensitive::value) { *os << "(ignoring case) "; } UniversalPrint(string_, os); @@ -972,7 +1000,6 @@ class [[nodiscard]] StrEqualityMatcher { const StringType string_; const bool expect_eq_; - const bool case_sensitive_; }; // Implements the polymorphic HasSubstr(substring) matcher, which @@ -4780,117 +4807,71 @@ internal::ResultOfMatcher ResultOf( // String matchers. // Matches a string equal to str. -template -PolymorphicMatcher> StrEq( +template > +PolymorphicMatcher>> StrEq( const internal::StringLike& str) { return MakePolymorphicMatcher( - internal::StrEqualityMatcher(std::string(str), true, true)); + internal::StrEqualityMatcher>(std::basic_string(str), true)); } // Matches a string not equal to str. -template -PolymorphicMatcher> StrNe( +template > +PolymorphicMatcher>> StrNe( const internal::StringLike& str) { return MakePolymorphicMatcher( - internal::StrEqualityMatcher(std::string(str), false, true)); + internal::StrEqualityMatcher>(std::basic_string(str), false)); } // Matches a string equal to str, ignoring case. -template -PolymorphicMatcher> StrCaseEq( +template > +PolymorphicMatcher, std::false_type>> StrCaseEq( const internal::StringLike& str) { return MakePolymorphicMatcher( - internal::StrEqualityMatcher(std::string(str), true, false)); + internal::StrEqualityMatcher, std::false_type>( + std::basic_string(str), true)); } // Matches a string not equal to str, ignoring case. -template -PolymorphicMatcher> StrCaseNe( +template > +PolymorphicMatcher, std::false_type>> StrCaseNe( const internal::StringLike& str) { - return MakePolymorphicMatcher(internal::StrEqualityMatcher( - std::string(str), false, false)); + return MakePolymorphicMatcher( + internal::StrEqualityMatcher, std::false_type>( + std::basic_string(str), false)); } // Creates a matcher that matches any string, std::string, or C string // that contains the given substring. -template -PolymorphicMatcher> HasSubstr( +template > +PolymorphicMatcher>> HasSubstr( const internal::StringLike& substring) { return MakePolymorphicMatcher( - internal::HasSubstrMatcher(std::string(substring))); + internal::HasSubstrMatcher>(std::basic_string(substring))); } // Matches a string that starts with 'prefix' (case-sensitive). -template -PolymorphicMatcher> StartsWith( +template > +PolymorphicMatcher>> StartsWith( const internal::StringLike& prefix) { return MakePolymorphicMatcher( - internal::StartsWithMatcher(std::string(prefix))); + internal::StartsWithMatcher>(std::basic_string(prefix))); } // Matches a string that ends with 'suffix' (case-sensitive). -template -PolymorphicMatcher> EndsWith( +template > +PolymorphicMatcher>> EndsWith( const internal::StringLike& suffix) { return MakePolymorphicMatcher( - internal::EndsWithMatcher(std::string(suffix))); + internal::EndsWithMatcher>(std::basic_string(suffix))); } -#if GTEST_HAS_STD_WSTRING -// Wide string matchers. - -// Matches a string equal to str. -inline PolymorphicMatcher> StrEq( - const std::wstring& str) { - return MakePolymorphicMatcher( - internal::StrEqualityMatcher(str, true, true)); -} - -// Matches a string not equal to str. -inline PolymorphicMatcher> StrNe( - const std::wstring& str) { - return MakePolymorphicMatcher( - internal::StrEqualityMatcher(str, false, true)); -} - -// Matches a string equal to str, ignoring case. -inline PolymorphicMatcher> StrCaseEq( - const std::wstring& str) { - return MakePolymorphicMatcher( - internal::StrEqualityMatcher(str, true, false)); -} - -// Matches a string not equal to str, ignoring case. -inline PolymorphicMatcher> StrCaseNe( - const std::wstring& str) { - return MakePolymorphicMatcher( - internal::StrEqualityMatcher(str, false, false)); -} - -// Creates a matcher that matches any ::wstring, std::wstring, or C wide string -// that contains the given substring. -inline PolymorphicMatcher> HasSubstr( - const std::wstring& substring) { - return MakePolymorphicMatcher( - internal::HasSubstrMatcher(substring)); -} - -// Matches a string that starts with 'prefix' (case-sensitive). -inline PolymorphicMatcher> StartsWith( - const std::wstring& prefix) { - return MakePolymorphicMatcher( - internal::StartsWithMatcher(prefix)); -} - -// Matches a string that ends with 'suffix' (case-sensitive). -inline PolymorphicMatcher> EndsWith( - const std::wstring& suffix) { - return MakePolymorphicMatcher( - internal::EndsWithMatcher(suffix)); -} - -#endif // GTEST_HAS_STD_WSTRING - // Creates a polymorphic matcher that matches a 2-tuple where the // first field == the second field. inline internal::Eq2Matcher Eq() { return internal::Eq2Matcher(); } diff --git a/googlemock/test/gmock-matchers-comparisons_test.cc b/googlemock/test/gmock-matchers-comparisons_test.cc index 3a21a9a7e..a94febf4e 100644 --- a/googlemock/test/gmock-matchers-comparisons_test.cc +++ b/googlemock/test/gmock-matchers-comparisons_test.cc @@ -1270,6 +1270,10 @@ TEST(StrEqTest, MatchesEqualString) { EXPECT_TRUE(m2.Matches("Hello")); EXPECT_FALSE(m2.Matches("Hi")); + Matcher m4 = StrEq("Hello"); + EXPECT_TRUE(m4.Matches("Hello")); + EXPECT_FALSE(m4.Matches("Hi")); + #if GTEST_INTERNAL_HAS_STRING_VIEW Matcher m3 = StrEq(internal::StringView("Hello")); @@ -1282,6 +1286,10 @@ TEST(StrEqTest, MatchesEqualString) { EXPECT_TRUE(m_empty.Matches(internal::StringView())); EXPECT_FALSE(m_empty.Matches(internal::StringView("hello"))); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + Matcher m5 = StrEq(std::string_view("Hello")); + EXPECT_TRUE(m5.Matches("Hello")); + EXPECT_FALSE(m5.Matches("Hi")); } TEST(StrEqTest, CanDescribeSelf) { @@ -1298,6 +1306,64 @@ TEST(StrEqTest, CanDescribeSelf) { EXPECT_EQ("is equal to \"\\012\\045\\0\\08\\0\\0\"", Describe(m3)); } +TEST(StrEqTest, AllowsWideChars) { + Matcher mu16c = StrEq(u"Hello"); + EXPECT_TRUE(mu16c.Matches(u"Hello")); + EXPECT_FALSE(mu16c.Matches(u"hello")); + EXPECT_FALSE(mu16c.Matches(nullptr)); + + Matcher mu16 = StrEq(std::u16string(u"Hello")); + EXPECT_TRUE(mu16.Matches(std::u16string(u"Hello"))); + EXPECT_FALSE(mu16.Matches(std::u16string(u"hello"))); + + Matcher mu16sv = StrEq(std::u16string_view(u"Hello")); + EXPECT_TRUE(mu16sv.Matches(std::u16string_view(u"Hello"))); + EXPECT_FALSE(mu16sv.Matches(std::u16string_view(u"hello"))); + + Matcher mu32c = StrEq(U"Hello"); + EXPECT_TRUE(mu32c.Matches(U"Hello")); + EXPECT_FALSE(mu32c.Matches(U"hello")); + EXPECT_FALSE(mu32c.Matches(nullptr)); + + Matcher mu32 = StrEq(std::u32string(U"Hello")); + EXPECT_TRUE(mu32.Matches(std::u32string(U"Hello"))); + EXPECT_FALSE(mu32.Matches(std::u32string(U"hello"))); + + Matcher mu32sv = StrEq(std::u32string_view(U"Hello")); + EXPECT_TRUE(mu32sv.Matches(std::u32string_view(U"Hello"))); + EXPECT_FALSE(mu32sv.Matches(std::u32string_view(U"hello"))); + +#ifdef __cpp_lib_char8_t + Matcher mu8c = StrEq(u8"Hello"); + EXPECT_TRUE(mu8c.Matches("Hello")); + EXPECT_FALSE(mu8c.Matches("hello")); + EXPECT_FALSE(mu8c.Matches(nullptr)); + + Matcher mu8 = StrEq(std::u8string(u8"Hello")); + EXPECT_TRUE(mu8.Matches(std::u8string("Hello"))); + EXPECT_FALSE(mu8.Matches(std::u8string("hello"))); + + Matcher mu8sv = StrEq(std::u8string_view(u8"Hello")); + EXPECT_TRUE(mu8sv.Matches(std::u8string_view("Hello"))); + EXPECT_FALSE(mu8sv.Matches(std::u8string_view("hello"))); +#endif // __cpp_lib_char8_t + +#if GTEST_HAS_STD_WSTRING + Matcher mwc = StrEq(L"Hello"); + EXPECT_TRUE(mwc.Matches(L"Hello")); + EXPECT_FALSE(mwc.Matches(L"hello")); + EXPECT_FALSE(mwc.Matches(nullptr)); + + Matcher mw = StrEq(std::wstring(L"Hello")); + EXPECT_TRUE(mw.Matches(std::wstring(L"Hello"))); + EXPECT_FALSE(mw.Matches(std::wstring(L"hello"))); + + Matcher mwvu = StrEq(std::wstring_view(L"Hello")); + EXPECT_TRUE(mwvu.Matches(std::wstring_view(L"Hello"))); + EXPECT_FALSE(mwvu.Matches(std::wstring_view(L"hello"))); +#endif // GTEST_HAS_STD_WSTRING +} + TEST(StrNeTest, MatchesUnequalString) { Matcher m = StrNe("Hello"); EXPECT_TRUE(m.Matches("")); @@ -1314,6 +1380,10 @@ TEST(StrNeTest, MatchesUnequalString) { EXPECT_TRUE(m3.Matches(internal::StringView())); EXPECT_FALSE(m3.Matches(internal::StringView("Hello"))); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + Matcher m4 = StrNe(std::string_view("Hello")); + EXPECT_TRUE(m4.Matches("hello")); + EXPECT_FALSE(m4.Matches("Hello")); } TEST(StrNeTest, CanDescribeSelf) { @@ -1321,6 +1391,64 @@ TEST(StrNeTest, CanDescribeSelf) { EXPECT_EQ("isn't equal to \"Hi\"", Describe(m)); } +TEST(StrNeTest, AllowsWideChars) { + Matcher mu16 = StrNe(u"Hello"); + EXPECT_TRUE(mu16.Matches(u"")); + EXPECT_TRUE(mu16.Matches(nullptr)); + EXPECT_FALSE(mu16.Matches(u"Hello")); + + Matcher mu16s = StrNe(std::u16string(u"Hello")); + EXPECT_TRUE(mu16s.Matches(u"")); + EXPECT_FALSE(mu16s.Matches(u"Hello")); + + Matcher mu16sv = StrNe(std::u16string_view(u"Hello")); + EXPECT_TRUE(mu16sv.Matches(u"")); + EXPECT_FALSE(mu16sv.Matches(u"Hello")); + + Matcher mu32 = StrNe(U"Hello"); + EXPECT_TRUE(mu32.Matches(U"")); + EXPECT_TRUE(mu32.Matches(nullptr)); + EXPECT_FALSE(mu32.Matches(U"Hello")); + + Matcher mu32s = StrNe(std::u32string(U"Hello")); + EXPECT_TRUE(mu32s.Matches(U"")); + EXPECT_FALSE(mu32s.Matches(U"Hello")); + + Matcher mu32sv = StrNe(std::u32string_view(U"Hello")); + EXPECT_TRUE(mu32sv.Matches(U"")); + EXPECT_FALSE(mu32sv.Matches(U"Hello")); + +#ifdef __cpp_lib_char8_t + Matcher mu8 = StrNe(u8"Hello"); + EXPECT_TRUE(mu8.Matches(u8"")); + EXPECT_TRUE(mu8.Matches(nullptr)); + EXPECT_FALSE(mu8.Matches(u8"Hello")); + + Matcher mu8s = StrNe(std::u8string(u8"Hello")); + EXPECT_TRUE(mu8s.Matches(u8"")); + EXPECT_FALSE(mu8s.Matches(u8"Hello")); + + Matcher mu8sv = StrNe(std::u8string_view(u8"Hello")); + EXPECT_TRUE(mu8sv.Matches(u8"")); + EXPECT_FALSE(mu8sv.Matches(u8"Hello")); +#endif // __cpp_lib_char8_t + +#if GTEST_HAS_STD_WSTRING + Matcher mw = StrNe(L"Hello"); + EXPECT_TRUE(mw.Matches(L"")); + EXPECT_TRUE(mw.Matches(nullptr)); + EXPECT_FALSE(mw.Matches(L"Hello")); + + Matcher mws = StrNe(std::wstring(L"Hello")); + EXPECT_TRUE(mws.Matches(L"")); + EXPECT_FALSE(mws.Matches(L"Hello")); + + Matcher mwsv = StrNe(std::wstring_view(L"Hello")); + EXPECT_TRUE(mwsv.Matches(L"")); + EXPECT_FALSE(mwsv.Matches(L"Hello")); +#endif // GTEST_HAS_STD_WSTRING +} + TEST(StrCaseEqTest, MatchesEqualStringIgnoringCase) { Matcher m = StrCaseEq(std::string("Hello")); EXPECT_TRUE(m.Matches("Hello")); @@ -1340,6 +1468,10 @@ TEST(StrCaseEqTest, MatchesEqualStringIgnoringCase) { EXPECT_FALSE(m3.Matches(internal::StringView("Hi"))); EXPECT_FALSE(m3.Matches(internal::StringView())); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + Matcher m4 = StrCaseEq(std::string_view("Hello")); + EXPECT_TRUE(m4.Matches("hello")); + EXPECT_FALSE(m4.Matches("Hi")); } TEST(StrCaseEqTest, MatchesEqualStringWith0IgnoringCase) { @@ -1372,6 +1504,23 @@ TEST(StrCaseEqTest, CanDescribeSelf) { EXPECT_EQ("is equal to (ignoring case) \"Hi\"", Describe(m)); } +TEST(StrCaseEqTest, AllowsWideChars) { +#if GTEST_HAS_STD_WSTRING + Matcher mw = StrCaseEq(L"Hello"); + EXPECT_TRUE(mw.Matches(L"hello")); + EXPECT_FALSE(mw.Matches(L"Hi")); + EXPECT_FALSE(mw.Matches(nullptr)); + + Matcher mws = StrCaseEq(std::wstring(L"Hello")); + EXPECT_TRUE(mws.Matches(L"hello")); + EXPECT_FALSE(mws.Matches(L"Hi")); + + Matcher mwsv = StrCaseEq(std::wstring_view(L"Hello")); + EXPECT_TRUE(mwsv.Matches(L"hello")); + EXPECT_FALSE(mwsv.Matches(L"Hi")); +#endif // GTEST_HAS_STD_WSTRING +} + TEST(StrCaseNeTest, MatchesUnequalStringIgnoringCase) { Matcher m = StrCaseNe("Hello"); EXPECT_TRUE(m.Matches("Hi")); @@ -1391,6 +1540,11 @@ TEST(StrCaseNeTest, MatchesUnequalStringIgnoringCase) { EXPECT_FALSE(m3.Matches(internal::StringView("Hello"))); EXPECT_FALSE(m3.Matches(internal::StringView("hello"))); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + Matcher m4 = StrCaseNe(std::string_view("Hello")); + EXPECT_TRUE(m4.Matches("Hi")); + EXPECT_FALSE(m4.Matches("Hello")); + EXPECT_FALSE(m4.Matches("hello")); } TEST(StrCaseNeTest, CanDescribeSelf) { @@ -1398,6 +1552,25 @@ TEST(StrCaseNeTest, CanDescribeSelf) { EXPECT_EQ("isn't equal to (ignoring case) \"Hi\"", Describe(m)); } +TEST(StrCaseNeTest, AllowsWideChars) { +#if GTEST_HAS_STD_WSTRING + Matcher mw = StrCaseNe(L"Hello"); + EXPECT_TRUE(mw.Matches(L"Hi")); + EXPECT_TRUE(mw.Matches(nullptr)); + EXPECT_FALSE(mw.Matches(L"hello")); + + Matcher mws = StrCaseNe(std::wstring(L"Hello")); + EXPECT_TRUE(mws.Matches(L"Hi")); + EXPECT_FALSE(mws.Matches(L"Hello")); + EXPECT_FALSE(mws.Matches(L"hello")); + + Matcher mwsv = StrCaseNe(std::wstring_view(L"Hello")); + EXPECT_TRUE(mwsv.Matches(L"Hi")); + EXPECT_FALSE(mwsv.Matches(L"Hello")); + EXPECT_FALSE(mwsv.Matches(L"hello")); +#endif // GTEST_HAS_STD_WSTRING +} + // Tests that HasSubstr() works for matching string-typed values. TEST(HasSubstrTest, WorksForStringClasses) { const Matcher m1 = HasSubstr("foo"); @@ -1452,12 +1625,82 @@ TEST(HasSubstrTest, WorksForStringViewClasses) { } #endif // GTEST_INTERNAL_HAS_STRING_VIEW +TEST(HasSubstrTest, WorksForStdStringViewClasses) { + const Matcher m1 = HasSubstr(std::string_view("foo")); + EXPECT_TRUE(m1.Matches("I love food.")); + EXPECT_FALSE(m1.Matches("tofo")); + EXPECT_FALSE(m1.Matches(std::string_view())); + + const Matcher m2 = HasSubstr(std::string_view("")); + EXPECT_TRUE(m2.Matches("foo")); + EXPECT_TRUE(m2.Matches("")); + EXPECT_TRUE(m2.Matches(std::string_view())); +} + // Tests that HasSubstr(s) describes itself properly. TEST(HasSubstrTest, CanDescribeSelf) { Matcher m = HasSubstr("foo\n\""); EXPECT_EQ("has substring \"foo\\n\\\"\"", Describe(m)); } +TEST(HasSubstrTest, AllowsWideChars) { + Matcher m16 = HasSubstr(u"foo"); + EXPECT_TRUE(m16.Matches(u"I love food")); + EXPECT_FALSE(m16.Matches(nullptr)); + EXPECT_FALSE(m16.Matches(u"hello")); + + Matcher m16s = HasSubstr(std::u16string(u"foo")); + EXPECT_TRUE(m16s.Matches(u"I love food")); + EXPECT_FALSE(m16s.Matches(u"hello")); + + Matcher m16sv = HasSubstr(std::u16string_view(u"foo")); + EXPECT_TRUE(m16sv.Matches(u"I love food")); + EXPECT_FALSE(m16sv.Matches(u"hello")); + + Matcher m32 = HasSubstr(U"foo"); + EXPECT_TRUE(m32.Matches(U"I love food")); + EXPECT_FALSE(m32.Matches(nullptr)); + EXPECT_FALSE(m32.Matches(U"hello")); + + Matcher m32s = HasSubstr(std::u32string(U"foo")); + EXPECT_TRUE(m32s.Matches(U"I love food")); + EXPECT_FALSE(m32s.Matches(U"hello")); + + Matcher m32sv = HasSubstr(std::u32string_view(U"foo")); + EXPECT_TRUE(m32sv.Matches(U"I love food")); + EXPECT_FALSE(m32sv.Matches(U"hello")); + +#ifdef __cpp_lib_char8_t + Matcher m8 = HasSubstr(u8"foo"); + EXPECT_TRUE(m8.Matches(u8"I love food")); + EXPECT_FALSE(m8.Matches(nullptr)); + EXPECT_FALSE(m8.Matches(u8"hello")); + + Matcher m8s = HasSubstr(std::u8string(u8"foo")); + EXPECT_TRUE(m8s.Matches(u8"I love food")); + EXPECT_FALSE(m8s.Matches(u8"hello")); + + Matcher m8sv = HasSubstr(std::u8string_view(u8"foo")); + EXPECT_TRUE(m8sv.Matches(u8"I love food")); + EXPECT_FALSE(m8sv.Matches(u8"hello")); +#endif // __cpp_lib_char8_t + +#if GTEST_HAS_STD_WSTRING + Matcher mw = HasSubstr(L"foo"); + EXPECT_TRUE(mw.Matches(L"I love food")); + EXPECT_FALSE(mw.Matches(nullptr)); + EXPECT_FALSE(mw.Matches(L"hello")); + + Matcher mws = HasSubstr(std::wstring(L"foo")); + EXPECT_TRUE(mws.Matches(L"I love food")); + EXPECT_FALSE(mws.Matches(L"hello")); + + Matcher mwsv = HasSubstr(std::wstring_view(L"foo")); + EXPECT_TRUE(mwsv.Matches(L"I love food")); + EXPECT_FALSE(mwsv.Matches(L"hello")); +#endif // GTEST_HAS_STD_WSTRING +} + INSTANTIATE_GTEST_MATCHER_TEST_P(KeyTest); TEST(KeyTest, CanDescribeSelf) { @@ -1856,6 +2099,13 @@ TEST(StartsWithTest, MatchesStringWithGivenPrefix) { EXPECT_TRUE(m_empty.Matches(internal::StringView(""))); EXPECT_TRUE(m_empty.Matches(internal::StringView("not empty"))); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + const Matcher m3 = StartsWith(std::string_view("Hi")); + EXPECT_TRUE(m3.Matches("Hi")); + EXPECT_TRUE(m3.Matches("Hi Hi!")); + EXPECT_TRUE(m3.Matches("High")); + EXPECT_FALSE(m3.Matches("H")); + EXPECT_FALSE(m3.Matches(" Hi")); } TEST(StartsWithTest, CanDescribeSelf) { @@ -1872,6 +2122,64 @@ TEST(StartsWithTest, WorksWithStringMatcherOnStringViewMatchee) { #endif // GTEST_INTERNAL_HAS_STRING_VIEW } +TEST(StartsWithTest, AllowsWideChars) { + const Matcher m16 = StartsWith(u"Hi"); + EXPECT_TRUE(m16.Matches(u"Hi")); + EXPECT_FALSE(m16.Matches(u"hi")); + EXPECT_FALSE(m16.Matches(nullptr)); + + const Matcher m16s = StartsWith(std::u16string(u"Hi")); + EXPECT_TRUE(m16s.Matches(u"Hi")); + EXPECT_FALSE(m16s.Matches(u"hi")); + + const Matcher m16sv = StartsWith(std::u16string_view(u"Hi")); + EXPECT_TRUE(m16sv.Matches(u"Hi")); + EXPECT_FALSE(m16sv.Matches(u"hi")); + + const Matcher m32 = StartsWith(U"Hi"); + EXPECT_TRUE(m32.Matches(U"Hi")); + EXPECT_FALSE(m32.Matches(U"hi")); + EXPECT_FALSE(m32.Matches(nullptr)); + + const Matcher m32s = StartsWith(std::u32string(U"Hi")); + EXPECT_TRUE(m32s.Matches(U"Hi")); + EXPECT_FALSE(m32s.Matches(U"hi")); + + const Matcher m32sv = StartsWith(std::u32string_view(U"Hi")); + EXPECT_TRUE(m32sv.Matches(U"Hi")); + EXPECT_FALSE(m32sv.Matches(U"hi")); + +#ifdef __cpp_lib_char8_t + const Matcher m8 = StartsWith(u8"Hi"); + EXPECT_TRUE(m8.Matches(u8"Hi")); + EXPECT_FALSE(m8.Matches(u8"hi")); + EXPECT_FALSE(m8.Matches(nullptr)); + + const Matcher m8s = StartsWith(std::u8string(u8"Hi")); + EXPECT_TRUE(m8s.Matches(u8"Hi")); + EXPECT_FALSE(m8s.Matches(u8"hi")); + + const Matcher m8sv = StartsWith(std::u8string_view(u8"Hi")); + EXPECT_TRUE(m8sv.Matches(u8"Hi")); + EXPECT_FALSE(m8sv.Matches(u8"hi")); +#endif // __cpp_lib_char8_t + +#if GTEST_HAS_STD_WSTRING + const Matcher mw = StartsWith(L"Hi"); + EXPECT_TRUE(mw.Matches(L"Hi")); + EXPECT_FALSE(mw.Matches(L"hi")); + EXPECT_FALSE(mw.Matches(nullptr)); + + const Matcher mws = StartsWith(std::wstring(L"Hi")); + EXPECT_TRUE(mws.Matches(L"Hi")); + EXPECT_FALSE(mws.Matches(L"hi")); + + const Matcher mwsv = StartsWith(std::wstring_view(L"Hi")); + EXPECT_TRUE(mwsv.Matches(L"Hi")); + EXPECT_FALSE(mwsv.Matches(L"hi")); +#endif // GTEST_HAS_STD_WSTRING +} + // Tests EndsWith(s). TEST(EndsWithTest, MatchesStringWithGivenSuffix) { @@ -1895,6 +2203,13 @@ TEST(EndsWithTest, MatchesStringWithGivenSuffix) { EXPECT_TRUE(m4.Matches(internal::StringView())); EXPECT_TRUE(m4.Matches(internal::StringView(""))); #endif // GTEST_INTERNAL_HAS_STRING_VIEW + + const Matcher m5 = EndsWith(std::string_view("Hi")); + EXPECT_TRUE(m5.Matches("Hi")); + EXPECT_TRUE(m5.Matches("Wow Hi Hi")); + EXPECT_TRUE(m5.Matches("Super Hi")); + EXPECT_FALSE(m5.Matches("i")); + EXPECT_FALSE(m5.Matches("Hi ")); } TEST(EndsWithTest, CanDescribeSelf) { @@ -1902,6 +2217,64 @@ TEST(EndsWithTest, CanDescribeSelf) { EXPECT_EQ("ends with \"Hi\"", Describe(m)); } +TEST(EndsWithTest, AllowsWideChars) { + const Matcher m16 = EndsWith(u"Hi"); + EXPECT_TRUE(m16.Matches(u"Wow Hi Hi")); + EXPECT_FALSE(m16.Matches(u"hi")); + EXPECT_FALSE(m16.Matches(nullptr)); + + const Matcher m16s = EndsWith(std::u16string(u"Hi")); + EXPECT_TRUE(m16s.Matches(u"Wow Hi Hi")); + EXPECT_FALSE(m16s.Matches(u"hi")); + + const Matcher m16sv = EndsWith(std::u16string_view(u"Hi")); + EXPECT_TRUE(m16sv.Matches(u"Wow Hi Hi")); + EXPECT_FALSE(m16sv.Matches(u"hi")); + + const Matcher m32 = EndsWith(U"Hi"); + EXPECT_TRUE(m32.Matches(U"Wow Hi Hi")); + EXPECT_FALSE(m32.Matches(U"hi")); + EXPECT_FALSE(m32.Matches(nullptr)); + + const Matcher m32s = EndsWith(std::u32string(U"Hi")); + EXPECT_TRUE(m32s.Matches(U"Wow Hi Hi")); + EXPECT_FALSE(m32s.Matches(U"hi")); + + const Matcher m32sv = EndsWith(std::u32string_view(U"Hi")); + EXPECT_TRUE(m32sv.Matches(U"Wow Hi Hi")); + EXPECT_FALSE(m32sv.Matches(U"hi")); + +#ifdef __cpp_lib_char8_t + const Matcher m8 = EndsWith(u8"Hi"); + EXPECT_TRUE(m8.Matches(u8"Wow Hi Hi")); + EXPECT_FALSE(m8.Matches(u8"hi")); + EXPECT_FALSE(m8.Matches(nullptr)); + + const Matcher m8s = EndsWith(std::u8string(u8"Hi")); + EXPECT_TRUE(m8s.Matches(u8"Wow Hi Hi")); + EXPECT_FALSE(m8s.Matches(u8"hi")); + + const Matcher m8sv = EndsWith(std::u8string_view(u8"Hi")); + EXPECT_TRUE(m8sv.Matches(u8"Wow Hi Hi")); + EXPECT_FALSE(m8sv.Matches(u8"hi")); +#endif // __cpp_lib_char8_t + +#if GTEST_HAS_STD_WSTRING + const Matcher mw = EndsWith(L"Hi"); + EXPECT_TRUE(mw.Matches(L"Wow Hi Hi")); + EXPECT_FALSE(mw.Matches(L"hi")); + EXPECT_FALSE(mw.Matches(nullptr)); + + const Matcher mws = EndsWith(std::wstring(L"Hi")); + EXPECT_TRUE(mws.Matches(L"Wow Hi Hi")); + EXPECT_FALSE(mws.Matches(L"hi")); + + const Matcher mwsv = EndsWith(std::wstring_view(L"Hi")); + EXPECT_TRUE(mwsv.Matches(L"Wow Hi Hi")); + EXPECT_FALSE(mwsv.Matches(L"hi")); +#endif // GTEST_HAS_STD_WSTRING +} + // Tests WhenBase64Unescaped. TEST(WhenBase64UnescapedTest, MatchesUnescapedBase64Strings) { diff --git a/googletest/include/gtest/gtest-matchers.h b/googletest/include/gtest/gtest-matchers.h index 6d2ab14d2..b0e61289f 100644 --- a/googletest/include/gtest/gtest-matchers.h +++ b/googletest/include/gtest/gtest-matchers.h @@ -815,8 +815,64 @@ class [[nodiscard]] ImplicitCastEqMatcher { StoredRhs stored_rhs_; }; -template ::value>::type> +// Backported std::remove_cvref_t (since C++20) +template +struct remove_cvref { + using type = std::remove_cv_t>; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +// Character type detecting traits +// It is used to deduct the CharT (Character Trait) out of +// [const] char/wchar_t/char8_t/char16_t/char32_t *, basic_string<...>, basic_string_view<...> + +template +struct is_char_type : std::false_type {}; + +template <> struct is_char_type : std::true_type {}; +template <> struct is_char_type : std::true_type {}; +template <> struct is_char_type : std::true_type {}; + +#if GTEST_HAS_STD_WSTRING +template <> struct is_char_type : std::true_type {}; +#endif // GTEST_HAS_STD_WSTRING + +#ifdef __cpp_lib_char8_t +template <> struct is_char_type : std::true_type {}; +#endif // __cpp_lib_char8_t + +template +struct type_identity { using type = T; }; + +template +struct char_type_traits_impl {}; + +template +struct char_type_traits_impl, + std::void_t>::value>>> : + type_identity {}; + +template +struct char_type_traits_impl, + std::void_t>::value>>> : + type_identity {}; + +template +struct char_type_traits_impl>::value>>> : + type_identity> {}; + +template +struct char_type_traits : char_type_traits_impl>> {}; + +template +using char_type_traits_t = typename char_type_traits::type; + +template >, T>>> using StringLike = T; // Implements polymorphic matchers MatchesRegex(regex) and From f75b7dfcda4fe930158f5a5f73e974838c85585b Mon Sep 17 00:00:00 2001 From: XAMeLeOH <14215665+XAMeLeOH@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:54:43 +0100 Subject: [PATCH 2/2] Changed the way the char type is deducted for StringLike, eliminated function overloads in favor of if constexpr --- googlemock/include/gmock/gmock-matchers.h | 39 +++++------ .../test/gmock-matchers-comparisons_test.cc | 51 ++++++++++++++ googletest/include/gtest/gtest-matchers.h | 67 +++++-------------- 3 files changed, 86 insertions(+), 71 deletions(-) diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 1c15e8242..dbbe6f455 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -913,8 +913,10 @@ struct ci_matcher_supported : std::false_type {}; template<> struct ci_matcher_supported : std::true_type {}; +#if GTEST_HAS_STD_WSTRING template<> struct ci_matcher_supported : std::true_type {}; +#endif // GTEST_HAS_STD_WSTRING // String matchers. @@ -965,8 +967,14 @@ class [[nodiscard]] StrEqualityMatcher { bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType s2(s); - const bool eq = MatchStrings(s2, ci_matcher_supported{}); - return expect_eq_ == eq; + if constexpr(ci_matcher_supported::value) { + const bool eq = CaseSensitive::value ? + s2 == string_ : CaseInsensitiveStringEquals(s2, string_); + return expect_eq_ == eq; + } else { + const bool eq = s2 == string_; + return expect_eq_ == eq; + } } void DescribeTo(::std::ostream* os) const { @@ -978,21 +986,10 @@ class [[nodiscard]] StrEqualityMatcher { } private: - template - bool MatchStrings(const S& s2, std::true_type) const { - return CaseSensitive::value ? s2 == string_ - : CaseInsensitiveStringEquals(s2, string_); - } - - template - bool MatchStrings(const S& s2, std::false_type) const { - return s2 == string_; - } - void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { *os << (expect_eq ? "is " : "isn't "); *os << "equal to "; - if (!CaseSensitive::value) { + if constexpr(!CaseSensitive::value) { *os << "(ignoring case) "; } UniversalPrint(string_, os); @@ -4808,7 +4805,7 @@ internal::ResultOfMatcher ResultOf( // Matches a string equal to str. template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher>> StrEq( const internal::StringLike& str) { return MakePolymorphicMatcher( @@ -4817,7 +4814,7 @@ PolymorphicMatcher>> StrEq // Matches a string not equal to str. template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher>> StrNe( const internal::StringLike& str) { return MakePolymorphicMatcher( @@ -4826,7 +4823,7 @@ PolymorphicMatcher>> StrNe // Matches a string equal to str, ignoring case. template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher, std::false_type>> StrCaseEq( const internal::StringLike& str) { return MakePolymorphicMatcher( @@ -4836,7 +4833,7 @@ PolymorphicMatcher, std::f // Matches a string not equal to str, ignoring case. template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher, std::false_type>> StrCaseNe( const internal::StringLike& str) { return MakePolymorphicMatcher( @@ -4847,7 +4844,7 @@ PolymorphicMatcher, std::f // Creates a matcher that matches any string, std::string, or C string // that contains the given substring. template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher>> HasSubstr( const internal::StringLike& substring) { return MakePolymorphicMatcher( @@ -4856,7 +4853,7 @@ PolymorphicMatcher>> HasSubs // Matches a string that starts with 'prefix' (case-sensitive). template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher>> StartsWith( const internal::StringLike& prefix) { return MakePolymorphicMatcher( @@ -4865,7 +4862,7 @@ PolymorphicMatcher>> Starts // Matches a string that ends with 'suffix' (case-sensitive). template > + typename CharT = internal::get_char_type_t> PolymorphicMatcher>> EndsWith( const internal::StringLike& suffix) { return MakePolymorphicMatcher( diff --git a/googlemock/test/gmock-matchers-comparisons_test.cc b/googlemock/test/gmock-matchers-comparisons_test.cc index a94febf4e..d7888cb62 100644 --- a/googlemock/test/gmock-matchers-comparisons_test.cc +++ b/googlemock/test/gmock-matchers-comparisons_test.cc @@ -1364,6 +1364,57 @@ TEST(StrEqTest, AllowsWideChars) { #endif // GTEST_HAS_STD_WSTRING } +TEST(StrEqTest, AllowsCustomStringLikeType) { + struct MyString { + operator std::string() const { return std::string("Hello"); } + }; + + Matcher m = StrEq(MyString{}); + EXPECT_TRUE(m.Matches("Hello")); + EXPECT_FALSE(m.Matches("hello")); + EXPECT_FALSE(m.Matches(nullptr)); + +#if GTEST_HAS_STD_WSTRING + struct MyWString { + operator std::wstring() const { return std::wstring(L"Hello"); } + }; + + Matcher mw = StrEq(MyWString{}); + EXPECT_TRUE(mw.Matches(L"Hello")); + EXPECT_FALSE(mw.Matches(L"hello")); + EXPECT_FALSE(mw.Matches(nullptr)); +#endif // GTEST_HAS_STD_WSTRING + + struct MyU16String { + operator std::u16string() const { return std::u16string(u"Hello"); } + }; + + Matcher m16 = StrEq(MyU16String{}); + EXPECT_TRUE(m16.Matches(u"Hello")); + EXPECT_FALSE(m16.Matches(u"hello")); + EXPECT_FALSE(m16.Matches(nullptr)); + + struct MyU32String { + operator std::u32string() const { return std::u32string(U"Hello"); } + }; + + Matcher m32 = StrEq(MyU32String{}); + EXPECT_TRUE(m32.Matches(U"Hello")); + EXPECT_FALSE(m32.Matches(U"hello")); + EXPECT_FALSE(m32.Matches(nullptr)); + +#ifdef __cpp_lib_char8_t + struct MyU8String { + operator std::u8string() const { return std::u8string(u8"Hello"); } + }; + + Matcher m8 = StrEq(MyU8String{}); + EXPECT_TRUE(m8.Matches(u8"Hello")); + EXPECT_FALSE(m8.Matches(u8"hello")); + EXPECT_FALSE(m8.Matches(nullptr)); +#endif // __cpp_lib_char8_t +} + TEST(StrNeTest, MatchesUnequalString) { Matcher m = StrNe("Hello"); EXPECT_TRUE(m.Matches("")); diff --git a/googletest/include/gtest/gtest-matchers.h b/googletest/include/gtest/gtest-matchers.h index b0e61289f..b39216282 100644 --- a/googletest/include/gtest/gtest-matchers.h +++ b/googletest/include/gtest/gtest-matchers.h @@ -815,64 +815,31 @@ class [[nodiscard]] ImplicitCastEqMatcher { StoredRhs stored_rhs_; }; -// Backported std::remove_cvref_t (since C++20) -template -struct remove_cvref { - using type = std::remove_cv_t>; +template +struct maybe_stringish : std::bool_constant, T>> { + using type = CharT; +}; + +struct default_char_type : std::true_type +{ + using type = char; }; template -using remove_cvref_t = typename remove_cvref::type; - -// Character type detecting traits -// It is used to deduct the CharT (Character Trait) out of -// [const] char/wchar_t/char8_t/char16_t/char32_t *, basic_string<...>, basic_string_view<...> - -template -struct is_char_type : std::false_type {}; - -template <> struct is_char_type : std::true_type {}; -template <> struct is_char_type : std::true_type {}; -template <> struct is_char_type : std::true_type {}; - +using get_char_type_t = typename std::disjunction< + maybe_stringish, #if GTEST_HAS_STD_WSTRING -template <> struct is_char_type : std::true_type {}; + maybe_stringish, #endif // GTEST_HAS_STD_WSTRING - + maybe_stringish, + maybe_stringish, #ifdef __cpp_lib_char8_t -template <> struct is_char_type : std::true_type {}; + maybe_stringish, #endif // __cpp_lib_char8_t + default_char_type // we will default to char anyway + >::type; -template -struct type_identity { using type = T; }; - -template -struct char_type_traits_impl {}; - -template -struct char_type_traits_impl, - std::void_t>::value>>> : - type_identity {}; - -template -struct char_type_traits_impl, - std::void_t>::value>>> : - type_identity {}; - -template -struct char_type_traits_impl>::value>>> : - type_identity> {}; - -template -struct char_type_traits : char_type_traits_impl>> {}; - -template -using char_type_traits_t = typename char_type_traits::type; - -template >, T>>> +template>, T>>> using StringLike = T; // Implements polymorphic matchers MatchesRegex(regex) and