diff options
Diffstat (limited to 'indra/llcommon/tests')
-rw-r--r-- | indra/llcommon/tests/llcond_test.cpp | 67 | ||||
-rw-r--r-- | indra/llcommon/tests/llerror_test.cpp | 58 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 739 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventdispatcher_test.cpp | 206 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventfilter_test.cpp | 87 | ||||
-rw-r--r-- | indra/llcommon/tests/llexception_test.cpp | 15 | ||||
-rw-r--r-- | indra/llcommon/tests/llinstancetracker_test.cpp | 122 | ||||
-rw-r--r-- | indra/llcommon/tests/lllazy_test.cpp | 13 | ||||
-rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 48 | ||||
-rw-r--r-- | indra/llcommon/tests/llmainthreadtask_test.cpp | 137 | ||||
-rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 24 | ||||
-rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 29 | ||||
-rw-r--r-- | indra/llcommon/tests/llsingleton_test.cpp | 147 | ||||
-rw-r--r-- | indra/llcommon/tests/wrapllerrs.h | 48 |
14 files changed, 847 insertions, 893 deletions
diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp new file mode 100644 index 0000000000..478149eacf --- /dev/null +++ b/indra/llcommon/tests/llcond_test.cpp @@ -0,0 +1,67 @@ +/** + * @file llcond_test.cpp + * @author Nat Goodspeed + * @date 2019-07-18 + * @brief Test for llcond. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcond.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llcoros.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llcond_data + { + LLScalarCond<int> cond{0}; + }; + typedef test_group<llcond_data> llcond_group; + typedef llcond_group::object object; + llcond_group llcondgrp("llcond"); + + template<> template<> + void object::test<1>() + { + set_test_name("Immediate gratification"); + cond.set_one(1); + ensure("wait_for_equal() failed", + cond.wait_for_equal(F32Milliseconds(1), 1)); + ensure("wait_for_unequal() should have failed", + ! cond.wait_for_unequal(F32Milliseconds(1), 1)); + } + + template<> template<> + void object::test<2>() + { + set_test_name("Simple two-coroutine test"); + LLCoros::instance().launch( + "test<2>", + [this]() + { + // Lambda immediately entered -- control comes here first. + ensure_equals(cond.get(), 0); + cond.set_all(1); + cond.wait_equal(2); + ensure_equals(cond.get(), 2); + cond.set_all(3); + }); + // Main coroutine is resumed only when the lambda waits. + ensure_equals(cond.get(), 1); + cond.set_all(2); + cond.wait_equal(3); + } +} // namespace tut diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index 8e1f4c14ac..148c18aabe 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -26,6 +26,7 @@ */ #include <vector> +#include <stdexcept> #include "linden_common.h" @@ -69,21 +70,41 @@ namespace namespace { - static bool fatalWasCalled; - void fatalCall(const std::string&) { fatalWasCalled = true; } + static bool fatalWasCalled = false; + struct FatalWasCalled: public std::runtime_error + { + FatalWasCalled(const std::string& what): std::runtime_error(what) {} + }; + void fatalCall(const std::string& msg) { throw FatalWasCalled(msg); } } +// Because we use LLError::setFatalFunction(fatalCall), any LL_ERRS call we +// issue will throw FatalWasCalled. But we want the test program to continue. +// So instead of writing: +// LL_ERRS("tag") << "some message" << LL_ENDL; +// write: +// CATCH(LL_ERRS("tag"), "some message"); +#define CATCH(logcall, expr) \ + try \ + { \ + logcall << expr << LL_ENDL; \ + } \ + catch (const FatalWasCalled&) \ + { \ + fatalWasCalled = true; \ + } + namespace tut { class TestRecorder : public LLError::Recorder { public: TestRecorder() - { - showTime(false); - } + { + showTime(false); + } virtual ~TestRecorder() - {} + {} virtual void recordMessage(LLError::ELevel level, const std::string& message) @@ -252,7 +273,7 @@ namespace LL_DEBUGS("WriteTag","AnotherTag") << "one" << LL_ENDL; LL_INFOS("WriteTag") << "two" << LL_ENDL; LL_WARNS("WriteTag") << "three" << LL_ENDL; - LL_ERRS("WriteTag") << "four" << LL_ENDL; + CATCH(LL_ERRS("WriteTag"), "four"); } }; @@ -380,7 +401,7 @@ namespace std::string errorReturningLocation() { - LL_ERRS() << "die" << LL_ENDL; int this_line = __LINE__; + int this_line = __LINE__; CATCH(LL_ERRS(), "die"); return locationString(this_line); } } @@ -701,7 +722,7 @@ public: static void doDebug() { LL_DEBUGS() << "add dice" << LL_ENDL; } static void doInfo() { LL_INFOS() << "any idea" << LL_ENDL; } static void doWarn() { LL_WARNS() << "aim west" << LL_ENDL; } - static void doError() { LL_ERRS() << "ate eels" << LL_ENDL; } + static void doError() { CATCH(LL_ERRS(), "ate eels"); } static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } }; @@ -712,7 +733,7 @@ public: static void doDebug() { LL_DEBUGS() << "bed down" << LL_ENDL; } static void doInfo() { LL_INFOS() << "buy iron" << LL_ENDL; } static void doWarn() { LL_WARNS() << "bad word" << LL_ENDL; } - static void doError() { LL_ERRS() << "big easy" << LL_ENDL; } + static void doError() { CATCH(LL_ERRS(), "big easy"); } static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } }; @@ -874,13 +895,10 @@ namespace tut namespace { std::string writeTagWithSpaceReturningLocation() - { - LL_DEBUGS("Write Tag") << "not allowed" << LL_ENDL; int this_line = __LINE__; - - std::ostringstream location; - location << LLError::abbreviateFile(__FILE__).c_str() << "(" << this_line << ")"; - return location.str(); - } + { + int this_line = __LINE__; CATCH(LL_DEBUGS("Write Tag"), "not allowed"); + return locationString(this_line); + } }; namespace tut @@ -894,9 +912,9 @@ namespace tut std::string location = writeTagWithSpaceReturningLocation(); std::string expected = "Space is not allowed in a log tag at " + location; - ensure_message_field_equals(0, LEVEL_FIELD, "ERROR"); - ensure_message_field_equals(0, MSG_FIELD, expected); - ensure("fatal callback called", fatalWasCalled); + ensure_message_field_equals(0, LEVEL_FIELD, "ERROR"); + ensure_message_field_equals(0, MSG_FIELD, expected); + ensure("fatal callback called", fatalWasCalled); } } diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index a459d17fb8..032923a108 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -26,102 +26,33 @@ * $/LicenseInfo$ */ -/*****************************************************************************/ -// test<1>() is cloned from a Boost.Coroutine example program whose copyright -// info is reproduced here: -/*---------------------------------------------------------------------------*/ -// Copyright (c) 2006, Giovanni P. Deretta -// -// This code may be used under either of the following two licences: -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. OF SUCH DAMAGE. -// -// Or: -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -/*****************************************************************************/ - #define BOOST_RESULT_OF_USE_TR1 1 -// On some platforms, Boost.Coroutine must #define magic symbols before -// #including platform-API headers. Naturally, that's ineffective unless the -// Boost.Coroutine #include is the *first* #include of the platform header. -// That means that client code must generally #include Boost.Coroutine headers -// before anything else. -#include <boost/dcoroutine/coroutine.hpp> #include <boost/bind.hpp> #include <boost/range.hpp> #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> #include "linden_common.h" #include <iostream> #include <string> +#include <typeinfo> #include "../test/lltut.h" +#include "../test/lltestapp.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" -#include "tests/wrapllerrs.h" -#include "stringize.h" #include "llcoros.h" +#include "lleventfilter.h" #include "lleventcoro.h" #include "../test/debug.h" +#include "../test/sync.h" using namespace llcoro; /***************************************************************************** -* from the banana.cpp example program borrowed for test<1>() -*****************************************************************************/ -namespace coroutines = boost::dcoroutines; -using coroutines::coroutine; - -template<typename Iter> -bool match(Iter first, Iter last, std::string match) { - std::string::iterator i = match.begin(); - for(; (first != last) && (i != match.end()); ++i) { - if (*first != *i) - return false; - ++first; - } - return i == match.end(); -} - -template<typename BidirectionalIterator> -BidirectionalIterator -match_substring(BidirectionalIterator begin, - BidirectionalIterator end, - std::string xmatch, - BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) { -//BidirectionalIterator begin_ = begin; - for(; begin != end; ++begin) - if(match(begin, end, xmatch)) { - self.yield(begin); - } - return end; -} - -typedef coroutine<std::string::iterator(void)> match_coroutine_type; - -/***************************************************************************** * Test helpers *****************************************************************************/ /// Simulate an event API whose response is immediate: sent on receipt of the @@ -131,8 +62,9 @@ typedef coroutine<std::string::iterator(void)> match_coroutine_type; class ImmediateAPI { public: - ImmediateAPI(): - mPump("immediate", true) + ImmediateAPI(Sync& sync): + mPump("immediate", true), + mSync(sync) { mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1)); } @@ -141,20 +73,18 @@ public: // Invoke this with an LLSD map containing: // ["value"]: Integer value. We will reply with ["value"] + 1. - // ["reply"]: Name of LLEventPump on which to send success response. - // ["error"]: Name of LLEventPump on which to send error response. - // ["fail"]: Presence of this key selects ["error"], else ["success"] as - // the name of the pump on which to send the response. + // ["reply"]: Name of LLEventPump on which to send response. bool operator()(const LLSD& event) const { + mSync.bump(); LLSD::Integer value(event["value"]); - LLSD::String replyPumpName(event.has("fail")? "error" : "reply"); - LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1); + LLEventPumps::instance().obtain(event["reply"]).post(value + 1); return false; } private: LLEventStream mPump; + Sync& mSync; }; /***************************************************************************** @@ -162,644 +92,247 @@ private: *****************************************************************************/ namespace tut { - struct coroutine_data {}; - typedef test_group<coroutine_data> coroutine_group; + struct test_data + { + Sync mSync; + ImmediateAPI immediateAPI{mSync}; + std::string replyName, errorName, threw, stringdata; + LLSD result, errordata; + int which; + LLTestApp testApp; + + void explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp); + void waitForEventOn1(); + void coroPump(); + void postAndWait1(); + void coroPumpPost(); + }; + typedef test_group<test_data> coroutine_group; typedef coroutine_group::object object; coroutine_group coroutinegrp("coroutine"); - template<> template<> - void object::test<1>() - { - set_test_name("From banana.cpp example program in Boost.Coroutine distro"); - std::string buffer = "banananana"; - std::string match = "nana"; - std::string::iterator begin = buffer.begin(); - std::string::iterator end = buffer.end(); - -#if defined(BOOST_CORO_POSIX_IMPL) -// std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n'; -#else -// std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl; -#endif - - typedef std::string::iterator signature(std::string::iterator, - std::string::iterator, - std::string, - match_coroutine_type::self&); - - coroutine<std::string::iterator(void)> matcher - (boost::bind(static_cast<signature*>(match_substring), - begin, - end, - match, - _1)); - - std::string::iterator i = matcher(); -/*==========================================================================*| - while(matcher && i != buffer.end()) { - std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n'; - i = matcher(); - } -|*==========================================================================*/ - size_t matches[] = { 2, 4, 6 }; - for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches)); - mi != mend; ++mi, i = matcher()) - { - ensure("more", matcher); - ensure("found", i != buffer.end()); - ensure_equals("value", std::distance(buffer.begin(), i), *mi); - } - ensure("done", ! matcher); - } - - // use static data so we can intersperse coroutine functions with the - // tests that engage them - ImmediateAPI immediateAPI; - std::string replyName, errorName, threw, stringdata; - LLSD result, errordata; - int which; - - // reinit vars at the start of each test - void clear() - { - replyName.clear(); - errorName.clear(); - threw.clear(); - stringdata.clear(); - result = LLSD(); - errordata = LLSD(); - which = 0; - } - - void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp) + void test_data::explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp) { BEGIN { + mSync.bump(); // The point of this test is to verify / illustrate suspending a // coroutine for something other than an LLEventPump. In other // words, this shows how to adapt to any async operation that // provides a callback-style notification (and prove that it // works). - LLCoros::Future<std::string> future; - // get the callback from that future - LLCoros::Future<std::string>::callback_t callback(future.make_callback()); - // Perhaps we would send a request to a remote server and arrange - // for 'callback' to be called on response. Of course that might - // involve an adapter object from the actual callback signature to - // the signature of 'callback' -- in this case, void(std::string). - // For test purposes, instead of handing 'callback' (or the + // for cbp->set_value() to be called on response. + // For test purposes, instead of handing 'callback' (or an // adapter) off to some I/O subsystem, we'll just pass it back to // our caller. - cbp.reset(new LLCoros::Future<std::string>::callback_t(callback)); + cbp = boost::make_shared<LLCoros::Promise<std::string>>(); + LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp); - ensure("Not yet", ! future); // calling get() on the future causes us to suspend debug("about to suspend"); stringdata = future.get(); - ensure("Got it", bool(future)); + mSync.bump(); + ensure_equals("Got it", stringdata, "received"); } END } template<> template<> - void object::test<2>() + void object::test<1>() { - clear(); set_test_name("explicit_wait"); DEBUG; // Construct the coroutine instance that will run explicit_wait. - boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond; - LLCoros::instance().launch("test<2>", - boost::bind(explicit_wait, boost::ref(respond))); + boost::shared_ptr<LLCoros::Promise<std::string>> respond; + LLCoros::instance().launch("test<1>", + [this, &respond](){ explicit_wait(respond); }); + mSync.bump(); // When the coroutine waits for the future, it returns here. debug("about to respond"); - // Now we're the I/O subsystem delivering a result. This immediately - // transfers control back to the coroutine. - (*respond)("received"); + // Now we're the I/O subsystem delivering a result. This should make + // the coroutine ready. + respond->set_value("received"); + // but give it a chance to wake up + mSync.yield(); // ensure the coroutine ran and woke up again with the intended result ensure_equals(stringdata, "received"); } - void waitForEventOn1() + void test_data::waitForEventOn1() { BEGIN { + mSync.bump(); result = suspendUntilEventOn("source"); + mSync.bump(); } END } template<> template<> - void object::test<3>() + void object::test<2>() { - clear(); set_test_name("waitForEventOn1"); DEBUG; - LLCoros::instance().launch("test<3>", waitForEventOn1); + LLCoros::instance().launch("test<2>", [this](){ waitForEventOn1(); }); + mSync.bump(); debug("about to send"); LLEventPumps::instance().obtain("source").post("received"); + // give waitForEventOn1() a chance to run + mSync.yield(); debug("back from send"); ensure_equals(result.asString(), "received"); } - void waitForEventOn2() - { - BEGIN - { - LLEventWithID pair = suspendUntilEventOn("reply", "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - template<> template<> - void object::test<4>() - { - clear(); - set_test_name("waitForEventOn2 reply"); - { - DEBUG; - LLCoros::instance().launch("test<4>", waitForEventOn2); - debug("about to send"); - LLEventPumps::instance().obtain("reply").post("received"); - debug("back from send"); - } - ensure_equals(result.asString(), "received"); - ensure_equals("which pump", which, 0); - } - - template<> template<> - void object::test<5>() - { - clear(); - set_test_name("waitForEventOn2 error"); - DEBUG; - LLCoros::instance().launch("test<5>", waitForEventOn2); - debug("about to send"); - LLEventPumps::instance().obtain("error").post("badness"); - debug("back from send"); - ensure_equals(result.asString(), "badness"); - ensure_equals("which pump", which, 1); - } - - void coroPump() + void test_data::coroPump() { BEGIN { + mSync.bump(); LLCoroEventPump waiter; replyName = waiter.getName(); result = waiter.suspend(); + mSync.bump(); } END } template<> template<> - void object::test<6>() + void object::test<3>() { - clear(); set_test_name("coroPump"); DEBUG; - LLCoros::instance().launch("test<6>", coroPump); + LLCoros::instance().launch("test<3>", [this](){ coroPump(); }); + mSync.bump(); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); + // give coroPump() a chance to run + mSync.yield(); debug("back from send"); ensure_equals(result.asString(), "received"); } - void coroPumps() - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - LLEventWithID pair(waiter.suspend()); - result = pair.first; - which = pair.second; - } - END - } - - template<> template<> - void object::test<7>() - { - clear(); - set_test_name("coroPumps reply"); - DEBUG; - LLCoros::instance().launch("test<7>", coroPumps); - debug("about to send"); - LLEventPumps::instance().obtain(replyName).post("received"); - debug("back from send"); - ensure_equals(result.asString(), "received"); - ensure_equals("which pump", which, 0); - } - - template<> template<> - void object::test<8>() - { - clear(); - set_test_name("coroPumps error"); - DEBUG; - LLCoros::instance().launch("test<8>", coroPumps); - debug("about to send"); - LLEventPumps::instance().obtain(errorName).post("badness"); - debug("back from send"); - ensure_equals(result.asString(), "badness"); - ensure_equals("which pump", which, 1); - } - - void coroPumpsNoEx() - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - result = waiter.suspendWithException(); - } - END - } - - template<> template<> - void object::test<9>() - { - clear(); - set_test_name("coroPumpsNoEx"); - DEBUG; - LLCoros::instance().launch("test<9>", coroPumpsNoEx); - debug("about to send"); - LLEventPumps::instance().obtain(replyName).post("received"); - debug("back from send"); - ensure_equals(result.asString(), "received"); - } - - void coroPumpsEx() - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - try - { - result = waiter.suspendWithException(); - debug("no exception"); - } - catch (const LLErrorEvent& e) - { - debug(STRINGIZE("exception " << e.what())); - errordata = e.getData(); - } - } - END - } - - template<> template<> - void object::test<10>() - { - clear(); - set_test_name("coroPumpsEx"); - DEBUG; - LLCoros::instance().launch("test<10>", coroPumpsEx); - debug("about to send"); - LLEventPumps::instance().obtain(errorName).post("badness"); - debug("back from send"); - ensure("no result", result.isUndefined()); - ensure_equals("got error", errordata.asString(), "badness"); - } - - void coroPumpsNoLog() - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - result = waiter.suspendWithLog(); - } - END - } - - template<> template<> - void object::test<11>() - { - clear(); - set_test_name("coroPumpsNoLog"); - DEBUG; - LLCoros::instance().launch("test<11>", coroPumpsNoLog); - debug("about to send"); - LLEventPumps::instance().obtain(replyName).post("received"); - debug("back from send"); - ensure_equals(result.asString(), "received"); - } - - void coroPumpsLog() - { - BEGIN - { - LLCoroEventPumps waiter; - replyName = waiter.getName0(); - errorName = waiter.getName1(); - WrapLLErrs capture; - try - { - result = waiter.suspendWithLog(); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } - } - END - } - - template<> template<> - void object::test<12>() - { - clear(); - set_test_name("coroPumpsLog"); - DEBUG; - LLCoros::instance().launch("test<12>", coroPumpsLog); - debug("about to send"); - LLEventPumps::instance().obtain(errorName).post("badness"); - debug("back from send"); - ensure("no result", result.isUndefined()); - ensure_contains("got error", threw, "badness"); - } - - void postAndWait1() + void test_data::postAndWait1() { BEGIN { + mSync.bump(); result = postAndSuspend(LLSDMap("value", 17), // request event immediateAPI.getPump(), // requestPump "reply1", // replyPump "reply"); // request["reply"] = name + mSync.bump(); } END } template<> template<> - void object::test<13>() + void object::test<4>() { - clear(); set_test_name("postAndWait1"); DEBUG; - LLCoros::instance().launch("test<13>", postAndWait1); + LLCoros::instance().launch("test<4>", [this](){ postAndWait1(); }); ensure_equals(result.asInteger(), 18); } - void postAndWait2() - { - BEGIN - { - LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18), - immediateAPI.getPump(), - "reply2", - "error2", - "reply", - "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - template<> template<> - void object::test<14>() - { - clear(); - set_test_name("postAndWait2"); - DEBUG; - LLCoros::instance().launch("test<14>", postAndWait2); - ensure_equals(result.asInteger(), 19); - ensure_equals(which, 0); - } - - void postAndWait2_1() - { - BEGIN - { - LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18)("fail", LLSD()), - immediateAPI.getPump(), - "reply2", - "error2", - "reply", - "error"); - result = pair.first; - which = pair.second; - debug(STRINGIZE("result = " << result << ", which = " << which)); - } - END - } - - template<> template<> - void object::test<15>() - { - clear(); - set_test_name("postAndWait2_1"); - DEBUG; - LLCoros::instance().launch("test<15>", postAndWait2_1); - ensure_equals(result.asInteger(), 19); - ensure_equals(which, 1); - } - - void coroPumpPost() + void test_data::coroPumpPost() { BEGIN { + mSync.bump(); LLCoroEventPump waiter; result = waiter.postAndSuspend(LLSDMap("value", 17), immediateAPI.getPump(), "reply"); + mSync.bump(); } END } template<> template<> - void object::test<16>() + void object::test<5>() { - clear(); set_test_name("coroPumpPost"); DEBUG; - LLCoros::instance().launch("test<16>", coroPumpPost); + LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); }); ensure_equals(result.asInteger(), 18); } - void coroPumpsPost() - { - BEGIN - { - LLCoroEventPumps waiter; - LLEventWithID pair(waiter.postAndSuspend(LLSDMap("value", 23), - immediateAPI.getPump(), "reply", "error")); - result = pair.first; - which = pair.second; - } - END - } - - template<> template<> - void object::test<17>() - { - clear(); - set_test_name("coroPumpsPost reply"); - DEBUG; - LLCoros::instance().launch("test<17>", coroPumpsPost); - ensure_equals(result.asInteger(), 24); - ensure_equals("which pump", which, 0); - } - - void coroPumpsPost_1() - { - BEGIN - { - LLCoroEventPumps waiter; - LLEventWithID pair( - waiter.postAndSuspend(LLSDMap("value", 23)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error")); - result = pair.first; - which = pair.second; - } - END - } - - template<> template<> - void object::test<18>() - { - clear(); - set_test_name("coroPumpsPost error"); - DEBUG; - LLCoros::instance().launch("test<18>", coroPumpsPost_1); - ensure_equals(result.asInteger(), 24); - ensure_equals("which pump", which, 1); - } - - void coroPumpsPostNoEx() - { - BEGIN - { - LLCoroEventPumps waiter; - result = waiter.postAndSuspendWithException(LLSDMap("value", 8), - immediateAPI.getPump(), "reply", "error"); - } - END - } - - template<> template<> - void object::test<19>() - { - clear(); - set_test_name("coroPumpsPostNoEx"); - DEBUG; - LLCoros::instance().launch("test<19>", coroPumpsPostNoEx); - ensure_equals(result.asInteger(), 9); - } - - void coroPumpsPostEx() - { - BEGIN + template <class PUMP> + void test() + { + PUMP pump(typeid(PUMP).name()); + bool running{false}; + LLSD data{LLSD::emptyArray()}; + // start things off by posting once before even starting the listener + // coro + LL_DEBUGS() << "test() posting first" << LL_ENDL; + LLSD first{LLSDMap("desc", "first")("value", 0)}; + bool consumed = pump.post(first); + ensure("should not have consumed first", ! consumed); + // now launch the coro + LL_DEBUGS() << "test() launching listener coro" << LL_ENDL; + running = true; + LLCoros::instance().launch( + "listener", + [&pump, &running, &data](){ + // important for this test that we consume posted values + LLCoros::instance().set_consuming(true); + // should immediately retrieve 'first' without waiting + LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL; + data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); + // Don't use ensure() from within the coro -- ensure() failure + // throws tut::fail, which won't propagate out to the main + // test driver, which will result in an odd failure. + // Wait for 'second' because it's not already pending. + LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL; + data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); + // and wait for 'third', which should involve no further waiting + LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL; + data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); + LL_DEBUGS() << "listener coro done" << LL_ENDL; + running = false; + }); + // back from coro at the point where it's waiting for 'second' + LL_DEBUGS() << "test() posting second" << LL_ENDL; + LLSD second{llsd::map("desc", "second", "value", 1)}; + consumed = pump.post(second); + ensure("should have consumed second", consumed); + // This is a key point: even though we've post()ed the value for which + // the coroutine is waiting, it's actually still suspended until we + // pause for some other reason. The coroutine will only pick up one + // value at a time from our 'pump'. It's important to exercise the + // case when we post() two values before it picks up either. + LL_DEBUGS() << "test() posting third" << LL_ENDL; + LLSD third{llsd::map("desc", "third", "value", 2)}; + consumed = pump.post(third); + ensure("should NOT yet have consumed third", ! consumed); + // now just wait for coro to finish -- which it eventually will, given + // that all its suspend calls have short timeouts. + while (running) { - LLCoroEventPumps waiter; - try - { - result = waiter.postAndSuspendWithException( - LLSDMap("value", 9)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error"); - debug("no exception"); - } - catch (const LLErrorEvent& e) - { - debug(STRINGIZE("exception " << e.what())); - errordata = e.getData(); - } + LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL; + llcoro::suspendUntilTimeout(0.1); } - END + // okay, verify expected results + ensure_equals("should have received three values", data, + llsd::array(first, second, third)); + LL_DEBUGS() << "test() done" << LL_ENDL; } template<> template<> - void object::test<20>() - { - clear(); - set_test_name("coroPumpsPostEx"); - DEBUG; - LLCoros::instance().launch("test<20>", coroPumpsPostEx); - ensure("no result", result.isUndefined()); - ensure_equals("got error", errordata.asInteger(), 10); - } - - void coroPumpsPostNoLog() - { - BEGIN - { - LLCoroEventPumps waiter; - result = waiter.postAndSuspendWithLog(LLSDMap("value", 30), - immediateAPI.getPump(), "reply", "error"); - } - END - } - - template<> template<> - void object::test<21>() - { - clear(); - set_test_name("coroPumpsPostNoLog"); - DEBUG; - LLCoros::instance().launch("test<21>", coroPumpsPostNoLog); - ensure_equals(result.asInteger(), 31); - } - - void coroPumpsPostLog() + void object::test<6>() { - BEGIN - { - LLCoroEventPumps waiter; - WrapLLErrs capture; - try - { - result = waiter.postAndSuspendWithLog( - LLSDMap("value", 31)("fail", LLSD()), - immediateAPI.getPump(), "reply", "error"); - debug("no exception"); - } - catch (const WrapLLErrs::FatalException& e) - { - debug(STRINGIZE("exception " << e.what())); - threw = e.what(); - } - } - END + set_test_name("LLEventMailDrop"); + tut::test<LLEventMailDrop>(); } template<> template<> - void object::test<22>() + void object::test<7>() { - clear(); - set_test_name("coroPumpsPostLog"); - DEBUG; - LLCoros::instance().launch("test<22>", coroPumpsPostLog); - ensure("no result", result.isUndefined()); - ensure_contains("got error", threw, "32"); + set_test_name("LLEventLogProxyFor<LLEventMailDrop>"); + tut::test< LLEventLogProxyFor<LLEventMailDrop> >(); } } - -/*==========================================================================*| -#include <boost/context/guarded_stack_allocator.hpp> - -namespace tut -{ - template<> template<> - void object::test<23>() - { - set_test_name("stacksize"); - std::cout << "default_stacksize: " << boost::context::guarded_stack_allocator::default_stacksize() << '\n'; - } -} // namespace tut -|*==========================================================================*/ diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 5a4df81bf1..9da1ecfd67 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -22,6 +22,8 @@ #include "llsdutil.h" #include "stringize.h" #include "tests/wrapllerrs.h" +#include "../test/catch_and_store_what_in.h" +#include "../test/debug.h" #include <map> #include <string> @@ -45,15 +47,6 @@ using boost::lambda::var; using namespace llsd; /***************************************************************************** -* Output control -*****************************************************************************/ -#ifdef DEBUG_ON -using std::cout; -#else -static std::ostringstream cout; -#endif - -/***************************************************************************** * Example data, functions, classes *****************************************************************************/ // We don't need a whole lot of different arbitrary-params methods, just (no | @@ -154,13 +147,13 @@ struct Vars /*------------- no-args (non-const, const, static) methods -------------*/ void method0() { - cout << "method0()\n"; + debug()("method0()"); i = 17; } void cmethod0() const { - cout << 'c'; + debug()('c', NONL); const_cast<Vars*>(this)->method0(); } @@ -169,13 +162,13 @@ struct Vars /*------------ Callable (non-const, const, static) methods -------------*/ void method1(const LLSD& obj) { - cout << "method1(" << obj << ")\n"; + debug()("method1(", obj, ")"); llsd = obj; } void cmethod1(const LLSD& obj) const { - cout << 'c'; + debug()('c', NONL); const_cast<Vars*>(this)->method1(obj); } @@ -195,12 +188,12 @@ struct Vars else vcp = std::string("'") + cp + "'"; - cout << "methodna(" << b - << ", " << i - << ", " << f - << ", " << d - << ", " << vcp - << ")\n"; + debug()("methodna(", b, + ", ", i, + ", ", f, + ", ", d, + ", ", vcp, + ")"); this->b = b; this->i = i; @@ -217,12 +210,12 @@ struct Vars vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte); } - cout << "methodnb(" << "'" << s << "'" - << ", " << uuid - << ", " << date - << ", '" << uri << "'" - << ", " << vbin.str() - << ")\n"; + debug()("methodnb(", "'", s, "'", + ", ", uuid, + ", ", date, + ", '", uri, "'", + ", ", vbin.str(), + ")"); this->s = s; this->uuid = uuid; @@ -233,18 +226,30 @@ struct Vars void cmethodna(NPARAMSa) const { - cout << 'c'; + debug()('c', NONL); const_cast<Vars*>(this)->methodna(NARGSa); } void cmethodnb(NPARAMSb) const { - cout << 'c'; + debug()('c', NONL); const_cast<Vars*>(this)->methodnb(NARGSb); } static void smethodna(NPARAMSa); static void smethodnb(NPARAMSb); + + static Debug& debug() + { + // Lazily initialize this Debug instance so it can notice if main() + // has forcibly set LOGTEST. If it were simply a static member, it + // would already have examined the environment variable by the time + // main() gets around to checking command-line switches. Since we have + // a global static Vars instance, the same would be true of a plain + // non-static member. + static Debug sDebug("Vars"); + return sDebug; + } }; /*------- Global Vars instance for free functions and static methods -------*/ static Vars g; @@ -252,25 +257,25 @@ static Vars g; /*------------ Static Vars method implementations reference 'g' ------------*/ void Vars::smethod0() { - cout << "smethod0() -> "; + debug()("smethod0() -> ", NONL); g.method0(); } void Vars::smethod1(const LLSD& obj) { - cout << "smethod1(" << obj << ") -> "; + debug()("smethod1(", obj, ") -> ", NONL); g.method1(obj); } void Vars::smethodna(NPARAMSa) { - cout << "smethodna(...) -> "; + debug()("smethodna(...) -> ", NONL); g.methodna(NARGSa); } void Vars::smethodnb(NPARAMSb) { - cout << "smethodnb(...) -> "; + debug()("smethodnb(...) -> ", NONL); g.methodnb(NARGSb); } @@ -283,25 +288,25 @@ void clear() /*------------------- Free functions also reference 'g' --------------------*/ void free0() { - cout << "free0() -> "; + g.debug()("free0() -> ", NONL); g.method0(); } void free1(const LLSD& obj) { - cout << "free1(" << obj << ") -> "; + g.debug()("free1(", obj, ") -> ", NONL); g.method1(obj); } void freena(NPARAMSa) { - cout << "freena(...) -> "; + g.debug()("freena(...) -> ", NONL); g.methodna(NARGSa); } void freenb(NPARAMSb) { - cout << "freenb(...) -> "; + g.debug()("freenb(...) -> ", NONL); g.methodnb(NARGSb); } @@ -312,6 +317,7 @@ namespace tut { struct lleventdispatcher_data { + Debug debug{"test"}; WrapLLErrs redirect; Dispatcher work; Vars v; @@ -430,12 +436,17 @@ namespace tut // Same for freenb() et al. params = LLSDMap("a", LLSDArray("b")("i")("f")("d")("cp")) ("b", LLSDArray("s")("uuid")("date")("uri")("bin")); - cout << "params:\n" << params << "\nparams[\"a\"]:\n" << params["a"] << "\nparams[\"b\"]:\n" << params["b"] << std::endl; + debug("params:\n", + params, "\n" + "params[\"a\"]:\n", + params["a"], "\n" + "params[\"b\"]:\n", + params["b"]); // default LLSD::Binary value std::vector<U8> binary; for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11) { - binary.push_back(h); + binary.push_back((U8)h); } // Full defaults arrays. We actually don't care what the LLUUID or // LLDate values are, as long as they're different from the @@ -447,7 +458,8 @@ namespace tut (LLDate::now()) (LLURI("http://www.ietf.org/rfc/rfc3986.txt")) (binary)); - cout << "dft_array_full:\n" << dft_array_full << std::endl; + debug("dft_array_full:\n", + dft_array_full); // Partial defaults arrays. foreach(LLSD::String a, ab) { @@ -456,7 +468,8 @@ namespace tut llsd_copy_array(dft_array_full[a].beginArray() + partition, dft_array_full[a].endArray()); } - cout << "dft_array_partial:\n" << dft_array_partial << std::endl; + debug("dft_array_partial:\n", + dft_array_partial); foreach(LLSD::String a, ab) { @@ -472,7 +485,10 @@ namespace tut dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix]; } } - cout << "dft_map_full:\n" << dft_map_full << "\ndft_map_partial:\n" << dft_map_partial << '\n'; + debug("dft_map_full:\n", + dft_map_full, "\n" + "dft_map_partial:\n", + dft_map_partial); // (Free function | static method) with (no | arbitrary) params, // map style, no (empty array) defaults @@ -630,16 +646,9 @@ namespace tut void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string threw; - try - { - work(func, args); - } - catch (const std::runtime_error& e) - { - cout << "*** " << e.what() << '\n'; - threw = e.what(); - } + std::string threw = catch_what<std::runtime_error>([this, &func, &args](){ + work(func, args); + }); ensure_has(threw, exc_frag); } @@ -717,15 +726,9 @@ namespace tut LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); foreach(LLSD ae, inArray(attempts)) { - std::string threw; - try - { - work.add("freena_err", "freena", freena, ae); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what<std::exception>([this, &ae](){ + work.add("freena_err", "freena", freena, ae); + }); ensure_has(threw, "must be an array"); } } @@ -734,15 +737,9 @@ namespace tut void object::test<2>() { set_test_name("map-style registration with badly-formed defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what<std::exception>([this](){ + work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17); + }); ensure_has(threw, "must be a map or an array"); } @@ -750,17 +747,11 @@ namespace tut void object::test<3>() { set_test_name("map-style registration with too many array defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, - LLSDArray("a")("b"), - LLSDArray(17)(0.9)("gack")); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what<std::exception>([this](){ + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDArray(17)(0.9)("gack")); + }); ensure_has(threw, "shorter than"); } @@ -768,17 +759,11 @@ namespace tut void object::test<4>() { set_test_name("map-style registration with too many map defaults"); - std::string threw; - try - { - work.add("freena_err", "freena", freena, - LLSDArray("a")("b"), - LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); - } - catch (const std::exception& e) - { - threw = e.what(); - } + std::string threw = catch_what<std::exception>([this](){ + work.add("freena_err", "freena", freena, + LLSDArray("a")("b"), + LLSDMap("b", 17)("foo", 3.14)("bar", "sinister")); + }); ensure_has(threw, "nonexistent params"); ensure_has(threw, "foo"); ensure_has(threw, "bar"); @@ -948,7 +933,12 @@ namespace tut params[a].endArray()), dft_array_partial[a]); } - cout << "allreq:\n" << allreq << "\nleftreq:\n" << leftreq << "\nrightdft:\n" << rightdft << std::endl; + debug("allreq:\n", + allreq, "\n" + "leftreq:\n", + leftreq, "\n" + "rightdft:\n", + rightdft); // Generate maps containing parameter names not provided by the // dft_map_partial maps. @@ -960,7 +950,8 @@ namespace tut skipreq[a].erase(me.first); } } - cout << "skipreq:\n" << skipreq << std::endl; + debug("skipreq:\n", + skipreq); LLSD groups(LLSDArray // array of groups @@ -1005,7 +996,11 @@ namespace tut LLSD names(grp[0]); LLSD required(grp[1][0]); LLSD optional(grp[1][1]); - cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; + debug("For ", names, ",\n", + "required:\n", + required, "\n" + "optional:\n", + optional); // Loop through 'names' foreach(LLSD nm, inArray(names)) @@ -1039,16 +1034,9 @@ namespace tut // We don't have a comparable helper function for the one-arg // operator() method, and it's not worth building one just for this // case. Write it out. - std::string threw; - try - { - work(LLSDMap("op", "freek")); - } - catch (const std::runtime_error& e) - { - cout << "*** " << e.what() << "\n"; - threw = e.what(); - } + std::string threw = catch_what<std::runtime_error>([this](){ + work(LLSDMap("op", "freek")); + }); ensure_has(threw, "bad"); ensure_has(threw, "op"); ensure_has(threw, "freek"); @@ -1182,7 +1170,7 @@ namespace tut std::vector<U8> binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { - binary.push_back(h); + binary.push_back((U8)h); } LLSD args(LLSDMap("a", LLSDArray(true)(17)(3.14)(123.456)("char*")) ("b", LLSDArray("string") @@ -1200,7 +1188,7 @@ namespace tut } // Adjust expect["a"]["cp"] for special Vars::cp treatment. expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; - cout << "expect: " << expect << '\n'; + debug("expect: ", expect); // Use substantially the same logic for args and argsplus LLSD argsarrays(LLSDArray(args)(argsplus)); @@ -1255,7 +1243,8 @@ namespace tut { array_overfull[a].append("bogus"); } - cout << "array_full: " << array_full << "\narray_overfull: " << array_overfull << std::endl; + debug("array_full: ", array_full, "\n" + "array_overfull: ", array_overfull); // We rather hope that LLDate::now() will generate a timestamp // distinct from the one it generated in the constructor, moments ago. ensure_not_equals("Timestamps too close", @@ -1270,7 +1259,8 @@ namespace tut map_overfull[a] = map_full[a]; map_overfull[a]["extra"] = "ignore"; } - cout << "map_full: " << map_full << "\nmap_overfull: " << map_overfull << std::endl; + debug("map_full: ", map_full, "\n" + "map_overfull: ", map_overfull); LLSD expect(map_full); // Twiddle the const char* param. expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; @@ -1285,7 +1275,7 @@ namespace tut // so won't bother returning it. Predict that behavior to match the // LLSD values. expect["a"].erase("b"); - cout << "expect: " << expect << std::endl; + debug("expect: ", expect); // For this test, calling functions registered with different sets of // parameter defaults should make NO DIFFERENCE WHATSOEVER. Every call // should pass all params. diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index eb98b12ef5..fa2cb03e95 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -36,9 +36,12 @@ // other Linden headers #include "../test/lltut.h" #include "stringize.h" +#include "llsdutil.h" #include "listener.h" #include "tests/wrapllerrs.h" +#include <typeinfo> + /***************************************************************************** * Test classes *****************************************************************************/ @@ -350,15 +353,9 @@ namespace tut // Now let the timer expire. filter.forceTimeout(); // Notice the timeout. - std::string threw; - try - { - mainloop.post(17); - } - catch (const WrapLLErrs::FatalException& e) - { - threw = e.what(); - } + std::string threw = capture.catch_llerrs([this](){ + mainloop.post(17); + }); ensure_contains("errorAfter() timeout exception", threw, "timeout"); // Timing out cancels the timer. Verify that. listener0.reset(0); @@ -407,6 +404,78 @@ namespace tut throttle.post(";17"); ensure_equals("17", cat.result, "136;12;17"); // "17" delivered } + + template<class PUMP> + void test() + { + PUMP pump(typeid(PUMP).name()); + LLSD data{LLSD::emptyArray()}; + bool consumed{true}; + // listener that appends to 'data' + // but that also returns the current value of 'consumed' + // Instantiate this separately because we're going to listen() + // multiple times with the same lambda: LLEventMailDrop only replays + // queued events on a new listen() call. + auto lambda = + [&data, &consumed](const LLSD& event)->bool + { + data.append(event); + return consumed; + }; + { + LLTempBoundListener conn = pump.listen("lambda", lambda); + pump.post("first"); + } + // first post() should certainly be received by listener + ensure_equals("first", data, llsd::array("first")); + // the question is, since consumed was true, did it queue the value? + data = LLSD::emptyArray(); + { + // if it queued the value, it would be delivered on subsequent + // listen() call + LLTempBoundListener conn = pump.listen("lambda", lambda); + } + ensure_equals("empty1", data, LLSD::emptyArray()); + data = LLSD::emptyArray(); + // now let's NOT consume the posted data + consumed = false; + { + LLTempBoundListener conn = pump.listen("lambda", lambda); + pump.post("second"); + pump.post("third"); + } + // the two events still arrive + ensure_equals("second,third1", data, llsd::array("second", "third")); + data = LLSD::emptyArray(); + { + // when we reconnect, these should be delivered again + // but this time they should be consumed + consumed = true; + LLTempBoundListener conn = pump.listen("lambda", lambda); + } + // unconsumed events were delivered again + ensure_equals("second,third2", data, llsd::array("second", "third")); + data = LLSD::emptyArray(); + { + // when we reconnect this time, no more unconsumed events + LLTempBoundListener conn = pump.listen("lambda", lambda); + } + ensure_equals("empty2", data, LLSD::emptyArray()); + } + + template<> template<> + void filter_object::test<6>() + { + set_test_name("LLEventMailDrop"); + tut::test<LLEventMailDrop>(); + } + + template<> template<> + void filter_object::test<7>() + { + set_test_name("LLEventLogProxyFor<LLEventMailDrop>"); + tut::test< LLEventLogProxyFor<LLEventMailDrop> >(); + } } // namespace tut /***************************************************************************** diff --git a/indra/llcommon/tests/llexception_test.cpp b/indra/llcommon/tests/llexception_test.cpp index 6bee1943c2..8ddf636cd1 100644 --- a/indra/llcommon/tests/llexception_test.cpp +++ b/indra/llcommon/tests/llexception_test.cpp @@ -305,4 +305,19 @@ namespace tut std::cout << center("int", '=', margin) << std::endl; catch_several(throw_int, "throw_int"); } + + template<> template<> + void object::test<2>() + { + set_test_name("reporting exceptions"); + + try + { + LLTHROW(LLException("badness")); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("llexception test<2>()"); + } + } } // namespace tut diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index c7d4b8a06b..9b89159625 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -41,7 +41,6 @@ #include <boost/scoped_ptr.hpp> // other Linden headers #include "../test/lltut.h" -#include "wrapllerrs.h" struct Badness: public std::runtime_error { @@ -112,24 +111,22 @@ namespace tut void object::test<2>() { ensure_equals(Unkeyed::instanceCount(), 0); - Unkeyed* dangling = NULL; + std::weak_ptr<Unkeyed> dangling; { Unkeyed one; ensure_equals(Unkeyed::instanceCount(), 1); - Unkeyed* found = Unkeyed::getInstance(&one); - ensure_equals(found, &one); + std::weak_ptr<Unkeyed> found = one.getWeak(); + ensure(! found.expired()); { boost::scoped_ptr<Unkeyed> two(new Unkeyed); ensure_equals(Unkeyed::instanceCount(), 2); - Unkeyed* found = Unkeyed::getInstance(two.get()); - ensure_equals(found, two.get()); } ensure_equals(Unkeyed::instanceCount(), 1); - // store an unwise pointer to a temp Unkeyed instance - dangling = &one; + // store a weak pointer to a temp Unkeyed instance + dangling = found; } // make that instance vanish // check the now-invalid pointer to the destroyed instance - ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling)); + ensure("weak_ptr<Unkeyed> failed to track destruction", dangling.expired()); ensure_equals(Unkeyed::instanceCount(), 0); } @@ -142,7 +139,8 @@ namespace tut // reimplement LLInstanceTracker using, say, a hash map instead of a // std::map. We DO insist that every key appear exactly once. typedef std::vector<std::string> StringVector; - StringVector keys(Keyed::beginKeys(), Keyed::endKeys()); + auto snap = Keyed::key_snapshot(); + StringVector keys(snap.begin(), snap.end()); std::sort(keys.begin(), keys.end()); StringVector::const_iterator ki(keys.begin()); ensure_equals(*ki++, "one"); @@ -153,17 +151,15 @@ namespace tut ensure("didn't reach end", ki == keys.end()); // Use a somewhat different approach to order independence with - // beginInstances(): explicitly capture the instances we know in a + // instance_snapshot(): explicitly capture the instances we know in a // set, and delete them as we iterate through. typedef std::set<Keyed*> InstanceSet; InstanceSet instances; instances.insert(&one); instances.insert(&two); instances.insert(&three); - for (Keyed::instance_iter ii(Keyed::beginInstances()), iend(Keyed::endInstances()); - ii != iend; ++ii) + for (auto& ref : Keyed::instance_snapshot()) { - Keyed& ref = *ii; ensure_equals("spurious instance", instances.erase(&ref), 1); } ensure_equals("unreported instance", instances.size(), 0); @@ -180,11 +176,10 @@ namespace tut instances.insert(&two); instances.insert(&three); - for (Unkeyed::instance_iter ii(Unkeyed::beginInstances()), iend(Unkeyed::endInstances()); ii != iend; ++ii) - { - Unkeyed& ref = *ii; - ensure_equals("spurious instance", instances.erase(&ref), 1); - } + for (auto& ref : Unkeyed::instance_snapshot()) + { + ensure_equals("spurious instance", instances.erase(&ref), 1); + } ensure_equals("unreported instance", instances.size(), 0); } @@ -192,64 +187,49 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("delete Keyed with outstanding instance_iter"); - std::string what; - Keyed* keyed = new Keyed("delete Keyed with outstanding instance_iter"); - { - WrapLLErrs wrapper; - Keyed::instance_iter i(Keyed::beginInstances()); - try - { - delete keyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } - } - ensure(! what.empty()); + std::string desc("delete Keyed with outstanding instance_snapshot"); + set_test_name(desc); + Keyed* keyed = new Keyed(desc); + // capture a snapshot but do not yet traverse it + auto snapshot = Keyed::instance_snapshot(); + // delete the one instance + delete keyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> void object::test<6>() { - set_test_name("delete Keyed with outstanding key_iter"); - std::string what; - Keyed* keyed = new Keyed("delete Keyed with outstanding key_it"); - { - WrapLLErrs wrapper; - Keyed::key_iter i(Keyed::beginKeys()); - try - { - delete keyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } - } - ensure(! what.empty()); + std::string desc("delete Keyed with outstanding key_snapshot"); + set_test_name(desc); + Keyed* keyed = new Keyed(desc); + // capture a snapshot but do not yet traverse it + auto snapshot = Keyed::key_snapshot(); + // delete the one instance + delete keyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> void object::test<7>() { - set_test_name("delete Unkeyed with outstanding instance_iter"); + set_test_name("delete Unkeyed with outstanding instance_snapshot"); std::string what; Unkeyed* unkeyed = new Unkeyed; - { - WrapLLErrs wrapper; - Unkeyed::instance_iter i(Unkeyed::beginInstances()); - try - { - delete unkeyed; - } - catch (const WrapLLErrs::FatalException& e) - { - what = e.what(); - } - } - ensure(! what.empty()); + // capture a snapshot but do not yet traverse it + auto snapshot = Unkeyed::instance_snapshot(); + // delete the one instance + delete unkeyed; + // traversing the snapshot should reflect the deletion + // avoid ensure_equals() because it requires the ability to stream the + // two values to std::ostream + ensure(snapshot.begin() == snapshot.end()); } template<> template<> @@ -261,11 +241,9 @@ namespace tut // We can't use the iterator-range InstanceSet constructor because // beginInstances() returns an iterator that dereferences to an // Unkeyed&, not an Unkeyed*. - for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), - ukend(Unkeyed::endInstances()); - uki != ukend; ++uki) + for (auto& ref : Unkeyed::instance_snapshot()) { - existing.insert(&*uki); + existing.insert(&ref); } try { @@ -288,11 +266,9 @@ namespace tut // instances was also present in the original set. If that's not true, // it's because our new Unkeyed ended up in the updated set despite // its constructor exception. - for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), - ukend(Unkeyed::endInstances()); - uki != ukend; ++uki) + for (auto& ref : Unkeyed::instance_snapshot()) { - ensure("failed to remove instance", existing.find(&*uki) != existing.end()); + ensure("failed to remove instance", existing.find(&ref) != existing.end()); } } } // namespace tut diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp index 32a717f4fc..542306ee22 100644 --- a/indra/llcommon/tests/lllazy_test.cpp +++ b/indra/llcommon/tests/lllazy_test.cpp @@ -38,6 +38,7 @@ #include <boost/lambda/bind.hpp> // other Linden headers #include "../test/lltut.h" +#include "../test/catch_and_store_what_in.h" namespace bll = boost::lambda; @@ -200,15 +201,9 @@ namespace tut void lllazy_object::test<2>() { TestNeedsTesting tnt; - std::string threw; - try - { - tnt.toolate(); - } - catch (const LLLazyCommon::InstanceChange& e) - { - threw = e.what(); - } + std::string threw = catch_what<LLLazyCommon::InstanceChange>([&tnt](){ + tnt.toolate(); + }); ensure_contains("InstanceChange exception", threw, "replace LLLazy instance"); } diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 45648536c4..9d71e327d8 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -23,7 +23,7 @@ #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h" // CaptureLog #include "llevents.h" #include "llprocess.h" #include "llstring.h" @@ -49,24 +49,28 @@ const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte #endif -void waitfor(const std::vector<LLLeap*>& instances, int timeout=60) +// capture std::weak_ptrs to LLLeap instances so we can tell when they expire +typedef std::vector<std::weak_ptr<LLLeap>> LLLeapVector; + +void waitfor(const LLLeapVector& instances, int timeout=60) { int i; for (i = 0; i < timeout; ++i) { // Every iteration, test whether any of the passed LLLeap instances // still exist (are still running). - std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end()); - for ( ; vli != vlend; ++vli) + bool found = false; + for (auto& ptr : instances) { - // getInstance() returns NULL if it's terminated/gone, non-NULL if - // it's still running - if (LLLeap::getInstance(*vli)) + if (! ptr.expired()) + { + found = true; break; + } } // If we made it through all of 'instances' without finding one that's // still running, we're done. - if (vli == vlend) + if (! found) { /*==========================================================================*| std::cout << instances.size() << " LLLeap instances terminated in " @@ -86,8 +90,8 @@ void waitfor(const std::vector<LLLeap*>& instances, int timeout=60) void waitfor(LLLeap* instance, int timeout=60) { - std::vector<LLLeap*> instances; - instances.push_back(instance); + LLLeapVector instances; + instances.push_back(instance->getWeak()); waitfor(instances, timeout); } @@ -218,11 +222,11 @@ namespace tut NamedTempFile script("py", "import time\n" "time.sleep(1)\n"); - std::vector<LLLeap*> instances; + LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + sv(list_of(PYTHON)(script.getName())))->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + sv(list_of(PYTHON)(script.getName())))->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other // way. Wait for them to terminate. @@ -290,12 +294,9 @@ namespace tut void object::test<6>() { set_test_name("empty plugin vector"); - std::string threw; - try - { - LLLeap::create("empty", StringVec()); - } - CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + std::string threw = catch_what<LLLeap::Error>([](){ + LLLeap::create("empty", StringVec()); + }); ensure_contains("LLLeap::Error", threw, "no plugin"); // try the suppress-exception variant ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false)); @@ -308,12 +309,9 @@ namespace tut // Synthesize bogus executable name std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x"); CaptureLog log; - std::string threw; - try - { - LLLeap::create("bad exe", BADPYTHON); - } - CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + std::string threw = catch_what<LLLeap::Error>([&BADPYTHON](){ + LLLeap::create("bad exe", BADPYTHON); + }); ensure_contains("LLLeap::create() didn't throw", threw, "failed"); log.messageWith("failed"); log.messageWith(BADPYTHON); diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp new file mode 100644 index 0000000000..69b11ccafb --- /dev/null +++ b/indra/llcommon/tests/llmainthreadtask_test.cpp @@ -0,0 +1,137 @@ +/** + * @file llmainthreadtask_test.cpp + * @author Nat Goodspeed + * @date 2019-12-05 + * @brief Test for llmainthreadtask. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llmainthreadtask.h" +// STL headers +// std headers +#include <atomic> +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "../test/sync.h" +#include "llthread.h" // on_main_thread() +#include "lleventtimer.h" +#include "lockstatic.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llmainthreadtask_data + { + // 5-second timeout + Sync mSync{F32Milliseconds(5000.0f)}; + + llmainthreadtask_data() + { + // we're not testing the result; this is just to cache the + // initial thread as the main thread. + on_main_thread(); + } + }; + typedef test_group<llmainthreadtask_data> llmainthreadtask_group; + typedef llmainthreadtask_group::object object; + llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask"); + + template<> template<> + void object::test<1>() + { + set_test_name("inline"); + bool ran = false; + bool result = LLMainThreadTask::dispatch( + [&ran]()->bool{ + ran = true; + return true; + }); + ensure("didn't run lambda", ran); + ensure("didn't return result", result); + } + + struct StaticData + { + std::mutex mMutex; // LockStatic looks for mMutex + bool ran{false}; + }; + typedef llthread::LockStatic<StaticData> LockStatic; + + template<> template<> + void object::test<2>() + { + set_test_name("cross-thread"); + skip("This test is prone to build-time hangs"); + std::atomic_bool result(false); + // wrapping our thread lambda in a packaged_task will catch any + // exceptions it might throw and deliver them via future + std::packaged_task<void()> thread_work( + [this, &result](){ + // unblock test<2>()'s yield_until(1) + mSync.set(1); + // dispatch work to main thread -- should block here + bool on_main( + LLMainThreadTask::dispatch( + []()->bool{ + // have to lock static mutex to set static data + LockStatic()->ran = true; + // indicate whether task was run on the main thread + return on_main_thread(); + })); + // wait for test<2>() to unblock us again + mSync.yield_until(3); + result = on_main; + }); + auto thread_result = thread_work.get_future(); + std::thread thread; + try + { + // run thread_work + thread = std::thread(std::move(thread_work)); + // wait for thread to set(1) + mSync.yield_until(1); + // try to acquire the lock, should block because thread has it + LockStatic lk; + // wake up when dispatch() unlocks the static mutex + ensure("shouldn't have run yet", !lk->ran); + ensure("shouldn't have returned yet", !result); + // unlock so the task can acquire the lock + lk.unlock(); + // run the task -- should unblock thread, which will immediately block + // on mSync + LLEventTimer::updateClass(); + // 'lk', having unlocked, can no longer be used to access; relock with + // a new LockStatic instance + ensure("should now have run", LockStatic()->ran); + ensure("returned too early", !result); + // okay, let thread perform the assignment + mSync.set(3); + } + catch (...) + { + // A test failure exception anywhere in the try block can cause + // the test program to terminate without explanation when + // ~thread() finds that 'thread' is still joinable. We could + // either join() or detach() it -- but since it might be blocked + // waiting for something from the main thread that now can never + // happen, it's safer to detach it. + thread.detach(); + throw; + } + // 'thread' should be all done now + thread.join(); + // deliver any exception thrown by thread_work + thread_result.get(); + ensure("ran changed", LockStatic()->ran); + ensure("didn't run on main thread", result); + } +} // namespace tut diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 5c87cdabd9..f0eafa8201 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -25,8 +25,6 @@ #include <boost/function.hpp> #include <boost/algorithm/string/find_iterator.hpp> #include <boost/algorithm/string/finder.hpp> -//#include <boost/lambda/lambda.hpp> -//#include <boost/lambda/bind.hpp> // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -35,7 +33,7 @@ #include "llsdutil.h" #include "llevents.h" #include "llstring.h" -#include "wrapllerrs.h" +#include "wrapllerrs.h" // CaptureLog #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -45,8 +43,7 @@ #include <sys/wait.h> #endif -//namespace lambda = boost::lambda; - std::string apr_strerror_helper(apr_status_t rv) +std::string apr_strerror_helper(apr_status_t rv) { char errbuf[256]; apr_strerror(rv, errbuf, sizeof(errbuf)); @@ -496,14 +493,18 @@ namespace tut } // std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE); - ensure_equals_(wi.why, APR_PROC_EXIT); - ensure_equals_(wi.rc, 0); // Beyond merely executing all the above successfully, verify that we // obtained expected output -- and that we duly got control while // waiting, proving the non-blocking nature of these pipes. try { + // Perform these ensure_equals_() within this try/catch so that if + // we don't get expected results, we'll dump whatever we did get + // to help diagnose. + ensure_equals_(wi.why, APR_PROC_EXIT); + ensure_equals_(wi.rc, 0); + unsigned i = 0; ensure("blocking I/O on child pipe (0)", history[i].tries); ensure_equals_(history[i].which, "out"); @@ -960,12 +961,9 @@ namespace tut #define CATCH_IN(THREW, EXCEPTION, CODE) \ do \ { \ - (THREW).clear(); \ - try \ - { \ - CODE; \ - } \ - CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION) \ + (THREW) = catch_what<EXCEPTION>([&](){ \ + CODE; \ + }); \ ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \ } while (0) diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 6ac974e659..642c1c3879 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -271,10 +271,10 @@ namespace tut LLSD w; mParser->reset(); // reset() call is needed since test code re-uses mParser mParser->parse(stream, w, stream.str().size()); - + try { - ensure_equals(msg.c_str(), w, v); + ensure_equals(msg, w, v); } catch (...) { @@ -432,6 +432,7 @@ namespace tut const char source[] = "it must be a blue moon again"; std::vector<U8> data; + // note, includes terminating '\0' copy(&source[0], &source[sizeof(source)], back_inserter(data)); v = data; @@ -468,28 +469,36 @@ namespace tut checkRoundTrip(msg + " many nested maps", v); } - typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerialzeGroup; - typedef TestLLSDSerialzeGroup::object TestLLSDSerializeObject; - TestLLSDSerialzeGroup gTestLLSDSerializeGroup("llsd serialization"); + typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerializeGroup; + typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject; + TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization"); template<> template<> void TestLLSDSerializeObject::test<1>() { - mFormatter = new LLSDNotationFormatter(); + mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY); mParser = new LLSDNotationParser(); - doRoundTripTests("notation serialization"); + doRoundTripTests("pretty binary notation serialization"); } - + template<> template<> void TestLLSDSerializeObject::test<2>() { + mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE); + mParser = new LLSDNotationParser(); + doRoundTripTests("raw binary notation serialization"); + } + + template<> template<> + void TestLLSDSerializeObject::test<3>() + { mFormatter = new LLSDXMLFormatter(); mParser = new LLSDXMLParser(); doRoundTripTests("xml serialization"); } - + template<> template<> - void TestLLSDSerializeObject::test<3>() + void TestLLSDSerializeObject::test<4>() { mFormatter = new LLSDBinaryFormatter(); mParser = new LLSDBinaryParser(); diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 56886bc73f..15ffe68e67 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -29,7 +29,8 @@ #include "llsingleton.h" #include "../test/lltut.h" - +#include "wrapllerrs.h" +#include "llsd.h" // Capture execution sequence by appending to log string. std::string sLog; @@ -142,8 +143,6 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \ LLSingletonBase::deleteAll(); \ ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \ } \ @@ -158,10 +157,8 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } \ \ template<> template<> \ @@ -174,10 +171,8 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } \ \ template<> template<> \ @@ -190,12 +185,140 @@ namespace tut \ (void)CLS::instance(); \ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \ - LLSingletonBase::cleanupAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \ LLSingletonBase::deleteAll(); \ - ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \ + ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \ } TESTS(A, B, 4, 5, 6, 7) TESTS(B, A, 8, 9, 10, 11) + +#define PARAMSINGLETON(cls) \ + class cls: public LLParamSingleton<cls> \ + { \ + LLSINGLETON(cls, const LLSD::String& str): mDesc(str) {} \ + cls(LLSD::Integer i): mDesc(i) {} \ + \ + public: \ + std::string desc() const { return mDesc.asString(); } \ + \ + private: \ + LLSD mDesc; \ + } + + // Declare two otherwise-identical LLParamSingleton classes so we can + // validly initialize each using two different constructors. If we tried + // to test that with a single LLParamSingleton class within the same test + // program, we'd get 'trying to use deleted LLParamSingleton' errors. + PARAMSINGLETON(PSing1); + PARAMSINGLETON(PSing2); + + template<> template<> + void singleton_object_t::test<12>() + { + set_test_name("LLParamSingleton"); + + WrapLLErrs catcherr; + // query methods + ensure("false positive on instanceExists()", ! PSing1::instanceExists()); + ensure("false positive on wasDeleted()", ! PSing1::wasDeleted()); + // try to reference before initializing + std::string threw = catcherr.catch_llerrs([](){ + (void)PSing1::instance(); + }); + ensure_contains("too-early instance() didn't throw", threw, "Uninitialized"); + // getInstance() behaves the same as instance() + threw = catcherr.catch_llerrs([](){ + (void)PSing1::getInstance(); + }); + ensure_contains("too-early getInstance() didn't throw", threw, "Uninitialized"); + // initialize using LLSD::String constructor + PSing1::initParamSingleton("string"); + ensure_equals(PSing1::instance().desc(), "string"); + ensure("false negative on instanceExists()", PSing1::instanceExists()); + // try to initialize again + threw = catcherr.catch_llerrs([](){ + PSing1::initParamSingleton("again"); + }); + ensure_contains("second ctor(string) didn't throw", threw, "twice"); + // try to initialize using the other constructor -- should be + // well-formed, but illegal at runtime + threw = catcherr.catch_llerrs([](){ + PSing1::initParamSingleton(17); + }); + ensure_contains("other ctor(int) didn't throw", threw, "twice"); + PSing1::deleteSingleton(); + ensure("false negative on wasDeleted()", PSing1::wasDeleted()); + threw = catcherr.catch_llerrs([](){ + (void)PSing1::instance(); + }); + ensure_contains("accessed deleted LLParamSingleton", threw, "deleted"); + } + + template<> template<> + void singleton_object_t::test<13>() + { + set_test_name("LLParamSingleton alternate ctor"); + + WrapLLErrs catcherr; + // We don't have to restate all the tests for PSing1. Only test validly + // using the other constructor. + PSing2::initParamSingleton(17); + ensure_equals(PSing2::instance().desc(), "17"); + // can't do it twice + std::string threw = catcherr.catch_llerrs([](){ + PSing2::initParamSingleton(34); + }); + ensure_contains("second ctor(int) didn't throw", threw, "twice"); + // can't use the other constructor either + threw = catcherr.catch_llerrs([](){ + PSing2::initParamSingleton("string"); + }); + ensure_contains("other ctor(string) didn't throw", threw, "twice"); + } + + class CircularPCtor: public LLParamSingleton<CircularPCtor> + { + LLSINGLETON(CircularPCtor) + { + // never mind indirection, just go straight for the circularity + (void)instance(); + } + }; + + template<> template<> + void singleton_object_t::test<14>() + { + set_test_name("Circular LLParamSingleton constructor"); + WrapLLErrs catcherr; + std::string threw = catcherr.catch_llerrs([](){ + CircularPCtor::initParamSingleton(); + }); + ensure_contains("constructor circularity didn't throw", threw, "constructor"); + } + + class CircularPInit: public LLParamSingleton<CircularPInit> + { + LLSINGLETON_EMPTY_CTOR(CircularPInit); + public: + virtual void initSingleton() + { + // never mind indirection, just go straight for the circularity + CircularPInit *pt = getInstance(); + if (!pt) + { + throw; + } + } + }; + + template<> template<> + void singleton_object_t::test<15>() + { + set_test_name("Circular LLParamSingleton initSingleton()"); + WrapLLErrs catcherr; + std::string threw = catcherr.catch_llerrs([](){ + CircularPInit::initParamSingleton(); + }); + ensure("initSingleton() circularity threw", threw.empty()); + } } diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 08fbf19b1c..3779fb41bc 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -37,16 +37,13 @@ #include "llerrorcontrol.h" #include "llexception.h" #include "stringize.h" +#include "../test/catch_and_store_what_in.h" #include <boost/bind.hpp> #include <boost/noncopyable.hpp> #include <boost/shared_ptr.hpp> #include <list> #include <string> -// statically reference the function in test.cpp... it's short, we could -// replicate, but better to reuse -extern void wouldHaveCrashed(const std::string& message); - struct WrapLLErrs { WrapLLErrs(): @@ -58,7 +55,8 @@ struct WrapLLErrs mPriorFatal(LLError::getFatalFunction()) { // Make LL_ERRS call our own operator() method - LLError::setFatalFunction(boost::bind(&WrapLLErrs::operator(), this, _1)); + LLError::setFatalFunction( + [this](const std::string& message){ (*this)(message); }); } ~WrapLLErrs() @@ -81,6 +79,31 @@ struct WrapLLErrs LLTHROW(FatalException(message)); } + /// Convenience wrapper for catch_what<FatalException>() + // + // The implementation makes it clear that this function need not be a + // member; it could easily be a free function. It is a member because it + // makes no sense to attempt to catch FatalException unless there is a + // WrapLLErrs instance in scope. Without a live WrapLLErrs instance, any + // LL_ERRS() reached by code within 'func' would terminate the test + // program instead of throwing FatalException. + // + // We were tempted to introduce a free function, likewise accepting + // arbitrary 'func', that would instantiate WrapLLErrs and then call + // catch_llerrs() on that instance. We decided against it, for this + // reason: on extending a test function containing a single call to that + // free function, a maintainer would most likely make additional calls to + // that free function, instead of switching to an explicit WrapLLErrs + // declaration with several calls to its catch_llerrs() member function. + // Even a construct such as WrapLLErrs().catch_llerrs(...) would make the + // object declaration more visible; it's not unreasonable to expect a + // maintainer to extend that by naming and reusing the WrapLLErrs instance. + template <typename FUNC> + std::string catch_llerrs(FUNC func) + { + return catch_what<FatalException>(func); + } + std::string error; LLError::SettingsStoragePtr mPriorErrorSettings; LLError::FatalFunction mPriorFatal; @@ -173,11 +196,13 @@ public: // with that output. If it turns out that saveAndResetSettings() has // some bad effect, give up and just let the DEBUG level log messages // display. - : boost::noncopyable(), + : boost::noncopyable(), + mFatalFunction(LLError::getFatalFunction()), mOldSettings(LLError::saveAndResetSettings()), - mRecorder(new CaptureLogRecorder()) + mRecorder(new CaptureLogRecorder()) { - LLError::setFatalFunction(wouldHaveCrashed); + // reinstate the FatalFunction we just reset + LLError::setFatalFunction(mFatalFunction); LLError::setDefaultLevel(level); LLError::addRecorder(mRecorder); } @@ -193,17 +218,18 @@ public: /// for the sought string. std::string messageWith(const std::string& search, bool required=true) { - return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->messageWith(search, required); + return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->messageWith(search, required); } std::ostream& streamto(std::ostream& out) const { - return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out); + return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out); } private: + LLError::FatalFunction mFatalFunction; LLError::SettingsStoragePtr mOldSettings; - LLError::RecorderPtr mRecorder; + LLError::RecorderPtr mRecorder; }; #endif /* ! defined(LL_WRAPLLERRS_H) */ |