summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/unittest/SConscript2
-rw-r--r--src/mongo/unittest/assert.h547
-rw-r--r--src/mongo/unittest/assert_that.h2
-rw-r--r--src/mongo/unittest/assert_that_test.cpp4
-rw-r--r--src/mongo/unittest/bson_test_util.h2
-rw-r--r--src/mongo/unittest/death_test.cpp4
-rw-r--r--src/mongo/unittest/death_test.h2
-rw-r--r--src/mongo/unittest/framework.h444
-rw-r--r--src/mongo/unittest/matcher.h7
-rw-r--r--src/mongo/unittest/matcher_core.h105
-rw-r--r--src/mongo/unittest/stringify.cpp (renamed from src/mongo/unittest/matcher_core.cpp)10
-rw-r--r--src/mongo/unittest/stringify.h154
-rw-r--r--src/mongo/unittest/unittest.h902
-rw-r--r--src/mongo/unittest/unittest_test.cpp161
14 files changed, 1226 insertions, 1120 deletions
diff --git a/src/mongo/unittest/SConscript b/src/mongo/unittest/SConscript
index 3c5bb2b21ea..9d3dedeca8c 100644
--- a/src/mongo/unittest/SConscript
+++ b/src/mongo/unittest/SConscript
@@ -27,7 +27,7 @@ utEnv.Library(
'death_test.cpp',
'golden_test.cpp',
'matcher.cpp',
- 'matcher_core.cpp',
+ 'stringify.cpp',
'temp_dir.cpp',
'unittest.cpp',
],
diff --git a/src/mongo/unittest/assert.h b/src/mongo/unittest/assert.h
new file mode 100644
index 00000000000..c70ccc644ad
--- /dev/null
+++ b/src/mongo/unittest/assert.h
@@ -0,0 +1,547 @@
+/**
+ * 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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 <cmath>
+#include <fmt/format.h>
+#include <functional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.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<Char>());
+ * ASSERT_DOES_NOT_COMPILE(MyTest2, bool B = false, std::enable_if_t<B, int>{});
+ *
+ * Examples that fail:
+ * ASSERT_DOES_NOT_COMPILE(MyTest3, typename Char = char, *std::declval<Char*>());
+ * ASSERT_DOES_NOT_COMPILE(MyTest4, bool B = true, std::enable_if_t<B, int>{});
+ *
+ */
+#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 <Alias> \
+ static std::conditional_t<true, std::false_type, decltype(Expr)> 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 <ComparisonOp op>
+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 <typename A, typename B>
+ 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<TestAssertionFailure>(
+ 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(typename A, typename B)
+ REQUIRES(!(std::is_convertible_v<A, StringData> && std::is_convertible_v<B, StringData>)&& //
+ !(std::is_pointer_v<A> && std::is_pointer_v<B>)&& //
+ !(std::is_array_v<A> && std::is_array_v<B>))
+ 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<bool>(_assertion);
+ }
+ TestAssertionFailure failure() {
+ return *_assertion;
+ }
+
+private:
+ std::unique_ptr<TestAssertionFailure> _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<ComparisonOp::OP>::ComparisonAssertion( \
+ const char*, unsigned, StringData, StringData, const A&, const B&); \
+ EXTERN template ComparisonAssertion<ComparisonOp::OP> \
+ ComparisonAssertion<ComparisonOp::OP>::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<ComparisonOp::kEq>; \
+ __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kNe>; \
+ __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kGt>; \
+ __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kGe>; \
+ __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kLt>; \
+ __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kLe>; \
+ \
+ 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<T>, or throw an exception if it is not OK.
+ */
+template <typename T>
+const T& assertGet(const StatusWith<T>& swt) {
+ ASSERT_OK(swt.getStatus());
+ return swt.getValue();
+}
+
+template <typename T>
+T assertGet(StatusWith<T>&& swt) {
+ ASSERT_OK(swt.getStatus());
+ return std::move(swt.getValue());
+}
+
+} // namespace mongo::unittest
diff --git a/src/mongo/unittest/assert_that.h b/src/mongo/unittest/assert_that.h
index ab1d3cbf75c..9a79871cc27 100644
--- a/src/mongo/unittest/assert_that.h
+++ b/src/mongo/unittest/assert_that.h
@@ -31,9 +31,9 @@
#include <tuple>
+#include "mongo/unittest/assert.h"
#include "mongo/unittest/matcher.h"
#include "mongo/unittest/matcher_core.h"
-#include "mongo/unittest/unittest.h"
/**
* unittest-style ASSERT that an `expr` successfully matches a `matcher`.
diff --git a/src/mongo/unittest/assert_that_test.cpp b/src/mongo/unittest/assert_that_test.cpp
index 27febc03fbe..a20ec42197a 100644
--- a/src/mongo/unittest/assert_that_test.cpp
+++ b/src/mongo/unittest/assert_that_test.cpp
@@ -249,9 +249,9 @@ TEST(AssertThat, UnprintableValues) {
struct Unprintable {
int i;
} v{123};
- std::string lastResort = detail::lastResortFormat(typeid(v), &v, sizeof(v));
+ std::string lastResort = stringify::lastResortFormat(typeid(v), &v, sizeof(v));
// Test that the lastResortFormat function is used for unprintable values.
- using detail::stringifyForAssert; // Augment ADL with the "detail" NS.
+ using stringify::stringifyForAssert; // Augment ADL with the "detail" NS.
ASSERT_EQ(stringifyForAssert(v), lastResort);
// Test that a typical matcher like Eq uses it.
ASSERT_STRING_CONTAINS(Eq(v).describe(), lastResort);
diff --git a/src/mongo/unittest/bson_test_util.h b/src/mongo/unittest/bson_test_util.h
index 2e7c928c339..fb8bfe4ce3b 100644
--- a/src/mongo/unittest/bson_test_util.h
+++ b/src/mongo/unittest/bson_test_util.h
@@ -31,7 +31,7 @@
#include "mongo/bson/simple_bsonelement_comparator.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
-#include "mongo/unittest/unittest.h"
+#include "mongo/unittest/assert.h"
/**
* BSON comparison utility macro. Do not use directly.
diff --git a/src/mongo/unittest/death_test.cpp b/src/mongo/unittest/death_test.cpp
index d7f48112953..6df47dece01 100644
--- a/src/mongo/unittest/death_test.cpp
+++ b/src/mongo/unittest/death_test.cpp
@@ -29,11 +29,13 @@
#include "mongo/platform/basic.h"
+#include "mongo/unittest/death_test.h"
+
#include <fmt/format.h>
#include <stdio.h>
#include "mongo/bson/json.h"
-#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/assert.h"
#include "mongo/unittest/temp_dir.h"
#include "mongo/util/exit_code.h"
diff --git a/src/mongo/unittest/death_test.h b/src/mongo/unittest/death_test.h
index 4e1643625e5..cbdf8778d75 100644
--- a/src/mongo/unittest/death_test.h
+++ b/src/mongo/unittest/death_test.h
@@ -33,7 +33,7 @@
#include <memory>
#include <string>
-#include "mongo/unittest/unittest.h"
+#include "mongo/unittest/framework.h"
/**
* Constructs a single death test named `TEST_NAME` within the test suite `SUITE_NAME`.
diff --git a/src/mongo/unittest/framework.h b/src/mongo/unittest/framework.h
new file mode 100644
index 00000000000..6f0a4f85c89
--- /dev/null
+++ b/src/mongo/unittest/framework.h
@@ -0,0 +1,444 @@
+/**
+ * Copyright (C) 2018-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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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.
+ */
+
+/*
+ * A C++ unit testing framework.
+ *
+ * Most users will include the umbrella header "mongo/unittest/unittest.h".
+ *
+ * For examples of basic usage, see mongo/unittest/unittest_test.cpp.
+ *
+ * ASSERT macros and supporting definitions are in mongo/unittest/assert.h.
+ *
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <cmath>
+#include <fmt/format.h>
+#include <functional>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/logv2/log_debug.h"
+#include "mongo/logv2/log_detail.h"
+#include "mongo/unittest/test_info.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/optional_util.h"
+#include "mongo/util/str.h"
+
+/**
+ * Construct a single test, named `TEST_NAME` within the test Suite `SUITE_NAME`.
+ *
+ * Usage:
+ *
+ * TEST(MyModuleTests, TestThatFooFailsOnErrors) {
+ * ASSERT_EQUALS(error_success, foo(invalidValue));
+ * }
+ */
+#define TEST(SUITE_NAME, TEST_NAME) \
+ UNIT_TEST_DETAIL_DEFINE_TEST_(SUITE_NAME, TEST_NAME, ::mongo::unittest::Test)
+
+/**
+ * Construct a single test named TEST_NAME that has access to a common class (a "fixture")
+ * named "FIXTURE_NAME". FIXTURE_NAME will be the name of the Suite in which the test appears.
+ *
+ * Usage:
+ *
+ * class FixtureClass : public mongo::unittest::Test {
+ * protected:
+ * int myVar;
+ * void setUp() { myVar = 10; }
+ * };
+ *
+ * TEST(FixtureClass, TestThatUsesFixture) {
+ * ASSERT_EQUALS(10, myVar);
+ * }
+ */
+#define TEST_F(FIXTURE_NAME, TEST_NAME) \
+ UNIT_TEST_DETAIL_DEFINE_TEST_(FIXTURE_NAME, TEST_NAME, FIXTURE_NAME)
+
+#define UNIT_TEST_DETAIL_DEFINE_TEST_(SUITE_NAME, TEST_NAME, TEST_BASE) \
+ UNIT_TEST_DETAIL_DEFINE_TEST_PRIMITIVE_( \
+ SUITE_NAME, TEST_NAME, UNIT_TEST_DETAIL_TEST_TYPE_NAME(SUITE_NAME, TEST_NAME), TEST_BASE)
+
+#define UNIT_TEST_DETAIL_DEFINE_TEST_PRIMITIVE_(FIXTURE_NAME, TEST_NAME, TEST_TYPE, TEST_BASE) \
+ class TEST_TYPE : public TEST_BASE { \
+ private: \
+ void _doTest() override; \
+ static inline const ::mongo::unittest::TestInfo _testInfo{ \
+ #FIXTURE_NAME, #TEST_NAME, __FILE__, __LINE__}; \
+ static inline const RegistrationAgent<TEST_TYPE> _agent{&_testInfo}; \
+ }; \
+ void TEST_TYPE::_doTest()
+
+/**
+ * Macro to construct a type name for a test, from its `SUITE_NAME` and `TEST_NAME`.
+ * Do not use directly in test code.
+ */
+#define UNIT_TEST_DETAIL_TEST_TYPE_NAME(SUITE_NAME, TEST_NAME) \
+ UnitTest_SuiteName##SUITE_NAME##TestName##TEST_NAME
+
+namespace mongo::unittest {
+
+class Result;
+
+/**
+ * Representation of a collection of tests.
+ *
+ * One Suite is constructed for each SUITE_NAME when using the TEST macro.
+ *
+ * See `OldStyleSuiteSpecification` which adapts dbtests into this framework.
+ */
+class Suite : public std::enable_shared_from_this<Suite> {
+private:
+ struct SuiteTest {
+ std::string name;
+ std::string fileName;
+ std::function<void()> fn;
+ };
+
+ struct ConstructorEnable {
+ explicit ConstructorEnable() = default;
+ };
+
+public:
+ explicit Suite(ConstructorEnable, std::string name);
+ Suite(const Suite&) = delete;
+ Suite& operator=(const Suite&) = delete;
+
+ void add(std::string name, std::string fileName, std::function<void()> testFn);
+
+ std::unique_ptr<Result> run(const std::string& filter,
+ const std::string& fileNameFilter,
+ int runsPerTest);
+
+ static int run(const std::vector<std::string>& suites,
+ const std::string& filter,
+ const std::string& fileNameFilter,
+ int runsPerTest);
+
+ /**
+ * Get a suite with the given name, creating and registering it if necessary.
+ * This is the only way to make a Suite object.
+ *
+ * Safe to call during static initialization.
+ */
+ static Suite& getSuite(StringData name);
+
+private:
+ /** Points to the string data of the _name field. */
+ StringData key() const {
+ return _name;
+ }
+
+ std::string _name;
+ std::vector<SuiteTest> _tests;
+};
+
+/**
+ * Adaptor to set up a Suite from a dbtest-style suite.
+ * Support for deprecated dbtest-style test suites. Tests are are added by overriding setupTests()
+ * in a subclass of OldStyleSuiteSpecification, and defining an OldStyleSuiteInstance<T> object.
+ * This approach is
+ * deprecated.
+ *
+ * Example:
+ * class All : public OldStyleSuiteSpecification {
+ * public:
+ * All() : OldStyleSuiteSpecification("BunchaTests") {}
+ * void setupTests() {
+ * add<TestThis>();
+ * add<TestThat>();
+ * add<TestTheOtherThing>();
+ * }
+ * };
+ * OldStyleSuiteInitializer<All> all;
+ */
+class OldStyleSuiteSpecification {
+public:
+ struct SuiteTest {
+ std::string name;
+ std::function<void()> fn;
+ };
+
+ OldStyleSuiteSpecification(std::string name) : _name(std::move(name)) {}
+ virtual ~OldStyleSuiteSpecification() = default;
+
+ // Note: setupTests() is run by a OldStyleSuiteInitializer at static initialization time.
+ // It should in most cases be just a simple sequence of add<T>() calls.
+ virtual void setupTests() = 0;
+
+ const std::string& name() const {
+ return _name;
+ }
+
+ const std::vector<SuiteTest>& tests() const {
+ return _tests;
+ }
+
+ /**
+ * Add an old-style test of type `T` to this Suite, saving any test constructor args
+ * that would be needed at test run time.
+ * The added test's name will be synthesized as the demangled typename of T.
+ * At test run time, the test will be created and run with `T(args...).run()`.
+ */
+ template <typename T, typename... Args>
+ void add(Args&&... args) {
+ addNameCallback(nameForTestClass<T>(), [=] { T(args...).run(); });
+ }
+
+ void addNameCallback(std::string name, std::function<void()> cb) {
+ _tests.push_back({std::move(name), std::move(cb)});
+ }
+
+ template <typename T>
+ static std::string nameForTestClass() {
+ return demangleName(typeid(T));
+ }
+
+private:
+ std::string _name;
+ std::vector<SuiteTest> _tests;
+};
+
+/**
+ * Define a namespace-scope instance of `OldStyleSuiteInitializer<T>` to properly create and
+ * initialize an instance of `T` (an `OldStyleSuiteSpecification`). See
+ * `OldStyleSuiteSpecification`.
+ */
+template <typename T>
+struct OldStyleSuiteInitializer {
+ template <typename... Args>
+ explicit OldStyleSuiteInitializer(Args&&... args) {
+ T t(std::forward<Args>(args)...);
+ init(t);
+ }
+
+ void init(OldStyleSuiteSpecification& suiteSpec) const {
+ suiteSpec.setupTests();
+ auto& suite = Suite::getSuite(suiteSpec.name());
+ for (auto&& t : suiteSpec.tests()) {
+ suite.add(t.name, "", t.fn);
+ }
+ }
+};
+
+/**
+ * UnitTest singleton class. Provides access to information about current execution state.
+ */
+class UnitTest {
+ UnitTest() = default;
+
+public:
+ static UnitTest* getInstance();
+
+ UnitTest(const UnitTest& other) = delete;
+ UnitTest& operator=(const UnitTest&) = delete;
+
+public:
+ /**
+ * Returns the currently running test, or `nullptr` if a test is not running.
+ */
+ const TestInfo* currentTestInfo() const;
+
+public:
+ /**
+ * Used to set/unset currently running test information.
+ */
+ class TestRunScope {
+ public:
+ explicit TestRunScope(const TestInfo* testInfo) {
+ UnitTest::getInstance()->setCurrentTestInfo(testInfo);
+ }
+
+ ~TestRunScope() {
+ UnitTest::getInstance()->setCurrentTestInfo(nullptr);
+ }
+ };
+
+private:
+ /**
+ * Sets the currently running tests. Internal: should only be used by unit test framework.
+ * testInfo - test info of the currently running test, or `nullptr` is a test is not running.
+ */
+ void setCurrentTestInfo(const TestInfo* testInfo);
+
+private:
+ const TestInfo* _currentTestInfo = nullptr;
+};
+
+/**
+ * Base type for unit test fixtures. Also, the default fixture type used
+ * by the TEST() macro.
+ */
+class Test {
+public:
+ Test();
+ virtual ~Test();
+ Test(const Test&) = delete;
+ Test& operator=(const Test&) = delete;
+
+ void run();
+
+ /**
+ * Called on the test object before running the test.
+ */
+ virtual void setUp() {}
+
+ /**
+ * Called on the test object after running the test.
+ */
+ virtual void tearDown() {}
+
+protected:
+ /**
+ * Adds a Test to a Suite, used by TEST/TEST_F macros.
+ */
+ template <typename T>
+ class RegistrationAgent {
+ public:
+ /**
+ * These TestInfo must point to data that outlives this RegistrationAgent.
+ * In the case of TEST/TEST_F, these are static variables.
+ */
+ explicit RegistrationAgent(const TestInfo* testInfo) : _testInfo{testInfo} {
+ Suite::getSuite(_testInfo->suiteName())
+ .add(
+ std::string{_testInfo->testName()}, std::string{_testInfo->file()}, [testInfo] {
+ UnitTest::TestRunScope trs(testInfo);
+ T{}.run();
+ });
+ }
+
+ StringData getSuiteName() const {
+ return _testInfo->suiteName();
+ }
+
+ StringData getTestName() const {
+ return _testInfo->testName();
+ }
+
+ StringData getFileName() const {
+ return _testInfo->file();
+ }
+
+ private:
+ const TestInfo* _testInfo;
+ };
+
+ /**
+ * This exception class is used to exercise the testing framework itself. If a test
+ * case throws it, the framework would not consider it an error.
+ */
+ class FixtureExceptionForTesting : public std::exception {};
+
+ /**
+ * Starts capturing messages logged by code under test.
+ *
+ * Log messages will still also go to their default destination; this
+ * code simply adds an additional sink for log messages.
+ *
+ * Clears any previously captured log lines.
+ */
+ void startCapturingLogMessages();
+
+ /**
+ * Stops capturing log messages logged by code under test.
+ */
+ void stopCapturingLogMessages();
+
+ /**
+ * Gets a vector of strings, one log line per string, captured since
+ * the last call to startCapturingLogMessages() in this test.
+ */
+ const std::vector<std::string>& getCapturedTextFormatLogMessages() const;
+ std::vector<BSONObj> getCapturedBSONFormatLogMessages() const;
+
+ /**
+ * Returns the number of collected log lines containing "needle".
+ */
+ int64_t countTextFormatLogLinesContaining(const std::string& needle);
+
+ /**
+ * Returns the number of collected log lines where "needle" is a subset of a line.
+ *
+ * Does a Depth-First-Search of a BSON document. Validates each element in "needle" exists in
+ * "haystack". It ignores extra elements in "haystack".
+ *
+ * In example haystack: { i : 1, a : { b : 1 } },
+ * a valid needles include: { i : 1} or {a : { b : 1}}
+ * It will not find { b: 1 } since it does not search recursively for sub-tree matches.
+ *
+ * If a BSON Element is undefined, it simply checks for its existence, not its type or value.
+ * This allows callers to test for the existence of elements in variable length log lines.
+ */
+ int64_t countBSONFormatLogLinesIsSubset(const BSONObj& needle);
+
+ /**
+ * Prints the captured log lines.
+ */
+ void printCapturedTextFormatLogLines() const;
+
+private:
+ /**
+ * The test itself.
+ */
+ virtual void _doTest() = 0;
+};
+
+/**
+ * Return a list of suite names.
+ */
+std::vector<std::string> getAllSuiteNames();
+
+/** Invocation info (used e.g. by death test to exec). */
+struct SpawnInfo {
+ /** Copy of the original `argv` from main. */
+ std::vector<std::string> argVec;
+ /** If nonempty, this process is a death test respawn. */
+ std::string internalRunDeathTest;
+ /**
+ * A unit test main has to turn this on to indicate that it can be brought to
+ * the death test from a fresh exec with `--suite` and `--filter` options.
+ * Otherwise death tests simply fork. See death_test.cpp.
+ */
+ bool deathTestExecAllowed = false;
+};
+SpawnInfo& getSpawnInfo();
+
+} // namespace mongo::unittest
diff --git a/src/mongo/unittest/matcher.h b/src/mongo/unittest/matcher.h
index be7e7014e12..f1895039918 100644
--- a/src/mongo/unittest/matcher.h
+++ b/src/mongo/unittest/matcher.h
@@ -40,6 +40,7 @@
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/stdx/type_traits.h"
+#include "mongo/unittest/assert.h"
#include "mongo/unittest/matcher_core.h"
/**
@@ -151,7 +152,7 @@ public:
explicit RelOpBase(T v) : _v{std::move(v)} {}
std::string describe() const {
- return format(FMT_STRING("{}({})"), self().name, stringifyForAssert(_v));
+ return format(FMT_STRING("{}({})"), self().name, stringify::stringifyForAssert(_v));
}
template <typename X, std::enable_if_t<stdx::is_detected_v<CanMatchOp, X>, int> = 0>
@@ -396,7 +397,7 @@ private:
auto it = begin(x);
std::array arr{std::get<Is>(_ms).match(*it++)...};
bool allOk = true;
- detail::Joiner joiner;
+ stringify::Joiner joiner;
for (size_t i = 0; i != sizeof...(Ms); ++i) {
if (!arr[i]) {
allOk = false;
@@ -556,7 +557,7 @@ public:
MatchResult match(const Status& st) const {
MatchResult cr = _code.match(st.code());
MatchResult rr = _reason.match(st.reason());
- detail::Joiner joiner;
+ stringify::Joiner joiner;
if (!cr.message().empty())
joiner(format(FMT_STRING("code:{}"), cr.message()));
if (!rr.message().empty()) {
diff --git a/src/mongo/unittest/matcher_core.h b/src/mongo/unittest/matcher_core.h
index c6ff0fb2509..9ae44c7801a 100644
--- a/src/mongo/unittest/matcher_core.h
+++ b/src/mongo/unittest/matcher_core.h
@@ -39,7 +39,8 @@
#include "mongo/base/string_data.h"
#include "mongo/stdx/type_traits.h"
-#include "mongo/unittest/unittest.h"
+#include "mongo/unittest/assert.h"
+#include "mongo/unittest/stringify.h"
#include "mongo/util/optional_util.h"
/**
@@ -100,64 +101,11 @@ class Matcher {};
namespace detail {
/**
- * `stringifyForAssert` can be overloaded to extend stringification
- * capabilities of the matchers via ADL.
- *
- * The overload in the match::detail namespace is used for types for
- * which the unittest library has built-in support.
- */
-template <typename T>
-std::string stringifyForAssert(const T& x);
-
-template <typename T>
-std::string doFormat(const T& x) {
- return format(FMT_STRING("{}"), x);
-}
-
-template <typename T>
-std::string doOstream(const T& x) {
- std::ostringstream os;
- os << x;
- return os.str();
-}
-
-using std::begin;
-using std::end;
-
-template <typename T>
-using HasToStringOp = decltype(std::declval<T>().toString());
-template <typename T>
-constexpr bool HasToString = stdx::is_detected_v<HasToStringOp, T>;
-
-template <typename T>
-using HasBeginEndOp =
- std::tuple<decltype(begin(std::declval<T>())), decltype(end(std::declval<T>()))>;
-template <typename T>
-constexpr bool IsSequence = stdx::is_detected_v<HasBeginEndOp, T>;
-
-class Joiner {
-public:
- template <typename T>
- Joiner& operator()(const T& v) {
- _out += format(FMT_STRING("{}{}"), _sep, stringifyForAssert(v));
- _sep = ", ";
- return *this;
- }
- explicit operator const std::string&() const {
- return _out;
- }
-
-private:
- std::string _out;
- const char* _sep = "";
-};
-
-/**
* Describes a tuple of matchers. This is just a comma-separated list of descriptions.
* Used in the `describe()` function of variadic matchers.
*/
template <typename MTuple, size_t I = 0>
-std::string describeTupleOfMatchers(const MTuple& ms, Joiner&& joiner = {}) {
+std::string describeTupleOfMatchers(const MTuple& ms, stringify::Joiner&& joiner = {}) {
if constexpr (I == std::tuple_size_v<MTuple>) {
return std::string{joiner};
} else {
@@ -176,7 +124,7 @@ std::string describeTupleOfMatchers(const MTuple& ms, Joiner&& joiner = {}) {
template <typename MTuple, size_t N, size_t I = 0>
std::string matchTupleMessage(const MTuple& ms,
const std::array<MatchResult, N>& arr,
- Joiner&& joiner = {}) {
+ stringify::Joiner&& joiner = {}) {
if constexpr (I == std::tuple_size_v<MTuple>) {
return format(FMT_STRING("failed: [{}]"), std::string{joiner});
} else {
@@ -192,56 +140,13 @@ std::string matchTupleMessage(const MTuple& ms,
}
}
-template <typename T>
-std::string doSequence(const T& seq) {
- std::string r;
- Joiner joiner;
- for (const auto& e : seq)
- joiner(e);
- return format(FMT_STRING("[{}]"), std::string{joiner});
-}
-
-std::string lastResortFormat(const std::type_info& ti, const void* p, size_t sz);
-
-/**
- * The default stringifyForAssert implementation.
- * Encodes the steps by which we determine how to print an object.
- * There's a wildcard branch so everything is printable in some way.
- */
-template <typename T>
-std::string stringifyForAssert(const T& x) {
- if constexpr (optional_io::canStreamWithExtension<T>) {
- return doOstream(optional_io::Extension{x});
- } else if constexpr (HasToString<T>) {
- return x.toString();
- } else if constexpr (std::is_convertible_v<T, StringData>) {
- return doFormat(StringData(x));
- } else if constexpr (std::is_pointer_v<T>) {
- return doFormat(static_cast<const void*>(x));
- } else if constexpr (IsSequence<T>) {
- return doSequence(x);
- } else {
- return lastResortFormat(typeid(x), &x, sizeof(x));
- }
-}
-
-/** Portably support stringifying `nullptr`. */
-inline std::string stringifyForAssert(std::nullptr_t) {
- return "nullptr";
-}
-
-/** Built-in support to stringify `ErrorCode::Error`. */
-inline std::string stringifyForAssert(ErrorCodes::Error ec) {
- return ErrorCodes::errorString(ec);
-}
-
template <typename E, typename M>
struct MatchAssertion {
MatchAssertion(const E& e, const M& m, const char* eStr) : mr{m.match(e)} {
if (!mr) {
msg = format(FMT_STRING("value: {}, actual: {}{}, expected: {}"),
eStr,
- stringifyForAssert(e),
+ stringify::stringifyForAssert(e),
mr.message().empty() ? "" : format(FMT_STRING(", {}"), mr.message()),
m.describe());
}
diff --git a/src/mongo/unittest/matcher_core.cpp b/src/mongo/unittest/stringify.cpp
index 370e79faf93..5ca50bf3d33 100644
--- a/src/mongo/unittest/matcher_core.cpp
+++ b/src/mongo/unittest/stringify.cpp
@@ -38,10 +38,14 @@
#include "mongo/util/assert_util.h"
#include "mongo/util/hex.h"
-namespace mongo::unittest::match::detail {
+namespace mongo::unittest::stringify {
+
+std::string formatTypedObj(const std::type_info& ti, StringData s) {
+ return format(FMT_STRING("[{}={}]"), demangleName(ti), s);
+}
std::string lastResortFormat(const std::type_info& ti, const void* p, size_t sz) {
- return format(FMT_STRING("[{}={}]"), demangleName(ti), hexdump(p, sz));
+ return formatTypedObj(ti, hexdump(p, sz));
}
-} // namespace mongo::unittest::match::detail
+} // namespace mongo::unittest::stringify
diff --git a/src/mongo/unittest/stringify.h b/src/mongo/unittest/stringify.h
new file mode 100644
index 00000000000..ab62444c8d6
--- /dev/null
+++ b/src/mongo/unittest/stringify.h
@@ -0,0 +1,154 @@
+/**
+ * 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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <boost/optional.hpp>
+#include <fmt/format.h>
+#include <memory>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <typeinfo>
+#include <utility>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/base/string_data.h"
+#include "mongo/stdx/type_traits.h"
+#include "mongo/util/optional_util.h"
+
+/**
+ * Mechanisms and extensibility hooks used by this library to format arbitrary
+ * user-provided objects.
+ */
+namespace mongo::unittest::stringify {
+
+std::string formatTypedObj(const std::type_info& ti, StringData obj);
+
+std::string lastResortFormat(const std::type_info& ti, const void* p, size_t sz);
+
+/**
+ * `stringifyForAssert` can be overloaded to extend stringification
+ * capabilities of the matchers via ADL.
+ *
+ * The overload in this namespace is used for types for
+ * which the unittest library has built-in support.
+ */
+template <typename T>
+std::string stringifyForAssert(const T& x);
+
+template <typename T>
+std::string doFormat(const T& x) {
+ return format(FMT_STRING("{}"), x);
+}
+
+template <typename T>
+std::string doOstream(const T& x) {
+ std::ostringstream os;
+ os << x;
+ return os.str();
+}
+
+using std::begin;
+using std::end;
+
+template <typename T>
+using HasToStringOp = decltype(std::declval<T>().toString());
+template <typename T>
+constexpr bool HasToString = stdx::is_detected_v<HasToStringOp, T>;
+
+template <typename T>
+using HasBeginEndOp =
+ std::tuple<decltype(begin(std::declval<T>())), decltype(end(std::declval<T>()))>;
+template <typename T>
+constexpr bool IsSequence = stdx::is_detected_v<HasBeginEndOp, T>;
+
+class Joiner {
+public:
+ template <typename T>
+ Joiner& operator()(const T& v) {
+ _out += format(FMT_STRING("{}{}"), _sep, stringifyForAssert(v));
+ _sep = ", ";
+ return *this;
+ }
+ explicit operator const std::string&() const {
+ return _out;
+ }
+
+private:
+ std::string _out;
+ const char* _sep = "";
+};
+
+template <typename T>
+std::string doSequence(const T& seq) {
+ std::string r;
+ Joiner joiner;
+ for (const auto& e : seq)
+ joiner(e);
+ return format(FMT_STRING("[{}]"), std::string{joiner});
+}
+
+/**
+ * The default stringifyForAssert implementation.
+ * Encodes the steps by which we determine how to print an object.
+ * There's a wildcard branch so everything is printable in some way.
+ */
+template <typename T>
+std::string stringifyForAssert(const T& x) {
+ if constexpr (optional_io::canStreamWithExtension<T>) {
+ return doOstream(optional_io::Extension{x});
+ } else if constexpr (HasToString<T>) {
+ return x.toString();
+ } else if constexpr (std::is_convertible_v<T, StringData>) {
+ return doFormat(StringData(x));
+ } else if constexpr (std::is_pointer_v<T>) {
+ return doFormat(static_cast<const void*>(x));
+ } else if constexpr (IsSequence<T>) {
+ return doSequence(x);
+ } else if constexpr (std::is_enum_v<T>) {
+ return formatTypedObj(typeid(T), doFormat(static_cast<std::underlying_type_t<T>>(x)));
+ } else {
+ return lastResortFormat(typeid(x), &x, sizeof(x));
+ }
+}
+
+/** Portably support stringifying `nullptr`. */
+inline std::string stringifyForAssert(std::nullptr_t) {
+ return "nullptr";
+}
+
+/** Built-in support to stringify `ErrorCode::Error`. */
+inline std::string stringifyForAssert(ErrorCodes::Error ec) {
+ return ErrorCodes::errorString(ec);
+}
+
+} // namespace mongo::unittest::stringify
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index 4dd5515e22a..596b1501e25 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -31,904 +31,14 @@
* A C++ unit testing framework.
*
* For examples of basic usage, see mongo/unittest/unittest_test.cpp.
- */
-
-#pragma once
-
-#include <boost/optional.hpp>
-#include <cmath>
-#include <fmt/format.h>
-#include <functional>
-#include <optional>
-#include <sstream>
-#include <string>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-#include "mongo/base/status_with.h"
-#include "mongo/base/string_data.h"
-#include "mongo/logv2/log_debug.h"
-#include "mongo/logv2/log_detail.h"
-#include "mongo/unittest/bson_test_util.h"
-#include "mongo/unittest/test_info.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/optional_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<Char>());
- * ASSERT_DOES_NOT_COMPILE(MyTest2, bool B = false, std::enable_if_t<B, int>{});
- *
- * Examples that fail:
- * ASSERT_DOES_NOT_COMPILE(MyTest3, typename Char = char, *std::declval<Char*>());
- * ASSERT_DOES_NOT_COMPILE(MyTest4, bool B = true, std::enable_if_t<B, int>{});
- *
- */
-#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 <Alias> \
- static std::conditional_t<true, std::false_type, decltype(Expr)> 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); \
- }()))
-
-
-/**
- * Construct a single test, named `TEST_NAME` within the test Suite `SUITE_NAME`.
- *
- * Usage:
- *
- * TEST(MyModuleTests, TestThatFooFailsOnErrors) {
- * ASSERT_EQUALS(error_success, foo(invalidValue));
- * }
- */
-#define TEST(SUITE_NAME, TEST_NAME) \
- UNIT_TEST_DETAIL_DEFINE_TEST_(SUITE_NAME, TEST_NAME, ::mongo::unittest::Test)
-
-/**
- * Construct a single test named TEST_NAME that has access to a common class (a "fixture")
- * named "FIXTURE_NAME". FIXTURE_NAME will be the name of the Suite in which the test appears.
- *
- * Usage:
- *
- * class FixtureClass : public mongo::unittest::Test {
- * protected:
- * int myVar;
- * void setUp() { myVar = 10; }
- * };
- *
- * TEST(FixtureClass, TestThatUsesFixture) {
- * ASSERT_EQUALS(10, myVar);
- * }
- */
-#define TEST_F(FIXTURE_NAME, TEST_NAME) \
- UNIT_TEST_DETAIL_DEFINE_TEST_(FIXTURE_NAME, TEST_NAME, FIXTURE_NAME)
-
-#define UNIT_TEST_DETAIL_DEFINE_TEST_(SUITE_NAME, TEST_NAME, TEST_BASE) \
- UNIT_TEST_DETAIL_DEFINE_TEST_PRIMITIVE_( \
- SUITE_NAME, TEST_NAME, UNIT_TEST_DETAIL_TEST_TYPE_NAME(SUITE_NAME, TEST_NAME), TEST_BASE)
-
-#define UNIT_TEST_DETAIL_DEFINE_TEST_PRIMITIVE_(FIXTURE_NAME, TEST_NAME, TEST_TYPE, TEST_BASE) \
- class TEST_TYPE : public TEST_BASE { \
- private: \
- void _doTest() override; \
- static inline const ::mongo::unittest::TestInfo _testInfo{ \
- #FIXTURE_NAME, #TEST_NAME, __FILE__, __LINE__}; \
- static inline const RegistrationAgent<TEST_TYPE> _agent{&_testInfo}; \
- }; \
- void TEST_TYPE::_doTest()
-
-/**
- * Macro to construct a type name for a test, from its `SUITE_NAME` and `TEST_NAME`.
- * Do not use directly in test code.
- */
-#define UNIT_TEST_DETAIL_TEST_TYPE_NAME(SUITE_NAME, TEST_NAME) \
- UnitTest_SuiteName##SUITE_NAME##TestName##TEST_NAME
-
-namespace mongo::unittest {
-
-template <typename T>
-std::string extendedFormat(T&& t) {
- std::ostringstream os;
- os << optional_io::Extension{t};
- return os.str();
-}
-
-bool searchRegex(const std::string& pattern, const std::string& string);
-
-class Result;
-
-/**
- * Representation of a collection of tests.
*
- * One Suite is constructed for each SUITE_NAME when using the TEST macro.
- *
- * See `OldStyleSuiteSpecification` which adapts dbtests into this framework.
- */
-class Suite : public std::enable_shared_from_this<Suite> {
-private:
- struct SuiteTest {
- std::string name;
- std::string fileName;
- std::function<void()> fn;
- };
-
- struct ConstructorEnable {
- explicit ConstructorEnable() = default;
- };
-
-public:
- explicit Suite(ConstructorEnable, std::string name);
- Suite(const Suite&) = delete;
- Suite& operator=(const Suite&) = delete;
-
- void add(std::string name, std::string fileName, std::function<void()> testFn);
-
- std::unique_ptr<Result> run(const std::string& filter,
- const std::string& fileNameFilter,
- int runsPerTest);
-
- static int run(const std::vector<std::string>& suites,
- const std::string& filter,
- const std::string& fileNameFilter,
- int runsPerTest);
-
- /**
- * Get a suite with the given name, creating and registering it if necessary.
- * This is the only way to make a Suite object.
- *
- * Safe to call during static initialization.
- */
- static Suite& getSuite(StringData name);
-
-private:
- /** Points to the string data of the _name field. */
- StringData key() const {
- return _name;
- }
-
- std::string _name;
- std::vector<SuiteTest> _tests;
-};
-
-/**
- * Adaptor to set up a Suite from a dbtest-style suite.
- * Support for deprecated dbtest-style test suites. Tests are are added by overriding setupTests()
- * in a subclass of OldStyleSuiteSpecification, and defining an OldStyleSuiteInstance<T> object.
- * This approach is
- * deprecated.
+ * ASSERT macros and supporting definitions are in mongo/unittest/assert.h.
*
- * Example:
- * class All : public OldStyleSuiteSpecification {
- * public:
- * All() : OldStyleSuiteSpecification("BunchaTests") {}
- * void setupTests() {
- * add<TestThis>();
- * add<TestThat>();
- * add<TestTheOtherThing>();
- * }
- * };
- * OldStyleSuiteInitializer<All> all;
*/
-class OldStyleSuiteSpecification {
-public:
- struct SuiteTest {
- std::string name;
- std::function<void()> fn;
- };
-
- OldStyleSuiteSpecification(std::string name) : _name(std::move(name)) {}
- virtual ~OldStyleSuiteSpecification() = default;
-
- // Note: setupTests() is run by a OldStyleSuiteInitializer at static initialization time.
- // It should in most cases be just a simple sequence of add<T>() calls.
- virtual void setupTests() = 0;
-
- const std::string& name() const {
- return _name;
- }
-
- const std::vector<SuiteTest>& tests() const {
- return _tests;
- }
-
- /**
- * Add an old-style test of type `T` to this Suite, saving any test constructor args
- * that would be needed at test run time.
- * The added test's name will be synthesized as the demangled typename of T.
- * At test run time, the test will be created and run with `T(args...).run()`.
- */
- template <typename T, typename... Args>
- void add(Args&&... args) {
- addNameCallback(nameForTestClass<T>(), [=] { T(args...).run(); });
- }
-
- void addNameCallback(std::string name, std::function<void()> cb) {
- _tests.push_back({std::move(name), std::move(cb)});
- }
-
- template <typename T>
- static std::string nameForTestClass() {
- return demangleName(typeid(T));
- }
-
-private:
- std::string _name;
- std::vector<SuiteTest> _tests;
-};
-
-/**
- * Define a namespace-scope instance of `OldStyleSuiteInitializer<T>` to properly create and
- * initialize an instance of `T` (an `OldStyleSuiteSpecification`). See
- * `OldStyleSuiteSpecification`.
- */
-template <typename T>
-struct OldStyleSuiteInitializer {
- template <typename... Args>
- explicit OldStyleSuiteInitializer(Args&&... args) {
- T t(std::forward<Args>(args)...);
- init(t);
- }
-
- void init(OldStyleSuiteSpecification& suiteSpec) const {
- suiteSpec.setupTests();
- auto& suite = Suite::getSuite(suiteSpec.name());
- for (auto&& t : suiteSpec.tests()) {
- suite.add(t.name, "", t.fn);
- }
- }
-};
-
-/**
- * UnitTest singleton class. Provides access to information about current execution state.
- */
-class UnitTest {
- UnitTest() = default;
-
-public:
- static UnitTest* getInstance();
-
- UnitTest(const UnitTest& other) = delete;
- UnitTest& operator=(const UnitTest&) = delete;
-
-public:
- /**
- * Returns the currently running test, or `nullptr` if a test is not running.
- */
- const TestInfo* currentTestInfo() const;
-
-public:
- /**
- * Used to set/unset currently running test information.
- */
- class TestRunScope {
- public:
- explicit TestRunScope(const TestInfo* testInfo) {
- UnitTest::getInstance()->setCurrentTestInfo(testInfo);
- }
-
- ~TestRunScope() {
- UnitTest::getInstance()->setCurrentTestInfo(nullptr);
- }
- };
-
-private:
- /**
- * Sets the currently running tests. Internal: should only be used by unit test framework.
- * testInfo - test info of the currently running test, or `nullptr` is a test is not running.
- */
- void setCurrentTestInfo(const TestInfo* testInfo);
-
-private:
- const TestInfo* _currentTestInfo = nullptr;
-};
-
-/**
- * Base type for unit test fixtures. Also, the default fixture type used
- * by the TEST() macro.
- */
-class Test {
-public:
- Test();
- virtual ~Test();
- Test(const Test&) = delete;
- Test& operator=(const Test&) = delete;
-
- void run();
-
- /**
- * Called on the test object before running the test.
- */
- virtual void setUp() {}
-
- /**
- * Called on the test object after running the test.
- */
- virtual void tearDown() {}
-
-protected:
- /**
- * Adds a Test to a Suite, used by TEST/TEST_F macros.
- */
- template <typename T>
- class RegistrationAgent {
- public:
- /**
- * These TestInfo must point to data that outlives this RegistrationAgent.
- * In the case of TEST/TEST_F, these are static variables.
- */
- explicit RegistrationAgent(const TestInfo* testInfo) : _testInfo{testInfo} {
- Suite::getSuite(_testInfo->suiteName())
- .add(
- std::string{_testInfo->testName()}, std::string{_testInfo->file()}, [testInfo] {
- UnitTest::TestRunScope trs(testInfo);
- T{}.run();
- });
- }
-
- StringData getSuiteName() const {
- return _testInfo->suiteName();
- }
-
- StringData getTestName() const {
- return _testInfo->testName();
- }
-
- StringData getFileName() const {
- return _testInfo->file();
- }
- private:
- const TestInfo* _testInfo;
- };
-
- /**
- * This exception class is used to exercise the testing framework itself. If a test
- * case throws it, the framework would not consider it an error.
- */
- class FixtureExceptionForTesting : public std::exception {};
-
- /**
- * Starts capturing messages logged by code under test.
- *
- * Log messages will still also go to their default destination; this
- * code simply adds an additional sink for log messages.
- *
- * Clears any previously captured log lines.
- */
- void startCapturingLogMessages();
-
- /**
- * Stops capturing log messages logged by code under test.
- */
- void stopCapturingLogMessages();
-
- /**
- * Gets a vector of strings, one log line per string, captured since
- * the last call to startCapturingLogMessages() in this test.
- */
- const std::vector<std::string>& getCapturedTextFormatLogMessages() const;
- std::vector<BSONObj> getCapturedBSONFormatLogMessages() const;
-
- /**
- * Returns the number of collected log lines containing "needle".
- */
- int64_t countTextFormatLogLinesContaining(const std::string& needle);
-
- /**
- * Returns the number of collected log lines where "needle" is a subset of a line.
- *
- * Does a Depth-First-Search of a BSON document. Validates each element in "needle" exists in
- * "haystack". It ignores extra elements in "haystack".
- *
- * In example haystack: { i : 1, a : { b : 1 } },
- * a valid needles include: { i : 1} or {a : { b : 1}}
- * It will not find { b: 1 } since it does not search recursively for sub-tree matches.
- *
- * If a BSON Element is undefined, it simply checks for its existence, not its type or value.
- * This allows callers to test for the existence of elements in variable length log lines.
- */
- int64_t countBSONFormatLogLinesIsSubset(const BSONObj& needle);
-
- /**
- * Prints the captured log lines.
- */
- void printCapturedTextFormatLogLines() const;
-
-private:
- /**
- * The test itself.
- */
- virtual void _doTest() = 0;
-};
-
-/**
- * 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 <ComparisonOp op>
-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 <typename A, typename B>
- 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<TestAssertionFailure>(
- theFile,
- theLine,
- format(FMT_STRING("Expected {1} {0} {2} ({3} {0} {4})"),
- name(),
- aExpression,
- bExpression,
- extendedFormat(a),
- extendedFormat(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(typename A, typename B)
- REQUIRES(!(std::is_convertible_v<A, StringData> && std::is_convertible_v<B, StringData>)&& //
- !(std::is_pointer_v<A> && std::is_pointer_v<B>)&& //
- !(std::is_array_v<A> && std::is_array_v<B>))
- 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<bool>(_assertion);
- }
- TestAssertionFailure failure() {
- return *_assertion;
- }
-
-private:
- std::unique_ptr<TestAssertionFailure> _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<ComparisonOp::OP>::ComparisonAssertion( \
- const char*, unsigned, StringData, StringData, const A&, const B&); \
- EXTERN template ComparisonAssertion<ComparisonOp::OP> \
- ComparisonAssertion<ComparisonOp::OP>::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<ComparisonOp::kEq>; \
- __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kNe>; \
- __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kGt>; \
- __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kGe>; \
- __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kLt>; \
- __VA_ARGS__ template class ComparisonAssertion<ComparisonOp::kLe>; \
- \
- 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<T>, or throw an exception if it is not OK.
- */
-template <typename T>
-const T& assertGet(const StatusWith<T>& swt) {
- ASSERT_OK(swt.getStatus());
- return swt.getValue();
-}
-
-template <typename T>
-T assertGet(StatusWith<T>&& swt) {
- ASSERT_OK(swt.getStatus());
- return std::move(swt.getValue());
-}
-
-/**
- * Return a list of suite names.
- */
-std::vector<std::string> getAllSuiteNames();
-
-/** Invocation info (used e.g. by death test to exec). */
-struct SpawnInfo {
- /** Copy of the original `argv` from main. */
- std::vector<std::string> argVec;
- /** If nonempty, this process is a death test respawn. */
- std::string internalRunDeathTest;
- /**
- * A unit test main has to turn this on to indicate that it can be brought to
- * the death test from a fresh exec with `--suite` and `--filter` options.
- * Otherwise death tests simply fork. See death_test.cpp.
- */
- bool deathTestExecAllowed = false;
-};
-SpawnInfo& getSpawnInfo();
+#pragma once
-} // namespace mongo::unittest
+#include "mongo/unittest/assert.h"
+#include "mongo/unittest/assert_that.h"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/framework.h"
diff --git a/src/mongo/unittest/unittest_test.cpp b/src/mongo/unittest/unittest_test.cpp
index bcc4cbccc56..b4402ea770c 100644
--- a/src/mongo/unittest/unittest_test.cpp
+++ b/src/mongo/unittest/unittest_test.cpp
@@ -163,108 +163,32 @@ TEST(UnitTestSelfTest, TestNoDoubleEvaluation) {
ASSERT_TEST_FAILS_MATCH(ASSERT_EQ(0, ++i), "(0 == 1)");
}
-TEST(UnitTestSelfTest, BSONObjEQ) {
- ASSERT_BSONOBJ_EQ(BSON("foo"
- << "bar"),
- BSON("foo"
- << "bar"));
+TEST(UnitTestSelfTest, BSONObjComparisons) {
+ auto a = mongo::BSONObjBuilder{}.append("foo", "bar").obj();
+ auto b = mongo::BSONObjBuilder{}.append("foo", "baz").obj();
+ ASSERT_BSONOBJ_EQ(a, a);
+ ASSERT_BSONOBJ_NE(a, b);
+ ASSERT_BSONOBJ_LT(a, b);
+ ASSERT_BSONOBJ_LTE(a, b);
+ ASSERT_BSONOBJ_LTE(a, b);
+ ASSERT_BSONOBJ_GT(b, a);
+ ASSERT_BSONOBJ_GTE(b, a);
+ ASSERT_BSONOBJ_GTE(a, a);
}
-TEST(UnitTestSelfTest, BSONObjNE) {
- ASSERT_BSONOBJ_NE(BSON("foo"
- << "bar"),
- BSON("foo"
- << "baz"));
-}
-
-TEST(UnitTestSelfTest, BSONObjLT) {
- ASSERT_BSONOBJ_LT(BSON("foo"
- << "bar"),
- BSON("foo"
- << "baz"));
-}
-
-TEST(UnitTestSelfTest, BSONObjLTE) {
- ASSERT_BSONOBJ_LTE(BSON("foo"
- << "bar"),
- BSON("foo"
- << "baz"));
- ASSERT_BSONOBJ_LTE(BSON("foo"
- << "bar"),
- BSON("foo"
- << "bar"));
-}
-
-TEST(UnitTestSelfTest, BSONObjGT) {
- ASSERT_BSONOBJ_GT(BSON("foo"
- << "baz"),
- BSON("foo"
- << "bar"));
-}
-
-TEST(UnitTestSelfTest, BSONObjGTE) {
- ASSERT_BSONOBJ_GTE(BSON("foo"
- << "baz"),
- BSON("foo"
- << "bar"));
- ASSERT_BSONOBJ_GTE(BSON("foo"
- << "bar"),
- BSON("foo"
- << "bar"));
-}
-
-TEST(UnitTestSelfTest, BSONElementEQ) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "bar");
- ASSERT_BSONELT_EQ(obj1.firstElement(), obj2.firstElement());
-}
-
-TEST(UnitTestSelfTest, BSONElementNE) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "baz");
- ASSERT_BSONELT_NE(obj1.firstElement(), obj2.firstElement());
-}
-
-TEST(UnitTestSelfTest, BSONElementLT) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "baz");
- ASSERT_BSONELT_LT(obj1.firstElement(), obj2.firstElement());
-}
-
-TEST(UnitTestSelfTest, BSONElementLTE) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "bar");
- mongo::BSONObj obj3 = BSON("foo"
- << "baz");
- ASSERT_BSONELT_LTE(obj1.firstElement(), obj2.firstElement());
- ASSERT_BSONELT_LTE(obj1.firstElement(), obj3.firstElement());
-}
-
-TEST(UnitTestSelfTest, BSONElementGT) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "baz");
- ASSERT_BSONELT_GT(obj2.firstElement(), obj1.firstElement());
-}
-
-TEST(UnitTestSelfTest, BSONElementGTE) {
- mongo::BSONObj obj1 = BSON("foo"
- << "bar");
- mongo::BSONObj obj2 = BSON("foo"
- << "bar");
- mongo::BSONObj obj3 = BSON("foo"
- << "baz");
- ASSERT_BSONELT_GTE(obj3.firstElement(), obj2.firstElement());
- ASSERT_BSONELT_GTE(obj2.firstElement(), obj1.firstElement());
+TEST(UnitTestSelfTest, BSONElementComparisons) {
+ auto ao = mongo::BSONObjBuilder{}.append("foo", "bar").obj();
+ auto bo = mongo::BSONObjBuilder{}.append("foo", "baz").obj();
+ auto a = ao.firstElement();
+ auto b = bo.firstElement();
+ ASSERT_BSONELT_EQ(a, a);
+ ASSERT_BSONELT_NE(a, b);
+ ASSERT_BSONELT_LT(a, b);
+ ASSERT_BSONELT_LTE(a, a);
+ ASSERT_BSONELT_LTE(a, b);
+ ASSERT_BSONELT_GT(b, a);
+ ASSERT_BSONELT_GTE(b, a);
+ ASSERT_BSONELT_GTE(a, a);
}
class UnitTestFormatTest : public mongo::unittest::Test {
@@ -275,16 +199,16 @@ public:
}
template <template <typename...> class OptionalTemplate>
- void runFormatTest() {
- using mongo::unittest::extendedFormat;
- ASSERT_EQ(extendedFormat(mkOptional<OptionalTemplate, int>()), "--");
- ASSERT_EQ(extendedFormat(mkOptional<OptionalTemplate, std::string>()), "--");
- ASSERT_EQ(extendedFormat(mkOptional<OptionalTemplate, int>(123)), " 123");
- ASSERT_EQ(extendedFormat(mkOptional<OptionalTemplate, std::string>("hey")), " hey");
+ void runFormatOptionalTest() {
+ using mongo::unittest::stringify::stringifyForAssert;
+ ASSERT_EQ(stringifyForAssert(mkOptional<OptionalTemplate, int>()), "--");
+ ASSERT_EQ(stringifyForAssert(mkOptional<OptionalTemplate, std::string>()), "--");
+ ASSERT_EQ(stringifyForAssert(mkOptional<OptionalTemplate, int>(123)), " 123");
+ ASSERT_EQ(stringifyForAssert(mkOptional<OptionalTemplate, std::string>("hey")), " hey");
}
template <template <typename...> class OptionalTemplate, class None>
- void runEqTest(None none) {
+ void runEqOptionalTest(None none) {
ASSERT_EQ(OptionalTemplate<int>{1}, OptionalTemplate<int>{1});
ASSERT_NE(OptionalTemplate<int>{1}, OptionalTemplate<int>{2});
ASSERT_EQ(OptionalTemplate<int>{}, OptionalTemplate<int>{});
@@ -293,19 +217,34 @@ public:
};
TEST_F(UnitTestFormatTest, FormatBoostOptional) {
- runFormatTest<boost::optional>();
+ runFormatOptionalTest<boost::optional>();
}
TEST_F(UnitTestFormatTest, EqBoostOptional) {
- runEqTest<boost::optional>(boost::none);
+ runEqOptionalTest<boost::optional>(boost::none);
}
TEST_F(UnitTestFormatTest, FormatStdOptional) {
- runFormatTest<std::optional>(); // NOLINT
+ runFormatOptionalTest<std::optional>(); // NOLINT
}
TEST_F(UnitTestFormatTest, EqStdOptional) {
- runEqTest<std::optional>(std::nullopt); // NOLINT
+ runEqOptionalTest<std::optional>(std::nullopt); // NOLINT
+}
+
+enum class Color { r, g, b };
+enum class NamedColor { r, g, b };
+
+inline std::ostream& operator<<(std::ostream& os, const NamedColor& e) {
+ return os << std::array{"r", "g", "b"}[static_cast<size_t>(e)];
+}
+
+TEST_F(UnitTestFormatTest, FormatEnumClass) {
+ using mongo::unittest::stringify::stringifyForAssert;
+ ASSERT_STRING_CONTAINS(stringifyForAssert(Color::r), "Color=0");
+ ASSERT_EQ(stringifyForAssert(NamedColor::r), "r");
+ ASSERT_EQ(Color::r, Color::r);
+ ASSERT_EQ(NamedColor::r, NamedColor::r);
}
DEATH_TEST_REGEX(DeathTestSelfTest, TestDeath, "Invariant failure.*false") {