summaryrefslogtreecommitdiff
path: root/indra/test
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2020-04-03 10:38:53 -0400
committerNat Goodspeed <nat@lindenlab.com>2020-04-03 10:38:53 -0400
commitdc07509f296661cf7a4d4bceb88a1a897757de98 (patch)
tree07637f41c8dfbc4cab5cefa65749a8a748603c99 /indra/test
parentb793ab8619d8c52a099aa85fdd831990ca95c6f4 (diff)
DRTVWR-476: Cherry-pick debug aids from commit 77b0c53 (fiber-mutex)
Diffstat (limited to 'indra/test')
-rw-r--r--indra/test/chained_callback.h105
-rw-r--r--indra/test/debug.h40
-rw-r--r--indra/test/print.h42
-rw-r--r--indra/test/test.cpp105
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())
{