summaryrefslogtreecommitdiff
path: root/src/mongo/unittest
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2019-10-02 13:50:00 +0000
committerevergreen <evergreen@mongodb.com>2019-10-02 13:50:00 +0000
commitdadd0a797c334219b5980e7ac2dc9e38288bdc38 (patch)
treea58d527f69c5c3cc839113a3fd54adc852e4ae9c /src/mongo/unittest
parent3af1b9921b1fd802bfbb84681da445207b5f8f37 (diff)
downloadmongo-dadd0a797c334219b5980e7ac2dc9e38288bdc38.tar.gz
SERVER-43418 ASSERT macro repairs
- fix dangling else - all ASSERT* yield a stream expression for appending detail
Diffstat (limited to 'src/mongo/unittest')
-rw-r--r--src/mongo/unittest/unittest.h143
-rw-r--r--src/mongo/unittest/unittest_test.cpp21
2 files changed, 104 insertions, 60 deletions
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index d1ec2d7d515..fc8635bdd75 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -37,6 +37,7 @@
#include <boost/preprocessor/cat.hpp>
#include <cmath>
+#include <fmt/format.h>
#include <functional>
#include <sstream>
#include <string>
@@ -63,8 +64,10 @@
* Fails unless "EXPRESSION" is true.
*/
#define ASSERT_TRUE(EXPRESSION) \
- if (!(EXPRESSION)) \
- FAIL("Expected: " #EXPRESSION)
+ if (EXPRESSION) { \
+ } else \
+ FAIL("Expected: " #EXPRESSION)
+
#define ASSERT(EXPRESSION) ASSERT_TRUE(EXPRESSION)
/**
@@ -104,11 +107,15 @@
/**
* Binary comparison utility macro. Do not use directly.
*/
-#define ASSERT_COMPARISON_(OP, a, b) \
+#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__, #a, #b, a, b)) \
- ca.failure().stream()
+ __FILE__, __LINE__, aExpr, bExpr, a, b); \
+ !ca) { \
+ } else \
+ ca.failure().stream()
/**
* Approximate equality assertion. Useful for comparisons on limited precision floating point
@@ -119,18 +126,20 @@
/**
* Assert a function call returns its input unchanged.
*/
-#define ASSERT_IDENTITY(INPUT, FUNCTION) \
- [&](auto&& v) { \
- if (auto ca = ::mongo::unittest::ComparisonAssertion< \
- ::mongo::unittest::ComparisonOp::kEq>::make(__FILE__, \
- __LINE__, \
- #INPUT, \
- #FUNCTION "(" #INPUT ")", \
- v, \
- FUNCTION( \
- std::forward<decltype(v)>(v)))) \
- ca.failure().stream(); \
- }(INPUT)
+#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.
@@ -225,45 +234,48 @@
* Behaves like ASSERT_THROWS, above, but also calls CHECK(caughtException) which may contain
* additional assertions.
*/
-#define ASSERT_THROWS_WITH_CHECK(EXPRESSION, EXCEPTION_TYPE, CHECK) \
- do { \
- try { \
- UNIT_TEST_INTERNALS_IGNORE_UNUSED_RESULT_WARNINGS(EXPRESSION); \
- } catch (const EXCEPTION_TYPE& ex) { \
- CHECK(ex); \
- break; \
- } \
- /* \
- * 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."); \
- } while (false)
-
-#define ASSERT_STRING_CONTAINS(BIG_STRING, CONTAINS) \
- do { \
- std::string myString(BIG_STRING); \
- std::string myContains(CONTAINS); \
- if (myString.find(myContains) == std::string::npos) { \
- ::mongo::str::stream err; \
- err << "Expected to find " #CONTAINS " (" << myContains << ") in " #BIG_STRING " (" \
- << myString << ")"; \
- ::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, err).stream(); \
- } \
- } while (false)
-
-#define ASSERT_STRING_OMITS(BIG_STRING, OMITS) \
- do { \
- std::string myString(BIG_STRING); \
- std::string myOmits(OMITS); \
- if (myString.find(myOmits) != std::string::npos) { \
- ::mongo::str::stream err; \
- err << "Did not expect to find " #OMITS " (" << myOmits << ") in " #BIG_STRING " (" \
- << myString << ")"; \
- ::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, err).stream(); \
- } \
- } while (false)
+#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); \
+ }()))
/**
* Construct a single test, named `TEST_NAME` within the test Suite `SUITE_NAME`.
@@ -413,7 +425,6 @@ public:
return _tests;
}
-protected:
/**
* Add an old-style test of type `T` to this Suite, saving any test constructor args
* that would be needed at test run time.
@@ -671,10 +682,22 @@ private:
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(a),
+ _stringify(b)));
+ }
+
+ template <typename T>
+ static std::string _stringify(const T& t) {
std::ostringstream os;
- os << "Expected " << aExpression << " " << name() << " " << bExpression << " (" << a << " "
- << name() << " " << b << ")";
- _assertion = std::make_unique<TestAssertionFailure>(theFile, theLine, os.str());
+ os << t;
+ return os.str();
}
public:
diff --git a/src/mongo/unittest/unittest_test.cpp b/src/mongo/unittest/unittest_test.cpp
index e3325c73e1f..7a1c7b62338 100644
--- a/src/mongo/unittest/unittest_test.cpp
+++ b/src/mongo/unittest/unittest_test.cpp
@@ -124,6 +124,27 @@ TEST(UnitTestSelfTest, TestStringComparisons) {
ASSERT_TEST_FAILS(ASSERT_EQUALS("hello", std::string("good bye!")));
}
+TEST(UnitTestSelfTest, TestAssertStringContains) {
+ ASSERT_STRING_CONTAINS("abcdef", "bcd");
+ ASSERT_TEST_FAILS(ASSERT_STRING_CONTAINS("abcdef", "AAA"));
+ ASSERT_TEST_FAILS_MATCH(ASSERT_STRING_CONTAINS("abcdef", "AAA") << "XmsgX", "XmsgX");
+}
+
+TEST(UnitTestSelfTest, TestAssertStringOmits) {
+ ASSERT_STRING_OMITS("abcdef", "AAA");
+ ASSERT_TEST_FAILS(ASSERT_STRING_OMITS("abcdef", "bcd"));
+ ASSERT_TEST_FAILS_MATCH(ASSERT_STRING_OMITS("abcdef", "bcd") << "XmsgX", "XmsgX");
+}
+
+TEST(UnitTestSelfTest, TestAssertIdentity) {
+ auto intIdentity = [](int x) { return x; };
+ ASSERT_IDENTITY(123, intIdentity);
+ ASSERT_IDENTITY(123, [](int x) { return x; });
+ auto zero = [](auto) { return 0; };
+ ASSERT_TEST_FAILS(ASSERT_IDENTITY(1, zero));
+ ASSERT_TEST_FAILS_MATCH(ASSERT_IDENTITY(1, zero) << "XmsgX", "XmsgX");
+}
+
TEST(UnitTestSelfTest, TestStreamingIntoFailures) {
ASSERT_TEST_FAILS_MATCH(ASSERT_TRUE(false) << "Told you so", "Told you so");
ASSERT_TEST_FAILS_MATCH(ASSERT(false) << "Told you so", "Told you so");