From 331e932857e1156a68b6d39d3ea2d8c1f39ec7ae Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 22 May 2015 14:02:24 -0400 Subject: MAINT-5232: Clean up some dubious LLSingleton methods. Remove evil getIfExists() method, used by no one. Remove evil destroyed() method, used in exactly three places -- one of which is a test. Replace with equally evil instanceExists() method, which is used EVERYWHERE -- sigh. --- indra/llcommon/tests/llsingleton_test.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/llcommon/tests') diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 385289aefe..bed436283a 100755 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -65,7 +65,6 @@ namespace tut //Delete the instance LLSingletonTest::deleteSingleton(); - ensure(LLSingletonTest::destroyed()); ensure(!LLSingletonTest::instanceExists()); //Construct it again. -- cgit v1.3 From df3da846243cce973468b15d1f1752db2009d4ba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 22 May 2015 22:05:16 -0400 Subject: MAINT-5232: Add LLPounceable template for delayed registrations. LLMuteList, an LLSingleton, overrides its getInstance() method to intercept control every time a consumer wants LLMuteList. This "polling" is to notice when gMessageSystem becomes non-NULL, and register a couple callbacks on it. Unfortunately there are a couple ways to request the LLMuteList instance without specifically calling the subclass getInstance(), which would bypass that logic. Moreover, the polling feels a bit dubious to start with. LLPounceable presents an idiom in which you can callWhenReady(callable) on the LLPounceable instance. If the T* is already non-NULL, it calls the callable immediately; otherwise it enqueues it for when the T* is set non-NULL. (This lets you "pounce" on the T* as soon as it becomes available, hence the name.) So if gMessageSystem were an LLPounceable, LLMuteList's constructor could simply call gMessageSystem.callWhenReady() and relax: the callbacks would be registered either on LLMuteList construction or LLMessageSystem initialization, whichever comes later. LLPounceable comes with its very own set of unit tests. However, as of this commit it is not yet used in actual viewer code. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llpounceable.h | 217 +++++++++++++++++++++++++++++ indra/llcommon/tests/llpounceable_test.cpp | 200 ++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 indra/llcommon/llpounceable.h create mode 100644 indra/llcommon/tests/llpounceable_test.cpp (limited to 'indra/llcommon/tests') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 1459b9ada2..d2d507d676 100755 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -178,6 +178,7 @@ set(llcommon_HEADER_FILES llmortician.h llnametable.h llpointer.h + llpounceable.h llpredicate.h llpreprocessor.h llpriqueuemap.h @@ -310,6 +311,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h new file mode 100644 index 0000000000..94d508d917 --- /dev/null +++ b/indra/llcommon/llpounceable.h @@ -0,0 +1,217 @@ +/** + * @file llpounceable.h + * @author Nat Goodspeed + * @date 2015-05-22 + * @brief LLPounceable is tangentially related to a future: it's a holder for + * a value that may or may not exist yet. Unlike a future, though, + * LLPounceable freely allows reading the held value. (If the held + * type T does not have a distinguished "empty" value, consider using + * LLPounceable>.) + * + * LLPounceable::callWhenReady() is this template's claim to fame. It + * allows its caller to "pounce" on the held value as soon as it + * becomes non-empty. Call callWhenReady() with any C++ callable + * accepting T. If the held value is already non-empty, callWhenReady() + * will immediately call the callable with the held value. If the held + * value is empty, though, callWhenReady() will enqueue the callable + * for later. As soon as LLPounceable is assigned a non-empty held + * value, it will flush the queue of deferred callables. + * + * Consider a global LLMessageSystem* gMessageSystem. Message system + * initialization happens at a very specific point during viewer + * initialization. Other subsystems want to register callbacks on the + * LLMessageSystem instance as soon as it's initialized, but their own + * initialization may precede that. If we define gMessageSystem to be + * an LLPounceable, a subsystem can use + * callWhenReady() to either register immediately (if gMessageSystem + * is already up and runnning) or register as soon as gMessageSystem + * is set with a new, initialized instance. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLPOUNCEABLE_H) +#define LL_LLPOUNCEABLE_H + +#include "llsingleton.h" +#include +#include +#include +#include +#include +#include + +// Forward declare the user template, since we want to be able to point to it +// in some of its implementation classes. +template +class LLPounceable; + +template +struct LLPounceableTraits +{ + // Call callWhenReady() with any callable accepting T. + typedef boost::function::param_type)> func_t; + // Our actual queue is a simple queue of such callables. + typedef std::queue queue_t; + // owner pointer type + typedef LLPounceable* owner_ptr; +}; + +// Tag types distinguish the two different implementations of LLPounceable's +// queue. +struct LLPounceableQueue {}; +struct LLPounceableStatic {}; + +// generic LLPounceableQueueImpl deliberately omitted: only the above tags are +// legal +template +class LLPounceableQueueImpl; + +// The implementation selected by LLPounceableStatic uses an LLSingleton +// because we can't count on a data member queue being initialized at the time +// we start getting callWhenReady() calls. This is that LLSingleton. +template +class LLPounceableQueueSingleton: + public LLSingleton > +{ +private: + typedef LLPounceableTraits traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::queue_t queue_t; + + // For a given held type T, every LLPounceable + // instance will call on the SAME LLPounceableQueueSingleton instance -- + // given how class statics work. We must keep a separate queue for each + // LLPounceable instance. Use a hash map for that. + typedef boost::unordered_map map_t; + +public: + // Disambiguate queues belonging to different LLPounceables. + queue_t& get(owner_ptr owner) + { + // operator[] has find-or-create semantics -- just what we want! + return mMap[owner]; + } + +private: + map_t mMap; +}; + +// LLPounceableQueueImpl that uses the above LLSingleton +template +class LLPounceableQueueImpl +{ +public: + typedef LLPounceableTraits traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::queue_t queue_t; + + queue_t& get(owner_ptr owner) const + { + // this Impl contains nothing; it delegates to the Singleton + return LLPounceableQueueSingleton::instance().get(owner); + } +}; + +// The implementation selected by LLPounceableQueue directly contains the +// queue of interest, suitable for an LLPounceable we can trust to be fully +// initialized when it starts getting callWhenReady() calls. +template +class LLPounceableQueueImpl +{ +public: + typedef LLPounceableTraits traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::queue_t queue_t; + + queue_t& get(owner_ptr) + { + return mQueue; + } + +private: + queue_t mQueue; +}; + +// LLPounceable is for an LLPounceable instance on the heap or the stack. +// LLPounceable is for a static LLPounceable instance. +template +class LLPounceable +{ +private: + typedef LLPounceableTraits traits; + typedef typename traits::owner_ptr owner_ptr; + typedef typename traits::queue_t queue_t; + +public: + typedef typename traits::func_t func_t; + + // By default, both the initial value and the distinguished empty value + // are a default-constructed T instance. However you can explicitly + // specify each. + LLPounceable(typename boost::call_traits::value_type init =boost::value_initialized(), + typename boost::call_traits::param_type empty=boost::value_initialized()): + mHeld(init), + mEmpty(empty) + {} + + // make read access to mHeld as cheap and transparent as possible + operator T () const { return mHeld; } + typename boost::remove_pointer::type operator*() const { return *mHeld; } + typename boost::call_traits::value_type operator->() const { return mHeld; } + // uncomment 'explicit' as soon as we allow C++11 compilation + /*explicit*/ operator bool() const { return bool(mHeld); } + bool operator!() const { return ! mHeld; } + + // support both assignment (dumb ptr idiom) and reset() (smart ptr) + void operator=(typename boost::call_traits::param_type value) + { + reset(value); + } + + void reset(typename boost::call_traits::param_type value) + { + mHeld = value; + // If this new value is non-empty, flush anything pending in the queue. + if (mHeld != mEmpty) + { + queue_t& queue(get_queue()); + while (! queue.empty()) + { + queue.front()(mHeld); + queue.pop(); + } + } + } + + // our claim to fame + void callWhenReady(const func_t& func) + { + if (mHeld != mEmpty) + { + // If the held value is already non-empty, immediately call func() + func(mHeld); + } + else + { + // held value still empty, queue func() for later + get_queue().push(func); + } + } + +private: + queue_t& get_queue() { return mQueue.get(this); } + + // Store both the current and the empty value. + // MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of + // testing for "empty." For some types we want operator!(); for others we + // want to compare to a distinguished value. + typename boost::call_traits::value_type mHeld, mEmpty; + // This might either contain the queue (LLPounceableQueue) or delegate to + // an LLSingleton (LLPounceableStatic). + LLPounceableQueueImpl mQueue; +}; + +#endif /* ! defined(LL_LLPOUNCEABLE_H) */ diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp new file mode 100644 index 0000000000..1f8cdca145 --- /dev/null +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -0,0 +1,200 @@ +/** + * @file llpounceable_test.cpp + * @author Nat Goodspeed + * @date 2015-05-22 + * @brief Test for llpounceable. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llpounceable.h" +// STL headers +// std headers +// external library headers +#include +// other Linden headers +#include "../test/lltut.h" + +struct Data +{ + Data(const std::string& data): + mData(data) + {} + const std::string mData; +}; + +void setter(Data** dest, Data* ptr) +{ + *dest = ptr; +} + +static Data* static_check = 0; + +// Set up an extern pointer to an LLPounceableStatic so the linker will fill +// in the forward reference from below, before runtime. +extern LLPounceable gForward; + +struct EnqueueCall +{ + EnqueueCall() + { + // Intentionally use a forward reference to an LLPounceableStatic that + // we believe is NOT YET CONSTRUCTED. This models the scenario in + // which a constructor in another translation unit runs before + // constructors in this one. We very specifically want callWhenReady() + // to work even in that case: we need the LLPounceableQueueImpl to be + // initialized even if the LLPounceable itself is not. + gForward.callWhenReady(boost::bind(setter, &static_check, _1)); + } +} nqcall; +// When this declaration is processed, we should enqueue the +// setter(&static_check, _1) call for when gForward is set non-NULL. Needless +// to remark, we want this call not to crash. + +// Now declare gForward. Its constructor should not run until after nqcall's. +LLPounceable gForward; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llpounceable_data + { + }; + typedef test_group llpounceable_group; + typedef llpounceable_group::object object; + llpounceable_group llpounceablegrp("llpounceable"); + + template<> template<> + void object::test<1>() + { + set_test_name("LLPounceableStatic out-of-order test"); + // LLPounceable::callWhenReady() must work even + // before LLPounceable's constructor runs. That's the whole point of + // implementing it with an LLSingleton queue. This models (say) + // LLPounceableStatic. + ensure("static_check should still be null", ! static_check); + Data myData("test<1>"); + gForward = &myData; // should run setter + ensure_equals("static_check should be &myData", static_check, &myData); + } + + template<> template<> + void object::test<2>() + { + set_test_name("LLPounceableQueue different queues"); + // We expect that LLPounceable should have + // different queues because that specialization stores the queue + // directly in the LLPounceable instance. + Data *aptr = 0, *bptr = 0; + LLPounceable a, b; + a.callWhenReady(boost::bind(setter, &aptr, _1)); + b.callWhenReady(boost::bind(setter, &bptr, _1)); + ensure("aptr should be null", ! aptr); + ensure("bptr should be null", ! bptr); + Data adata("a"), bdata("b"); + a = &adata; + ensure_equals("aptr should be &adata", aptr, &adata); + // but we haven't yet set b + ensure("bptr should still be null", !bptr); + b = &bdata; + ensure_equals("bptr should be &bdata", bptr, &bdata); + } + + template<> template<> + void object::test<3>() + { + set_test_name("LLPounceableStatic different queues"); + // LLPounceable should also have a distinct + // queue for each instance, but that engages an additional map lookup + // because there's only one LLSingleton for each T. + Data *aptr = 0, *bptr = 0; + LLPounceable a, b; + a.callWhenReady(boost::bind(setter, &aptr, _1)); + b.callWhenReady(boost::bind(setter, &bptr, _1)); + ensure("aptr should be null", ! aptr); + ensure("bptr should be null", ! bptr); + Data adata("a"), bdata("b"); + a = &adata; + ensure_equals("aptr should be &adata", aptr, &adata); + // but we haven't yet set b + ensure("bptr should still be null", !bptr); + b = &bdata; + ensure_equals("bptr should be &bdata", bptr, &bdata); + } + + template<> template<> + void object::test<4>() + { + set_test_name("LLPounceable looks like T"); + // We want LLPounceable to be drop-in replaceable for a plain + // T for read constructs. In particular, it should behave like a dumb + // pointer -- and with zero abstraction cost for such usage. + Data* aptr = 0; + Data a("a"); + // should be able to initialize a pounceable (when its constructor + // runs) + LLPounceable pounceable = &a; + // should be able to pass LLPounceable to function accepting T + setter(&aptr, pounceable); + ensure_equals("aptr should be &a", aptr, &a); + // should be able to dereference with * + ensure_equals("deref with *", (*pounceable).mData, "a"); + // should be able to dereference with -> + ensure_equals("deref with ->", pounceable->mData, "a"); + // bool operations + ensure("test with operator bool()", pounceable); + ensure("test with operator !()", ! (! pounceable)); + } + + template<> template<> + void object::test<5>() + { + set_test_name("Multiple callWhenReady() queue items"); + Data *p1 = 0, *p2 = 0, *p3 = 0; + Data a("a"); + LLPounceable pounceable; + // queue up a couple setter() calls for later + pounceable.callWhenReady(boost::bind(setter, &p1, _1)); + pounceable.callWhenReady(boost::bind(setter, &p2, _1)); + // should still be pending + ensure("p1 should be null", !p1); + ensure("p2 should be null", !p2); + ensure("p3 should be null", !p3); + pounceable = 0; + // assigning a new empty value shouldn't flush the queue + ensure("p1 should still be null", !p1); + ensure("p2 should still be null", !p2); + ensure("p3 should still be null", !p3); + // using whichever syntax + pounceable.reset(0); + // try to make ensure messages distinct... tough to pin down which + // ensure() failed if multiple ensure() calls in the same test have + // the same message! + ensure("p1 should again be null", !p1); + ensure("p2 should again be null", !p2); + ensure("p3 should again be null", !p3); + pounceable.reset(&a); // should flush queue + ensure_equals("p1 should be &a", p1, &a); + ensure_equals("p2 should be &a", p2, &a); + ensure("p3 still not set", !p3); + // immediate call + pounceable.callWhenReady(boost::bind(setter, &p3, _1)); + ensure_equals("p3 should be &a", p3, &a); + } + + template<> template<> + void object::test<6>() + { + set_test_name("compile-fail test, uncomment to check"); + // The following declaration should fail: only LLPounceableQueue and + // LLPounceableStatic should work as tags. +// LLPounceable pounceable; + } +} // namespace tut -- cgit v1.3 From a9650ba22219d91438d91fd9371966f510884cbf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 29 May 2015 15:10:54 -0400 Subject: MAINT-5232: Per Vir review, use Boost.Signals2 for LLPounceable. Vir points out that "queue of callables" is pretty much exactly what a signal does. Add unit tests to verify chronological order, also queue reset when fired. --- indra/llcommon/llpounceable.h | 43 ++++++++++++++---------------- indra/llcommon/tests/llpounceable_test.cpp | 30 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 23 deletions(-) (limited to 'indra/llcommon/tests') diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h index 94d508d917..9c8aba2154 100644 --- a/indra/llcommon/llpounceable.h +++ b/indra/llcommon/llpounceable.h @@ -40,8 +40,7 @@ #include #include #include -#include -#include +#include // Forward declare the user template, since we want to be able to point to it // in some of its implementation classes. @@ -51,10 +50,10 @@ class LLPounceable; template struct LLPounceableTraits { + // Our "queue" is a signal object with correct signature. + typedef boost::signals2::signal::param_type)> signal_t; // Call callWhenReady() with any callable accepting T. - typedef boost::function::param_type)> func_t; - // Our actual queue is a simple queue of such callables. - typedef std::queue queue_t; + typedef typename signal_t::slot_type func_t; // owner pointer type typedef LLPounceable* owner_ptr; }; @@ -79,17 +78,17 @@ class LLPounceableQueueSingleton: private: typedef LLPounceableTraits traits; typedef typename traits::owner_ptr owner_ptr; - typedef typename traits::queue_t queue_t; + typedef typename traits::signal_t signal_t; // For a given held type T, every LLPounceable // instance will call on the SAME LLPounceableQueueSingleton instance -- // given how class statics work. We must keep a separate queue for each // LLPounceable instance. Use a hash map for that. - typedef boost::unordered_map map_t; + typedef boost::unordered_map map_t; public: // Disambiguate queues belonging to different LLPounceables. - queue_t& get(owner_ptr owner) + signal_t& get(owner_ptr owner) { // operator[] has find-or-create semantics -- just what we want! return mMap[owner]; @@ -106,9 +105,9 @@ class LLPounceableQueueImpl public: typedef LLPounceableTraits traits; typedef typename traits::owner_ptr owner_ptr; - typedef typename traits::queue_t queue_t; + typedef typename traits::signal_t signal_t; - queue_t& get(owner_ptr owner) const + signal_t& get(owner_ptr owner) const { // this Impl contains nothing; it delegates to the Singleton return LLPounceableQueueSingleton::instance().get(owner); @@ -124,15 +123,15 @@ class LLPounceableQueueImpl public: typedef LLPounceableTraits traits; typedef typename traits::owner_ptr owner_ptr; - typedef typename traits::queue_t queue_t; + typedef typename traits::signal_t signal_t; - queue_t& get(owner_ptr) + signal_t& get(owner_ptr) { return mQueue; } private: - queue_t mQueue; + signal_t mQueue; }; // LLPounceable is for an LLPounceable instance on the heap or the stack. @@ -143,7 +142,7 @@ class LLPounceable private: typedef LLPounceableTraits traits; typedef typename traits::owner_ptr owner_ptr; - typedef typename traits::queue_t queue_t; + typedef typename traits::signal_t signal_t; public: typedef typename traits::func_t func_t; @@ -177,12 +176,9 @@ public: // If this new value is non-empty, flush anything pending in the queue. if (mHeld != mEmpty) { - queue_t& queue(get_queue()); - while (! queue.empty()) - { - queue.front()(mHeld); - queue.pop(); - } + signal_t& signal(get_signal()); + signal(mHeld); + signal.disconnect_all_slots(); } } @@ -196,13 +192,14 @@ public: } else { - // held value still empty, queue func() for later - get_queue().push(func); + // Held value still empty, queue func() for later. By default, + // connect() enqueues slots in FIFO order. + get_signal().connect(func); } } private: - queue_t& get_queue() { return mQueue.get(this); } + signal_t& get_signal() { return mQueue.get(this); } // Store both the current and the empty value. // MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp index 1f8cdca145..25458865a6 100644 --- a/indra/llcommon/tests/llpounceable_test.cpp +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -20,6 +20,13 @@ // other Linden headers #include "../test/lltut.h" +/*----------------------------- string testing -----------------------------*/ +void append(std::string* dest, const std::string& src) +{ + dest->append(src); +} + +/*-------------------------- Data-struct testing ---------------------------*/ struct Data { Data(const std::string& data): @@ -191,6 +198,29 @@ namespace tut template<> template<> void object::test<6>() + { + set_test_name("queue order"); + std::string data; + LLPounceable pounceable; + pounceable.callWhenReady(boost::bind(append, _1, "a")); + pounceable.callWhenReady(boost::bind(append, _1, "b")); + pounceable.callWhenReady(boost::bind(append, _1, "c")); + pounceable = &data; + ensure_equals("callWhenReady() must preserve chronological order", + data, "abc"); + + std::string data2; + pounceable = NULL; + pounceable.callWhenReady(boost::bind(append, _1, "d")); + pounceable.callWhenReady(boost::bind(append, _1, "e")); + pounceable.callWhenReady(boost::bind(append, _1, "f")); + pounceable = &data2; + ensure_equals("LLPounceable must reset queue when fired", + data2, "def"); + } + + template<> template<> + void object::test<7>() { set_test_name("compile-fail test, uncomment to check"); // The following declaration should fail: only LLPounceableQueue and -- cgit v1.3 From ccc55255c57f8449aa46cd8847dbda1ced1e851f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 29 May 2015 16:37:15 -0400 Subject: MAINT-5232: Make LLPounceable noncopyable. Changing the queue-of-callables implementation to boost::signals2::signal, which is noncopyable, means that LLPounceable itself should be noncopyable. --- indra/llcommon/llpounceable.h | 3 ++- indra/llcommon/tests/llpounceable_test.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/tests') diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h index 9c8aba2154..77b711bdc6 100644 --- a/indra/llcommon/llpounceable.h +++ b/indra/llcommon/llpounceable.h @@ -36,6 +36,7 @@ #define LL_LLPOUNCEABLE_H #include "llsingleton.h" +#include #include #include #include @@ -137,7 +138,7 @@ private: // LLPounceable is for an LLPounceable instance on the heap or the stack. // LLPounceable is for a static LLPounceable instance. template -class LLPounceable +class LLPounceable: public boost::noncopyable { private: typedef LLPounceableTraits traits; diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp index 25458865a6..2f4915ce11 100644 --- a/indra/llcommon/tests/llpounceable_test.cpp +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -147,7 +147,7 @@ namespace tut Data a("a"); // should be able to initialize a pounceable (when its constructor // runs) - LLPounceable pounceable = &a; + LLPounceable pounceable(&a); // should be able to pass LLPounceable to function accepting T setter(&aptr, pounceable); ensure_equals("aptr should be &a", aptr, &a); -- cgit v1.3 From d4fb82c217bccda536f7a7b2ca1809bb8c2dba40 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 30 Jun 2015 14:49:18 -0400 Subject: MAINT-5232: Add tests for new LLSingleton dependency functionality. --- indra/llcommon/llsingleton.h | 6 +- indra/llcommon/tests/llsingleton_test.cpp | 205 ++++++++++++++++++++++++------ 2 files changed, 167 insertions(+), 44 deletions(-) (limited to 'indra/llcommon/tests') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 253e0b9a6b..6a7f27bed4 100755 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -32,8 +32,6 @@ #include #include -// TODO: -// Tests for all this! class LLSingletonBase: private boost::noncopyable { public: @@ -80,8 +78,8 @@ protected: // Maintain a stack of the LLSingleton subclass instance currently being // initialized. We use this to notice direct dependencies: we want to know - // if A requires B. We deduce that if while initializing A, control - // reaches B::getInstance(). + // if A requires B. We deduce a dependency if while initializing A, + // control reaches B::getInstance(). // We want &A to be at the top of that stack during both A::A() and // A::initSingleton(), since a call to B::getInstance() might occur during // either. diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index bed436283a..a05f650f25 100755 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -30,46 +30,171 @@ #include "llsingleton.h" #include "../test/lltut.h" + +// Capture execution sequence by appending to log string. +std::string sLog; + +#define DECLARE_CLASS(CLS) \ +struct CLS: public LLSingleton \ +{ \ + static enum dep_flag { \ + DEP_NONE, /* no dependency */ \ + DEP_CTOR, /* dependency in ctor */ \ + DEP_INIT /* dependency in initSingleton */ \ + } sDepFlag; \ + \ + CLS(); \ + void initSingleton(); \ + void cleanupSingleton(); \ + ~CLS(); \ +}; \ + \ +CLS::dep_flag CLS::sDepFlag = DEP_NONE + +DECLARE_CLASS(A); +DECLARE_CLASS(B); + +#define DEFINE_MEMBERS(CLS, OTHER) \ +CLS::CLS() \ +{ \ + sLog.append(#CLS); \ + if (sDepFlag == DEP_CTOR) \ + { \ + (void)OTHER::instance(); \ + } \ +} \ + \ +void CLS::initSingleton() \ +{ \ + sLog.append("i" #CLS); \ + if (sDepFlag == DEP_INIT) \ + { \ + (void)OTHER::instance(); \ + } \ +} \ + \ +void CLS::cleanupSingleton() \ +{ \ + sLog.append("x" #CLS); \ +} \ + \ +CLS::~CLS() \ +{ \ + sLog.append("~" #CLS); \ +} + +DEFINE_MEMBERS(A, B) +DEFINE_MEMBERS(B, A) + namespace tut { - struct singleton - { - // We need a class created with the LLSingleton template to test with. - class LLSingletonTest: public LLSingleton - { - - }; - }; - - typedef test_group singleton_t; - typedef singleton_t::object singleton_object_t; - tut::singleton_t tut_singleton("LLSingleton"); - - template<> template<> - void singleton_object_t::test<1>() - { - - } - template<> template<> - void singleton_object_t::test<2>() - { - LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); - } - template<> template<> - void singleton_object_t::test<3>() - { - //Construct the instance - LLSingletonTest::getInstance(); - ensure(LLSingletonTest::instanceExists()); - - //Delete the instance - LLSingletonTest::deleteSingleton(); - ensure(!LLSingletonTest::instanceExists()); - - //Construct it again. - LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); - ensure(singleton_test); - ensure(LLSingletonTest::instanceExists()); - } + struct singleton + { + // We need a class created with the LLSingleton template to test with. + class LLSingletonTest: public LLSingleton + { + + }; + }; + + typedef test_group singleton_t; + typedef singleton_t::object singleton_object_t; + tut::singleton_t tut_singleton("LLSingleton"); + + template<> template<> + void singleton_object_t::test<1>() + { + + } + template<> template<> + void singleton_object_t::test<2>() + { + LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); + ensure(singleton_test); + } + + template<> template<> + void singleton_object_t::test<3>() + { + //Construct the instance + LLSingletonTest::getInstance(); + ensure(LLSingletonTest::instanceExists()); + + //Delete the instance + LLSingletonTest::deleteSingleton(); + ensure(!LLSingletonTest::instanceExists()); + + //Construct it again. + LLSingletonTest* singleton_test = LLSingletonTest::getInstance(); + ensure(singleton_test); + ensure(LLSingletonTest::instanceExists()); + } + +#define TESTS(CLS, OTHER, N0, N1, N2, N3) \ + template<> template<> \ + void singleton_object_t::test() \ + { \ + set_test_name("just " #CLS); \ + CLS::sDepFlag = CLS::DEP_NONE; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test() \ + { \ + set_test_name(#CLS " ctor depends " #OTHER); \ + CLS::sDepFlag = CLS::DEP_CTOR; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test() \ + { \ + set_test_name(#CLS " init depends " #OTHER); \ + CLS::sDepFlag = CLS::DEP_INIT; \ + OTHER::sDepFlag = OTHER::DEP_NONE; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } \ + \ + template<> template<> \ + void singleton_object_t::test() \ + { \ + set_test_name(#CLS " circular init"); \ + CLS::sDepFlag = CLS::DEP_INIT; \ + OTHER::sDepFlag = OTHER::DEP_CTOR; \ + sLog.clear(); \ + \ + (void)CLS::instance(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ + LLSingletonBase::cleanupAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ + LLSingletonBase::deleteAll(); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + } + + TESTS(A, B, 4, 5, 6, 7) + TESTS(B, A, 8, 9, 10, 11) } -- cgit v1.3