summaryrefslogtreecommitdiff
path: root/indra/test
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2009-05-08 21:08:08 +0000
committerNat Goodspeed <nat@lindenlab.com>2009-05-08 21:08:08 +0000
commit3800c0df910c83e987184d541b868168fc2b5bec (patch)
tree91bcf4e13972ae02b9d6500c1d14de7bb8d37dc4 /indra/test
parent5da967dc744f35d5270c7cb0b8b23b993ecda3e1 (diff)
svn merge -r114679:114681 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-7 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-8
Diffstat (limited to 'indra/test')
-rw-r--r--indra/test/CMakeLists.txt4
-rw-r--r--indra/test/llevents_tut.cpp793
2 files changed, 797 insertions, 0 deletions
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt
index c74ef06636..88ef15a8d9 100644
--- a/indra/test/CMakeLists.txt
+++ b/indra/test/CMakeLists.txt
@@ -13,6 +13,7 @@ include(LLXML)
include(LScript)
include(Linking)
include(Tut)
+include(Boost)
include_directories(
${LLCOMMON_INCLUDE_DIRS}
@@ -35,7 +36,9 @@ set(test_SOURCE_FILES
llbuffer_tut.cpp
lldate_tut.cpp
lldoubledispatch_tut.cpp
+ lldependencies_tut.cpp
llerror_tut.cpp
+ llevents_tut.cpp
llhost_tut.cpp
llhttpdate_tut.cpp
llhttpclient_tut.cpp
@@ -73,6 +76,7 @@ set(test_SOURCE_FILES
math.cpp
message_tut.cpp
reflection_tut.cpp
+ stringize_tut.cpp
test.cpp
v2math_tut.cpp
v3color_tut.cpp
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
new file mode 100644
index 0000000000..e401f89b22
--- /dev/null
+++ b/indra/test/llevents_tut.cpp
@@ -0,0 +1,793 @@
+/**
+ * @file llevents_tut.cpp
+ * @author Nat Goodspeed
+ * @date 2008-09-12
+ * @brief Test of llevents.h
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * Copyright (c) 2008, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+// UGLY HACK! We want to verify state internal to the classes without
+// providing public accessors.
+#define testable public
+#include "llevents.h"
+#undef testable
+// STL headers
+// std headers
+#include <iostream>
+#include <typeinfo>
+// external library headers
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/assign/list_of.hpp>
+// other Linden headers
+#include "lltut.h"
+#include "stringize.h"
+
+using boost::assign::list_of;
+
+/*****************************************************************************
+* test listener class
+*****************************************************************************/
+class Listener;
+std::ostream& operator<<(std::ostream&, const Listener&);
+
+class Listener
+{
+public:
+ Listener(const std::string& name):
+ mName(name)
+ {
+// std::cout << *this << ": ctor\n";
+ }
+ Listener(const Listener& that):
+ mName(that.mName),
+ mLastEvent(that.mLastEvent)
+ {
+// std::cout << *this << ": copy\n";
+ }
+ virtual ~Listener()
+ {
+// std::cout << *this << ": dtor\n";
+ }
+ std::string getName() const { return mName; }
+ bool call(const LLSD& event)
+ {
+// std::cout << *this << "::call(" << event << ")\n";
+ mLastEvent = event;
+ return false;
+ }
+ bool callstop(const LLSD& event)
+ {
+// std::cout << *this << "::callstop(" << event << ")\n";
+ mLastEvent = event;
+ return true;
+ }
+ LLSD getLastEvent() const
+ {
+// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n";
+ return mLastEvent;
+ }
+ void reset(const LLSD& to = LLSD())
+ {
+// std::cout << *this << "::reset(" << to << ")\n";
+ mLastEvent = to;
+ }
+
+private:
+ std::string mName;
+ LLSD mLastEvent;
+};
+
+std::ostream& operator<<(std::ostream& out, const Listener& listener)
+{
+ out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')';
+ return out;
+}
+
+struct Collect
+{
+ bool add(const std::string& bound, const LLSD& event)
+ {
+ result.push_back(bound);
+ return false;
+ }
+ void clear() { result.clear(); }
+ typedef std::vector<std::string> StringList;
+ StringList result;
+};
+
+std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings)
+{
+ out << '(';
+ Collect::StringList::const_iterator begin(strings.begin()), end(strings.end());
+ if (begin != end)
+ {
+ out << '"' << *begin << '"';
+ while (++begin != end)
+ {
+ out << ", \"" << *begin << '"';
+ }
+ }
+ out << ')';
+ return out;
+}
+
+template<typename T>
+T make(const T& value) { return value; }
+
+/*****************************************************************************
+* tut test group
+*****************************************************************************/
+namespace tut
+{
+ struct events_data
+ {
+ events_data():
+ pumps(LLEventPumps::instance()),
+ listener0("first"),
+ listener1("second")
+ {}
+ LLEventPumps& pumps;
+ Listener listener0;
+ Listener listener1;
+
+ void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got)
+ {
+ ensure_equals(STRINGIZE(listener << ' ' << desc),
+ listener.getLastEvent().asInteger(), got);
+ }
+ };
+ typedef test_group<events_data> events_group;
+ typedef events_group::object events_object;
+ tut::events_group evgr("events");
+
+ 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-
+ // 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);
+ size_t initial_pumps(pumps.mPumpMap.size());
+ LLEventPump& per_frame(pumps.obtain("per-frame"));
+ ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps+1);
+ // Verify that per_frame was instantiated as an LLEventStream.
+ ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*>(&per_frame));
+ ensure("enabled", per_frame.enabled());
+ // Trivial test, but posting an event to an EventPump with no
+ // listeners should not blow up. The test is relevant because defining
+ // a boost::signal with a non-void return signature, using the default
+ // combiner, blows up if there are no listeners. This is because the
+ // default combiner is defined to return the value returned by the
+ // last listener, which is meaningless if there were no listeners.
+ per_frame.post(0);
+ // NOTE: boost::bind() saves its arguments by VALUE! If you pass an
+ // object instance rather than a pointer, you'll end up binding to an
+ // internal copy of that instance! Use boost::ref() to capture a
+ // reference instead.
+ LLBoundListener connection = per_frame.listen(listener0.getName(),
+ boost::bind(&Listener::call,
+ boost::ref(listener0),
+ _1));
+ ensure("connected", connection.connected());
+ ensure("not blocked", ! connection.blocked());
+ per_frame.post(1);
+ check_listener("received", listener0, 1);
+ { // block the connection
+ LLEventPump::Blocker block(connection);
+ ensure("blocked", connection.blocked());
+ per_frame.post(2);
+ check_listener("not updated", listener0, 1);
+ } // unblock
+ ensure("unblocked", ! connection.blocked());
+ per_frame.post(3);
+ check_listener("unblocked", listener0, 3);
+ LLBoundListener sameConnection = per_frame.getListener(listener0.getName());
+ ensure("still connected", sameConnection.connected());
+ ensure("still not blocked", ! sameConnection.blocked());
+ { // block it again
+ LLEventPump::Blocker block(sameConnection);
+ ensure("re-blocked", sameConnection.blocked());
+ per_frame.post(4);
+ check_listener("re-blocked", listener0, 3);
+ } // unblock
+ bool threw = false;
+ try
+ {
+ per_frame.listen(listener0.getName(), // note bug, dup name
+ boost::bind(&Listener::call, boost::ref(listener1), _1));
+ }
+ catch (const LLEventPump::DupListenerName& e)
+ {
+ threw = true;
+ ensure_equals(e.what(),
+ std::string("DupListenerName: "
+ "Attempt to register duplicate listener name '") +
+ listener0.getName() +
+ "' on " + typeid(per_frame).name() + " '" + per_frame.getName() + "'");
+ }
+ ensure("threw DupListenerName", threw);
+ // do it right this time
+ per_frame.listen(listener1.getName(),
+ boost::bind(&Listener::call, boost::ref(listener1), _1));
+ per_frame.post(5);
+ check_listener("got", listener0, 5);
+ check_listener("got", listener1, 5);
+ per_frame.enable(false);
+ per_frame.post(6);
+ check_listener("didn't get", listener0, 5);
+ check_listener("didn't get", listener1, 5);
+ per_frame.enable();
+ per_frame.post(7);
+ check_listener("got", listener0, 7);
+ check_listener("got", listener1, 7);
+ per_frame.stopListening(listener0.getName());
+ ensure("disconnected 0", ! connection.connected());
+ ensure("disconnected 1", ! sameConnection.connected());
+ per_frame.post(8);
+ check_listener("disconnected", listener0, 7);
+ check_listener("still connected", listener1, 8);
+ per_frame.stopListening(listener1.getName());
+ per_frame.post(9);
+ check_listener("disconnected", listener1, 8);
+ }
+
+ template<> template<>
+ void events_object::test<2>()
+ {
+ set_test_name("callstop() returning true");
+ LLEventPump& per_frame(pumps.obtain("per-frame"));
+ listener0.reset(0);
+ listener1.reset(0);
+ LLBoundListener bound0 = per_frame.listen(listener0.getName(),
+ boost::bind(&Listener::callstop,
+ boost::ref(listener0),
+ _1));
+ LLBoundListener bound1 = per_frame.listen(listener1.getName(),
+ boost::bind(&Listener::call,
+ boost::ref(listener1),
+ _1),
+ // after listener0
+ make<LLEventPump::NameList>(list_of(listener0.getName())));
+ ensure("enabled", per_frame.enabled());
+ ensure("connected 0", bound0.connected());
+ ensure("unblocked 0", ! bound0.blocked());
+ ensure("connected 1", bound1.connected());
+ ensure("unblocked 1", ! bound1.blocked());
+ per_frame.post(1);
+ check_listener("got", listener0, 1);
+ // Because listener0.callstop() returns true, control never reaches listener1.call().
+ check_listener("got", listener1, 0);
+ }
+
+ bool chainEvents(Listener& someListener, const LLSD& event)
+ {
+ // Make this call so we can watch for side effects for test purposes.
+ someListener.call(event);
+ // This function represents a recursive event chain -- or some other
+ // scenario in which an event handler raises additional events.
+ int value = event.asInteger();
+ if (value)
+ {
+ LLEventPumps::instance().obtain("login").post(value - 1);
+ }
+ return false;
+ }
+
+ 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));
+ login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+ 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
+ size_t registered = pumps.mPumpMap.size();
+ size_t owned = pumps.mOurPumps.size();
+ LLEventPump* localInstance;
+ {
+ LLEventStream myEventStream("stream");
+ localInstance = &myEventStream;
+ LLEventPump& stream(pumps.obtain("stream"));
+ ensure("found named LLEventStream instance", &stream == localInstance);
+ ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1);
+ ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned);
+ } // destroy myEventStream -- should unregister
+ ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered);
+ ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned);
+ LLEventPump& stream(pumps.obtain("stream"));
+ ensure("new LLEventStream instance", &stream != localInstance);
+ ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1);
+ ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1);
+ }
+
+ template<> template<>
+ void events_object::test<5>()
+ {
+ set_test_name("stopListening()");
+ LLEventPump& login(pumps.obtain("login"));
+ login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+ login.stopListening(listener0.getName());
+ // should not throw because stopListening() should have removed name
+ login.listen(listener0.getName(),
+ boost::bind(&Listener::callstop, boost::ref(listener0), _1));
+ LLBoundListener wrong = login.getListener("bogus");
+ ensure("bogus connection disconnected", ! wrong.connected());
+ ensure("bogus connection blocked", wrong.blocked());
+ }
+
+ template<> template<>
+ void events_object::test<6>()
+ {
+ set_test_name("chaining LLEventPump instances");
+ LLEventPump& upstream(pumps.obtain("upstream"));
+ // One potentially-useful construct is to chain LLEventPumps together.
+ // Among other things, this allows you to turn subsets of listeners on
+ // and off in groups.
+ LLEventPump& filter0(pumps.obtain("filter0"));
+ LLEventPump& filter1(pumps.obtain("filter1"));
+ upstream.listen(filter0.getName(),
+ boost::bind(&LLEventPump::post, boost::ref(filter0), _1));
+ upstream.listen(filter1.getName(),
+ boost::bind(&LLEventPump::post, boost::ref(filter1), _1));
+ filter0.listen(listener0.getName(),
+ boost::bind(&Listener::call, boost::ref(listener0), _1));
+ filter1.listen(listener1.getName(),
+ boost::bind(&Listener::call, boost::ref(listener1), _1));
+ listener0.reset(0);
+ listener1.reset(0);
+ upstream.post(1);
+ check_listener("got unfiltered", listener0, 1);
+ check_listener("got unfiltered", listener1, 1);
+ filter0.enable(false);
+ upstream.post(2);
+ check_listener("didn't get filtered", listener0, 1);
+ check_listener("got filtered", listener1, 2);
+ }
+
+ template<> template<>
+ void events_object::test<7>()
+ {
+ set_test_name("listener dependency order");
+ typedef LLEventPump::NameList NameList;
+ typedef Collect::StringList StringList;
+ LLEventPump& button(pumps.obtain("button"));
+ Collect collector;
+ button.listen("Mary",
+ boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
+ // state that "Mary" must come after "checked"
+ make<NameList>(list_of("checked")));
+ button.listen("checked",
+ boost::bind(&Collect::add, boost::ref(collector), "checked", _1),
+ // "checked" must come after "spot"
+ make<NameList>(list_of("spot")));
+ button.listen("spot",
+ boost::bind(&Collect::add, boost::ref(collector), "spot", _1));
+ button.post(1);
+ ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary")));
+ collector.clear();
+ button.stopListening("Mary");
+ button.listen("Mary",
+ boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
+ LLEventPump::empty, // no after dependencies
+ // now "Mary" must come before "spot"
+ make<NameList>(list_of("spot")));
+ button.post(2);
+ ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked")));
+ collector.clear();
+ button.stopListening("spot");
+ std::string threw;
+ try
+ {
+ button.listen("spot",
+ boost::bind(&Collect::add, boost::ref(collector), "spot", _1),
+ // after "Mary" and "checked" -- whoops!
+ make<NameList>(list_of("Mary")("checked")));
+ }
+ catch (const LLEventPump::Cycle& e)
+ {
+ threw = e.what();
+// std::cout << "Caught: " << e.what() << '\n';
+ }
+ // Obviously the specific wording of the exception text can
+ // change; go ahead and change the test to match.
+ // Establish that it contains:
+ // - the name and runtime type of the LLEventPump
+ ensure_contains("LLEventPump type", threw, typeid(button).name());
+ ensure_contains("LLEventPump name", threw, "'button'");
+ // - the name of the new listener that caused the problem
+ ensure_contains("new listener name", threw, "'spot'");
+ // - a synopsis of the problematic dependencies.
+ ensure_contains("cyclic dependencies", threw,
+ "\"Mary\" -> before (\"spot\")");
+ ensure_contains("cyclic dependencies", threw,
+ "after (\"spot\") -> \"checked\"");
+ ensure_contains("cyclic dependencies", threw,
+ "after (\"Mary\", \"checked\") -> \"spot\"");
+ button.listen("yellow",
+ boost::bind(&Collect::add, boost::ref(collector), "yellow", _1),
+ make<NameList>(list_of("checked")));
+ button.listen("shoelaces",
+ boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),
+ make<NameList>(list_of("checked")));
+ button.post(3);
+ ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
+ collector.clear();
+ threw.clear();
+ try
+ {
+ button.listen("of",
+ boost::bind(&Collect::add, boost::ref(collector), "of", _1),
+ make<NameList>(list_of("shoelaces")),
+ make<NameList>(list_of("yellow")));
+ }
+ catch (const LLEventPump::OrderChange& e)
+ {
+ threw = e.what();
+// std::cout << "Caught: " << e.what() << '\n';
+ }
+ // Same remarks about the specific wording of the exception. Just
+ // ensure that it contains enough information to clarify the
+ // problem and what must be done to resolve it.
+ ensure_contains("LLEventPump type", threw, typeid(button).name());
+ ensure_contains("LLEventPump name", threw, "'button'");
+ ensure_contains("new listener name", threw, "'of'");
+ ensure_contains("prev listener name", threw, "'yellow'");
+ ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");
+ ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");
+ button.post(4);
+ ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
+ }
+
+ template<> template<>
+ void events_object::test<8>()
+ {
+ set_test_name("tweaked and untweaked LLEventPump instance names");
+ { // nested scope
+ // Hand-instantiate an LLEventStream...
+ LLEventStream bob("bob");
+ bool threw = false;
+ try
+ {
+ // then another with a duplicate name.
+ LLEventStream bob2("bob");
+ }
+ catch (const LLEventPump::DupPumpName& /*e*/)
+ {
+ threw = true;
+// std::cout << "Caught: " << e.what() << '\n';
+ }
+ ensure("Caught DupPumpName", threw);
+ } // delete first 'bob'
+ LLEventStream bob("bob"); // should work, previous one unregistered
+ LLEventStream bob1("bob", true); // allowed to tweak name
+ ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1");
+ std::vector< boost::shared_ptr<LLEventStream> > streams;
+ for (int i = 2; i <= 10; ++i)
+ {
+ streams.push_back(boost::shared_ptr<LLEventStream>(new LLEventStream("bob", true)));
+ }
+ ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10");
+ }
+
+ // Define a function that accepts an LLListenerOrPumpName
+ void eventSource(const LLListenerOrPumpName& listener)
+ {
+ // Pretend that some time has elapsed. Call listener immediately.
+ listener(17);
+ }
+
+ template<> template<>
+ void events_object::test<9>()
+ {
+ set_test_name("LLListenerOrPumpName");
+ // Passing a boost::bind() expression to LLListenerOrPumpName
+ listener0.reset(0);
+ eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1));
+ check_listener("got by listener", listener0, 17);
+ // Passing a string LLEventPump name to LLListenerOrPumpName
+ listener0.reset(0);
+ LLEventStream random("random");
+ random.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1));
+ eventSource("random");
+ check_listener("got by pump name", listener0, 17);
+ bool threw = false;
+ try
+ {
+ LLListenerOrPumpName empty;
+ empty(17);
+ }
+ catch (const LLListenerOrPumpName::Empty&)
+ {
+ threw = true;
+ }
+ ensure("threw Empty", threw);
+ }
+
+ class TempListener: public Listener
+ {
+ public:
+ TempListener(const std::string& name, bool& liveFlag):
+ Listener(name),
+ mLiveFlag(liveFlag)
+ {
+ mLiveFlag = true;
+ }
+
+ virtual ~TempListener()
+ {
+ mLiveFlag = false;
+ }
+
+ private:
+ bool& mLiveFlag;
+ };
+
+ template<> template<>
+ void events_object::test<10>()
+ {
+ set_test_name("listen(boost::bind(...TempListener...))");
+ // listen() can't do anything about a plain TempListener instance:
+ // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempListener tempListener("temp", live);
+ ensure("TempListener constructed", live);
+ connection = heaptest.listen(tempListener.getName(),
+ boost::bind(&Listener::call,
+ boost::ref(tempListener),
+ _1));
+ heaptest.post(1);
+ check_listener("received", tempListener, 1);
+ } // presumably this will make newListener go away?
+ // verify that
+ ensure("TempListener destroyed", ! live);
+ // This is the case against which we can't defend. Don't even try to
+ // post to heaptest -- that would engage Undefined Behavior.
+ // Cautiously inspect connection...
+ ensure("misleadingly connected", connection.connected());
+ // then disconnect by hand.
+ 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)
+ {}
+ };
+
+ 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;
+ {
+ 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<15>()
+ {
+ 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