From b010847b060f99a988069ca387416a08e9dc221e Mon Sep 17 00:00:00 2001 From: Adam Simpkins Date: Wed, 15 Nov 2017 12:48:56 -0800 Subject: [PATCH] add EXPECT_THROW_RE() and EXPECT_THROW_ERRNO() test macros Summary: Add EXPECT_THROW_RE() and EXPECT_THROW_ERRNO() macros to folly/test/TestUtils.h These allow more precise checks than the basic EXPECT_THROW() macro provided as part of gtest. These macros are being moved into folly from Facebook's eden repository (https://github.com/facebookexperimental/eden) This will allow us to use them in folly tests and in other projects that depend on folly. Reviewed By: yfeldblum Differential Revision: D6301760 fbshipit-source-id: 1f434fb5bc9b7859f763171264fb0b2e1b4bda62 --- folly/test/TestUtils.h | 199 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/folly/test/TestUtils.h b/folly/test/TestUtils.h index 61b8c020..ceb71907 100644 --- a/folly/test/TestUtils.h +++ b/folly/test/TestUtils.h @@ -16,8 +16,26 @@ #pragma once +/* + * This file contains additional gtest-style check macros to use in unit tests. + * + * - SKIP() + * - EXPECT_THROW_RE(), ASSERT_THROW_RE() + * - EXPECT_THROW_ERRNO(), ASSERT_THROW_ERRNO() + * - AreWithinSecs() + * + * Additionally, it includes a PrintTo() function for StringPiece. + * Including this file in your tests will ensure that StringPiece is printed + * nicely when used in EXPECT_EQ() or EXPECT_NE() checks. + */ + #include +#include +#include +#include +#include +#include #include #include @@ -28,6 +46,65 @@ // interprets the message. #define SKIP() GTEST_FATAL_FAILURE_("Test skipped by client") +#define TEST_THROW_ERRNO_(statement, errnoValue, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::folly::test::detail::CheckResult gtest_result = \ + ::folly::test::detail::checkThrowErrno( \ + [&] { statement; }, errnoValue, #statement)) { \ + } else \ + fail(gtest_result.what()) + +/** + * Check that a statement throws a std::system_error with the expected errno + * value. This is useful for checking code that uses the functions in + * folly/Exception.h to throw exceptions. + * + * Like other EXPECT_* and ASSERT_* macros, additional message information + * can be included using the << stream operator. + * + * Example usage: + * + * EXPECT_THROW_ERRNO(readFile("notpresent.txt"), ENOENT) + * << "notpresent.txt should not exist"; + */ +#define EXPECT_THROW_ERRNO(statement, errnoValue) \ + TEST_THROW_ERRNO_(statement, errnoValue, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW_ERRNO(statement, errnoValue) \ + TEST_THROW_ERRNO_(statement, errnoValue, GTEST_FATAL_FAILURE_) + +#define TEST_THROW_RE_(statement, exceptionType, pattern, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::folly::test::detail::CheckResult gtest_result = \ + ::folly::test::detail::checkThrowRegex( \ + [&] { statement; }, pattern, #statement, #exceptionType)) { \ + } else \ + fail(gtest_result.what()) + +/** + * Check that a statement throws the expected exception type, and that the + * exception message matches the specified regular expression. + * + * Partial matches (against just a portion of the error message) are accepted + * if the regular expression does not explicitly start with "^" and end with + * "$". (The matching is performed using std::regex_search() rather than + * std::regex_match().) + * + * This uses ECMA-262 style regular expressions (the default behavior of + * std::regex). + * + * Like other EXPECT_* and ASSERT_* macros, additional message information + * can be included using the << stream operator. + * + * Example usage: + * + * EXPECT_THROW_RE(badFunction(), std::runtime_error, "oh noes") + * << "function did not throw the expected exception"; + */ +#define EXPECT_THROW_RE(statement, exceptionType, pattern) \ + TEST_THROW_RE_(statement, exceptionType, pattern, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW_RE(statement, exceptionType, pattern) \ + TEST_THROW_RE_(statement, exceptionType, pattern, GTEST_FATAL_FAILURE_) + namespace folly { namespace test { @@ -45,6 +122,127 @@ AreWithinSecs(T1 val1, T2 val2, std::chrono::seconds acceptableDeltaSecs) { << acceptableDeltaSecs.count() << " secs of each other"; } } + +namespace detail { + +/** + * Helper class for implementing test macros + */ +class CheckResult { + public: + explicit CheckResult(bool s) noexcept : success_(s) {} + + explicit operator bool() const noexcept { + return success_; + } + const char* what() const noexcept { + return message_.c_str(); + } + + /** + * Support the << operator for building up the error message. + * + * The arguments are treated as with folly::to(), and we do not + * support iomanip parameters. The main reason we use the << operator + * as opposed to a variadic function like folly::to is that clang-format + * formats long statements using << much nicer than function call arguments. + */ + template + CheckResult& operator<<(T&& t) { + toAppend(std::forward(t), &message_); + return *this; + } + + private: + bool success_; + std::string message_; +}; + +/** + * Helper function for implementing EXPECT_THROW + */ +template +CheckResult checkThrowErrno(Fn&& fn, int errnoValue, const char* statementStr) { + try { + fn(); + } catch (const std::system_error& ex) { + // TODO: POSIX errno values should really use std::generic_category(), + // but folly/Exception.h throws them with std::system_category() at the + // moment. + if (ex.code().category() != std::system_category()) { + return CheckResult(false) + << "Expected: " << statementStr << " throws an exception with errno " + << errnoValue << " (" << std::generic_category().message(errnoValue) + << ")\nActual: it throws a system_error with category " + << ex.code().category().name() << ": " << ex.what(); + } + if (ex.code().value() != errnoValue) { + return CheckResult(false) + << "Expected: " << statementStr << " throws an exception with errno " + << errnoValue << " (" << std::generic_category().message(errnoValue) + << ")\nActual: it throws errno " << ex.code().value() << ": " + << ex.what(); + } + return CheckResult(true); + } catch (const std::exception& ex) { + return CheckResult(false) + << "Expected: " << statementStr << " throws an exception with errno " + << errnoValue << " (" << std::generic_category().message(errnoValue) + << ")\nActual: it throws a different exception: " << exceptionStr(ex); + } catch (...) { + return CheckResult(false) + << "Expected: " << statementStr << " throws an exception with errno " + << errnoValue << " (" << std::generic_category().message(errnoValue) + << ")\nActual: it throws a non-exception type"; + } + return CheckResult(false) + << "Expected: " << statementStr << " throws an exception with errno " + << errnoValue << " (" << std::generic_category().message(errnoValue) + << ")\nActual: it throws nothing"; +} + +/** + * Helper function for implementing EXPECT_THROW_RE + */ +template +CheckResult checkThrowRegex( + Fn&& fn, + const char* pattern, + const char* statementStr, + const char* excTypeStr) { + static_assert( + std::is_base_of::value, + "EXPECT_THROW_RE() exception type must derive from std::exception"); + + try { + fn(); + } catch (const std::exception& ex) { + const auto* derived = dynamic_cast(&ex); + if (!derived) { + return CheckResult(false) + << "Expected: " << statementStr << "throws a " << excTypeStr + << ")\nActual: it throws a different exception type: " + << exceptionStr(ex); + } + + std::regex re(pattern); + if (!std::regex_search(derived->what(), re)) { + return CheckResult(false) + << "Expected: " << statementStr << " throws a " << excTypeStr + << " with message matching \"" << pattern + << "\"\nActual: message is: " << derived->what(); + } + return CheckResult(true); + } catch (...) { + return CheckResult(false) + << "Expected: " << statementStr << " throws a " << excTypeStr + << ")\nActual: it throws a non-exception type"; + } + return CheckResult(false) << "Expected: " << statementStr << " throws a " + << excTypeStr << ")\nActual: it throws nothing"; +} + +} // namespace detail } // namespace test // Define a PrintTo() function for StringPiece, so that gtest checks @@ -58,4 +256,5 @@ inline void PrintTo(StringPiece sp, ::std::ostream* os) { // standard string types. *os << ::testing::PrintToString(sp.str()); } + } // namespace folly -- 2.34.1