From 3800c0df910c83e987184d541b868168fc2b5bec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 May 2009 21:08:08 +0000 Subject: 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 --- indra/test/llevents_tut.cpp | 793 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 793 insertions(+) create mode 100644 indra/test/llevents_tut.cpp (limited to 'indra/test/llevents_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 +#include +// external library headers +#include +#include +#include +// 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 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 +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_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(&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(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(&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(list_of("checked"))); + button.listen("checked", + boost::bind(&Collect::add, boost::ref(collector), "checked", _1), + // "checked" must come after "spot" + make(list_of("spot"))); + button.listen("spot", + boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); + button.post(1); + ensure_equals(collector.result, make(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(list_of("spot"))); + button.post(2); + ensure_equals(collector.result, make(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(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(list_of("checked"))); + button.listen("shoelaces", + boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), + make(list_of("checked"))); + button.post(3); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + collector.clear(); + threw.clear(); + try + { + button.listen("of", + boost::bind(&Collect::add, boost::ref(collector), "of", _1), + make(list_of("shoelaces")), + make(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(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 > streams; + for (int i = 2; i <= 10; ++i) + { + streams.push_back(boost::shared_ptr(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 in boost::bind() object + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + ensure("default state", ! connection.connected()); + { + boost::shared_ptr 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 + // instead of binding a shared_ptr, 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 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 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 + { + 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 + 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& cref(*tempListener); + std::cout << "Capturing const ptr" << std::endl; + const boost::enable_shared_from_this* cp(&cref); + std::cout << "Capturing non-const ptr" << std::endl; + boost::enable_shared_from_this* p(const_cast*>(cp)); + std::cout << "Capturing shared_from_this()" << std::endl; + boost::shared_ptr sp(p->shared_from_this()); + std::cout << "Capturing weak_ptr" << std::endl; + boost::weak_ptr 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 -- cgit v1.2.3 From dc934629919bdcaea72c78e5291263914fb958ec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2009 20:05:46 +0000 Subject: svn merge -r113003:119136 svn+ssh://svn.lindenlab.com/svn/linden/branches/login-api/login-api-2 svn+ssh://svn.lindenlab.com/svn/linden/branches/login-api/login-api-3 --- indra/test/llevents_tut.cpp | 133 ++++++-------------------------------------- 1 file changed, 17 insertions(+), 116 deletions(-) (limited to 'indra/test/llevents_tut.cpp') diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index e401f89b22..31130c3c79 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -32,96 +32,10 @@ // other Linden headers #include "lltut.h" #include "stringize.h" +#include "tests/listener.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 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 T make(const T& value) { return value; } @@ -174,14 +88,7 @@ namespace tut // 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)); + LLBoundListener connection = listener0.listenTo(per_frame); ensure("connected", connection.connected()); ensure("not blocked", ! connection.blocked()); per_frame.post(1); @@ -207,6 +114,10 @@ namespace tut bool threw = false; try { + // 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. per_frame.listen(listener0.getName(), // note bug, dup name boost::bind(&Listener::call, boost::ref(listener1), _1)); } @@ -221,8 +132,7 @@ namespace tut } ensure("threw DupListenerName", threw); // do it right this time - per_frame.listen(listener1.getName(), - boost::bind(&Listener::call, boost::ref(listener1), _1)); + listener1.listenTo(per_frame); per_frame.post(5); check_listener("got", listener0, 5); check_listener("got", listener1, 5); @@ -252,16 +162,10 @@ namespace tut 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(list_of(listener0.getName()))); + LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop); + LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call, + // after listener0 + make(list_of(listener0.getName()))); ensure("enabled", per_frame.enabled()); ensure("connected 0", bound0.connected()); ensure("unblocked 0", ! bound0.blocked()); @@ -301,7 +205,7 @@ namespace tut // LLEventQueue. LLEventPump& mainloop(pumps.obtain("mainloop")); ensure("LLEventQueue leaf class", dynamic_cast(&login)); - login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.listenTo(login); listener0.reset(0); login.post(1); check_listener("waiting for queued event", listener0, 0); @@ -354,11 +258,10 @@ namespace tut { set_test_name("stopListening()"); LLEventPump& login(pumps.obtain("login")); - login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.listenTo(login); 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)); + listener0.listenTo(login, &Listener::callstop); LLBoundListener wrong = login.getListener("bogus"); ensure("bogus connection disconnected", ! wrong.connected()); ensure("bogus connection blocked", wrong.blocked()); @@ -378,10 +281,8 @@ namespace tut 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.listenTo(filter0); + listener1.listenTo(filter1); listener0.reset(0); listener1.reset(0); upstream.post(1); @@ -536,7 +437,7 @@ namespace tut // 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)); + listener0.listenTo(random); eventSource("random"); check_listener("got by pump name", listener0, 17); bool threw = false; -- cgit v1.2.3