/**
* Copyright (C) 2008 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General 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.
*
* For examples of basic usage, see mongo/unittest/unittest_test.cpp.
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include "mongo/base/status_with.h"
#include "mongo/logger/logstream_builder.h"
#include "mongo/logger/message_log_domain.h"
#include "mongo/stdx/functional.h"
#include "mongo/unittest/bson_test_util.h"
#include "mongo/unittest/unittest_helpers.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/mongoutils/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)) \
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) \
if (auto ca = ::mongo::unittest::ComparisonAssertion<::mongo::unittest::ComparisonOp::OP>( \
__FILE__, __LINE__, #a, #b, a, b)) \
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)
/**
* 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(STATEMENT, EXCEPTION_TYPE) \
ASSERT_THROWS_WITH_CHECK(STATEMENT, EXCEPTION_TYPE, ([](const EXCEPTION_TYPE&) {}))
/**
* 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(STATEMENT, EXCEPTION_TYPE, EXPECTED_WHAT) \
ASSERT_THROWS_WITH_CHECK(STATEMENT, 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(STATEMENT, EXCEPTION_TYPE, EXPECTED_CODE) \
ASSERT_THROWS_WITH_CHECK(STATEMENT, 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(STATEMENT, EXCEPTION_TYPE, EXPECTED_CODE, EXPECTED_WHAT) \
ASSERT_THROWS_WITH_CHECK(STATEMENT, EXCEPTION_TYPE, ([&](const EXCEPTION_TYPE& ex) { \
ASSERT_EQ(ex.toStatus().code(), EXPECTED_CODE); \
ASSERT_EQ(::mongo::StringData(ex.what()), \
::mongo::StringData(EXPECTED_WHAT)); \
}))
/**
* Behaves like ASSERT_THROWS, above, but also calls CHECK(caughtException) which may contain
* additional assertions.
*/
#define ASSERT_THROWS_WITH_CHECK(STATEMENT, EXCEPTION_TYPE, CHECK) \
do { \
try { \
STATEMENT; \
FAIL("Expected statement " #STATEMENT " to throw " #EXCEPTION_TYPE \
" but it threw nothing."); \
} catch (const EXCEPTION_TYPE& ex) { \
CHECK(ex); \
} \
} 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) { \
::mongoutils::str::stream err; \
err << "Expected to find " #CONTAINS " (" << myContains << ") in " #BIG_STRING " (" \
<< myString << ")"; \
::mongo::unittest::TestAssertionFailure(__FILE__, __LINE__, err).stream(); \
} \
} while (false)
/**
* Construct a single test, named "TEST_NAME" within the test case "CASE_NAME".
*
* Usage:
*
* TEST(MyModuleTests, TestThatFooFailsOnErrors) {
* ASSERT_EQUALS(error_success, foo(invalidValue));
* }
*/
#define TEST(CASE_NAME, TEST_NAME) \
class _TEST_TYPE_NAME(CASE_NAME, TEST_NAME) : public ::mongo::unittest::Test { \
private: \
virtual void _doTest(); \
\
static const RegistrationAgent<_TEST_TYPE_NAME(CASE_NAME, TEST_NAME)> _agent; \
}; \
const ::mongo::unittest::Test::RegistrationAgent<_TEST_TYPE_NAME(CASE_NAME, TEST_NAME)> \
_TEST_TYPE_NAME(CASE_NAME, TEST_NAME)::_agent(#CASE_NAME, #TEST_NAME); \
void _TEST_TYPE_NAME(CASE_NAME, TEST_NAME)::_doTest()
/**
* Construct a single test named TEST_NAME that has access to a common class (a "fixture")
* named "FIXTURE_NAME".
*
* 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) \
class _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME) : public FIXTURE_NAME { \
private: \
virtual void _doTest(); \
\
static const RegistrationAgent<_TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)> _agent; \
}; \
const ::mongo::unittest::Test::RegistrationAgent<_TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)> \
_TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)::_agent(#FIXTURE_NAME, #TEST_NAME); \
void _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)::_doTest()
/**
* Macro to construct a type name for a test, from its "CASE_NAME" and "TEST_NAME".
* Do not use directly in test code.
*/
#define _TEST_TYPE_NAME(CASE_NAME, TEST_NAME) UnitTest__##CASE_NAME##__##TEST_NAME
namespace mongo {
namespace unittest {
class Result;
void setupTestLogger();
/**
* Gets a LogstreamBuilder for logging to the unittest log domain, which may have
* different target from the global log domain.
*/
mongo::logger::LogstreamBuilder log();
/**
* Type representing the function composing a test.
*/
typedef stdx::function TestFunction;
/**
* Container holding a test function and its name. Suites
* contain lists of these.
*/
class TestHolder {
MONGO_DISALLOW_COPYING(TestHolder);
public:
TestHolder(const std::string& name, const TestFunction& fn) : _name(name), _fn(fn) {}
~TestHolder() {}
void run() const {
_fn();
}
std::string getName() const {
return _name;
}
private:
std::string _name;
TestFunction _fn;
};
/**
* Base type for unit test fixtures. Also, the default fixture type used
* by the TEST() macro.
*/
class Test {
MONGO_DISALLOW_COPYING(Test);
public:
Test();
virtual ~Test();
void run();
protected:
/**
* Registration agent for adding tests to suites, used by TEST macro.
*/
template
class RegistrationAgent {
MONGO_DISALLOW_COPYING(RegistrationAgent);
public:
RegistrationAgent(const std::string& suiteName, const std::string& testName);
std::string getSuiteName() const;
std::string getTestName() const;
private:
const std::string _suiteName;
const std::string _testName;
};
/**
* 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& getCapturedLogMessages() const {
return _capturedLogMessages;
}
/**
* Returns the number of collected log lines containing "needle".
*/
int64_t countLogLinesContaining(const std::string& needle);
/**
* Prints the captured log lines.
*/
void printCapturedLogLines() const;
/**
* Called on the test object before running the test.
*/
virtual void setUp();
/**
* Called on the test object after running the test.
*/
virtual void tearDown();
private:
/**
* The test itself.
*/
virtual void _doTest() = 0;
bool _isCapturingLogMessages;
std::vector _capturedLogMessages;
logger::MessageLogDomain::AppenderHandle _captureAppenderHandle;
std::unique_ptr _captureAppender;
};
/**
* Representation of a collection of tests.
*
* One suite is constructed for each "CASE_NAME" when using the TEST macro.
* Additionally, tests that are part of dbtests are manually assigned to suites
* by the programmer by overriding setupTests() in a subclass of Suite. This
* approach is deprecated.
*/
class Suite {
MONGO_DISALLOW_COPYING(Suite);
public:
Suite(const std::string& name);
virtual ~Suite();
template
void add() {
add(demangleName(typeid(T)));
}
template
void add(const A& a) {
add(demangleName(typeid(T)), [a] {
T testObj(a);
testObj.run();
});
}
template
void add(const std::string& name) {
add(name, [] {
T testObj;
testObj.run();
});
}
void add(const std::string& name, const TestFunction& testFn);
Result* run(const std::string& filter, int runsPerTest);
static int run(const std::vector& suites,
const std::string& filter,
int runsPerTest);
/**
* Get a suite with the given name, creating it if necessary.
*
* The implementation of this function must be safe to call during the global static
* initialization block before main() executes.
*/
static Suite* getSuite(const std::string& name);
protected:
virtual void setupTests();
private:
typedef std::vector> TestHolderList;
std::string _name;
TestHolderList _tests;
bool _ran;
void registerSuite(const std::string& name, Suite* s);
};
// A type that makes it easy to declare a self registering suite for old style test
// declarations. Suites are self registering so this is *not* a memory leak.
template
struct SuiteInstance {
SuiteInstance() {
new T;
}
template
SuiteInstance(const U& u) {
new T(u);
}
};
/**
* 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(const std::string& theFile,
unsigned theLine,
const std::string& theMessage);
const std::string& getFile() const {
return _file;
}
unsigned getLine() const {
return _line;
}
const std::string& getMessage() const {
return _message;
}
void setMessage(const std::string& message) {
_message = message;
}
const std::string& what() const {
return getMessage();
}
std::string toString() const;
const std::string& getStacktrace() const {
return _stacktrace;
}
private:
std::string _file;
unsigned _line;
std::string _message;
std::string _stacktrace;
};
class TestAssertionFailure {
public:
TestAssertionFailure(const std::string& file, unsigned line, const std::string& message);
TestAssertionFailure(const TestAssertionFailure& other);
~TestAssertionFailure() noexcept(false);
TestAssertionFailure& operator=(const TestAssertionFailure& other);
std::ostream& stream();
private:
TestAssertionFailureException _exception;
std::ostringstream _stream;
bool _enabled;
};
enum class ComparisonOp { kEq, kNe, kLt, kLe, kGt, kGe };
template
class ComparisonAssertion {
private:
template
using OpTag = std::integral_constant;
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a == b; };
}
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a != b; };
}
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a < b; };
}
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a <= b; };
}
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a > b; };
}
static auto comparator(OpTag) {
return [](auto&& a, auto&& b) { return a >= b; };
}
static constexpr StringData name(OpTag) {
return "=="_sd;
}
static constexpr StringData name(OpTag) {
return "!="_sd;
}
static constexpr StringData name(OpTag) {
return "<"_sd;
}
static constexpr StringData name(OpTag) {
return "<="_sd;
}
static constexpr StringData name(OpTag) {
return ">"_sd;
}
static constexpr StringData name(OpTag) {
return ">="_sd;
}
public:
template
ComparisonAssertion(const std::string& theFile,
unsigned theLine,
StringData aExpression,
StringData bExpression,
const A& a,
const B& b) {
if (comparator(OpTag{})(a, b)) {
return;
}
std::ostringstream os;
StringData opName = name(OpTag{});
os << "Expected " << aExpression << " " << opName << " " << bExpression << " (" << a << " "
<< opName << " " << b << ")";
_assertion = std::make_unique(theFile, theLine, os.str());
}
explicit operator bool() const {
return static_cast(_assertion);
}
TestAssertionFailure failure() {
return *_assertion;
}
private:
std::unique_ptr _assertion;
};
/**
* Get the value out of a StatusWith, or throw an exception if it is not OK.
*/
template
const T& assertGet(const StatusWith& swt) {
ASSERT_OK(swt.getStatus());
return swt.getValue();
}
template
T assertGet(StatusWith&& swt) {
ASSERT_OK(swt.getStatus());
return std::move(swt.getValue());
}
/**
* Return a list of suite names.
*/
std::vector getAllSuiteNames();
inline bool alwaysTrue() {
return true;
}
} // namespace unittest
} // namespace mongo
#include "mongo/unittest/unittest-inl.h"