/** * 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 "mongo/logger/logstream_builder.h" #include "mongo/stdx/functional.h" #include "mongo/util/assert_util.h" #include "mongo/util/mongoutils/str.h" /** * Fail unconditionally, reporting the given message. */ #define FAIL(MESSAGE) ::mongo::unittest::TestAssertion( __FILE__ , __LINE__ ).fail( (MESSAGE) ) /** * Fails unless "EXPRESSION" is true. */ #define ASSERT_TRUE(EXPRESSION) ::mongo::unittest::TestAssertion( __FILE__, __LINE__ ).failIf( \ !(EXPRESSION), "Expected: " #EXPRESSION ) #define ASSERT(EXPRESSION) ASSERT_TRUE(EXPRESSION) /** * Assert that a Status code is OK. */ #define ASSERT_OK(EXPRESSION) ASSERT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /** * Assert that a status code is anything but OK. */ #define ASSERT_NOT_OK(EXPRESSION) ASSERT_NOT_EQUALS(::mongo::Status::OK(), (EXPRESSION)) /** * Fails if "EXPRESSION" is true. */ #define ASSERT_FALSE(EXPRESSION) ::mongo::unittest::TestAssertion( __FILE__, __LINE__ ).failIf( \ (EXPRESSION), "Expected: !(" #EXPRESSION ")" ) /* * Binary comparison assertions. */ #define ASSERT_EQUALS(a,b) _ASSERT_COMPARISON(Equal, a, b) #define ASSERT_NOT_EQUALS(a,b) _ASSERT_COMPARISON(NotEqual, a, b) #define ASSERT_LESS_THAN(a, b) _ASSERT_COMPARISON(LessThan, a, b) #define ASSERT_NOT_LESS_THAN(a, b) _ASSERT_COMPARISON(NotLessThan, a, b) #define ASSERT_GREATER_THAN(a, b) _ASSERT_COMPARISON(GreaterThan, a, b) #define ASSERT_NOT_GREATER_THAN(a, b) _ASSERT_COMPARISON(NotGreaterThan, a, b) #define ASSERT_LESS_THAN_OR_EQUALS(a, b) ASSERT_NOT_GREATER_THAN(a, b) #define ASSERT_GREATER_THAN_OR_EQUALS(a, b) ASSERT_NOT_LESS_THAN(a, b) /** * Binary comparison utility macro. Do not use directly. */ #define _ASSERT_COMPARISON(COMPARISON, a, b) ::mongo::unittest::ComparisonAssertion( \ #a, #b , __FILE__ , __LINE__ ).assert##COMPARISON( (a), (b) ) /** * Approximate equality assertion. Useful for comparisons on limited precision floating point * values. */ #define ASSERT_APPROX_EQUAL(a,b,ABSOLUTE_ERR) ::mongo::unittest::assertApproxEqual( \ #a, #b, a, b, ABSOLUTE_ERR, __FILE__, __LINE__) /** * 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) \ do { \ bool threw = false; \ ::mongo::unittest::TestAssertion _testAssertion( __FILE__, __LINE__ ); \ try { \ EXPRESSION; \ } catch ( const EXCEPTION_TYPE& ) { threw = true; } \ if (!threw) \ _testAssertion.fail("Expected expression " #EXPRESSION \ " to throw " #EXCEPTION_TYPE " but it threw nothing."); \ } 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 { }; 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; }; /** * 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: typedef std::vector 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 ); }; /** * Collection of information about failed tests. Used in reporting * failures. */ class TestAssertionFailureDetails : private boost::noncopyable { public: TestAssertionFailureDetails( const std::string& theFile, unsigned theLine, const std::string& theMessage ); const std::string file; const unsigned line; const std::string message; }; /** * 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 _details->file; } unsigned getLine() const { return _details->line; } const std::string& getMessage() const { return _details->message; } std::string toString() const; private: boost::shared_ptr _details; }; /** * Object representing an assertion about some condition. */ class TestAssertion : private boost::noncopyable { public: /** * file std::string must stay in scope and remain unchanged for the lifetime * of the TestAssertion object. */ TestAssertion( const char* file, unsigned line ); ~TestAssertion(); MONGO_COMPILER_NORETURN void fail( const std::string& message) const; void failIf( bool flag, const std::string &message ) const { if ( flag ) fail( message ); } private: const char* _file; const unsigned _line; }; /** * Specialization of TestAssertion for binary comparisons. */ class ComparisonAssertion : private TestAssertion { public: /** * All char* arguments must stay in scope and remain unchanged for the lifetime * of the ComparisonAssertion object. */ ComparisonAssertion( const char* aexp , const char* bexp , const char* file , unsigned line ); template void assertEqual( const A& a , const B& b ) { if ( a == b ) return; fail(getComparisonFailureMessage("==", a, b)); } template void assertNotEqual( const A& a , const B& b ) { if ( a != b ) return; fail(getComparisonFailureMessage("!=", a, b)); } template void assertLessThan( const A& a , const B& b ) { if ( a < b ) return; fail(getComparisonFailureMessage("<", a, b)); } template void assertNotLessThan( const A& a , const B& b ) { if ( a >= b ) return; fail(getComparisonFailureMessage(">=", a, b)); } template void assertGreaterThan( const A& a , const B& b ) { if ( a > b ) return; fail(getComparisonFailureMessage(">", a, b)); } template void assertNotGreaterThan( const A& a , const B& b ) { if ( a <= b ) return; fail(getComparisonFailureMessage("<=", a, b)); } private: template< typename A, typename B> std::string getComparisonFailureMessage(const std::string &theOperator, const A& a, const B& b); const char* _aexp; const char* _bexp; }; /** * Helper for ASSERT_APPROX_EQUAL to ensure that the arguments are evaluated only once. */ template < typename A, typename B, typename ABSOLUTE_ERR > inline void assertApproxEqual(const char* aexp, const char* bexp, const A& a, const B& b, const ABSOLUTE_ERR& absoluteErr, const char* file , unsigned line) { if (std::abs(a - b) <= absoluteErr) return; TestAssertion(file, line).fail(mongoutils::str::stream() << "Expected " << aexp << " and " << bexp << " to be within " << absoluteErr << " of each other ((" << a << ") - (" << b << ") = " << (a - b) << ")"); } /** * 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(); } // namespace unittest } // namespace mongo #include "mongo/unittest/unittest-inl.h"