X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;ds=sidebyside;f=folly%2Ftest%2FTestUtils.h;h=ceb71907179a789930e0c42a6d0d8d84e2b8ab9e;hb=b71a1b76b3dd7d63bc1d27ed292ddb604fdd9388;hp=bbb16b824001500494741cc55b3b95c575e6b5da;hpb=140c62d25d930cdbdacaa337d254a2471875a4be;p=folly.git diff --git a/folly/test/TestUtils.h b/folly/test/TestUtils.h index bbb16b82..ceb71907 100644 --- a/folly/test/TestUtils.h +++ b/folly/test/TestUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2015 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,30 @@ * limitations under the License. */ -#ifndef FOLLY_TESTUTILS_H -#define FOLLY_TESTUTILS_H +#pragma once -#include +/* + * 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 // We use this to indicate that tests have failed because of timing // or dependencies that may be flakey. Internally this is used by @@ -26,4 +46,215 @@ // interprets the message. #define SKIP() GTEST_FATAL_FAILURE_("Test skipped by client") -#endif // FOLLY_TESTUTILS_H +#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 { + +template +::testing::AssertionResult +AreWithinSecs(T1 val1, T2 val2, std::chrono::seconds acceptableDeltaSecs) { + auto deltaSecs = + std::chrono::duration_cast(val1 - val2); + if (deltaSecs <= acceptableDeltaSecs && + deltaSecs >= -1 * acceptableDeltaSecs) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure() + << val1.count() << " and " << val2.count() << " are not within " + << 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 +// will print it as a string. Without this gtest identifies StringPiece as a +// container type, and therefore tries printing its elements individually, +// despite the fact that there is an ostream operator<<() defined for +// StringPiece. +inline void PrintTo(StringPiece sp, ::std::ostream* os) { + // gtest's PrintToString() function will quote the string and escape internal + // quotes and non-printable characters, the same way gtest does for the + // standard string types. + *os << ::testing::PrintToString(sp.str()); +} + +} // namespace folly