diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 0bc9b3649f..a897611c9c 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -4725,6 +4725,175 @@ PolymorphicMatcher > VariantWith( internal::variant_matcher::VariantMatcher(matcher)); } +#if GTEST_HAS_EXCEPTIONS + +// Anything inside the `internal` namespace is internal to the implementation +// and must not be used in user code! +namespace internal { + +class WithWhatMatcherImpl { + public: + WithWhatMatcherImpl(Matcher matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* os) const { + *os << "contains .what() that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "contains .what() that does not "; + matcher_.DescribeTo(os); + } + + template + bool MatchAndExplain(const Err& err, MatchResultListener* listener) const { + *listener << "which contains .what() that "; + return matcher_.MatchAndExplain(err.what(), listener); + } + + private: + const Matcher matcher_; +}; + +inline PolymorphicMatcher WithWhat( + Matcher m) { + return MakePolymorphicMatcher(WithWhatMatcherImpl(std::move(m))); +} + +template +class ExceptionMatcherImpl { + class NeverThrown { + public: + const char* what() const noexcept { + return "this exception should never be thrown"; + } + }; + + // If the matchee raises an exception of a wrong type, we'd like to + // catch it and print its message and type. To do that, we add an additional + // catch clause: + // + // try { ... } + // catch (const Err&) { /* an expected exception */ } + // catch (const std::exception&) { /* exception of a wrong type */ } + // + // However, if the `Err` itself is `std::exception`, we'd end up with two + // identical `catch` clauses: + // + // try { ... } + // catch (const std::exception&) { /* an expected exception */ } + // catch (const std::exception&) { /* exception of a wrong type */ } + // + // This can cause a warning or an error in some compilers. To resolve + // the issue, we use a fake error type whenever `Err` is `std::exception`: + // + // try { ... } + // catch (const std::exception&) { /* an expected exception */ } + // catch (const NeverThrown&) { /* exception of a wrong type */ } + using DefaultExceptionType = typename std::conditional< + std::is_same::type>::type, + std::exception>::value, + const NeverThrown&, const std::exception&>::type; + + public: + ExceptionMatcherImpl(Matcher matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* os) const { + *os << "throws an exception which is a " << GetTypeName(); + *os << " which "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "throws an exception which is not a " << GetTypeName(); + *os << " which "; + matcher_.DescribeNegationTo(os); + } + + template + bool MatchAndExplain(T&& x, MatchResultListener* listener) const { + try { + (void)(std::forward(x)()); + } catch (const Err& err) { + *listener << "throws an exception which is a " << GetTypeName(); + *listener << " "; + return matcher_.MatchAndExplain(err, listener); + } catch (DefaultExceptionType err) { +#if GTEST_HAS_RTTI + *listener << "throws an exception of type " << GetTypeName(typeid(err)); + *listener << " "; +#else + *listener << "throws an std::exception-derived type "; +#endif + *listener << "with description \"" << err.what() << "\""; + return false; + } catch (...) { + *listener << "throws an exception of an unknown type"; + return false; + } + + *listener << "does not throw any exception"; + return false; + } + + private: + const Matcher matcher_; +}; + +} // namespace internal + +// Throws() +// Throws(exceptionMatcher) +// ThrowsMessage(messageMatcher) +// +// This matcher accepts a callable and verifies that when invoked, it throws +// an exception with the given type and properties. +// +// Examples: +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// Throws()); +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// ThrowsMessage(HasSubstr("message"))); +// +// EXPECT_THAT( +// []() { throw std::runtime_error("message"); }, +// Throws( +// Property(&std::runtime_error::what, HasSubstr("message")))); + +template +PolymorphicMatcher> Throws() { + return MakePolymorphicMatcher( + internal::ExceptionMatcherImpl(A())); +} + +template +PolymorphicMatcher> Throws( + const ExceptionMatcher& exception_matcher) { + // Using matcher cast allows users to pass a matcher of a more broad type. + // For example user may want to pass Matcher + // to Throws, or Matcher to Throws. + return MakePolymorphicMatcher(internal::ExceptionMatcherImpl( + SafeMatcherCast(exception_matcher))); +} + +template +PolymorphicMatcher> ThrowsMessage( + MessageMatcher&& message_matcher) { + static_assert(std::is_base_of::value, + "expected an std::exception-derived type"); + return Throws(internal::WithWhat( + MatcherCast(std::forward(message_matcher)))); +} + +#endif // GTEST_HAS_EXCEPTIONS + // These macros allow using matchers to check values in Google Test // tests. ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher) // succeed if and only if the value matches the matcher. If the assertion diff --git a/googlemock/test/gmock-matchers_test.cc b/googlemock/test/gmock-matchers_test.cc index 015bb09a85..1cba156f3f 100644 --- a/googlemock/test/gmock-matchers_test.cc +++ b/googlemock/test/gmock-matchers_test.cc @@ -8117,6 +8117,190 @@ TEST(MatcherPMacroTest, WorksOnMoveOnlyType) { EXPECT_THAT(p, Not(UniquePointee(2))); } +#if GTEST_HAS_EXCEPTIONS + +// std::function is used below for compatibility with older copies of +// GCC. Normally, a raw lambda is all that is needed. + +// Test that examples from documentation compile +TEST(ThrowsTest, Examples) { + EXPECT_THAT( + std::function([]() { throw std::runtime_error("message"); }), + Throws()); + + EXPECT_THAT( + std::function([]() { throw std::runtime_error("message"); }), + ThrowsMessage(HasSubstr("message"))); +} + +TEST(ThrowsTest, DoesNotGenerateDuplicateCatchClauseWarning) { + EXPECT_THAT(std::function([]() { throw std::exception(); }), + Throws()); +} + +TEST(ThrowsTest, CallableExecutedExactlyOnce) { + size_t a = 0; + + EXPECT_THAT(std::function([&a]() { + a++; + throw 10; + }), + Throws()); + EXPECT_EQ(a, 1u); + + EXPECT_THAT(std::function([&a]() { + a++; + throw std::runtime_error("message"); + }), + Throws()); + EXPECT_EQ(a, 2u); + + EXPECT_THAT(std::function([&a]() { + a++; + throw std::runtime_error("message"); + }), + ThrowsMessage(HasSubstr("message"))); + EXPECT_EQ(a, 3u); + + EXPECT_THAT(std::function([&a]() { + a++; + throw std::runtime_error("message"); + }), + Throws( + Property(&std::runtime_error::what, HasSubstr("message")))); + EXPECT_EQ(a, 4u); +} + +TEST(ThrowsTest, Describe) { + Matcher> matcher = Throws(); + std::stringstream ss; + matcher.DescribeTo(&ss); + auto explanation = ss.str(); + EXPECT_THAT(explanation, HasSubstr("std::runtime_error")); +} + +TEST(ThrowsTest, Success) { + Matcher> matcher = Throws(); + StringMatchResultListener listener; + EXPECT_TRUE(matcher.MatchAndExplain( + []() { throw std::runtime_error("error message"); }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("std::runtime_error")); +} + +TEST(ThrowsTest, FailWrongType) { + Matcher> matcher = Throws(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain( + []() { throw std::logic_error("error message"); }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("std::logic_error")); + EXPECT_THAT(listener.str(), HasSubstr("\"error message\"")); +} + +TEST(ThrowsTest, FailWrongTypeNonStd) { + Matcher> matcher = Throws(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain([]() { throw 10; }, &listener)); + EXPECT_THAT(listener.str(), + HasSubstr("throws an exception of an unknown type")); +} + +TEST(ThrowsTest, FailNoThrow) { + Matcher> matcher = Throws(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain([]() { (void)0; }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("does not throw any exception")); +} + +class ThrowsPredicateTest + : public TestWithParam>> {}; + +TEST_P(ThrowsPredicateTest, Describe) { + Matcher> matcher = GetParam(); + std::stringstream ss; + matcher.DescribeTo(&ss); + auto explanation = ss.str(); + EXPECT_THAT(explanation, HasSubstr("std::runtime_error")); + EXPECT_THAT(explanation, HasSubstr("error message")); +} + +TEST_P(ThrowsPredicateTest, Success) { + Matcher> matcher = GetParam(); + StringMatchResultListener listener; + EXPECT_TRUE(matcher.MatchAndExplain( + []() { throw std::runtime_error("error message"); }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("std::runtime_error")); +} + +TEST_P(ThrowsPredicateTest, FailWrongType) { + Matcher> matcher = GetParam(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain( + []() { throw std::logic_error("error message"); }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("std::logic_error")); + EXPECT_THAT(listener.str(), HasSubstr("\"error message\"")); +} + +TEST_P(ThrowsPredicateTest, FailWrongTypeNonStd) { + Matcher> matcher = GetParam(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain([]() { throw 10; }, &listener)); + EXPECT_THAT(listener.str(), + HasSubstr("throws an exception of an unknown type")); +} + +TEST_P(ThrowsPredicateTest, FailWrongMessage) { + Matcher> matcher = GetParam(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain( + []() { throw std::runtime_error("wrong message"); }, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("std::runtime_error")); + EXPECT_THAT(listener.str(), Not(HasSubstr("wrong message"))); +} + +TEST_P(ThrowsPredicateTest, FailNoThrow) { + Matcher> matcher = GetParam(); + StringMatchResultListener listener; + EXPECT_FALSE(matcher.MatchAndExplain([]() {}, &listener)); + EXPECT_THAT(listener.str(), HasSubstr("does not throw any exception")); +} + +INSTANTIATE_TEST_SUITE_P( + AllMessagePredicates, ThrowsPredicateTest, + Values(Matcher>( + ThrowsMessage(HasSubstr("error message"))))); + +// Tests that Throws(Matcher{}) compiles even when E2 != const E1&. +TEST(ThrowsPredicateCompilesTest, ExceptionMatcherAcceptsBroadType) { + { + Matcher> matcher = + ThrowsMessage(HasSubstr("error message")); + EXPECT_TRUE( + matcher.Matches([]() { throw std::runtime_error("error message"); })); + EXPECT_FALSE( + matcher.Matches([]() { throw std::runtime_error("wrong message"); })); + } + + { + Matcher inner = Eq(10); + Matcher> matcher = Throws(inner); + EXPECT_TRUE(matcher.Matches([]() { throw(uint32_t) 10; })); + EXPECT_FALSE(matcher.Matches([]() { throw(uint32_t) 11; })); + } +} + +// Tests that ThrowsMessage("message") is equivalent +// to ThrowsMessage(Eq("message")). +TEST(ThrowsPredicateCompilesTest, MessageMatcherAcceptsNonMatcher) { + Matcher> matcher = + ThrowsMessage("error message"); + EXPECT_TRUE( + matcher.Matches([]() { throw std::runtime_error("error message"); })); + EXPECT_FALSE(matcher.Matches( + []() { throw std::runtime_error("wrong error message"); })); +} + +#endif // GTEST_HAS_EXCEPTIONS + } // namespace } // namespace gmock_matchers_test } // namespace testing