diff options
Diffstat (limited to 'indra/test/test.cpp')
-rwxr-xr-x[-rw-r--r--] | indra/test/test.cpp | 413 |
1 files changed, 307 insertions, 106 deletions
diff --git a/indra/test/test.cpp b/indra/test/test.cpp index ffdb0cb976..e42374d56b 100644..100755 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -37,6 +37,11 @@ #include "linden_common.h" #include "llerrorcontrol.h" #include "lltut.h" +#include "tests/wrapllerrs.h" // RecorderProxy +#include "stringize.h" +#include "namedtempfile.h" +#include "lltrace.h" +#include "lltracethreadrecorder.h" #include "apr_pools.h" #include "apr_getopt.h" @@ -53,24 +58,150 @@ #include <gtest/gtest.h> #endif +#if LL_MSVC +#pragma warning (push) +#pragma warning (disable : 4702) // warning C4702: unreachable code +#endif +#include <boost/iostreams/tee.hpp> +#include <boost/iostreams/stream.hpp> +#if LL_MSVC +#pragma warning (pop) +#endif + +#include <boost/scoped_ptr.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> +#include <boost/foreach.hpp> +#include <boost/lambda/lambda.hpp> + +#include <fstream> + +void wouldHaveCrashed(const std::string& message); + namespace tut { std::string sSourceDir; - - test_runner_singleton runner; + + test_runner_singleton runner; } +class LLReplayLog +{ +public: + LLReplayLog() {} + virtual ~LLReplayLog() {} + + virtual void reset() {} + virtual void replay(std::ostream&) {} +}; + +class RecordToTempFile : public LLError::Recorder, public boost::noncopyable +{ +public: + RecordToTempFile(apr_pool_t* pPool) + : LLError::Recorder(), + boost::noncopyable(), + mTempFile("log", "", pPool), + mFile(mTempFile.getName().c_str()) + { + } + + virtual ~RecordToTempFile() + { + mFile.close(); + } + + virtual void recordMessage(LLError::ELevel level, const std::string& message) + { + mFile << message << std::endl; + } + + void reset() + { + mFile.close(); + mFile.open(mTempFile.getName().c_str()); + } + + void replay(std::ostream& out) + { + mFile.close(); + std::ifstream inf(mTempFile.getName().c_str()); + std::string line; + while (std::getline(inf, line)) + { + out << line << std::endl; + } + } + +private: + NamedTempFile mTempFile; + std::ofstream mFile; +}; + +class LLReplayLogReal: public LLReplayLog, public boost::noncopyable +{ +public: + LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool) + : LLReplayLog(), + boost::noncopyable(), + mOldSettings(LLError::saveAndResetSettings()), + mRecorder(new RecordToTempFile(pool)) + { + LLError::setFatalFunction(wouldHaveCrashed); + LLError::setDefaultLevel(level); + LLError::addRecorder(mRecorder); + } + + virtual ~LLReplayLogReal() + { + LLError::removeRecorder(mRecorder); + LLError::restoreSettings(mOldSettings); + } + + virtual void reset() + { + boost::dynamic_pointer_cast<RecordToTempFile>(mRecorder)->reset(); + } + + virtual void replay(std::ostream& out) + { + boost::dynamic_pointer_cast<RecordToTempFile>(mRecorder)->replay(out); + } + +private: + LLError::SettingsStoragePtr mOldSettings; + LLError::RecorderPtr mRecorder; +}; + class LLTestCallback : public tut::callback { public: - LLTestCallback(bool verbose_mode, std::ostream *stream) : - mVerboseMode(verbose_mode), - mTotalTests(0), - mPassedTests(0), - mFailedTests(0), - mSkippedTests(0), - mStream(stream) + LLTestCallback(bool verbose_mode, std::ostream *stream, + boost::shared_ptr<LLReplayLog> replayer) : + mVerboseMode(verbose_mode), + mTotalTests(0), + mPassedTests(0), + mFailedTests(0), + mSkippedTests(0), + // By default, capture a shared_ptr to std::cout, with a no-op "deleter" + // so that destroying the shared_ptr makes no attempt to delete std::cout. + mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1)), + mReplayer(replayer) { + if (stream) + { + // We want a boost::iostreams::tee_device that will stream to two + // std::ostreams. + typedef boost::iostreams::tee_device<std::ostream, std::ostream> TeeDevice; + // More than that, though, we want an actual stream using that + // device. + typedef boost::iostreams::stream<TeeDevice> TeeStream; + // Allocate and assign in two separate steps, per Herb Sutter. + // (Until we turn on C++11 support, have to wrap *stream with + // boost::ref() due to lack of perfect forwarding.) + boost::shared_ptr<std::ostream> pstream(new TeeStream(std::cout, boost::ref(*stream))); + mStream = pstream; + } } ~LLTestCallback() @@ -80,21 +211,37 @@ public: virtual void run_started() { //std::cout << "run_started" << std::endl; + LL_INFOS("TestRunner")<<"Test Started"<< LL_ENDL; } virtual void group_started(const std::string& name) { - std::cout << "Unit test group_started name=" << name << std::endl; + LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL; + *mStream << "Unit test group_started name=" << name << std::endl; } virtual void group_completed(const std::string& name) { - std::cout << "Unit test group_completed name=" << name << std::endl; + LL_INFOS("TestRunner")<<"Unit test group_completed name=" << name << LL_ENDL; + *mStream << "Unit test group_completed name=" << name << std::endl; } virtual void test_completed(const tut::test_result& tr) { ++mTotalTests; + + // If this test failed, dump requested log messages BEFORE stating the + // test result. + if (tr.result != tut::test_result::ok && tr.result != tut::test_result::skip) + { + mReplayer->replay(*mStream); + } + // Either way, clear stored messages in preparation for next test. + mReplayer->reset(); + std::ostringstream out; - out << "[" << tr.group << ", " << tr.test << "] "; + out << "[" << tr.group << ", " << tr.test; + if (! tr.name.empty()) + out << ": " << tr.name; + out << "] "; switch(tr.result) { case tut::test_result::ok: @@ -123,56 +270,45 @@ public: break; default: ++mFailedTests; - out << "unknown"; + out << "unknown (tr.result == " << tr.result << ")"; } if(mVerboseMode || (tr.result != tut::test_result::ok)) { + *mStream << out.str(); if(!tr.message.empty()) { - out << ": '" << tr.message << "'"; + *mStream << ": '" << tr.message << "'"; + LL_WARNS("TestRunner") << "not ok : "<<tr.message << LL_ENDL; } - if (mStream) - { - *mStream << out.str() << std::endl; - } - - std::cout << out.str() << std::endl; + *mStream << std::endl; } - } - - virtual void run_completed() - { - if (mStream) - { - run_completed_(*mStream); - } - run_completed_(std::cout); + LL_INFOS("TestRunner")<<out.str()<<LL_ENDL; } virtual int getFailedTests() const { return mFailedTests; } - virtual void run_completed_(std::ostream &stream) + virtual void run_completed() { - stream << "\tTotal Tests:\t" << mTotalTests << std::endl; - stream << "\tPassed Tests:\t" << mPassedTests; + *mStream << "\tTotal Tests:\t" << mTotalTests << std::endl; + *mStream << "\tPassed Tests:\t" << mPassedTests; if (mPassedTests == mTotalTests) { - stream << "\tYAY!! \\o/"; + *mStream << "\tYAY!! \\o/"; } - stream << std::endl; + *mStream << std::endl; if (mSkippedTests > 0) { - stream << "\tSkipped known failures:\t" << mSkippedTests + *mStream << "\tSkipped known failures:\t" << mSkippedTests << std::endl; } if(mFailedTests > 0) { - stream << "*********************************" << std::endl; - stream << "Failed Tests:\t" << mFailedTests << std::endl; - stream << "Please report or fix the problem." << std::endl; - stream << "*********************************" << std::endl; + *mStream << "*********************************" << std::endl; + *mStream << "Failed Tests:\t" << mFailedTests << std::endl; + *mStream << "Please report or fix the problem." << std::endl; + *mStream << "*********************************" << std::endl; } } @@ -182,7 +318,8 @@ protected: int mPassedTests; int mFailedTests; int mSkippedTests; - std::ostream *mStream; + boost::shared_ptr<std::ostream> mStream; + boost::shared_ptr<LLReplayLog> mReplayer; }; // TeamCity specific class which emits service messages @@ -191,85 +328,113 @@ protected: class LLTCTestCallback : public LLTestCallback { public: - LLTCTestCallback(bool verbose_mode, std::ostream *stream) : - LLTestCallback(verbose_mode, stream), - mTCStream() + LLTCTestCallback(bool verbose_mode, std::ostream *stream, + boost::shared_ptr<LLReplayLog> replayer) : + LLTestCallback(verbose_mode, stream, replayer) { } ~LLTCTestCallback() { - } + } virtual void group_started(const std::string& name) { LLTestCallback::group_started(name); - mTCStream << "\n##teamcity[testSuiteStarted name='" << name << "']" << std::endl; + std::cout << "\n##teamcity[testSuiteStarted name='" << escape(name) << "']" << std::endl; } virtual void group_completed(const std::string& name) { LLTestCallback::group_completed(name); - mTCStream << "##teamcity[testSuiteFinished name='" << name << "']" << std::endl; + std::cout << "##teamcity[testSuiteFinished name='" << escape(name) << "']" << std::endl; } virtual void test_completed(const tut::test_result& tr) { + std::string testname(STRINGIZE(tr.group << "." << tr.test)); + if (! tr.name.empty()) + { + testname.append(":"); + testname.append(tr.name); + } + testname = escape(testname); + + // Sadly, tut::callback doesn't give us control at test start; have to + // backfill start message into TC output. + std::cout << "##teamcity[testStarted name='" << testname << "']" << std::endl; + + // now forward call to base class so any output produced there is in + // the right TC context LLTestCallback::test_completed(tr); switch(tr.result) { case tut::test_result::ok: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; break; + case tut::test_result::fail: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; - break; case tut::test_result::ex: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; - break; case tut::test_result::warn: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; - break; case tut::test_result::term: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; + std::cout << "##teamcity[testFailed name='" << testname + << "' message='" << escape(tr.message) << "']" << std::endl; break; + case tut::test_result::skip: - mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testIgnored name='" << tr.group << "." << tr.test << "']" << std::endl; - mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl; + std::cout << "##teamcity[testIgnored name='" << testname << "']" << std::endl; break; + default: break; } + std::cout << "##teamcity[testFinished name='" << testname << "']" << std::endl; } - virtual void run_completed() - { - LLTestCallback::run_completed(); - - // dump the TC reporting results to cout - tc_run_completed_(std::cout); - } - - virtual void tc_run_completed_(std::ostream &stream) + static std::string escape(const std::string& str) { - - // dump the TC reporting results to cout - stream << mTCStream.str() << std::endl; + // Per http://confluence.jetbrains.net/display/TCD65/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages + std::string result; + BOOST_FOREACH(char c, str) + { + switch (c) + { + case '\'': + result.append("|'"); + break; + case '\n': + result.append("|n"); + break; + case '\r': + result.append("|r"); + break; +/*==========================================================================*| + // These are not possible 'char' values from a std::string. + case '\u0085': // next line + result.append("|x"); + break; + case '\u2028': // line separator + result.append("|l"); + break; + case '\u2029': // paragraph separator + result.append("|p"); + break; +|*==========================================================================*/ + case '|': + result.append("||"); + break; + case '[': + result.append("|["); + break; + case ']': + result.append("|]"); + break; + default: + result.push_back(c); + break; + } + } + return result; } - -protected: - std::ostringstream mTCStream; - }; @@ -306,6 +471,14 @@ void stream_usage(std::ostream& s, const char* app) ++option; } + s << app << " is also sensitive to environment variables:\n" + << "LOGTEST=level : for all tests, emit log messages at level 'level'\n" + << "LOGFAIL=level : only for failed tests, emit log messages at level 'level'\n" + << "where 'level' is one of ALL, DEBUG, INFO, WARN, ERROR, NONE.\n" + << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST.\n" + << "Setting LOGFAIL overrides both LOGTEST and --debug: the only log\n" + << "messages you will see will be for failed tests.\n\n"; + s << "Examples:" << std::endl; s << " " << app << " --verbose" << std::endl; s << "\tRun all the tests and report all results." << std::endl; @@ -313,6 +486,13 @@ void stream_usage(std::ostream& s, const char* app) s << "\tList all available test groups." << std::endl; s << " " << app << " --group=uuid" << std::endl; s << "\tRun the test group 'uuid'." << std::endl; + + s << "\n\n" + << "In any event, logs are recorded in the build directory by appending\n" + << "the suffix '.log' to the full path name of this application.\n" + << "If no level is specified as described above, these log files are at\n" + << "DEBUG level.\n" + ; } void stream_groups(std::ostream& s, const char* app) @@ -332,6 +512,8 @@ void wouldHaveCrashed(const std::string& message) tut::fail("llerrs message: " + message); } +static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; + int main(int argc, char **argv) { // The following line must be executed to initialize Google Mock @@ -339,27 +521,40 @@ int main(int argc, char **argv) #ifndef LL_WINDOWS ::testing::InitGoogleMock(&argc, argv); #endif - LLError::initForApplication("."); + // LOGTEST overrides default, but can be overridden by --debug or LOGFAIL. + const char* LOGTEST = getenv("LOGTEST"); + if (LOGTEST) + { + LLError::initForApplication(".", true /* log to stderr */); + LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST)); + } + else + { + LLError::initForApplication(".", false /* do not log to stderr */); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + } LLError::setFatalFunction(wouldHaveCrashed); - LLError::setDefaultLevel(LLError::LEVEL_ERROR); - //< *TODO: should come from error config file. Note that we - // have a command line option that sets this to debug. + LLError::setPrintLocation(true); + std::string test_app_name(argv[0]); + std::string test_log = test_app_name + ".log"; + LLFile::remove(test_log); + LLError::logToFile(test_log); #ifdef CTYPE_WORKAROUND ctype_workaround(); #endif - apr_initialize(); - apr_pool_t* pool = NULL; - if(APR_SUCCESS != apr_pool_create(&pool, NULL)) + ll_init_apr(); + + if (!sMasterThreadRecorder) { - std::cerr << "Unable to initialize pool" << std::endl; - return 1; + sMasterThreadRecorder = new LLTrace::ThreadRecorder(); + LLTrace::set_master_thread_recorder(sMasterThreadRecorder); } apr_getopt_t* os = NULL; - if(APR_SUCCESS != apr_getopt_init(&os, pool, argc, argv)) + if(APR_SUCCESS != apr_getopt_init(&os, gAPRPoolp, argc, argv)) { - std::cerr << "Unable to pool" << std::endl; + std::cerr << "apr_getopt_init() failed" << std::endl; return 1; } @@ -373,7 +568,7 @@ int main(int argc, char **argv) apr_status_t apr_err; const char* opt_arg = NULL; int opt_id = 0; - std::ofstream *output = NULL; + boost::scoped_ptr<std::ofstream> output; const char *touch = NULL; while(true) @@ -403,7 +598,7 @@ int main(int argc, char **argv) verbose_mode = true; break; case 'o': - output = new std::ofstream; + output.reset(new std::ofstream); output->open(opt_arg); break; case 's': // --sourcedir @@ -418,8 +613,6 @@ int main(int argc, char **argv) wait_at_exit = true; break; case 'd': - // *TODO: should come from error config file. We set it to - // ERROR by default, so this allows full debug levels. LLError::setDefaultLevel(LLError::LEVEL_DEBUG); break; case 'x': @@ -434,14 +627,28 @@ int main(int argc, char **argv) // run the tests + const char* LOGFAIL = getenv("LOGFAIL"); + boost::shared_ptr<LLReplayLog> replayer; + // As described in stream_usage(), LOGFAIL overrides both --debug and + // LOGTEST. + if (LOGFAIL) + { + LLError::ELevel level = LLError::decodeLevel(LOGFAIL); + replayer.reset(new LLReplayLogReal(level, gAPRPoolp)); + } + else + { + replayer.reset(new LLReplayLog()); + } + LLTestCallback* mycallback; if (getenv("TEAMCITY_PROJECT_NAME")) { - mycallback = new LLTCTestCallback(verbose_mode, output); + mycallback = new LLTCTestCallback(verbose_mode, output.get(), replayer); } else { - mycallback = new LLTestCallback(verbose_mode, output); + mycallback = new LLTestCallback(verbose_mode, output.get(), replayer); } tut::runner.get().set_callback(mycallback); @@ -463,12 +670,6 @@ int main(int argc, char **argv) std::cin.get(); } - if (output) - { - output->close(); - delete output; - } - if (touch && success) { std::ofstream s; @@ -477,7 +678,7 @@ int main(int argc, char **argv) s.close(); } - apr_terminate(); + ll_cleanup_apr(); int retval = (success ? 0 : 1); return retval; |