diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2020-04-03 10:38:53 -0400 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2020-04-03 10:38:53 -0400 |
commit | dc07509f296661cf7a4d4bceb88a1a897757de98 (patch) | |
tree | 07637f41c8dfbc4cab5cefa65749a8a748603c99 /indra/test | |
parent | b793ab8619d8c52a099aa85fdd831990ca95c6f4 (diff) |
DRTVWR-476: Cherry-pick debug aids from commit 77b0c53 (fiber-mutex)
Diffstat (limited to 'indra/test')
-rw-r--r-- | indra/test/chained_callback.h | 105 | ||||
-rw-r--r-- | indra/test/debug.h | 40 | ||||
-rw-r--r-- | indra/test/print.h | 42 | ||||
-rw-r--r-- | indra/test/test.cpp | 105 |
4 files changed, 233 insertions, 59 deletions
diff --git a/indra/test/chained_callback.h b/indra/test/chained_callback.h new file mode 100644 index 0000000000..41a7f7c9fa --- /dev/null +++ b/indra/test/chained_callback.h @@ -0,0 +1,105 @@ +/** + * @file chained_callback.h + * @author Nat Goodspeed + * @date 2020-01-03 + * @brief Subclass of tut::callback used for chaining callbacks. + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Copyright (c) 2020, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CHAINED_CALLBACK_H) +#define LL_CHAINED_CALLBACK_H + +/** + * Derive your TUT callback from chained_callback instead of tut::callback to + * ensure that multiple such callbacks can coexist in a given test executable. + * The relevant callback method will be called for each callback instance in + * reverse order of the instance's link() methods being called: the most + * recently link()ed callback will be called first, then the previous, and so + * forth. + * + * Obviously, for this to work, all relevant callbacks must be derived from + * chained_callback instead of tut::callback. Given that, control should reach + * each of them regardless of their construction order. The chain is + * guaranteed to stop because the first link() call will link to test_runner's + * default_callback, which is simply an instance of the callback() base class. + * + * The rule for deriving from chained_callback is that you may override any of + * its virtual methods, but your override must at some point call the + * corresponding chained_callback method. + */ +class chained_callback: public tut::callback +{ +public: + /** + * Instead of calling tut::test_runner::set_callback(&your_callback), call + * your_callback.link(); + * This uses the canonical instance of tut::test_runner. + */ + void link() + { + link(tut::runner.get()); + } + + /** + * If for some reason you have a different instance of test_runner... + */ + void link(tut::test_runner& runner) + { + // Since test_runner's constructor sets a default callback, + // get_callback() will always return a reference to a valid callback + // instance. + mPrev = &runner.get_callback(); + runner.set_callback(this); + } + + /** + * Called when new test run started. + */ + virtual void run_started() + { + mPrev->run_started(); + } + + /** + * Called when a group started + * @param name Name of the group + */ + virtual void group_started(const std::string& name) + { + mPrev->group_started(name); + } + + /** + * Called when a test finished. + * @param tr Test results. + */ + virtual void test_completed(const tut::test_result& tr) + { + mPrev->test_completed(tr); + } + + /** + * Called when a group is completed + * @param name Name of the group + */ + virtual void group_completed(const std::string& name) + { + mPrev->group_completed(name); + } + + /** + * Called when all tests in run completed. + */ + virtual void run_completed() + { + mPrev->run_completed(); + } + +private: + tut::callback* mPrev; +}; + +#endif /* ! defined(LL_CHAINED_CALLBACK_H) */ diff --git a/indra/test/debug.h b/indra/test/debug.h index 33c3ea2d27..76dbb973b2 100644 --- a/indra/test/debug.h +++ b/indra/test/debug.h @@ -29,37 +29,59 @@ #if ! defined(LL_DEBUG_H) #define LL_DEBUG_H -#include <iostream> +#include "print.h" /***************************************************************************** * Debugging stuff *****************************************************************************/ -// This class is intended to illuminate entry to a given block, exit from the -// same block and checkpoints along the way. It also provides a convenient -// place to turn std::cout output on and off. +/** + * This class is intended to illuminate entry to a given block, exit from the + * same block and checkpoints along the way. It also provides a convenient + * place to turn std::cerr output on and off. + * + * If the environment variable LOGTEST is non-empty, each Debug instance will + * announce its construction and destruction, presumably at entry and exit to + * the block in which it's declared. Moreover, any arguments passed to its + * operator()() will be streamed to std::cerr, prefixed by the block + * description. + * + * The variable LOGTEST is used because that's the environment variable + * checked by test.cpp, our TUT main() program, to turn on LLError logging. It + * is expected that Debug is solely for use in test programs. + */ class Debug { public: Debug(const std::string& block): - mBlock(block) + mBlock(block), + mLOGTEST(getenv("LOGTEST")), + // debug output enabled when LOGTEST is set AND non-empty + mEnabled(mLOGTEST && *mLOGTEST) { (*this)("entry"); } + // non-copyable + Debug(const Debug&) = delete; + ~Debug() { (*this)("exit"); } - void operator()(const std::string& status) + template <typename... ARGS> + void operator()(ARGS&&... args) { -#if defined(DEBUG_ON) - std::cout << mBlock << ' ' << status << std::endl; -#endif + if (mEnabled) + { + print(mBlock, ' ', std::forward<ARGS>(args)...); + } } private: const std::string mBlock; + const char* mLOGTEST; + bool mEnabled; }; // It's often convenient to use the name of the enclosing function as the name diff --git a/indra/test/print.h b/indra/test/print.h new file mode 100644 index 0000000000..08e36caddf --- /dev/null +++ b/indra/test/print.h @@ -0,0 +1,42 @@ +/** + * @file print.h + * @author Nat Goodspeed + * @date 2020-01-02 + * @brief print() function for debugging + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Copyright (c) 2020, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_PRINT_H) +#define LL_PRINT_H + +#include <iostream> + +// print(..., NONL); +// leaves the output dangling, suppressing the normally appended std::endl +struct NONL_t {}; +#define NONL (NONL_t()) + +// normal recursion end +inline +void print() +{ + std::cerr << std::endl; +} + +// print(NONL) is a no-op +inline +void print(NONL_t) +{ +} + +template <typename T, typename... ARGS> +void print(T&& first, ARGS&&... rest) +{ + std::cerr << first; + print(std::forward<ARGS>(rest)...); +} + +#endif /* ! defined(LL_PRINT_H) */ diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 6b342ffe89..ea54ba658e 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -37,6 +37,7 @@ #include "linden_common.h" #include "llerrorcontrol.h" #include "lltut.h" +#include "chained_callback.h" #include "stringize.h" #include "namedtempfile.h" #include "lltrace.h" @@ -71,7 +72,6 @@ #include <boost/shared_ptr.hpp> #include <boost/make_shared.hpp> #include <boost/foreach.hpp> -#include <boost/lambda/lambda.hpp> #include <fstream> @@ -172,8 +172,10 @@ private: LLError::RecorderPtr mRecorder; }; -class LLTestCallback : public tut::callback +class LLTestCallback : public chained_callback { + typedef chained_callback super; + public: LLTestCallback(bool verbose_mode, std::ostream *stream, boost::shared_ptr<LLReplayLog> replayer) : @@ -184,7 +186,7 @@ public: 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)), + mStream(boost::shared_ptr<std::ostream>(&std::cout, [](std::ostream*){})), mReplayer(replayer) { if (stream) @@ -205,22 +207,25 @@ public: ~LLTestCallback() { - } + } virtual void run_started() { //std::cout << "run_started" << std::endl; LL_INFOS("TestRunner")<<"Test Started"<< LL_ENDL; + super::run_started(); } virtual void group_started(const std::string& name) { LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL; *mStream << "Unit test group_started name=" << name << std::endl; + super::group_started(name); } virtual void group_completed(const std::string& name) { LL_INFOS("TestRunner")<<"Unit test group_completed name=" << name << LL_ENDL; *mStream << "Unit test group_completed name=" << name << std::endl; + super::group_completed(name); } virtual void test_completed(const tut::test_result& tr) @@ -282,6 +287,7 @@ public: *mStream << std::endl; } LL_INFOS("TestRunner")<<out.str()<<LL_ENDL; + super::test_completed(tr); } virtual int getFailedTests() const { return mFailedTests; } @@ -309,6 +315,7 @@ public: *mStream << "Please report or fix the problem." << std::endl; *mStream << "*********************************" << std::endl; } + super::run_completed(); } protected: @@ -474,9 +481,8 @@ void stream_usage(std::ostream& s, const char* app) << "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"; + << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST,\n" + << "while LOGTEST overrides LOGFAIL.\n\n"; s << "Examples:" << std::endl; s << " " << app << " --verbose" << std::endl; @@ -520,35 +526,8 @@ int main(int argc, char **argv) #ifndef LL_WINDOWS ::testing::InitGoogleMock(&argc, argv); #endif - // 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); - 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 ll_init_apr(); - - if (!sMasterThreadRecorder) - { - sMasterThreadRecorder = new LLTrace::ThreadRecorder(); - LLTrace::set_master_thread_recorder(sMasterThreadRecorder); - } apr_getopt_t* os = NULL; if(APR_SUCCESS != apr_getopt_init(&os, gAPRPoolp, argc, argv)) { @@ -562,7 +541,13 @@ int main(int argc, char **argv) std::string test_group; std::string suite_name; - // values use for options parsing + // LOGTEST overrides default, but can be overridden by --debug. + const char* LOGTEST = getenv("LOGTEST"); + + // DELETE + LOGTEST = "DEBUG"; + + // values used for options parsing apr_status_t apr_err; const char* opt_arg = NULL; int opt_id = 0; @@ -611,10 +596,7 @@ int main(int argc, char **argv) wait_at_exit = true; break; case 'd': - // this is what LLError::initForApplication() does internally - // when you pass log_to_stderr=true - LLError::logToStderr(); - LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LOGTEST = "DEBUG"; break; case 'x': suite_name.assign(opt_arg); @@ -626,22 +608,44 @@ int main(int argc, char **argv) } } - // run the tests - + // set up logging const char* LOGFAIL = getenv("LOGFAIL"); - boost::shared_ptr<LLReplayLog> replayer; - // As described in stream_usage(), LOGFAIL overrides both --debug and - // LOGTEST. But allow user to set LOGFAIL empty to revert to LOGTEST - // and/or --debug. - if (LOGFAIL && *LOGFAIL) + boost::shared_ptr<LLReplayLog> replayer{boost::make_shared<LLReplayLog>()}; + + // Testing environment variables for both 'set' and 'not empty' allows a + // user to suppress a pre-existing environment variable by forcing empty. + if (LOGTEST && *LOGTEST) { - LLError::ELevel level = LLError::decodeLevel(LOGFAIL); - replayer.reset(new LLReplayLogReal(level, gAPRPoolp)); + LLError::initForApplication(".", ".", true /* log to stderr */); + LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST)); } else { - replayer.reset(new LLReplayLog()); + LLError::initForApplication(".", ".", false /* do not log to stderr */); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + if (LOGFAIL && *LOGFAIL) + { + LLError::ELevel level = LLError::decodeLevel(LOGFAIL); + replayer.reset(new LLReplayLogReal(level, gAPRPoolp)); + } } + LLError::setFatalFunction(wouldHaveCrashed); + 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 + + if (!sMasterThreadRecorder) + { + sMasterThreadRecorder = new LLTrace::ThreadRecorder(); + LLTrace::set_master_thread_recorder(sMasterThreadRecorder); + } + + // run the tests LLTestCallback* mycallback; if (getenv("TEAMCITY_PROJECT_NAME")) @@ -653,7 +657,8 @@ int main(int argc, char **argv) mycallback = new LLTestCallback(verbose_mode, output.get(), replayer); } - tut::runner.get().set_callback(mycallback); + // a chained_callback subclass must be linked with previous + mycallback->link(); if(test_group.empty()) { |