// framework.cpp /** * 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 . */ #include "pch.h" #include "../util/version.h" #include #undef assert #define assert MONGO_assert #include "framework.h" #include "../util/file_allocator.h" #include "../db/dur.h" #include "../util/background.h" #ifndef _WIN32 #include #include #endif namespace po = boost::program_options; namespace mongo { CmdLine cmdLine; namespace regression { map * mongo::regression::Suite::_suites = 0; class Result { public: Result( string name ) : _name( name ) , _rc(0) , _tests(0) , _fails(0) , _asserts(0) { } string toString() { stringstream ss; char result[128]; sprintf(result, "%-20s | tests: %4d | fails: %4d | assert calls: %6d\n", _name.c_str(), _tests, _fails, _asserts); ss << result; for ( list::iterator i=_messages.begin(); i!=_messages.end(); i++ ) { ss << "\t" << *i << '\n'; } return ss.str(); } int rc() { return _rc; } string _name; int _rc; int _tests; int _fails; int _asserts; list _messages; static Result * cur; }; Result * Result::cur = 0; int minutesRunning = 0; // reset to 0 each time a new test starts mutex minutesRunningMutex("minutesRunningMutex"); string currentTestName; Result * Suite::run( const string& filter ) { // set tlogLevel to -1 to suppress tlog() output in a test program tlogLevel = -1; log(1) << "\t about to setupTests" << endl; setupTests(); log(1) << "\t done setupTests" << endl; Result * r = new Result( _name ); Result::cur = r; /* see note in SavedContext */ //writelock lk(""); for ( list::iterator i=_tests.begin(); i!=_tests.end(); i++ ) { TestCase * tc = *i; if ( filter.size() && tc->getName().find( filter ) == string::npos ) { log(1) << "\t skipping test: " << tc->getName() << " because doesn't match filter" << endl; continue; } r->_tests++; bool passes = false; log(1) << "\t going to run test: " << tc->getName() << endl; stringstream err; err << tc->getName() << "\t"; { scoped_lock lk(minutesRunningMutex); minutesRunning = 0; currentTestName = tc->getName(); } try { tc->run(); passes = true; } catch ( MyAssertionException * ae ) { err << ae->ss.str(); delete( ae ); } catch ( std::exception& e ) { err << " exception: " << e.what(); } catch ( int x ) { err << " caught int : " << x << endl; } catch ( ... ) { cerr << "unknown exception in test: " << tc->getName() << endl; } if ( ! passes ) { string s = err.str(); log() << "FAIL: " << s << endl; r->_fails++; r->_messages.push_back( s ); } } if ( r->_fails ) r->_rc = 17; log(1) << "\t DONE running tests" << endl; return r; } 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; } class TestWatchDog : public BackgroundJob { public: virtual string name() const { return "TestWatchDog"; } virtual void run(){ while (true) { sleepsecs(60); scoped_lock lk(minutesRunningMutex); minutesRunning++; //reset to 0 when new test starts if (minutesRunning > 30){ log() << currentTestName << " has been running for more than 30 minutes. aborting." << endl; ::abort(); } else if (minutesRunning > 1){ warning() << currentTestName << " has been running for more than " << minutesRunning-1 << " minutes." << endl; } } } }; unsigned perfHist = 1; int Suite::run( int argc , char** argv , string default_dbpath ) { unsigned long long seed = time( 0 ); string dbpathSpec; po::options_description shell_options("options"); po::options_description hidden_options("Hidden options"); po::options_description cmdline_options("Command line options"); po::positional_options_description positional_options; shell_options.add_options() ("help,h", "show this usage information") ("dbpath", po::value(&dbpathSpec)->default_value(default_dbpath), "db data path for this test run. NOTE: the contents of this " "directory will be overwritten if it already exists") ("debug", "run tests with verbose output") ("list,l", "list available test suites") ("bigfiles", "use big datafiles instead of smallfiles which is the default") ("filter,f" , po::value() , "string substring filter on test name" ) ("verbose,v", "verbose") ("dur", "enable journaling") ("nodur", "disable journaling (currently the default)") ("seed", po::value(&seed), "random number seed") ("perfHist", po::value(&perfHist), "number of back runs of perf stats to display") ; hidden_options.add_options() ("suites", po::value< vector >(), "test suites to run") ; positional_options.add("suites", -1); cmdline_options.add(shell_options).add(hidden_options); po::variables_map params; int command_line_style = (((po::command_line_style::unix_style ^ po::command_line_style::allow_guessing) | po::command_line_style::allow_long_disguise) ^ po::command_line_style::allow_sticky); try { po::store(po::command_line_parser(argc, argv).options(cmdline_options). positional(positional_options). style(command_line_style).run(), params); po::notify(params); } catch (po::error &e) { cout << "ERROR: " << e.what() << endl << endl; show_help_text(argv[0], shell_options); return EXIT_BADOPTIONS; } if (params.count("help")) { show_help_text(argv[0], shell_options); return EXIT_CLEAN; } if( params.count("nodur") ) { cmdLine.dur = false; } if( params.count("dur") || cmdLine.dur ) { cmdLine.dur = true; } if (params.count("debug") || params.count("verbose") ) { logLevel = 1; } if (params.count("list")) { for ( map::iterator i = _suites->begin() ; i != _suites->end(); i++ ) cout << i->first << endl; return 0; } boost::filesystem::path p(dbpathSpec); /* remove the contents of the test directory if it exists. */ if (boost::filesystem::exists(p)) { if (!boost::filesystem::is_directory(p)) { cout << "ERROR: path \"" << p.string() << "\" is not a directory" << endl << endl; show_help_text(argv[0], shell_options); return EXIT_BADOPTIONS; } boost::filesystem::directory_iterator end_iter; for (boost::filesystem::directory_iterator dir_iter(p); dir_iter != end_iter; ++dir_iter) { boost::filesystem::remove_all(*dir_iter); } } else { boost::filesystem::create_directory(p); } string dbpathString = p.native_directory_string(); dbpath = dbpathString.c_str(); cmdLine.prealloc = false; // dbtest defaults to smallfiles cmdLine.smallfiles = true; if( params.count("bigfiles") ) { cmdLine.dur = true; } cmdLine.oplogSize = 10 * 1024 * 1024; Client::initThread("testsuite"); acquirePathLock(); srand( (unsigned) seed ); printGitVersion(); printSysInfo(); DEV log() << "_DEBUG build" << endl; if( sizeof(void*)==4 ) log() << "32bit" << endl; log() << "random seed: " << seed << endl; FileAllocator::get()->start(); vector suites; if (params.count("suites")) { suites = params["suites"].as< vector >(); } string filter = ""; if ( params.count( "filter" ) ) { filter = params["filter"].as(); } dur::startup(); if( debug && cmdLine.dur ) { cout << "_DEBUG: automatically enabling cmdLine.durOptions=8 (DurParanoid)" << endl; // cmdLine.durOptions |= 8; } TestWatchDog twd; twd.go(); int ret = run(suites,filter); #if !defined(_WIN32) && !defined(__sunos__) flock( lockFile, LOCK_UN ); #endif cc().shutdown(); dbexit( (ExitCode)ret ); // so everything shuts down cleanly return ret; } int Suite::run( vector suites , const string& filter ) { for ( unsigned int i = 0; i < suites.size(); i++ ) { if ( _suites->find( suites[i] ) == _suites->end() ) { cout << "invalid test [" << suites[i] << "], use --list to see valid names" << endl; return -1; } } list torun(suites.begin(), suites.end()); if ( torun.size() == 0 ) for ( map::iterator i=_suites->begin() ; i!=_suites->end(); i++ ) torun.push_back( i->first ); list results; for ( list::iterator i=torun.begin(); i!=torun.end(); i++ ) { string name = *i; Suite * s = (*_suites)[name]; assert( s ); log() << "going to run suite: " << name << endl; results.push_back( s->run( filter ) ); } Logstream::get().flush(); cout << "**************************************************" << endl; int rc = 0; int tests = 0; int fails = 0; int asserts = 0; for ( list::iterator i=results.begin(); i!=results.end(); i++ ) { Result * r = *i; cout << r->toString(); if ( abs( r->rc() ) > abs( rc ) ) rc = r->rc(); tests += r->_tests; fails += r->_fails; asserts += r->_asserts; } Result totals ("TOTALS"); totals._tests = tests; totals._fails = fails; totals._asserts = asserts; cout << totals.toString(); // includes endl return rc; } void Suite::registerSuite( string name , Suite * s ) { if ( ! _suites ) _suites = new map(); Suite*& m = (*_suites)[name]; uassert( 10162 , "already have suite with that name" , ! m ); m = s; } void assert_pass() { Result::cur->_asserts++; } void assert_fail( const char * exp , const char * file , unsigned line ) { Result::cur->_asserts++; MyAssertionException * e = new MyAssertionException(); e->ss << "ASSERT FAILED! " << file << ":" << line << endl; throw e; } void fail( const char * exp , const char * file , unsigned line ) { assert(0); } MyAssertionException * MyAsserts::getBase() { MyAssertionException * e = new MyAssertionException(); e->ss << _file << ":" << _line << " " << _aexp << " != " << _bexp << " "; return e; } void MyAsserts::printLocation() { log() << _file << ":" << _line << " " << _aexp << " != " << _bexp << " "; } void MyAsserts::_gotAssert() { Result::cur->_asserts++; } } void setupSignals( bool inFork ) {} }