define SKIP_IF
[folly.git] / folly / test / TestUtils.h
index a6642f926f905530f2ff08f7bd08c11dc8584fbb..62085ff8c23d65852c438c352734dc85567e17e6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 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.
 
 #pragma once
 
+/*
+ * This file contains additional gtest-style check macros to use in unit tests.
+ *
+ * - SKIP(), SKIP_IF(EXPR)
+ * - 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 <chrono>
+#include <regex>
+#include <system_error>
+#include <type_traits>
 
+#include <folly/Conv.h>
+#include <folly/ExceptionString.h>
+#include <folly/Range.h>
 #include <folly/portability/GTest.h>
 
 // We use this to indicate that tests have failed because of timing
 // interprets the message.
 #define SKIP() GTEST_FATAL_FAILURE_("Test skipped by client")
 
+// Encapsulate conditional-skip, since it's nontrivial to get right.
+#define SKIP_IF(expr)           \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (!(expr)) {                \
+  } else                        \
+    SKIP()
+
+#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<exceptionType>(          \
+              [&] { 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 {
 
@@ -44,5 +129,139 @@ 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<string>(), 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 <typename T>
+  CheckResult& operator<<(T&& t) {
+    toAppend(std::forward<T>(t), &message_);
+    return *this;
+  }
+
+ private:
+  bool success_;
+  std::string message_;
+};
+
+/**
+ * Helper function for implementing EXPECT_THROW
+ */
+template <typename Fn>
+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 <typename ExType, typename Fn>
+CheckResult checkThrowRegex(
+    Fn&& fn,
+    const char* pattern,
+    const char* statementStr,
+    const char* excTypeStr) {
+  static_assert(
+      std::is_base_of<std::exception, ExType>::value,
+      "EXPECT_THROW_RE() exception type must derive from std::exception");
+
+  try {
+    fn();
+  } catch (const std::exception& ex) {
+    const auto* derived = dynamic_cast<const ExType*>(&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