X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FTestUtils.h;h=6eb7a0b9d048324aa02012ee76ab81e5495cf581;hb=cd1bdc912603c0358ba733d379a74ae90ab3a437;hp=14a6012db664cf27e7efa058c6fa6bc5fb6ab78d;hpb=dee8a5180aa542d98d1b71c74f83a006e4627952;p=folly.git diff --git a/folly/test/TestUtils.h b/folly/test/TestUtils.h index 14a6012d..6eb7a0b9 100644 --- a/folly/test/TestUtils.h +++ b/folly/test/TestUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. + * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,28 @@ #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 @@ -24,3 +45,216 @@ // a normal test failure; there is only an effect if the test framework // 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 { + +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