PageRenderTime 70ms CodeModel.GetById 2ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/test/llevents_tut.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 783 lines | 560 code | 32 blank | 191 comment | 6 complexity | 76ba2881e61d2c040a30b9221f486f30 MD5 | raw file
  1/**
  2 * @file   llevents_tut.cpp
  3 * @author Nat Goodspeed
  4 * @date   2008-09-12
  5 * @brief  Test of llevents.h
  6 * 
  7 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29#if LL_WINDOWS
 30#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
 31#endif
 32
 33// Precompiled header
 34#include "linden_common.h"
 35// associated header
 36// UGLY HACK! We want to verify state internal to the classes without
 37// providing public accessors.
 38#define testable public
 39#include "llevents.h"
 40#undef testable
 41#include "lllistenerwrapper.h"
 42// STL headers
 43// std headers
 44#include <iostream>
 45#include <typeinfo>
 46// external library headers
 47#include <boost/bind.hpp>
 48#include <boost/shared_ptr.hpp>
 49#include <boost/assign/list_of.hpp>
 50// other Linden headers
 51#include "lltut.h"
 52#include "stringize.h"
 53#include "tests/listener.h"
 54
 55using boost::assign::list_of;
 56
 57#ifdef LL_LINUX
 58#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw)										\
 59catch (const std::runtime_error& ex)														\
 60{																							\
 61	/* This clause is needed on Linux, on the viewer side, because the	*/					\
 62	/* exception isn't caught by the clause above. Warn the user...		*/					\
 63	std::cerr << "Failed to catch " << typeid(ex).name() << std::endl;						\
 64	/* But if the expected exception was thrown, allow the test to		*/					\
 65	/* succeed anyway. Not sure how else to handle this odd case.		*/					\
 66	/* This approach is also used in llsdmessage_test.cpp. 				*/					\
 67	if (std::string(typeid(ex).name()) == typeid(exception).name())							\
 68	{																						\
 69		threw = ex.what();																	\
 70		/*std::cout << ex.what() << std::endl;*/											\
 71	}																						\
 72	else																					\
 73	{																						\
 74		/* We don't even recognize this exception. Let it propagate		*/					\
 75		/* out to TUT to fail the test.									*/					\
 76		throw;																				\
 77	}																						\
 78}																							\
 79catch (...)																					\
 80{																							\
 81	std::cerr << "Utterly failed to catch expected exception " << #exception << "!" <<		\
 82	std::endl;																				\
 83	/* This indicates a problem in the test that should be addressed.   */					\
 84	throw;																					\
 85}
 86
 87#else // LL_LINUX
 88#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw)										\
 89	/* Not needed on other platforms */
 90#endif // LL_LINUX
 91
 92template<typename T>
 93T make(const T& value)
 94{
 95	return value;
 96}
 97
 98/*****************************************************************************
 99 *   tut test group
100 *****************************************************************************/
101namespace tut
102{
103struct events_data
104{
105	events_data() :
106		pumps(LLEventPumps::instance()),
107		listener0("first"),
108		listener1("second")
109	{
110	}
111	LLEventPumps& pumps;
112	Listener listener0;
113	Listener listener1;
114
115	void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got)
116	{
117		ensure_equals(STRINGIZE(listener << ' ' << desc),
118					  listener.getLastEvent().asInteger(), got);
119	}
120};
121typedef test_group<events_data> events_group;
122typedef events_group::object events_object;
123tut::events_group evgr("events");
124
125template<> template<>
126void events_object::test<1>()
127{
128	set_test_name("basic operations");
129	// Now there's a static constructor in llevents.cpp that registers on
130	// the "mainloop" pump to call LLEventPumps::flush().
131	// Actually -- having to modify this to track the statically-
132	// constructed pumps in other TUT modules in this giant monolithic test
133	// executable isn't such a hot idea.
134	// ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
135	size_t initial_pumps(pumps.mPumpMap.size());
136	LLEventPump& per_frame(pumps.obtain("per-frame"));
137	ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps + 1);
138	// Verify that per_frame was instantiated as an LLEventStream.
139	ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*> (&per_frame));
140	ensure("enabled", per_frame.enabled());
141	// Trivial test, but posting an event to an EventPump with no
142	// listeners should not blow up. The test is relevant because defining
143	// a boost::signal with a non-void return signature, using the default
144	// combiner, blows up if there are no listeners. This is because the
145	// default combiner is defined to return the value returned by the
146	// last listener, which is meaningless if there were no listeners.
147	per_frame.post(0);
148	LLBoundListener connection = listener0.listenTo(per_frame);
149	ensure("connected", connection.connected());
150	ensure("not blocked", !connection.blocked());
151	per_frame.post(1);
152	check_listener("received", listener0, 1);
153	{ // block the connection
154		LLEventPump::Blocker block(connection);
155		ensure("blocked", connection.blocked());
156		per_frame.post(2);
157		check_listener("not updated", listener0, 1);
158	} // unblock
159	ensure("unblocked", !connection.blocked());
160	per_frame.post(3);
161	check_listener("unblocked", listener0, 3);
162	LLBoundListener sameConnection = per_frame.getListener(listener0.getName());
163	ensure("still connected", sameConnection.connected());
164	ensure("still not blocked", !sameConnection.blocked());
165	{ // block it again
166		LLEventPump::Blocker block(sameConnection);
167		ensure("re-blocked", sameConnection.blocked());
168		per_frame.post(4);
169		check_listener("re-blocked", listener0, 3);
170	} // unblock
171	std::string threw;
172	try
173	{
174		// NOTE: boost::bind() saves its arguments by VALUE! If you pass
175		// an object instance rather than a pointer, you'll end up binding
176		// to an internal copy of that instance! Use boost::ref() to
177		// capture a reference instead.
178		per_frame.listen(listener0.getName(), // note bug, dup name
179						 boost::bind(&Listener::call, boost::ref(listener1), _1));
180	}
181	catch (const LLEventPump::DupListenerName& e)
182	{
183		threw = e.what();
184	}
185	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupListenerName, threw)
186	ensure_equals(threw,
187				  std::string("DupListenerName: "
188							  "Attempt to register duplicate listener name '") +
189							  listener0.getName() + "' on " + typeid(per_frame).name() +
190							  " '" + per_frame.getName() + "'");
191	// do it right this time
192	listener1.listenTo(per_frame);
193	per_frame.post(5);
194	check_listener("got", listener0, 5);
195	check_listener("got", listener1, 5);
196	per_frame.enable(false);
197	per_frame.post(6);
198	check_listener("didn't get", listener0, 5);
199	check_listener("didn't get", listener1, 5);
200	per_frame.enable();
201	per_frame.post(7);
202	check_listener("got", listener0, 7);
203	check_listener("got", listener1, 7);
204	per_frame.stopListening(listener0.getName());
205	ensure("disconnected 0", ! connection.connected());
206	ensure("disconnected 1", ! sameConnection.connected());
207	per_frame.post(8);
208	check_listener("disconnected", listener0, 7);
209	check_listener("still connected", listener1, 8);
210	per_frame.stopListening(listener1.getName());
211	per_frame.post(9);
212	check_listener("disconnected", listener1, 8);
213}
214
215template<> template<>
216void events_object::test<2>()
217{
218	set_test_name("callstop() returning true");
219	LLEventPump& per_frame(pumps.obtain("per-frame"));
220	listener0.reset(0);
221	listener1.reset(0);
222	LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop);
223	LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call,
224												// after listener0
225												make<LLEventPump::NameList>(list_of(listener0.getName())));
226	ensure("enabled", per_frame.enabled());
227	ensure("connected 0", bound0.connected());
228	ensure("unblocked 0", !bound0.blocked());
229	ensure("connected 1", bound1.connected());
230	ensure("unblocked 1", !bound1.blocked());
231	per_frame.post(1);
232	check_listener("got", listener0, 1);
233	// Because listener0.callstop() returns true, control never reaches listener1.call().
234	check_listener("got", listener1, 0);
235}
236
237bool chainEvents(Listener& someListener, const LLSD& event)
238{
239	// Make this call so we can watch for side effects for test purposes.
240	someListener.call(event);
241	// This function represents a recursive event chain -- or some other
242	// scenario in which an event handler raises additional events.
243	int value = event.asInteger();
244	if (value)
245	{
246		LLEventPumps::instance().obtain("login").post(value - 1);
247	}
248	return false;
249}
250
251template<> template<>
252void events_object::test<3>()
253{
254	set_test_name("LLEventQueue delayed action");
255	// This access is NOT legal usage: we can do it only because we're
256	// hacking private for test purposes. Normally we'd either compile in
257	// a particular name, or (later) edit a config file.
258	pumps.mQueueNames.insert("login");
259	LLEventPump& login(pumps.obtain("login"));
260	// The "mainloop" pump is special: posting on that implicitly calls
261	// LLEventPumps::flush(), which in turn should flush our "login"
262	// LLEventQueue.
263	LLEventPump& mainloop(pumps.obtain("mainloop"));
264	ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*> (&login));
265	listener0.listenTo(login);
266	listener0.reset(0);
267	login.post(1);
268	check_listener("waiting for queued event", listener0, 0);
269	mainloop.post(LLSD());
270	check_listener("got queued event", listener0, 1);
271	login.stopListening(listener0.getName());
272	// Verify that when an event handler posts a new event on the same
273	// LLEventQueue, it doesn't get processed in the same flush() call --
274	// it waits until the next flush() call.
275	listener0.reset(17);
276	login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
277	login.post(1);
278	check_listener("chainEvents(1) not yet called", listener0, 17);
279	mainloop.post(LLSD());
280	check_listener("chainEvents(1) called", listener0, 1);
281	mainloop.post(LLSD());
282	check_listener("chainEvents(0) called", listener0, 0);
283	mainloop.post(LLSD());
284	check_listener("chainEvents(-1) not called", listener0, 0);
285	login.stopListening("chainEvents");
286}
287
288template<> template<>
289void events_object::test<4>()
290{
291	set_test_name("explicitly-instantiated LLEventStream");
292	// Explicitly instantiate an LLEventStream, and verify that it
293	// self-registers with LLEventPumps
294	size_t registered = pumps.mPumpMap.size();
295	size_t owned = pumps.mOurPumps.size();
296	LLEventPump* localInstance;
297	{
298		LLEventStream myEventStream("stream");
299		localInstance = &myEventStream;
300		LLEventPump& stream(pumps.obtain("stream"));
301		ensure("found named LLEventStream instance", &stream == localInstance);
302		ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1);
303		ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned);
304	} // destroy myEventStream -- should unregister
305	ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered);
306	ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned);
307	LLEventPump& stream(pumps.obtain("stream"));
308	ensure("new LLEventStream instance", &stream != localInstance);
309	ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1);
310	ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1);
311}
312
313template<> template<>
314void events_object::test<5>()
315{
316	set_test_name("stopListening()");
317	LLEventPump& login(pumps.obtain("login"));
318	listener0.listenTo(login);
319	login.stopListening(listener0.getName());
320	// should not throw because stopListening() should have removed name
321	listener0.listenTo(login, &Listener::callstop);
322	LLBoundListener wrong = login.getListener("bogus");
323	ensure("bogus connection disconnected", !wrong.connected());
324	ensure("bogus connection blocked", wrong.blocked());
325}
326
327template<> template<>
328void events_object::test<6>()
329{
330	set_test_name("chaining LLEventPump instances");
331	LLEventPump& upstream(pumps.obtain("upstream"));
332	// One potentially-useful construct is to chain LLEventPumps together.
333	// Among other things, this allows you to turn subsets of listeners on
334	// and off in groups.
335	LLEventPump& filter0(pumps.obtain("filter0"));
336	LLEventPump& filter1(pumps.obtain("filter1"));
337	upstream.listen(filter0.getName(), boost::bind(&LLEventPump::post, boost::ref(filter0), _1));
338	upstream.listen(filter1.getName(), boost::bind(&LLEventPump::post, boost::ref(filter1), _1));
339	listener0.listenTo(filter0);
340	listener1.listenTo(filter1);
341	listener0.reset(0);
342	listener1.reset(0);
343	upstream.post(1);
344	check_listener("got unfiltered", listener0, 1);
345	check_listener("got unfiltered", listener1, 1);
346	filter0.enable(false);
347	upstream.post(2);
348	check_listener("didn't get filtered", listener0, 1);
349	check_listener("got filtered", listener1, 2);
350}
351
352template<> template<>
353void events_object::test<7>()
354{
355	set_test_name("listener dependency order");
356	typedef LLEventPump::NameList NameList;
357	typedef Collect::StringList StringList;
358	LLEventPump& button(pumps.obtain("button"));
359	Collect collector;
360	button.listen("Mary",
361				  boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
362				  // state that "Mary" must come after "checked"
363				  make<NameList> (list_of("checked")));
364	button.listen("checked",
365				  boost::bind(&Collect::add, boost::ref(collector), "checked", _1),
366				  // "checked" must come after "spot"
367				  make<NameList> (list_of("spot")));
368	button.listen("spot",
369				  boost::bind(&Collect::add, boost::ref(collector), "spot", _1));
370	button.post(1);
371	ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary")));
372	collector.clear();
373	button.stopListening("Mary");
374	button.listen("Mary",
375			boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
376			LLEventPump::empty, // no after dependencies
377			// now "Mary" must come before "spot"
378			make<NameList>(list_of("spot")));
379	button.post(2);
380	ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked")));
381	collector.clear();
382	button.stopListening("spot");
383	std::string threw;
384	try
385	{
386		button.listen("spot",
387					  boost::bind(&Collect::add, boost::ref(collector), "spot", _1),
388					  // after "Mary" and "checked" -- whoops!
389			 		  make<NameList>(list_of("Mary")("checked")));
390	}
391	catch (const LLEventPump::Cycle& e)
392	{
393		threw = e.what();
394		// std::cout << "Caught: " << e.what() << '\n';
395	}
396	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::Cycle, threw)
397	// Obviously the specific wording of the exception text can
398	// change; go ahead and change the test to match.
399	// Establish that it contains:
400	// - the name and runtime type of the LLEventPump
401	ensure_contains("LLEventPump type", threw, typeid(button).name());
402	ensure_contains("LLEventPump name", threw, "'button'");
403	// - the name of the new listener that caused the problem
404	ensure_contains("new listener name", threw, "'spot'");
405	// - a synopsis of the problematic dependencies.
406	ensure_contains("cyclic dependencies", threw,
407					"\"Mary\" -> before (\"spot\")");
408	ensure_contains("cyclic dependencies", threw,
409					"after (\"spot\") -> \"checked\"");
410	ensure_contains("cyclic dependencies", threw,
411					"after (\"Mary\", \"checked\") -> \"spot\"");
412	button.listen("yellow",
413				  boost::bind(&Collect::add, boost::ref(collector), "yellow", _1),
414				  make<NameList>(list_of("checked")));
415	button.listen("shoelaces",
416				  boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),
417				  make<NameList>(list_of("checked")));
418	button.post(3);
419	ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
420	collector.clear();
421	threw.clear();
422	try
423	{
424		button.listen("of",
425					  boost::bind(&Collect::add, boost::ref(collector), "of", _1),
426					  make<NameList>(list_of("shoelaces")),
427					  make<NameList>(list_of("yellow")));
428	}
429	catch (const LLEventPump::OrderChange& e)
430	{
431		threw = e.what();
432		// std::cout << "Caught: " << e.what() << '\n';
433	}
434	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::OrderChange, threw)
435	// Same remarks about the specific wording of the exception. Just
436	// ensure that it contains enough information to clarify the
437	// problem and what must be done to resolve it.
438	ensure_contains("LLEventPump type", threw, typeid(button).name());
439	ensure_contains("LLEventPump name", threw, "'button'");
440	ensure_contains("new listener name", threw, "'of'");
441	ensure_contains("prev listener name", threw, "'yellow'");
442	// std::cout << "Thrown Exception: " << threw << std::endl;
443	ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");
444	ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");
445	button.post(4);
446	ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
447}
448
449template<> template<>
450void events_object::test<8>()
451{
452	set_test_name("tweaked and untweaked LLEventPump instance names");
453	{ 	// nested scope
454		// Hand-instantiate an LLEventStream...
455		LLEventStream bob("bob");
456		std::string threw;
457		try
458		{
459			// then another with a duplicate name.
460			LLEventStream bob2("bob");
461		}
462		catch (const LLEventPump::DupPumpName& e)
463		{
464			threw = e.what();
465			// std::cout << "Caught: " << e.what() << '\n';
466		}
467		CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupPumpName, threw)
468		ensure("Caught DupPumpName", !threw.empty());
469	} 	// delete first 'bob'
470	LLEventStream bob("bob"); 		// should work, previous one unregistered
471	LLEventStream bob1("bob", true);// allowed to tweak name
472	ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1");
473	std::vector<boost::shared_ptr<LLEventStream> > streams;
474	for (int i = 2; i <= 10; ++i)
475	{
476		streams.push_back(boost::shared_ptr<LLEventStream>(new LLEventStream("bob", true)));
477	}
478	ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10");
479}
480
481// Define a function that accepts an LLListenerOrPumpName
482void eventSource(const LLListenerOrPumpName& listener)
483{
484	// Pretend that some time has elapsed. Call listener immediately.
485	listener(17);
486}
487
488template<> template<>
489void events_object::test<9>()
490{
491	set_test_name("LLListenerOrPumpName");
492	// Passing a boost::bind() expression to LLListenerOrPumpName
493	listener0.reset(0);
494	eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1));
495	check_listener("got by listener", listener0, 17);
496	// Passing a string LLEventPump name to LLListenerOrPumpName
497	listener0.reset(0);
498	LLEventStream random("random");
499	listener0.listenTo(random);
500	eventSource("random");
501	check_listener("got by pump name", listener0, 17);
502	std::string threw;
503	try
504	{
505		LLListenerOrPumpName empty;
506		empty(17);
507	}
508	catch (const LLListenerOrPumpName::Empty& e)
509	{
510		threw = e.what();
511	}
512	CATCH_MISSED_LINUX_EXCEPTION(LLListenerOrPumpName::Empty, threw)
513
514	ensure("threw Empty", !threw.empty());
515}
516
517class TempListener: public Listener
518{
519public:
520	TempListener(const std::string& name, bool& liveFlag) :
521		Listener(name), mLiveFlag(liveFlag)
522	{
523		mLiveFlag = true;
524	}
525
526	virtual ~TempListener()
527	{
528		mLiveFlag = false;
529	}
530
531private:
532	bool& mLiveFlag;
533};
534
535template<> template<>
536void events_object::test<10>()
537{
538	set_test_name("listen(boost::bind(...TempListener...))");
539	// listen() can't do anything about a plain TempListener instance:
540	// it's not managed with shared_ptr, nor is it an LLEventTrackable subclass
541	bool live = false;
542	LLEventPump& heaptest(pumps.obtain("heaptest"));
543	LLBoundListener connection;
544	{
545		TempListener tempListener("temp", live);
546		ensure("TempListener constructed", live);
547		connection = heaptest.listen(tempListener.getName(),
548									 boost::bind(&Listener::call,
549												 boost::ref(tempListener),
550												 _1));
551		heaptest.post(1);
552		check_listener("received", tempListener, 1);
553	} // presumably this will make newListener go away?
554	// verify that
555	ensure("TempListener destroyed", !live);
556	// This is the case against which we can't defend. Don't even try to
557	// post to heaptest -- that would engage Undefined Behavior.
558	// Cautiously inspect connection...
559	ensure("misleadingly connected", connection.connected());
560	// then disconnect by hand.
561	heaptest.stopListening("temp");
562}
563
564template<> template<>
565void events_object::test<11>()
566{
567	set_test_name("listen(boost::bind(...weak_ptr...))");
568	// listen() detecting weak_ptr<TempListener> in boost::bind() object
569	bool live = false;
570	LLEventPump& heaptest(pumps.obtain("heaptest"));
571	LLBoundListener connection;
572	ensure("default state", !connection.connected());
573	{
574		boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
575		newListener->reset();
576		ensure("TempListener constructed", live);
577		connection = heaptest.listen(newListener->getName(),
578									 boost::bind(&Listener::call, 
579												 weaken(newListener), 
580												 _1));
581		ensure("new connection", connection.connected());
582		heaptest.post(1);
583		check_listener("received", *newListener, 1);
584	} // presumably this will make newListener go away?
585	// verify that
586	ensure("TempListener destroyed", !live);
587	ensure("implicit disconnect", !connection.connected());
588	// now just make sure we don't blow up trying to access a freed object!
589	heaptest.post(2);
590}
591
592template<> template<>
593void events_object::test<12>()
594{
595	set_test_name("listen(boost::bind(...shared_ptr...))");
596	/*==========================================================================*|
597	// DISABLED because I've made this case produce a compile error.
598	// Following the error leads the disappointed dev to a comment
599	// instructing her to use the weaken() function to bind a weak_ptr<T>
600	// instead of binding a shared_ptr<T>, and explaining why. I know of
601	// no way to use TUT to code a repeatable test in which the expected
602	// outcome is a compile error. The interested reader is invited to
603	// uncomment this block and build to see for herself.
604
605	// listen() detecting shared_ptr<TempListener> in boost::bind() object
606	bool live = false;
607	LLEventPump& heaptest(pumps.obtain("heaptest"));
608	LLBoundListener connection;
609	std::string listenerName("heap");
610	ensure("default state", !connection.connected());
611	{
612		boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
613		ensure_equals("use_count", newListener.use_count(), 1);
614		newListener->reset();
615		ensure("TempListener constructed", live);
616		connection = heaptest.listen(newListener->getName(),
617									 boost::bind(&Listener::call, newListener, _1));
618		ensure("new connection", connection.connected());
619		ensure_equals("use_count", newListener.use_count(), 2);
620		heaptest.post(1);
621		check_listener("received", *newListener, 1);
622	} // this should make newListener go away...
623	// Unfortunately, the fact that we've bound a shared_ptr by value into
624	// our LLEventPump means that copy will keep the referenced object alive.
625	ensure("TempListener still alive", live);
626	ensure("still connected", connection.connected());
627	// disconnecting explicitly should delete the TempListener...
628	heaptest.stopListening(listenerName);
629#if 0   // however, in my experience, it does not. I don't know why not.
630	// Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
631	// library, stated on the boost-users mailing list:
632	// 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
633	// "It will get destroyed eventually. The signal cleans up its slot
634	// list little by little during connect/invoke. It doesn't immediately
635	// remove disconnected slots from the slot list since other threads
636	// might be using the same slot list concurrently. It might be
637	// possible to make it immediately reset the shared_ptr owning the
638	// slot though, leaving an empty shared_ptr in the slot list, since
639	// that wouldn't invalidate any iterators."
640	ensure("TempListener destroyed", ! live);
641	ensure("implicit disconnect", ! connection.connected());
642#endif  // 0
643	// now just make sure we don't blow up trying to access a freed object!
644	heaptest.post(2);
645|*==========================================================================*/
646}
647
648class TempTrackableListener: public TempListener, public LLEventTrackable
649{
650public:
651TempTrackableListener(const std::string& name, bool& liveFlag):
652	TempListener(name, liveFlag)
653{}
654};
655
656template<> template<>
657void events_object::test<13>()
658{
659set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
660bool live = false;
661LLEventPump& heaptest(pumps.obtain("heaptest"));
662LLBoundListener connection;
663{
664	TempTrackableListener tempListener("temp", live);
665	ensure("TempTrackableListener constructed", live);
666	connection = heaptest.listen(tempListener.getName(),
667								 boost::bind(&TempTrackableListener::call,
668											 boost::ref(tempListener), _1));
669	heaptest.post(1);
670	check_listener("received", tempListener, 1);
671} // presumably this will make tempListener go away?
672// verify that
673ensure("TempTrackableListener destroyed", ! live);
674ensure("implicit disconnect", ! connection.connected());
675// now just make sure we don't blow up trying to access a freed object!
676heaptest.post(2);
677}
678
679template<> template<>
680void events_object::test<14>()
681{
682set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
683bool live = false;
684LLEventPump& heaptest(pumps.obtain("heaptest"));
685LLBoundListener connection;
686{
687	TempTrackableListener* newListener(new TempTrackableListener("temp", live));
688	ensure("TempTrackableListener constructed", live);
689	connection = heaptest.listen(newListener->getName(),
690								 boost::bind(&TempTrackableListener::call,
691											 newListener, _1));
692	heaptest.post(1);
693	check_listener("received", *newListener, 1);
694	// explicitly destroy newListener
695	delete newListener;
696}
697// verify that
698ensure("TempTrackableListener destroyed", ! live);
699ensure("implicit disconnect", ! connection.connected());
700// now just make sure we don't blow up trying to access a freed object!
701heaptest.post(2);
702}
703
704template<> template<>
705void events_object::test<15>()
706{
707// This test ensures that using an LLListenerWrapper subclass doesn't
708// block Boost.Signals2 from recognizing a bound LLEventTrackable
709// subclass.
710set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
711bool live = false;
712LLEventPump& heaptest(pumps.obtain("heaptest"));
713LLBoundListener connection;
714{
715	TempTrackableListener tempListener("temp", live);
716	ensure("TempTrackableListener constructed", live);
717	connection = heaptest.listen(tempListener.getName(),
718								 llwrap<LLLogListener>(
719								 boost::bind(&TempTrackableListener::call,
720											 boost::ref(tempListener), _1)));
721	heaptest.post(1);
722	check_listener("received", tempListener, 1);
723} // presumably this will make tempListener go away?
724// verify that
725ensure("TempTrackableListener destroyed", ! live);
726ensure("implicit disconnect", ! connection.connected());
727// now just make sure we don't blow up trying to access a freed object!
728heaptest.post(2);
729}
730
731class TempSharedListener: public TempListener,
732public boost::enable_shared_from_this<TempSharedListener>
733{
734public:
735TempSharedListener(const std::string& name, bool& liveFlag):
736	TempListener(name, liveFlag)
737{}
738};
739
740template<> template<>
741void events_object::test<16>()
742{
743	set_test_name("listen(boost::bind(...TempSharedListener ref...))");
744#if 0
745bool live = false;
746LLEventPump& heaptest(pumps.obtain("heaptest"));
747LLBoundListener connection;
748{
749	// We MUST have at least one shared_ptr to an
750	// enable_shared_from_this subclass object before
751	// shared_from_this() can work.
752	boost::shared_ptr<TempSharedListener>
753		tempListener(new TempSharedListener("temp", live));
754	ensure("TempSharedListener constructed", live);
755	// However, we're not passing either the shared_ptr or its
756	// corresponding weak_ptr -- instead, we're passing a reference to
757	// the TempSharedListener.
758/*==========================================================================*|
759	 std::cout << "Capturing const ref" << std::endl;
760	 const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
761	 std::cout << "Capturing const ptr" << std::endl;
762	 const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
763	 std::cout << "Capturing non-const ptr" << std::endl;
764	 boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
765	 std::cout << "Capturing shared_from_this()" << std::endl;
766	 boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
767	 std::cout << "Capturing weak_ptr" << std::endl;
768	 boost::weak_ptr<TempSharedListener> wp(weaken(sp));
769	 std::cout << "Binding weak_ptr" << std::endl;
770|*==========================================================================*/
771	connection = heaptest.listen(tempListener->getName(),
772								 boost::bind(&TempSharedListener::call, *tempListener, _1));
773	heaptest.post(1);
774	check_listener("received", *tempListener, 1);
775} // presumably this will make tempListener go away?
776// verify that
777ensure("TempSharedListener destroyed", ! live);
778ensure("implicit disconnect", ! connection.connected());
779// now just make sure we don't blow up trying to access a freed object!
780heaptest.post(2);
781#endif // 0
782}
783} // namespace tut