summaryrefslogtreecommitdiff
path: root/indra/test
diff options
context:
space:
mode:
Diffstat (limited to 'indra/test')
-rw-r--r--indra/test/CMakeLists.txt4
-rw-r--r--indra/test/chained_callback.h107
-rw-r--r--indra/test/debug.h42
-rw-r--r--indra/test/llevents_tut.cpp303
-rw-r--r--indra/test/lltestapp.h34
-rw-r--r--indra/test/print.h42
-rw-r--r--indra/test/setenv.h66
-rw-r--r--indra/test/sync.h116
-rw-r--r--indra/test/test.cpp98
9 files changed, 502 insertions, 310 deletions
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt
index 8344cead57..87536e146b 100644
--- a/indra/test/CMakeLists.txt
+++ b/indra/test/CMakeLists.txt
@@ -67,6 +67,7 @@ set(test_HEADER_FILES
llpipeutil.h
llsdtraits.h
lltut.h
+ sync.h
)
if (NOT WINDOWS)
@@ -83,6 +84,7 @@ list(APPEND test_SOURCE_FILES ${test_HEADER_FILES})
add_executable(lltest ${test_SOURCE_FILES})
target_link_libraries(lltest
+ ${LEGACY_STDIO_LIBS}
${LLDATABASE_LIBRARIES}
${LLINVENTORY_LIBRARIES}
${LLMESSAGE_LIBRARIES}
@@ -98,7 +100,7 @@ target_link_libraries(lltest
${WINDOWS_LIBRARIES}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${DL_LIBRARY}
diff --git a/indra/test/chained_callback.h b/indra/test/chained_callback.h
new file mode 100644
index 0000000000..05929e33ad
--- /dev/null
+++ b/indra/test/chained_callback.h
@@ -0,0 +1,107 @@
+/**
+ * @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
+
+#include "lltut.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 d61eba651b..76dbb973b2 100644
--- a/indra/test/debug.h
+++ b/indra/test/debug.h
@@ -29,42 +29,64 @@
#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
// of the Debug block.
-#define DEBUG Debug debug(__FUNCTION__)
+#define DEBUG Debug debug(LL_PRETTY_FUNCTION)
// These BEGIN/END macros are specifically for debugging output -- please
// don't assume you must use such for coroutines in general! They only help to
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
index 3abae3e43e..17f64a4953 100644
--- a/indra/test/llevents_tut.cpp
+++ b/indra/test/llevents_tut.cpp
@@ -38,7 +38,6 @@
#define testable public
#include "llevents.h"
#undef testable
-#include "lllistenerwrapper.h"
// STL headers
// std headers
#include <iostream>
@@ -92,9 +91,7 @@ template<> template<>
void events_object::test<1>()
{
set_test_name("basic operations");
- // Now there's a static constructor in llevents.cpp that registers on
- // the "mainloop" pump to call LLEventPumps::flush().
- // Actually -- having to modify this to track the statically-
+ // Having to modify this to track the statically-
// constructed pumps in other TUT modules in this giant monolithic test
// executable isn't such a hot idea.
// ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
@@ -211,43 +208,6 @@ bool chainEvents(Listener& someListener, const LLSD& event)
template<> template<>
void events_object::test<3>()
{
- set_test_name("LLEventQueue delayed action");
- // This access is NOT legal usage: we can do it only because we're
- // hacking private for test purposes. Normally we'd either compile in
- // a particular name, or (later) edit a config file.
- pumps.mQueueNames.insert("login");
- LLEventPump& login(pumps.obtain("login"));
- // The "mainloop" pump is special: posting on that implicitly calls
- // LLEventPumps::flush(), which in turn should flush our "login"
- // LLEventQueue.
- LLEventPump& mainloop(pumps.obtain("mainloop"));
- ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*> (&login));
- listener0.listenTo(login);
- listener0.reset(0);
- login.post(1);
- check_listener("waiting for queued event", listener0, 0);
- mainloop.post(LLSD());
- check_listener("got queued event", listener0, 1);
- login.stopListening(listener0.getName());
- // Verify that when an event handler posts a new event on the same
- // LLEventQueue, it doesn't get processed in the same flush() call --
- // it waits until the next flush() call.
- listener0.reset(17);
- login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
- login.post(1);
- check_listener("chainEvents(1) not yet called", listener0, 17);
- mainloop.post(LLSD());
- check_listener("chainEvents(1) called", listener0, 1);
- mainloop.post(LLSD());
- check_listener("chainEvents(0) called", listener0, 0);
- mainloop.post(LLSD());
- check_listener("chainEvents(-1) not called", listener0, 0);
- login.stopListening("chainEvents");
-}
-
-template<> template<>
-void events_object::test<4>()
-{
set_test_name("explicitly-instantiated LLEventStream");
// Explicitly instantiate an LLEventStream, and verify that it
// self-registers with LLEventPumps
@@ -271,7 +231,7 @@ void events_object::test<4>()
}
template<> template<>
-void events_object::test<5>()
+void events_object::test<4>()
{
set_test_name("stopListening()");
LLEventPump& login(pumps.obtain("login"));
@@ -285,7 +245,7 @@ void events_object::test<5>()
}
template<> template<>
-void events_object::test<6>()
+void events_object::test<5>()
{
set_test_name("chaining LLEventPump instances");
LLEventPump& upstream(pumps.obtain("upstream"));
@@ -310,7 +270,7 @@ void events_object::test<6>()
}
template<> template<>
-void events_object::test<7>()
+void events_object::test<6>()
{
set_test_name("listener dependency order");
typedef LLEventPump::NameList NameList;
@@ -392,7 +352,7 @@ void events_object::test<7>()
}
template<> template<>
-void events_object::test<8>()
+void events_object::test<7>()
{
set_test_name("tweaked and untweaked LLEventPump instance names");
{ // nested scope
@@ -424,7 +384,7 @@ void eventSource(const LLListenerOrPumpName& listener)
}
template<> template<>
-void events_object::test<9>()
+void events_object::test<8>()
{
set_test_name("LLListenerOrPumpName");
// Passing a boost::bind() expression to LLListenerOrPumpName
@@ -465,7 +425,7 @@ private:
};
template<> template<>
-void events_object::test<10>()
+void events_object::test<9>()
{
set_test_name("listen(boost::bind(...TempListener...))");
// listen() can't do anything about a plain TempListener instance:
@@ -493,223 +453,60 @@ void events_object::test<10>()
heaptest.stopListening("temp");
}
-template<> template<>
-void events_object::test<11>()
-{
- set_test_name("listen(boost::bind(...weak_ptr...))");
- // listen() detecting weak_ptr<TempListener> in boost::bind() object
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- ensure("default state", !connection.connected());
- {
- boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
- newListener->reset();
- ensure("TempListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&Listener::call,
- weaken(newListener),
- _1));
- ensure("new connection", connection.connected());
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- } // presumably this will make newListener go away?
- // verify that
- ensure("TempListener destroyed", !live);
- ensure("implicit disconnect", !connection.connected());
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-}
-
-template<> template<>
-void events_object::test<12>()
-{
- set_test_name("listen(boost::bind(...shared_ptr...))");
- /*==========================================================================*|
- // DISABLED because I've made this case produce a compile error.
- // Following the error leads the disappointed dev to a comment
- // instructing her to use the weaken() function to bind a weak_ptr<T>
- // instead of binding a shared_ptr<T>, and explaining why. I know of
- // no way to use TUT to code a repeatable test in which the expected
- // outcome is a compile error. The interested reader is invited to
- // uncomment this block and build to see for herself.
-
- // listen() detecting shared_ptr<TempListener> in boost::bind() object
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- std::string listenerName("heap");
- ensure("default state", !connection.connected());
- {
- boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
- ensure_equals("use_count", newListener.use_count(), 1);
- newListener->reset();
- ensure("TempListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&Listener::call, newListener, _1));
- ensure("new connection", connection.connected());
- ensure_equals("use_count", newListener.use_count(), 2);
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- } // this should make newListener go away...
- // Unfortunately, the fact that we've bound a shared_ptr by value into
- // our LLEventPump means that copy will keep the referenced object alive.
- ensure("TempListener still alive", live);
- ensure("still connected", connection.connected());
- // disconnecting explicitly should delete the TempListener...
- heaptest.stopListening(listenerName);
-#if 0 // however, in my experience, it does not. I don't know why not.
- // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
- // library, stated on the boost-users mailing list:
- // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html
- // "It will get destroyed eventually. The signal cleans up its slot
- // list little by little during connect/invoke. It doesn't immediately
- // remove disconnected slots from the slot list since other threads
- // might be using the same slot list concurrently. It might be
- // possible to make it immediately reset the shared_ptr owning the
- // slot though, leaving an empty shared_ptr in the slot list, since
- // that wouldn't invalidate any iterators."
- ensure("TempListener destroyed", ! live);
- ensure("implicit disconnect", ! connection.connected());
-#endif // 0
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-|*==========================================================================*/
-}
-
class TempTrackableListener: public TempListener, public LLEventTrackable
{
public:
-TempTrackableListener(const std::string& name, bool& liveFlag):
- TempListener(name, liveFlag)
-{}
+ TempTrackableListener(const std::string& name, bool& liveFlag):
+ TempListener(name, liveFlag)
+ {}
};
template<> template<>
-void events_object::test<13>()
-{
-set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
-{
- TempTrackableListener tempListener("temp", live);
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(tempListener.getName(),
- boost::bind(&TempTrackableListener::call,
- boost::ref(tempListener), _1));
- heaptest.post(1);
- check_listener("received", tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
-}
-
-template<> template<>
-void events_object::test<14>()
-{
-set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
+void events_object::test<10>()
{
- TempTrackableListener* newListener(new TempTrackableListener("temp", live));
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&TempTrackableListener::call,
- newListener, _1));
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- // explicitly destroy newListener
- delete newListener;
-}
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
+ set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempTrackableListener tempListener("temp", live);
+ ensure("TempTrackableListener constructed", live);
+ connection = heaptest.listen(tempListener.getName(),
+ boost::bind(&TempTrackableListener::call,
+ boost::ref(tempListener), _1));
+ heaptest.post(1);
+ check_listener("received", tempListener, 1);
+ } // presumably this will make tempListener go away?
+ // verify that
+ ensure("TempTrackableListener destroyed", ! live);
+ ensure("implicit disconnect", ! connection.connected());
+ // now just make sure we don't blow up trying to access a freed object!
+ heaptest.post(2);
}
template<> template<>
-void events_object::test<15>()
-{
-// This test ensures that using an LLListenerWrapper subclass doesn't
-// block Boost.Signals2 from recognizing a bound LLEventTrackable
-// subclass.
-set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
+void events_object::test<11>()
{
- TempTrackableListener tempListener("temp", live);
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(tempListener.getName(),
- llwrap<LLLogListener>(
- boost::bind(&TempTrackableListener::call,
- boost::ref(tempListener), _1)));
- heaptest.post(1);
- check_listener("received", tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
+ set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempTrackableListener* newListener(new TempTrackableListener("temp", live));
+ ensure("TempTrackableListener constructed", live);
+ connection = heaptest.listen(newListener->getName(),
+ boost::bind(&TempTrackableListener::call,
+ newListener, _1));
+ heaptest.post(1);
+ check_listener("received", *newListener, 1);
+ // explicitly destroy newListener
+ delete newListener;
+ }
+ // verify that
+ ensure("TempTrackableListener destroyed", ! live);
+ ensure("implicit disconnect", ! connection.connected());
+ // now just make sure we don't blow up trying to access a freed object!
+ heaptest.post(2);
}
-class TempSharedListener: public TempListener,
-public boost::enable_shared_from_this<TempSharedListener>
-{
-public:
-TempSharedListener(const std::string& name, bool& liveFlag):
- TempListener(name, liveFlag)
-{}
-};
-
-template<> template<>
-void events_object::test<16>()
-{
- set_test_name("listen(boost::bind(...TempSharedListener ref...))");
-#if 0
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
-{
- // We MUST have at least one shared_ptr to an
- // enable_shared_from_this subclass object before
- // shared_from_this() can work.
- boost::shared_ptr<TempSharedListener>
- tempListener(new TempSharedListener("temp", live));
- ensure("TempSharedListener constructed", live);
- // However, we're not passing either the shared_ptr or its
- // corresponding weak_ptr -- instead, we're passing a reference to
- // the TempSharedListener.
-/*==========================================================================*|
- std::cout << "Capturing const ref" << std::endl;
- const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
- std::cout << "Capturing const ptr" << std::endl;
- const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
- std::cout << "Capturing non-const ptr" << std::endl;
- boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
- std::cout << "Capturing shared_from_this()" << std::endl;
- boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
- std::cout << "Capturing weak_ptr" << std::endl;
- boost::weak_ptr<TempSharedListener> wp(weaken(sp));
- std::cout << "Binding weak_ptr" << std::endl;
-|*==========================================================================*/
- connection = heaptest.listen(tempListener->getName(),
- boost::bind(&TempSharedListener::call, *tempListener, _1));
- heaptest.post(1);
- check_listener("received", *tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempSharedListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
-#endif // 0
-}
} // namespace tut
diff --git a/indra/test/lltestapp.h b/indra/test/lltestapp.h
new file mode 100644
index 0000000000..382516cd2b
--- /dev/null
+++ b/indra/test/lltestapp.h
@@ -0,0 +1,34 @@
+/**
+ * @file lltestapp.h
+ * @author Nat Goodspeed
+ * @date 2019-10-21
+ * @brief LLApp subclass useful for testing.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLTESTAPP_H)
+#define LL_LLTESTAPP_H
+
+#include "llapp.h"
+
+/**
+ * LLTestApp is a dummy LLApp that simply sets LLApp::isRunning() for anyone
+ * who cares.
+ */
+class LLTestApp: public LLApp
+{
+public:
+ LLTestApp()
+ {
+ setStatus(APP_STATUS_RUNNING);
+ }
+
+ bool init() { return true; }
+ bool cleanup() { return true; }
+ bool frame() { return true; }
+};
+
+#endif /* ! defined(LL_LLTESTAPP_H) */
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/setenv.h b/indra/test/setenv.h
new file mode 100644
index 0000000000..ed2de9ccca
--- /dev/null
+++ b/indra/test/setenv.h
@@ -0,0 +1,66 @@
+/**
+ * @file setenv.h
+ * @author Nat Goodspeed
+ * @date 2020-04-01
+ * @brief Provide a way for a particular test program to alter the
+ * environment before entry to main().
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Copyright (c) 2020, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SETENV_H)
+#define LL_SETENV_H
+
+#include <stdlib.h> // setenv()
+
+/**
+ * Our test.cpp main program responds to environment variables LOGTEST and
+ * LOGFAIL. But if you set (e.g.) LOGTEST=DEBUG before a viewer build, @em
+ * every test program in the build emits debug log output. This can be so
+ * voluminous as to slow down the build.
+ *
+ * With an integration test program, you can specifically build (e.g.) the
+ * INTEGRATION_TEST_llstring target, and set any environment variables you
+ * want for that. But with a unit test program, since executing the program is
+ * a side effect rather than an explicit target, specifically building (e.g.)
+ * PROJECT_lllogin_TEST_lllogin only builds the executable without running it.
+ *
+ * To set an environment variable for a particular test program, declare a
+ * static instance of SetEnv in its .cpp file. SetEnv's constructor takes
+ * pairs of strings, e.g.
+ *
+ * @code
+ * static SetEnv sLOGGING("LOGTEST", "INFO");
+ * @endcode
+ *
+ * Declaring a static instance of SetEnv is important because that ensures
+ * that the environment variables are set before main() is entered, since it
+ * is main() that examines LOGTEST and LOGFAIL.
+ */
+struct SetEnv
+{
+ // degenerate constructor, terminate recursion
+ SetEnv() {}
+
+ /**
+ * SetEnv() accepts an arbitrary number of pairs of strings: variable
+ * name, value, variable name, value ... Entering the constructor sets
+ * those variables in the process environment using Posix setenv(),
+ * overriding any previous value. If static SetEnv declarations in
+ * different translation units specify overlapping sets of variable names,
+ * it is indeterminate which instance will "win."
+ */
+ template <typename VAR, typename VAL, typename... ARGS>
+ SetEnv(VAR&& var, VAL&& val, ARGS&&... rest):
+ // constructor forwarding handles the tail of the list
+ SetEnv(std::forward<ARGS>(rest)...)
+ {
+ // set just the first (variable, value) pair
+ // 1 means override previous value if any
+ setenv(std::forward<VAR>(var), std::forward<VAL>(val), 1);
+ }
+};
+
+#endif /* ! defined(LL_SETENV_H) */
diff --git a/indra/test/sync.h b/indra/test/sync.h
new file mode 100644
index 0000000000..ca8b7262d6
--- /dev/null
+++ b/indra/test/sync.h
@@ -0,0 +1,116 @@
+/**
+ * @file sync.h
+ * @author Nat Goodspeed
+ * @date 2019-03-13
+ * @brief Synchronize coroutines within a test program so we can observe side
+ * effects. Certain test programs test coroutine synchronization
+ * mechanisms. Such tests usually want to interleave coroutine
+ * executions in strictly stepwise fashion. This class supports that
+ * paradigm.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SYNC_H)
+#define LL_SYNC_H
+
+#include "llcond.h"
+#include "lltut.h"
+#include "stringize.h"
+#include "llerror.h"
+#include "llcoros.h"
+
+/**
+ * Instantiate Sync in any test in which we need to suspend one coroutine
+ * until we're sure that another has had a chance to run. Simply calling
+ * llcoro::suspend() isn't necessarily enough; that provides a chance for the
+ * other to run, but doesn't guarantee that it has. If each coroutine is
+ * consistent about calling Sync::bump() every time it wakes from any
+ * suspension, Sync::yield() and yield_until() should at least ensure that
+ * somebody else has had a chance to run.
+ */
+class Sync
+{
+ LLScalarCond<int> mCond{0};
+ F32Milliseconds mTimeout;
+
+public:
+ Sync(F32Milliseconds timeout=F32Milliseconds(10000.0f)):
+ mTimeout(timeout)
+ {}
+
+ /**
+ * Bump mCond by n steps -- ideally, do this every time a participating
+ * coroutine wakes up from any suspension. The choice to bump() after
+ * resumption rather than just before suspending is worth calling out:
+ * this practice relies on the fact that condition_variable::notify_all()
+ * merely marks a suspended coroutine ready to run, rather than
+ * immediately resuming it. This way, though, even if a coroutine exits
+ * before reaching its next suspend point, the other coroutine isn't
+ * left waiting forever.
+ */
+ void bump(int n=1)
+ {
+ // Calling mCond.set_all(mCond.get() + n) would be great for
+ // coroutines -- but not so good between kernel threads -- it would be
+ // racy. Make the increment atomic by calling update_all(), which runs
+ // the passed lambda within a mutex lock.
+ int updated;
+ mCond.update_all(
+ [&n, &updated](int& data)
+ {
+ data += n;
+ // Capture the new value for possible logging purposes.
+ updated = data;
+ });
+ // In the multi-threaded case, this log message could be a bit
+ // misleading, as it will be emitted after waiting threads have
+ // already awakened. But emitting the log message within the lock
+ // would seem to hold the lock longer than we really ought.
+ LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << updated << LL_ENDL;
+ }
+
+ /**
+ * Set mCond to a specific n. Use of bump() and yield() is nicely
+ * maintainable, since you can insert or delete matching operations in a
+ * test function and have the rest of the Sync operations continue to
+ * line up as before. But sometimes you need to get very specific, which
+ * is where set() and yield_until() come in handy: less maintainable,
+ * more precise.
+ */
+ void set(int n)
+ {
+ LL_DEBUGS() << llcoro::logname() << " set(" << n << ")" << LL_ENDL;
+ mCond.set_all(n);
+ }
+
+ /// suspend until "somebody else" has bumped mCond by n steps
+ void yield(int n=1)
+ {
+ return yield_until(STRINGIZE("Sync::yield_for(" << n << ") timed out after "
+ << int(mTimeout.value()) << "ms"),
+ mCond.get() + n);
+ }
+
+ /// suspend until "somebody else" has bumped mCond to a specific value
+ void yield_until(int until)
+ {
+ return yield_until(STRINGIZE("Sync::yield_until(" << until << ") timed out after "
+ << int(mTimeout.value()) << "ms"),
+ until);
+ }
+
+private:
+ void yield_until(const std::string& desc, int until)
+ {
+ std::string name(llcoro::logname());
+ LL_DEBUGS() << name << " yield_until(" << until << ") suspending" << LL_ENDL;
+ tut::ensure(name + ' ' + desc, mCond.wait_for_equal(mTimeout, until));
+ // each time we wake up, bump mCond
+ bump();
+ }
+};
+
+#endif /* ! defined(LL_SYNC_H) */
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index b14c2eb255..87c4a8d8a3 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,10 @@ 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");
+
+ // values used for options parsing
apr_status_t apr_err;
const char* opt_arg = NULL;
int opt_id = 0;
@@ -611,7 +593,7 @@ int main(int argc, char **argv)
wait_at_exit = true;
break;
case 'd':
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LOGTEST = "DEBUG";
break;
case 'x':
suite_name.assign(opt_arg);
@@ -623,22 +605,45 @@ 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.
- if (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"))
{
@@ -649,7 +654,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())
{