/**
* 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
#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/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(EQ, a, b)
#define ASSERT_NE(a,b) _ASSERT_COMPARISON(NE, a, b)
#define ASSERT_LT(a, b) _ASSERT_COMPARISON(LT, a, b)
#define ASSERT_LTE(a, b) _ASSERT_COMPARISON(LTE, a, b)
#define ASSERT_GT(a, b) _ASSERT_COMPARISON(GT, a, b)
#define ASSERT_GTE(a, b) _ASSERT_COMPARISON(GTE, a, b)
/**
* Binary comparison utility macro. Do not use directly.
*/
#define _ASSERT_COMPARISON(COMPARISON, a, b) \
if (::mongo::unittest::ComparisonAssertion_##COMPARISON ca = ::mongo::unittest::ComparisonAssertion_##COMPARISON(__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_PRED(STATEMENT, \
EXCEPTION_TYPE, \
::mongo::stdx::bind(::mongo::unittest::alwaysTrue))
/**
* 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_PRED(STATEMENT, \
EXCEPTION_TYPE, \
::mongo::stdx::bind(std::equal_to(), (EXPECTED_WHAT), \
::mongo::stdx::bind(&EXCEPTION_TYPE::what, \
::mongo::stdx::placeholders::_1)))
/**
* Behaves like ASSERT_THROWS, above, but also fails if PREDICATE(ex) for the throw exception, ex,
* is false.
*/
#define ASSERT_THROWS_PRED(STATEMENT, EXCEPTION_TYPE, PREDICATE) do { \
try { \
STATEMENT; \
FAIL("Expected statement " #STATEMENT \
" to throw " #EXCEPTION_TYPE \
" but it threw nothing."); \
} catch (const EXCEPTION_TYPE& ex) { \
if (!(PREDICATE(ex))) { \
FAIL("Expected " #STATEMENT \
" to throw an exception of type " \
#EXCEPTION_TYPE \
" where " #PREDICATE \
"(ex) was true, but it was false."); \
} \
} \
} while (false)
#define ASSERT_STRING_CONTAINS(BIG_STRING, CONTAINS ) do { \
std::string myString( BIG_STRING ); \
if ( myString.find(CONTAINS) == std::string::npos ) { \
std::string err( "Expected " #BIG_STRING " (" ); \
err += myString; \
err += std::string(") to contain " #CONTAINS ); \
::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;
/**
* 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 : private boost::noncopyable {
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 : private boost::noncopyable {
public:
Test();
virtual ~Test();
void run();
protected:
/**
* Registration agent for adding tests to suites, used by TEST macro.
*/
template
class RegistrationAgent : private boost::noncopyable {
public:
RegistrationAgent(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;
}
private:
/**
* Called on the test object before running the test.
*/
virtual void setUp();
/**
* Called on the test object after running the test.
*/
virtual void tearDown();
/**
* The test itself.
*/
virtual void _doTest() = 0;
bool _isCapturingLogMessages;
std::vector _capturedLogMessages;
logger::MessageLogDomain::AppenderHandle _captureAppenderHandle;
};
/**
* 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 : private boost::noncopyable {
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)), stdx::bind(&Suite::runTestObjectWithArg, a));
}
template
void add(const std::string& name) {
add(name, &Suite::runTestObject);
}
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:
// TODO(C++11): Make this hold unique_ptrs.
typedef std::vector< boost::shared_ptr > TestHolderList;
template
static void runTestObject() {
T testObj;
testObj.run();
}
template
static void runTestObjectWithArg(const A& a) {
T testObj(a);
testObj.run();
}
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; }
std::string toString() const;
private:
std::string _file;
unsigned _line;
std::string _message;
};
class TestAssertionFailure {
public:
TestAssertionFailure(
const std::string& file, unsigned line, const std::string& message);
TestAssertionFailure(const TestAssertionFailure& other);
~TestAssertionFailure() BOOST_NOEXCEPT_IF(false);
TestAssertionFailure& operator=(const TestAssertionFailure& other);
std::ostream& stream();
private:
TestAssertionFailureException _exception;
std::ostringstream _stream;
bool _enabled;
};
#define DECLARE_COMPARISON_ASSERTION(NAME, OPERATOR) \
class ComparisonAssertion_##NAME { \
typedef void (ComparisonAssertion_##NAME::*bool_type)() const; \
public: \
template \
ComparisonAssertion_##NAME( \
const std::string& theFile, \
unsigned theLine, \
StringData aExpression, \
StringData bExpression, \
const A& a, \
const B& b) { \
if (a OPERATOR b) { \
return; \
} \
std::ostringstream os; \
os << "Expected " << \
aExpression << " " #OPERATOR " " << bExpression << \
" (" << a << " " #OPERATOR " " << b << ")"; \
_assertion.reset(new TestAssertionFailure( \
theFile, \
theLine, \
os.str())); \
} \
operator bool_type() const { \
return _assertion.get() ? &ComparisonAssertion_##NAME::comparison_failed : NULL; \
} \
TestAssertionFailure failure() { return *_assertion; } \
private: \
void comparison_failed() const {} \
boost::shared_ptr _assertion; \
}
DECLARE_COMPARISON_ASSERTION(EQ, ==);
DECLARE_COMPARISON_ASSERTION(NE, !=);
DECLARE_COMPARISON_ASSERTION(LT, <);
DECLARE_COMPARISON_ASSERTION(LTE, <=);
DECLARE_COMPARISON_ASSERTION(GT, >);
DECLARE_COMPARISON_ASSERTION(GTE, >=);
#undef DECLARE_COMPARISON_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();
}
/**
* Hack to support the runaway test observer in dbtests. This is a hook that
* unit test running harnesses (unittest_main and dbtests) must implement.
*/
void onCurrentTestNameChange( const std::string& testName );
/**
* Return a list of suite names.
*/
std::vector getAllSuiteNames();
inline bool alwaysTrue() { return true; }
} // namespace unittest
} // namespace mongo
#include "mongo/unittest/unittest-inl.h"