/** * Copyright (C) 2022-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ /* * ASSERTion macros for the C++ unit testing framework. */ #pragma once #include #include #include #include #include #include #include #include #include #include "mongo/base/status_with.h" #include "mongo/base/string_data.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/logv2/log_debug.h" #include "mongo/logv2/log_detail.h" #include "mongo/unittest/bson_test_util.h" #include "mongo/unittest/framework.h" #include "mongo/unittest/stringify.h" #include "mongo/unittest/test_info.h" #include "mongo/util/assert_util.h" #include "mongo/util/str.h" /** * Fail unconditionally, reporting the given message. */ #define FAIL(MESSAGE) ::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, MESSAGE).stream() /** * Fails unless "EXPRESSION" is true. */ #define ASSERT_TRUE(EXPRESSION) \ if (EXPRESSION) { \ } else \ FAIL("Expected: " #EXPRESSION) #define ASSERT(EXPRESSION) ASSERT_TRUE(EXPRESSION) /** * Fails if "EXPRESSION" is true. */ #define ASSERT_FALSE(EXPRESSION) ASSERT(!(EXPRESSION)) /** * Asserts that a Status code is OK. */ #define ASSERT_OK(EXPRESSION) ASSERT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /** * Asserts that a status code is anything but OK. */ #define ASSERT_NOT_OK(EXPRESSION) ASSERT_NOT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /* * Binary comparison assertions. */ #define ASSERT_EQUALS(a, b) ASSERT_EQ(a, b) #define ASSERT_NOT_EQUALS(a, b) ASSERT_NE(a, b) #define ASSERT_LESS_THAN(a, b) ASSERT_LT(a, b) #define ASSERT_NOT_LESS_THAN(a, b) ASSERT_GTE(a, b) #define ASSERT_GREATER_THAN(a, b) ASSERT_GT(a, b) #define ASSERT_NOT_GREATER_THAN(a, b) ASSERT_LTE(a, b) #define ASSERT_LESS_THAN_OR_EQUALS(a, b) ASSERT_LTE(a, b) #define ASSERT_GREATER_THAN_OR_EQUALS(a, b) ASSERT_GTE(a, b) #define ASSERT_EQ(a, b) ASSERT_COMPARISON_(kEq, a, b) #define ASSERT_NE(a, b) ASSERT_COMPARISON_(kNe, a, b) #define ASSERT_LT(a, b) ASSERT_COMPARISON_(kLt, a, b) #define ASSERT_LTE(a, b) ASSERT_COMPARISON_(kLe, a, b) #define ASSERT_GT(a, b) ASSERT_COMPARISON_(kGt, a, b) #define ASSERT_GTE(a, b) ASSERT_COMPARISON_(kGe, a, b) /** * Binary comparison utility macro. Do not use directly. */ #define ASSERT_COMPARISON_(OP, a, b) ASSERT_COMPARISON_STR_(OP, a, b, #a, #b) #define ASSERT_COMPARISON_STR_(OP, a, b, aExpr, bExpr) \ if (auto ca = \ ::mongo::unittest::ComparisonAssertion<::mongo::unittest::ComparisonOp::OP>::make( \ __FILE__, __LINE__, aExpr, bExpr, a, b); \ !ca) { \ } else \ ca.failure().stream() /** * Approximate equality assertion. Useful for comparisons on limited precision floating point * values. */ #define ASSERT_APPROX_EQUAL(a, b, ABSOLUTE_ERR) ASSERT_LTE(std::abs((a) - (b)), ABSOLUTE_ERR) /** * Assert a function call returns its input unchanged. */ #define ASSERT_IDENTITY(INPUT, FUNCTION) \ if (auto ca = \ [&](const auto& v, const auto& fn) { \ return ::mongo::unittest::ComparisonAssertion< \ ::mongo::unittest::ComparisonOp::kEq>::make(__FILE__, \ __LINE__, \ #INPUT, \ #FUNCTION "(" #INPUT ")", \ v, \ fn(v)); \ }(INPUT, [&](auto&& x) { return FUNCTION(x); }); \ !ca) { \ } else \ ca.failure().stream() /** * Verify that the evaluation of "EXPRESSION" throws an exception of type EXCEPTION_TYPE. * * If "EXPRESSION" throws no exception, or one that is neither of type "EXCEPTION_TYPE" nor * of a subtype of "EXCEPTION_TYPE", the test is considered a failure and further evaluation * halts. */ #define ASSERT_THROWS(EXPRESSION, EXCEPTION_TYPE) \ ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, ([](const EXCEPTION_TYPE&) {})) /** * Verify that the evaluation of "EXPRESSION" does not throw any exceptions. * * If "EXPRESSION" throws an exception the test is considered a failure and further evaluation * halts. */ #define ASSERT_DOES_NOT_THROW(EXPRESSION) \ try { \ EXPRESSION; \ } catch (const AssertionException& e) { \ str::stream err; \ err << "Threw an exception incorrectly: " << e.toString(); \ FAIL(err); \ } /** * Behaves like ASSERT_THROWS, above, but also fails if calling what() on the thrown exception * does not return a string equal to EXPECTED_WHAT. */ #define ASSERT_THROWS_WHAT(EXPRESSION, EXCEPTION_TYPE, EXPECTED_WHAT) \ ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, ([&](const EXCEPTION_TYPE& ex) { \ ASSERT_EQ(::mongo::StringData(ex.what()), \ ::mongo::StringData(EXPECTED_WHAT)); \ })) /** * Behaves like ASSERT_THROWS, above, but also fails if calling getCode() on the thrown exception * does not return an error code equal to EXPECTED_CODE. */ #define ASSERT_THROWS_CODE(EXPRESSION, EXCEPTION_TYPE, EXPECTED_CODE) \ ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, ([&](const EXCEPTION_TYPE& ex) { \ ASSERT_EQ(ex.toStatus().code(), EXPECTED_CODE); \ })) /** * Behaves like ASSERT_THROWS, above, but also fails if calling getCode() on the thrown exception * does not return an error code equal to EXPECTED_CODE or if calling what() on the thrown exception * does not return a string equal to EXPECTED_WHAT. */ #define ASSERT_THROWS_CODE_AND_WHAT(EXPRESSION, EXCEPTION_TYPE, EXPECTED_CODE, EXPECTED_WHAT) \ ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, ([&](const EXCEPTION_TYPE& ex) { \ ASSERT_EQ(ex.toStatus().code(), EXPECTED_CODE); \ ASSERT_EQ(::mongo::StringData(ex.what()), \ ::mongo::StringData(EXPECTED_WHAT)); \ })) /** * Compiles if expr doesn't compile. * * This only works for compile errors in the "immediate context" of the expression, which matches * the rules for SFINAE. The first argument is a defaulted template parameter that is used in the * expression to make it dependent. This only works with expressions, not statements, although you * can separate multiple expressions with a comma. * * This should be used at namespace scope, not inside a TEST function. * * Examples that pass: * ASSERT_DOES_NOT_COMPILE(MyTest1, typename Char = char, *std::declval()); * ASSERT_DOES_NOT_COMPILE(MyTest2, bool B = false, std::enable_if_t{}); * * Examples that fail: * ASSERT_DOES_NOT_COMPILE(MyTest3, typename Char = char, *std::declval()); * ASSERT_DOES_NOT_COMPILE(MyTest4, bool B = true, std::enable_if_t{}); * */ #define ASSERT_DOES_NOT_COMPILE(Id, Alias, ...) \ ASSERT_DOES_NOT_COMPILE_1_(Id, Alias, #Alias, (__VA_ARGS__), #__VA_ARGS__) #define ASSERT_DOES_NOT_COMPILE_1_(Id, Alias, AliasString, Expr, ExprString) \ \ static std::true_type Id(...); \ \ template \ static std::conditional_t Id(int); \ \ static_assert(decltype(Id(0))::value, \ "Expression '" ExprString "' [with " AliasString "] shouldn't compile."); /** * This internal helper is used to ignore warnings about unused results. Some unit tests which test * `ASSERT_THROWS` and its variations are used on functions which both throw and return `Status` or * `StatusWith` objects. Although such function designs are undesirable, they do exist, presently. * Therefore this internal helper macro is used by `ASSERT_THROWS` and its variations to silence * such warnings without forcing the caller to invoke `.ignore()` on the called function. * * NOTE: This macro should NOT be used inside regular unit test code to ignore unchecked `Status` or * `StatusWith` instances -- if a `Status` or `StatusWith` result is to be ignored, please use the * normal `.ignore()` code. This macro exists only to make using `ASSERT_THROWS` less inconvenient * on functions which both throw and return `Status` or `StatusWith`. */ #define UNIT_TEST_INTERNALS_IGNORE_UNUSED_RESULT_WARNINGS(EXPRESSION) \ do { \ (void)(EXPRESSION); \ } while (false) /** * Behaves like ASSERT_THROWS, above, but also calls CHECK(caughtException) which may contain * additional assertions. */ #define ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, CHECK) \ if ([&] { \ try { \ UNIT_TEST_INTERNALS_IGNORE_UNUSED_RESULT_WARNINGS(EXPRESSION); \ return false; \ } catch (const EXCEPTION_TYPE& ex) { \ CHECK(ex); \ return true; \ } \ }()) { \ } else \ /* Fail outside of the try/catch, this way the code in the `FAIL` macro */ \ /* doesn't have the potential to throw an exception which we might also */ \ /* be checking for. */ \ FAIL("Expected expression " #EXPRESSION " to throw " #EXCEPTION_TYPE \ " but it threw nothing.") #define ASSERT_STRING_CONTAINS(BIG_STRING, CONTAINS) \ if (auto tup_ = std::tuple(std::string(BIG_STRING), std::string(CONTAINS)); \ std::get<0>(tup_).find(std::get<1>(tup_)) != std::string::npos) { \ } else \ FAIL(([&] { \ const auto& [haystack, sub] = tup_; \ return format(FMT_STRING("Expected to find {} ({}) in {} ({})"), \ #CONTAINS, \ sub, \ #BIG_STRING, \ haystack); \ }())) #define ASSERT_STRING_OMITS(BIG_STRING, OMITS) \ if (auto tup_ = std::tuple(std::string(BIG_STRING), std::string(OMITS)); \ std::get<0>(tup_).find(std::get<1>(tup_)) == std::string::npos) { \ } else \ FAIL(([&] { \ const auto& [haystack, omits] = tup_; \ return format(FMT_STRING("Did not expect to find {} ({}) in {} ({})"), \ #OMITS, \ omits, \ #BIG_STRING, \ haystack); \ }())) #define ASSERT_STRING_SEARCH_REGEX(BIG_STRING, REGEX) \ if (auto tup_ = std::tuple(std::string(BIG_STRING), std::string(REGEX)); \ ::mongo::unittest::searchRegex(std::get<1>(tup_), std::get<0>(tup_))) { \ } else \ FAIL(([&] { \ const auto& [haystack, sub] = tup_; \ return format(FMT_STRING("Expected to find regular expression {} /{}/ in {} ({})"), \ #REGEX, \ sub, \ #BIG_STRING, \ haystack); \ }())) namespace mongo::unittest { bool searchRegex(const std::string& pattern, const std::string& string); class Result; /** * Exception thrown when a test assertion fails. * * Typically thrown by helpers in the TestAssertion class and its ilk, below. * * NOTE(schwerin): This intentionally does _not_ extend std::exception, so that code under * test that (foolishly?) catches std::exception won't swallow test failures. Doesn't * protect you from code that foolishly catches ..., but you do what you can. */ class TestAssertionFailureException { public: TestAssertionFailureException(std::string file, unsigned line, std::string message); const std::string& getFile() const { return _file; } unsigned getLine() const { return _line; } const std::string& getMessage() const { return _message; } void setMessage(const std::string& message) { _message = message; } const std::string& what() const { return getMessage(); } std::string toString() const; const std::string& getStacktrace() const { return _stacktrace; } private: std::string _file; unsigned _line; std::string _message; std::string _stacktrace; }; class TestAssertionFailure { public: TestAssertionFailure(const std::string& file, unsigned line, const std::string& message); TestAssertionFailure(const TestAssertionFailure& other); ~TestAssertionFailure() noexcept(false); TestAssertionFailure& operator=(const TestAssertionFailure& other); std::ostream& stream(); private: TestAssertionFailureException _exception; std::ostringstream _stream; bool _enabled; }; enum class ComparisonOp { kEq, kNe, kLt, kLe, kGt, kGe }; template class ComparisonAssertion { private: static constexpr auto comparator() { if constexpr (op == ComparisonOp::kEq) { return std::equal_to<>{}; } else if constexpr (op == ComparisonOp::kNe) { return std::not_equal_to<>{}; } else if constexpr (op == ComparisonOp::kLt) { return std::less<>{}; } else if constexpr (op == ComparisonOp::kLe) { return std::less_equal<>{}; } else if constexpr (op == ComparisonOp::kGt) { return std::greater<>{}; } else if constexpr (op == ComparisonOp::kGe) { return std::greater_equal<>{}; } } static constexpr StringData name() { if constexpr (op == ComparisonOp::kEq) { return "=="_sd; } else if constexpr (op == ComparisonOp::kNe) { return "!="_sd; } else if constexpr (op == ComparisonOp::kLt) { return "<"_sd; } else if constexpr (op == ComparisonOp::kLe) { return "<="_sd; } else if constexpr (op == ComparisonOp::kGt) { return ">"_sd; } else if constexpr (op == ComparisonOp::kGe) { return ">="_sd; } } template MONGO_COMPILER_NOINLINE ComparisonAssertion(const char* theFile, unsigned theLine, StringData aExpression, StringData bExpression, const A& a, const B& b) { if (comparator()(a, b)) { return; } _assertion = std::make_unique( theFile, theLine, format(FMT_STRING("Expected {1} {0} {2} ({3} {0} {4})"), name(), aExpression, bExpression, stringify::stringifyForAssert(a), stringify::stringifyForAssert(b))); } public: // Use a single implementation (identical to the templated one) for all string-like types. // This is particularly important to avoid making unique instantiations for each length of // string literal. static ComparisonAssertion make(const char* theFile, unsigned theLine, StringData aExpression, StringData bExpression, StringData a, StringData b); // Use a single implementation (identical to the templated one) for all pointer and array types. // Note: this is selected instead of the StringData overload for char* and string literals // because they are supposed to compare pointers, not contents. static ComparisonAssertion make(const char* theFile, unsigned theLine, StringData aExpression, StringData bExpression, const void* a, const void* b); template requires( // !(std::is_convertible_v && std::is_convertible_v)&& // !(std::is_pointer_v && std::is_pointer_v)&& // !(std::is_array_v && std::is_array_v)) // static ComparisonAssertion make(const char* theFile, unsigned theLine, StringData aExpression, StringData bExpression, const A& a, const B& b) { return ComparisonAssertion(theFile, theLine, aExpression, bExpression, a, b); } explicit operator bool() const { return static_cast(_assertion); } TestAssertionFailure failure() { return *_assertion; } private: std::unique_ptr _assertion; }; // Explicit instantiation of ComparisonAssertion ctor and factory, for "A OP B". #define TEMPLATE_COMPARISON_ASSERTION_CTOR_A_OP_B(EXTERN, OP, A, B) \ EXTERN template ComparisonAssertion::ComparisonAssertion( \ const char*, unsigned, StringData, StringData, const A&, const B&); \ EXTERN template ComparisonAssertion \ ComparisonAssertion::make( \ const char*, unsigned, StringData, StringData, const A&, const B&); // Explicit instantiation of ComparisonAssertion ctor and factory for a pair of types. #define TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(EXTERN, OP, A, B) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_A_OP_B(EXTERN, OP, A, B) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_A_OP_B(EXTERN, OP, B, A) // Explicit instantiation of ComparisonAssertion ctor and factory for a single type. #define TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(EXTERN, OP, T) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_A_OP_B(EXTERN, OP, T, T) // Call with `extern` to declace extern instantiations, and with no args to explicitly instantiate. #define INSTANTIATE_COMPARISON_ASSERTION_CTORS(...) \ __VA_ARGS__ template class ComparisonAssertion; \ __VA_ARGS__ template class ComparisonAssertion; \ __VA_ARGS__ template class ComparisonAssertion; \ __VA_ARGS__ template class ComparisonAssertion; \ __VA_ARGS__ template class ComparisonAssertion; \ __VA_ARGS__ template class ComparisonAssertion; \ \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, int) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, long long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, unsigned int) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, unsigned long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, unsigned long long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, bool) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, double) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, OID) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, BSONType) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, Timestamp) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, Date_t) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, Status) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kEq, ErrorCodes::Error) \ \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, int, long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, int, long long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, long, long long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, unsigned int, unsigned long) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, Status, ErrorCodes::Error) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_SYMMETRIC(__VA_ARGS__, kEq, ErrorCodes::Error, int) \ \ /* These are the only types that are often used with ASSERT_NE*/ \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kNe, Status) \ TEMPLATE_COMPARISON_ASSERTION_CTOR_REFLEXIVE(__VA_ARGS__, kNe, unsigned long) // Declare that these definitions will be provided in unittest.cpp. INSTANTIATE_COMPARISON_ASSERTION_CTORS(extern); /** * Get the value out of a StatusWith, or throw an exception if it is not OK. */ template const T& assertGet(const StatusWith& swt) { ASSERT_OK(swt.getStatus()); return swt.getValue(); } template T assertGet(StatusWith&& swt) { ASSERT_OK(swt.getStatus()); return std::move(swt.getValue()); } } // namespace mongo::unittest