summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2015-05-22 22:05:16 -0400
committerNat Goodspeed <nat@lindenlab.com>2015-05-22 22:05:16 -0400
commitdf3da846243cce973468b15d1f1752db2009d4ba (patch)
tree4436a87239d418a27adc25506e684fa6f5793124 /indra/llcommon/tests
parent331e932857e1156a68b6d39d3ea2d8c1f39ec7ae (diff)
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<T*> 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<LLMessageSystem*>, 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.
Diffstat (limited to 'indra/llcommon/tests')
-rw-r--r--indra/llcommon/tests/llpounceable_test.cpp200
1 files changed, 200 insertions, 0 deletions
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 <boost/bind.hpp>
+// 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<Data*, LLPounceableStatic> 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<Data*, LLPounceableStatic> gForward;
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llpounceable_data
+ {
+ };
+ typedef test_group<llpounceable_data> 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<T, LLPounceableStatic>::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<LLMessageSystem*, 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<T, LLPounceableQueue> should have
+ // different queues because that specialization stores the queue
+ // directly in the LLPounceable instance.
+ Data *aptr = 0, *bptr = 0;
+ LLPounceable<Data*> 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<T, LLPounceableStatic> 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<Data*, LLPounceableStatic> 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<T> looks like T");
+ // We want LLPounceable<T, TAG> 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<Data*> pounceable = &a;
+ // should be able to pass LLPounceable<T> 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<Data*> 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<n> 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<Data*, int> pounceable;
+ }
+} // namespace tut