summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2012-05-30 17:48:52 -0400
committerAndy Schwerin <schwerin@10gen.com>2012-06-04 13:08:27 -0400
commitc04922ad14ebe43d99ba056b7b3ae79860b8cd91 (patch)
tree5057ff729113f61028ed4c94ae96c26796d49c92 /src
parent7cb8e1f11959d8cb45954fcb1cfe13cdc6caa169 (diff)
downloadmongo-c04922ad14ebe43d99ba056b7b3ae79860b8cd91.tar.gz
SERVER-5702: C++ unit test framework that can be compiled separately.
Includes a unit test of the unit test framework written in the unit test framework, itself.
Diffstat (limited to 'src')
-rw-r--r--src/mongo/dbtests/framework.cpp23
-rw-r--r--src/mongo/unittest/SConscript12
-rw-r--r--src/mongo/unittest/crutch.cpp34
-rw-r--r--src/mongo/unittest/unittest-inl.h38
-rw-r--r--src/mongo/unittest/unittest.cpp153
-rw-r--r--src/mongo/unittest/unittest.h373
-rw-r--r--src/mongo/unittest/unittest_main.cpp12
-rw-r--r--src/mongo/unittest/unittest_test.cpp65
8 files changed, 534 insertions, 176 deletions
diff --git a/src/mongo/dbtests/framework.cpp b/src/mongo/dbtests/framework.cpp
index 975212430b8..9ce7551b2bf 100644
--- a/src/mongo/dbtests/framework.cpp
+++ b/src/mongo/dbtests/framework.cpp
@@ -34,6 +34,7 @@
#include "mongo/db/instance.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/background.h"
+#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/file_allocator.h"
#include "mongo/util/version.h"
@@ -49,6 +50,9 @@ namespace mongo {
namespace dbtests {
+ mutex globalCurrentTestNameMutex("globalCurrentTestNameMutex");
+ std::string globalCurrentTestName;
+
void show_help_text(const char* name, po::options_description options) {
cout << "usage: " << name << " [options] [suite]..." << endl
<< options << "suite: run the specified test suite(s) only" << endl;
@@ -60,13 +64,21 @@ namespace mongo {
virtual void run(){
int minutesRunning = 0;
- std::string lastRunningTestName = ::mongo::unittest::getExecutingTestName();
+ std::string lastRunningTestName, currentTestName;
+
+ {
+ scoped_lock lk( globalCurrentTestNameMutex );
+ lastRunningTestName = globalCurrentTestName;
+ }
while (true) {
sleepsecs(60);
minutesRunning++;
- std::string currentTestName = ::mongo::unittest::getExecutingTestName();
+ {
+ scoped_lock lk( globalCurrentTestNameMutex );
+ currentTestName = globalCurrentTestName;
+ }
if (currentTestName != lastRunningTestName) {
minutesRunning = 0;
@@ -251,6 +263,9 @@ namespace mongo {
TestWatchDog twd;
twd.go();
+ // set tlogLevel to -1 to suppress tlog() output in a test program
+ tlogLevel = -1;
+
int ret = ::mongo::unittest::Suite::run(suites,filter);
#if !defined(_WIN32) && !defined(__sunos__)
@@ -267,3 +282,7 @@ namespace mongo {
} // namespace mongo
+void mongo::unittest::onCurrentTestNameChange( const std::string &testName ) {
+ scoped_lock lk( mongo::dbtests::globalCurrentTestNameMutex );
+ mongo::dbtests::globalCurrentTestName = testName;
+}
diff --git a/src/mongo/unittest/SConscript b/src/mongo/unittest/SConscript
index 9f434723991..43a945fe7ad 100644
--- a/src/mongo/unittest/SConscript
+++ b/src/mongo/unittest/SConscript
@@ -2,4 +2,14 @@
Import("env")
-env.StaticLibrary("unittest", ['unittest.cpp'])
+env.StaticLibrary("unittest", ['unittest.cpp'],
+ LIBDEPS=['$BUILD_DIR/mongo/foundation'])
+
+env.StaticLibrary("unittest_main", ['unittest_main.cpp'],
+ LIBDEPS=['unittest'])
+
+env.StaticLibrary("unittest_crutch", ['crutch.cpp'])
+
+env.Alias('unittests',
+ env.Program('unittest_test', 'unittest_test.cpp',
+ LIBDEPS=['unittest_main', 'unittest_crutch'])
diff --git a/src/mongo/unittest/crutch.cpp b/src/mongo/unittest/crutch.cpp
new file mode 100644
index 00000000000..e17a5e5428c
--- /dev/null
+++ b/src/mongo/unittest/crutch.cpp
@@ -0,0 +1,34 @@
+/**
+* Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * This file should go away. It contains stubs of functions that were needed to link the unit test
+ * framework. As we refactor the system, the contents of this file should _ONLY_ shrink, and
+ * eventually it should contain nothing.
+ */
+
+#include "mongo/pch.h"
+
+#include "mongo/db/lasterror.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/goodies.h"
+
+namespace mongo {
+ bool inShutdown() { return false; }
+ std::string getThreadName() { return "UNKNOWN"; }
+ void setLastError(int code, const char* msg) {}
+ bool StaticObserver::_destroyingStatics = false;
+} // namespace mongo
diff --git a/src/mongo/unittest/unittest-inl.h b/src/mongo/unittest/unittest-inl.h
new file mode 100644
index 00000000000..86539d3ab1a
--- /dev/null
+++ b/src/mongo/unittest/unittest-inl.h
@@ -0,0 +1,38 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+namespace mongo {
+ namespace unittest {
+
+ template <typename T>
+ Test::RegistrationAgent<T>::RegistrationAgent(const std::string& suiteName,
+ const std::string& testName) {
+ Suite::getSuite(suiteName)->add<T>(testName);
+ }
+
+ template<typename A, typename B>
+ std::string ComparisonAssertion::getComparisonFailureMessage(const std::string &theOperator,
+ const A &a, const B &b) {
+ std::ostringstream os;
+ os << "Expected " << _aexp << " " << theOperator << " " << _bexp
+ << " (" << a << " " << theOperator << " " << b << ")";
+ return os.str();
+ }
+
+ } // namespace mongo
+} // namespace unittest
diff --git a/src/mongo/unittest/unittest.cpp b/src/mongo/unittest/unittest.cpp
index 0844bd942da..34b90dccc33 100644
--- a/src/mongo/unittest/unittest.cpp
+++ b/src/mongo/unittest/unittest.cpp
@@ -1,5 +1,3 @@
-// mongo/unittest/unittest.cpp
-
/**
* Copyright (C) 2008 10gen Inc.
*
@@ -16,25 +14,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "mongo/pch.h"
-#include "pch.h"
#include "mongo/unittest/unittest.h"
#include <iostream>
#include <map>
#include "mongo/util/assert_util.h"
-#include "mongo/util/concurrency/mutex.h"
+#include "mongo/util/log.h"
namespace mongo {
namespace unittest {
namespace {
- std::map<std::string, Suite *> *_suites = 0;
+ typedef std::map<std::string, Suite*> SuiteMap;
+
+ inline SuiteMap &_allSuites() {
+ static SuiteMap allSuites;
+ return allSuites;
+ }
- mutex currentTestNameMutex("currentTestNameMutex");
- std::string currentTestName;
} // namespace
class Result {
@@ -72,9 +73,17 @@ namespace mongo {
Result *Result::cur = 0;
- TestCase::~TestCase() {}
- void TestCase::setUp() {}
- void TestCase::tearDown() {}
+ Test::Test() {}
+ Test::~Test() {}
+
+ void Test::run() {
+ setUp();
+ _doTest();
+ tearDown();
+ }
+
+ void Test::setUp() {}
+ void Test::tearDown() {}
Suite::Suite( const std::string &name ) : _name( name ) {
registerSuite( name , this );
@@ -82,9 +91,11 @@ namespace mongo {
Suite::~Suite() {}
+ void Suite::add(const std::string& name, const TestFunction& testFn) {
+ _tests.push_back(new TestHolder(name, testFn));
+ }
+
Result * Suite::run( const std::string& filter ) {
- // set tlogLevel to -1 to suppress tlog() output in a test program
- tlogLevel = -1;
log(1) << "\t about to setupTests" << std::endl;
setupTests();
@@ -93,11 +104,8 @@ namespace mongo {
Result * r = new Result( _name );
Result::cur = r;
- /* see note in SavedContext */
- //writelock lk("");
-
- for ( std::vector<TestCase*>::iterator i=_tests.begin(); i!=_tests.end(); i++ ) {
- TestCase * tc = *i;
+ for ( std::vector<TestHolder*>::iterator i=_tests.begin(); i!=_tests.end(); i++ ) {
+ TestHolder * tc = *i;
if ( filter.size() && tc->getName().find( filter ) == std::string::npos ) {
log(1) << "\t skipping test: " << tc->getName() << " because doesn't match filter" << std::endl;
continue;
@@ -107,12 +115,9 @@ namespace mongo {
bool passes = false;
- {
- scoped_lock lk(currentTestNameMutex);
- currentTestName = tc->getName();
- }
+ onCurrentTestNameChange( tc->getName() );
- log(1) << "\t going to run test: " << tc->getName() << std::endl;
+ std::cout << "\t going to run test: " << tc->getName() << std::endl;
std::stringstream err;
err << tc->getName() << "\t";
@@ -121,23 +126,22 @@ namespace mongo {
tc->run();
passes = true;
}
- catch ( MyAssertionException * ae ) {
- err << ae->ss.str();
- delete( ae );
+ catch ( const TestAssertionFailureException & ae ) {
+ err << ae.toString();
}
- catch ( std::exception& e ) {
- err << " exception: " << e.what();
+ catch ( const std::exception& e ) {
+ err << " std::exception: " << e.what() << " in test " << tc->getName();
}
catch ( int x ) {
- err << " caught int : " << x << std::endl;
+ err << " caught int " << x << " in test " << tc->getName();
}
catch ( ... ) {
- err << "unknown exception in test: " << tc->getName() << std::endl;
+ err << "unknown exception in test: " << tc->getName();
}
if ( ! passes ) {
std::string s = err.str();
- log() << "FAIL: " << s << std::endl;
+ std::cout << "FAIL: " << s << std::endl;
r->_fails++;
r->_messages.push_back( s );
}
@@ -147,44 +151,48 @@ namespace mongo {
r->_rc = 17;
- {
- scoped_lock lk(currentTestNameMutex);
- currentTestName = "";
- }
+ onCurrentTestNameChange( "" );
- log(1) << "\t DONE running tests" << std::endl;
+ std::cout << "\t DONE running tests" << std::endl;
return r;
}
int Suite::run( const std::vector<std::string> &suites , const std::string& filter ) {
+
+ if (_allSuites().empty()) {
+ std::cout << "error: no suites registered.";
+ return 1;
+ }
+
for ( unsigned int i = 0; i < suites.size(); i++ ) {
- if ( _suites->find( suites[i] ) == _suites->end() ) {
+ if ( _allSuites().count( suites[i] ) == 0 ) {
std::cout << "invalid test suite [" << suites[i] << "], use --list to see valid names" << std::endl;
- return -1;
+ return 1;
}
}
std::vector<std::string> torun(suites);
if ( torun.empty() ) {
- for ( std::map<std::string,Suite*>::iterator i=_suites->begin() ; i!=_suites->end(); i++ )
+ for ( SuiteMap::const_iterator i = _allSuites().begin();
+ i !=_allSuites().end(); ++i ) {
+
torun.push_back( i->first );
+ }
}
std::vector<Result*> results;
for ( std::vector<std::string>::iterator i=torun.begin(); i!=torun.end(); i++ ) {
std::string name = *i;
- Suite * s = (*_suites)[name];
+ Suite* s = _allSuites()[name];
fassert( 16145, s );
- log() << "going to run suite: " << name << std::endl;
+ std::cout << "going to run suite: " << name << std::endl;
results.push_back( s->run( filter ) );
}
- Logstream::get().flush();
-
std::cout << "**************************************************" << std::endl;
int rc = 0;
@@ -215,53 +223,60 @@ namespace mongo {
}
void Suite::registerSuite( const std::string &name , Suite * s ) {
- if ( ! _suites )
- _suites = new std::map<std::string,Suite*>();
- Suite*& m = (*_suites)[name];
+ Suite*& m = _allSuites()[name];
fassert( 10162, ! m );
m = s;
}
- void assert_pass() {
- Result::cur->_asserts++;
+ Suite* Suite::getSuite(const std::string& name) {
+ Suite* result = _allSuites()[name];
+ if (!result)
+ result = new Suite(name); // Suites are self-registering.
+ return result;
}
- void assert_fail( const char * exp , const char * file , unsigned line ) {
- Result::cur->_asserts++;
+ void Suite::setupTests() {}
- MyAssertionException * e = new MyAssertionException();
- e->ss << "ASSERT FAILED! " << file << ":" << line << std::endl;
- std::cout << e->ss.str() << std::endl;
- throw e;
+ TestAssertionFailureDetails::TestAssertionFailureDetails(
+ const std::string &theFile,
+ unsigned theLine,
+ const std::string &theMessage )
+ : file( theFile ), line( theLine ), message( theMessage ) {
}
- MyAssertionException * MyAsserts::getBase() {
- MyAssertionException * e = new MyAssertionException();
- e->ss << _file << ":" << _line << " " << _aexp << " != " << _bexp << " ";
- return e;
+ TestAssertionFailureException::TestAssertionFailureException(
+ const std::string &theFile,
+ unsigned theLine,
+ const std::string &theFailingExpression )
+ : _details( new TestAssertionFailureDetails( theFile, theLine, theFailingExpression ) ) {
}
- void MyAsserts::printLocation() {
- log() << _file << ":" << _line << " " << _aexp << " != " << _bexp << " ";
+ std::string TestAssertionFailureException::toString() const {
+ std::ostringstream os;
+ os << getMessage() << " @" << getFile() << ":" << getLine();
+ return os.str();
}
- void MyAsserts::_gotAssert() {
- Result::cur->_asserts++;
+ TestAssertion::TestAssertion( const std::string &file, unsigned line )
+ : _file( file ), _line( line ) {
+
+ ++Result::cur->_asserts;
}
- std::string getExecutingTestName() {
- scoped_lock lk(currentTestNameMutex);
- return currentTestName;
+ TestAssertion::~TestAssertion() {}
+
+ void TestAssertion::fail( const std::string &message ) const {
+ throw TestAssertionFailureException( _file, _line, message );
}
+ ComparisonAssertion::ComparisonAssertion( const std::string &aexp, const std::string &bexp,
+ const std::string &file, unsigned line )
+ : TestAssertion( file, line ), _aexp( aexp ), _bexp( bexp ) {}
+
std::vector<std::string> getAllSuiteNames() {
std::vector<std::string> result;
- if (_suites) {
- for ( std::map<std::string, Suite *>::const_iterator i = _suites->begin();
- i != _suites->end(); ++i ) {
-
+ for (SuiteMap::const_iterator i = _allSuites().begin(); i != _allSuites().end(); ++i) {
result.push_back(i->first);
- }
}
return result;
}
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index 28329d0308f..68a0699c66a 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -1,5 +1,3 @@
-// mongo/unittest/unittest.h
-
/**
* Copyright (C) 2008 10gen Inc.
*
@@ -17,35 +15,106 @@
*/
/*
-
- simple portable regression system
+ * A C++ unit testing framework.
+ *
+ * For examples of basic usage, see mongo/unittest/unittest_test.cpp.
*/
#include <sstream>
#include <string>
#include <vector>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
#include "mongo/util/assert_util.h"
-#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
-#define ASSERT_THROWS(a,b) \
- try { \
- a; \
- mongo::unittest::assert_fail( #a , __FILE__ , __LINE__ ); \
- } catch ( b& ){ \
- mongo::unittest::assert_pass(); \
- }
+/**
+ * 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__ ).failUnless( \
+ (EXPRESSION), "Expected: " #EXPRESSION )
+#define ASSERT(EXPRESSION) ASSERT_TRUE(EXPRESSION)
+/**
+ * Fails if "EXPRESSION" is true.
+ */
+#define ASSERT_FALSE(EXPRESSION) ::mongo::unittest::TestAssertion( __FILE__, __LINE__ ).failIf( \
+ (EXPRESSION), "Expected: !(" #EXPRESSION ")" )
-#define ASSERT_EQUALS(a,b) mongo::unittest::MyAsserts( #a , #b , __FILE__ , __LINE__ ).ae( (a) , (b) )
-#define ASSERT_NOT_EQUALS(a,b) mongo::unittest::MyAsserts( #a , #b , __FILE__ , __LINE__ ).nae( (a) , (b) )
+/*
+ * 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)
-#define ASSERT(x) (void)( (!(!(x))) ? mongo::unittest::assert_pass() : FAIL(x) )
-#define FAIL(x) mongo::unittest::assert_fail( #x , __FILE__ , __LINE__ )
+/**
+ * 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) )
+
+/**
+ * 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()
+
+/**
+ * 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 {
@@ -53,157 +122,251 @@ namespace mongo {
class Result;
- class TestCase {
+ /**
+ * Type representing the function composing a test.
+ */
+ typedef boost::function<void (void)> TestFunction;
+
+ /**
+ * Container holding a test function and its name. Suites
+ * contain lists of these.
+ */
+ class TestHolder : private boost::noncopyable {
public:
- virtual ~TestCase();
- virtual void setUp();
- virtual void tearDown();
- virtual void run() = 0;
- virtual std::string getName() = 0;
+ 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;
};
- class Test : public TestCase {
+ /**
+ * Base type for unit test fixtures. Also, the default fixture type used
+ * by the TEST() macro.
+ *
+ * TODO(schwerin): Implement a TEST_F macro that allows testers to specify
+ * different subclasses of Test to be used as the test fixture. These subclasses
+ * could then provide per-test set-up and tear-down code by overriding the
+ * setUp and tearDown methods.
+ */
+ class Test : private boost::noncopyable {
public:
Test();
virtual ~Test();
- virtual std::string getName();
+
+ void run();
protected:
- void setTestName( const std::string &name );
+ /**
+ * Registration agent for adding tests to suites, used by TEST macro.
+ */
+ template <typename T>
+ class RegistrationAgent : private boost::noncopyable {
+ public:
+ RegistrationAgent(const std::string& suiteName, const std::string& testName);
+ };
private:
- std::string _name;
- };
-
- template< class T >
- class TestHolderBase : public TestCase {
- public:
- TestHolderBase() {}
- virtual ~TestHolderBase() {}
- virtual void run() {
- boost::scoped_ptr<T> t;
- t.reset( create() );
- t->run();
- }
- virtual T * create() = 0;
- virtual std::string getName() {
- return demangleName( typeid(T) );
- }
- };
+ /**
+ * Called on the test object before running the test.
+ */
+ virtual void setUp();
- template< class T >
- class TestHolder0 : public TestHolderBase<T> {
- public:
- virtual T * create() {
- return new T();
- }
- };
+ /**
+ * Called on the test object after running the test.
+ */
+ virtual void tearDown();
- template< class T , typename A >
- class TestHolder1 : public TestHolderBase<T> {
- public:
- TestHolder1( const A& a ) : _a(a) {}
- virtual T * create() {
- return new T( _a );
- }
- const A _a;
+ /**
+ * The test itself.
+ */
+ virtual void _doTest() = 0;
};
- class Suite {
+ /**
+ * 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 string &name );
virtual ~Suite();
template<class T>
- void add() {
- _tests.push_back( new TestHolder0<T>() );
- }
+ void add() { add<T>(demangleName(typeid(T))); }
template<class T , typename A >
void add( const A& a ) {
- _tests.push_back( new TestHolder1<T,A>(a) );
+ add(demangleName(typeid(T)), boost::bind(&Suite::runTestObjectWithArg<T, A>, a));
+ }
+
+ template<class T>
+ void add(const std::string& name) {
+ add(name, &Suite::runTestObject<T>);
}
+ void add(const std::string& name, const TestFunction& testFn);
+
Result * run( const std::string& filter );
static int run( const std::vector<std::string> &suites , const std::string& filter );
+ /**
+ * 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 string& name);
+
protected:
- virtual void setupTests() = 0;
+ virtual void setupTests();
private:
- typedef std::vector<TestCase *> TestCaseList;
+ typedef std::vector<TestHolder *> TestHolderList;
+
+ template <typename T>
+ static void runTestObject() {
+ T testObj;
+ testObj.run();
+ }
+
+ template <typename T, typename A>
+ static void runTestObjectWithArg(const A& a) {
+ T testObj(a);
+ testObj.run();
+ }
+
std::string _name;
- TestCaseList _tests;
+ TestHolderList _tests;
bool _ran;
void registerSuite( const std::string &name , Suite *s );
};
- void assert_pass();
- void assert_fail( const char * exp , const char * file , unsigned line );
+ /**
+ * 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;
+ };
- class MyAssertionException : private boost::noncopyable {
+ /**
+ * 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:
- MyAssertionException() {
- ss << "assertion: ";
- }
- std::stringstream ss;
+ 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<TestAssertionFailureDetails> _details;
};
- class MyAsserts : private boost::noncopyable {
+ /**
+ * Object representing an assertion about some condition.
+ */
+ class TestAssertion : private boost::noncopyable {
+
public:
- MyAsserts( const char * aexp , const char * bexp , const char * file , unsigned line )
- : _aexp( aexp ) , _bexp( bexp ) , _file( file ) , _line( line ) {
+ TestAssertion( const std::string &file, unsigned line );
+ ~TestAssertion();
+ void fail( const std::string &message) const;
+ void failIf( bool flag, const std::string &message ) const {
+ if ( flag ) fail( message );
+ }
+ void failUnless( bool flag, const std::string &message ) const {
+ failIf( !flag, message );
}
- template<typename A,typename B>
- void ae( const A &a , const B &b ) {
- _gotAssert();
- if ( a == b )
- return;
+ private:
+ const std::string _file;
+ const unsigned _line;
+ };
+
+ /**
+ * Specialization of TestAssertion for binary comparisons.
+ */
+ class ComparisonAssertion : private TestAssertion {
+ public:
+ ComparisonAssertion( const std::string &aexp , const std::string &bexp ,
+ const std::string &file , unsigned line );
- printLocation();
+ template<typename A,typename B>
+ void assertEqual( const A &a , const B &b ) {
+ failUnless(a == b, getComparisonFailureMessage("==", a, b));
+ }
- MyAssertionException * e = getBase();
- e->ss << a << " != " << b << std::endl;
- log() << e->ss.str() << std::endl;
- throw e;
+ template<typename A,typename B>
+ void assertNotEqual( const A &a , const B &b ) {
+ failUnless(a != b, getComparisonFailureMessage("!=", a, b));
}
template<typename A,typename B>
- void nae( const A &a , const B &b ) {
- _gotAssert();
- if ( a != b )
- return;
+ void assertLessThan( const A &a , const B &b ) {
+ failUnless(a < b, getComparisonFailureMessage("<", a, b));
+ }
- printLocation();
+ template<typename A,typename B>
+ void assertNotLessThan( const A &a , const B &b ) {
+ failUnless(a >= b, getComparisonFailureMessage(">=", a, b));
+ }
- MyAssertionException * e = getBase();
- e->ss << a << " == " << b << std::endl;
- log() << e->ss.str() << std::endl;
- throw e;
+ template<typename A,typename B>
+ void assertGreaterThan( const A &a , const B &b ) {
+ failUnless(a > b, getComparisonFailureMessage(">", a, b));
}
- void printLocation();
+ template<typename A,typename B>
+ void assertNotGreaterThan( const A &a , const B &b ) {
+ failUnless(a <= b, getComparisonFailureMessage("<=", a, b));
+ }
private:
-
- void _gotAssert();
-
- MyAssertionException * getBase();
+ template< typename A, typename B>
+ std::string getComparisonFailureMessage(const std::string &theOperator,
+ const A &a, const B &b);
std::string _aexp;
std::string _bexp;
- std::string _file;
- unsigned _line;
};
/**
- * Returns the name of the currently executing unit test
+ * Hack to support the runaway test observer in dbtests. This is a hook that
+ * unit test running harnesses (unittest_main and dbtests) must implement.
*/
- std::string getExecutingTestName();
+ void onCurrentTestNameChange( const std::string &testName );
/**
* Return a list of suite names.
@@ -212,3 +375,5 @@ namespace mongo {
} // namespace unittest
} // namespace mongo
+
+#include "mongo/unittest/unittest-inl.h"
diff --git a/src/mongo/unittest/unittest_main.cpp b/src/mongo/unittest/unittest_main.cpp
new file mode 100644
index 00000000000..7f685ae4362
--- /dev/null
+++ b/src/mongo/unittest/unittest_main.cpp
@@ -0,0 +1,12 @@
+// mongo/unittest/unittest_main.cpp
+
+#include <string>
+#include <vector>
+
+#include "mongo/unittest/unittest.h"
+
+int main( int argc, char **argv ) {
+ return ::mongo::unittest::Suite::run(std::vector<std::string>(), "");
+}
+
+void mongo::unittest::onCurrentTestNameChange( const std::string &testName ) {}
diff --git a/src/mongo/unittest/unittest_test.cpp b/src/mongo/unittest/unittest_test.cpp
new file mode 100644
index 00000000000..f879158767b
--- /dev/null
+++ b/src/mongo/unittest/unittest_test.cpp
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2012 10gen Inc.
+ */
+
+/**
+ * Unit tests of the unittest framework itself.
+ */
+
+#include "mongo/unittest/unittest.h"
+
+#include <string>
+
+namespace {
+
+#define ASSERT_TEST_FAILS(TEST_EXPR) \
+ ASSERT_THROWS((TEST_EXPR), mongo::unittest::TestAssertionFailureException)
+
+ TEST(UnitTestSelfTest, DoNothing) {
+ }
+
+ void throwSomething() { throw std::exception(); }
+
+ TEST(UnitTestSelfTest, TestAssertThrowsSuccess) {
+ ASSERT_THROWS(throwSomething(), ::std::exception);
+ }
+
+ TEST(UnitTestSelfTest, TestSuccessfulNumericComparisons) {
+ ASSERT_EQUALS(1LL, 1.0);
+ ASSERT_NOT_EQUALS(1LL, 0.5);
+ ASSERT_LESS_THAN(1, 5);
+ ASSERT_LESS_THAN_OR_EQUALS(1, 5);
+ ASSERT_LESS_THAN_OR_EQUALS(5, 5);
+ ASSERT_GREATER_THAN(5, 1);
+ ASSERT_GREATER_THAN_OR_EQUALS(5, 1);
+ ASSERT_GREATER_THAN_OR_EQUALS(5, 5);
+ }
+
+ TEST(UnitTestSelfTest, TestNumericComparisonFailures) {
+ ASSERT_TEST_FAILS(ASSERT_EQUALS(10, 1LL));
+ ASSERT_TEST_FAILS(ASSERT_NOT_EQUALS(10, 10LL));
+ ASSERT_TEST_FAILS(ASSERT_LESS_THAN(10, 10LL));
+ ASSERT_TEST_FAILS(ASSERT_GREATER_THAN(10, 10LL));
+ ASSERT_TEST_FAILS(ASSERT_NOT_LESS_THAN(9, 10LL));
+ ASSERT_TEST_FAILS(ASSERT_NOT_GREATER_THAN(1, 0LL));
+ }
+
+ TEST(UnitTestSelfTest, TestStringComparisons) {
+ ASSERT_EQUALS("hello", "hello");
+ ASSERT_EQUALS(std::string("hello"), "hello");
+ ASSERT_EQUALS("hello", std::string("hello"));
+
+ ASSERT_NOT_EQUALS("hello", "good bye!");
+ ASSERT_NOT_EQUALS(std::string("hello"), "good bye!");
+ ASSERT_NOT_EQUALS("hello", std::string("good bye!"));
+
+ ASSERT_TEST_FAILS(ASSERT_NOT_EQUALS("hello", "hello"));
+ ASSERT_TEST_FAILS(ASSERT_NOT_EQUALS(std::string("hello"), "hello"));
+ ASSERT_TEST_FAILS(ASSERT_NOT_EQUALS("hello", std::string("hello")));
+
+ ASSERT_TEST_FAILS(ASSERT_EQUALS("hello", "good bye!"));
+ ASSERT_TEST_FAILS(ASSERT_EQUALS(std::string("hello"), "good bye!"));
+ ASSERT_TEST_FAILS(ASSERT_EQUALS("hello", std::string("good bye!")));
+ }
+
+} // namespace