diff options
| author | Steve Bennetts <steve@lindenlab.com> | 2009-10-19 17:31:05 -0700 | 
|---|---|---|
| committer | Steve Bennetts <steve@lindenlab.com> | 2009-10-19 17:31:05 -0700 | 
| commit | 1d5be6eca1969da3e6b923cbf5326d3bdc8b066f (patch) | |
| tree | a03a2e782f5022ebbbe1ef933e3a11c1b9de00a6 /indra/llcommon/tests | |
| parent | 97d2b740d3e700d86665183d5fc5cfcb3efe72d6 (diff) | |
| parent | d78520f6b7fd4a20bbb1d1291a34761efc1fd740 (diff) | |
merge
Diffstat (limited to 'indra/llcommon/tests')
| -rw-r--r-- | indra/llcommon/tests/listener.h | 139 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 782 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventfilter_test.cpp | 276 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 74 | ||||
| -rw-r--r-- | indra/llcommon/tests/wrapllerrs.h | 56 | 
5 files changed, 1326 insertions, 1 deletions
| diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h new file mode 100644 index 0000000000..fa12f944ef --- /dev/null +++ b/indra/llcommon/tests/listener.h @@ -0,0 +1,139 @@ +/** + * @file   listener.h + * @author Nat Goodspeed + * @date   2009-03-06 + * @brief  Useful for tests of the LLEventPump family of classes + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LISTENER_H) +#define LL_LISTENER_H + +#include "llsd.h" +#include <iostream> + +/***************************************************************************** +*   test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +/// Bear in mind that this is strictly for testing +class Listener +{ +public: +    /// Every Listener is instantiated with a name +    Listener(const std::string& name): +        mName(name) +    { +//      std::cout << *this << ": ctor\n"; +    } +/*==========================================================================*| +    // These methods are only useful when trying to track Listener instance +    // lifespan +    Listener(const Listener& that): +        mName(that.mName), +        mLastEvent(that.mLastEvent) +    { +        std::cout << *this << ": copy\n"; +    } +    virtual ~Listener() +    { +        std::cout << *this << ": dtor\n"; +    } +|*==========================================================================*/ +    /// You can request the name +    std::string getName() const { return mName; } +    /// This is a typical listener method that returns 'false' when done, +    /// allowing subsequent listeners on the LLEventPump to process the +    /// incoming event. +    bool call(const LLSD& event) +    { +//      std::cout << *this << "::call(" << event << ")\n"; +        mLastEvent = event; +        return false; +    } +    /// This is an alternate listener that returns 'true' when done, which +    /// stops processing of the incoming event. +    bool callstop(const LLSD& event) +    { +//      std::cout << *this << "::callstop(" << event << ")\n"; +        mLastEvent = event; +        return true; +    } +    /// ListenMethod can represent either call() or callstop(). +    typedef bool (Listener::*ListenMethod)(const LLSD&); +    /** +     * This helper method is only because our test code makes so many +     * repetitive listen() calls to ListenerMethods. In real code, you should +     * call LLEventPump::listen() directly so it can examine the specific +     * object you pass to boost::bind(). +     */ +    LLBoundListener listenTo(LLEventPump& pump, +                             ListenMethod method=&Listener::call, +                             const LLEventPump::NameList& after=LLEventPump::empty, +                             const LLEventPump::NameList& before=LLEventPump::empty) +    { +        return pump.listen(getName(), boost::bind(method, this, _1), after, before); +    } +    /// Both call() and callstop() set mLastEvent. Retrieve it. +    LLSD getLastEvent() const +    { +//      std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; +        return mLastEvent; +    } +    /// Reset mLastEvent to a known state. +    void reset(const LLSD& to = LLSD()) +    { +//      std::cout << *this << "::reset(" << to << ")\n"; +        mLastEvent = to; +    } + +private: +    std::string mName; +    LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ +    out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; +    return out; +} + +/** + * This class tests the relative order in which various listeners on a given + * LLEventPump are called. Each listen() call binds a particular string, which + * we collect for later examination. The actual event is ignored. + */ +struct Collect +{ +    bool add(const std::string& bound, const LLSD& event) +    { +        result.push_back(bound); +        return false; +    } +    void clear() { result.clear(); } +    typedef std::vector<std::string> StringList; +    StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ +    out << '('; +    Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); +    if (begin != end) +    { +        out << '"' << *begin << '"'; +        while (++begin != end) +        { +            out << ", \"" << *begin << '"'; +        } +    } +    out << ')'; +    return out; +} + +#endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp new file mode 100644 index 0000000000..3a2cda7735 --- /dev/null +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -0,0 +1,782 @@ +/** + * @file   coroutine_test.cpp + * @author Nat Goodspeed + * @date   2009-04-22 + * @brief  Test for coroutine. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/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) +/*****************************************************************************/ + +// 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/coroutine/coroutine.hpp> +// Normally, lleventcoro.h obviates future.hpp. We only include this because +// we implement a "by hand" test of future functionality. +#include <boost/coroutine/future.hpp> +#include <boost/bind.hpp> +#include <boost/range.hpp> + +#include "linden_common.h" + +#include <iostream> +#include <string> + +#include "../test/lltut.h" +#include "llsd.h" +#include "llevents.h" +#include "tests/wrapllerrs.h" +#include "stringize.h" +#include "lleventcoro.h" +#include "../test/debug.h" + +/***************************************************************************** +*   from the banana.cpp example program borrowed for test<1>() +*****************************************************************************/ +namespace coroutines = boost::coroutines; +using coroutines::coroutine; + +template<typename Iter> +bool match(Iter first, Iter last, std::string match) { +  std::string::iterator i = match.begin(); +  i != match.end(); +  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 +*****************************************************************************/ +// I suspect this will be typical of coroutines used in Linden software +typedef boost::coroutines::coroutine<void()> coroutine_type; + +/// Simulate an event API whose response is immediate: sent on receipt of the +/// initial request, rather than after some delay. This is the case that +/// distinguishes postAndWait() from calling post(), then calling +/// waitForEventOn(). +class ImmediateAPI +{ +public: +    ImmediateAPI(): +        mPump("immediate", true) +    { +        mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1)); +    } + +    LLEventPump& getPump() { return mPump; } + +    // 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. +    bool operator()(const LLSD& event) const +    { +        LLSD::Integer value(event["value"]); +        LLSD::String replyPumpName(event.has("fail")? "error" : "reply"); +        LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1); +        return false; +    } + +private: +    LLEventStream mPump; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct coroutine_data +    { +        // Define coroutine bodies as methods here so they can use ensure*() + +        void explicit_wait(coroutine_type::self& self) +        { +            BEGIN +            { +                // ... do whatever preliminary stuff must happen ... + +                // declare the future +                boost::coroutines::future<LLSD> future(self); +                // tell the future what to wait for +                LLTempBoundListener connection( +                    LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::coroutines::make_callback(future)))); +                ensure("Not yet", ! future); +                // attempting to dereference ("resolve") the future causes the calling +                // coroutine to wait for it +                debug("about to wait"); +                result = *future; +                ensure("Got it", future); +            } +            END +        } + +        void waitForEventOn1(coroutine_type::self& self) +        { +            BEGIN +            { +                result = waitForEventOn(self, "source"); +            } +            END +        } + +        void waitForEventOn2(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = waitForEventOn(self, "reply", "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void postAndWait1(coroutine_type::self& self) +        { +            BEGIN +            { +                result = postAndWait(self, +                                     LLSD().insert("value", 17), // request event +                                     immediateAPI.getPump(),     // requestPump +                                     "reply1",                   // replyPump +                                     "reply");                   // request["reply"] = name +            } +            END +        } + +        void postAndWait2(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = ::postAndWait2(self, +                                                    LLSD().insert("value", 18), +                                                    immediateAPI.getPump(), +                                                    "reply2", +                                                    "error2", +                                                    "reply", +                                                    "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void postAndWait2_1(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = ::postAndWait2(self, +                                                    LLSD().insert("value", 18).insert("fail", LLSD()), +                                                    immediateAPI.getPump(), +                                                    "reply2", +                                                    "error2", +                                                    "reply", +                                                    "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void coroPump(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPump waiter; +                replyName = waiter.getName(); +                result = waiter.wait(self); +            } +            END +        } + +        void coroPumpPost(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPump waiter; +                result = waiter.postAndWait(self, LLSD().insert("value", 17), +                                            immediateAPI.getPump(), "reply"); +            } +            END +        } + +        void coroPumps(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                LLEventWithID pair(waiter.wait(self)); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsNoEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                result = waiter.waitWithException(self); +            } +            END +        } + +        void coroPumpsEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                try +                { +                    result = waiter.waitWithException(self); +                    debug("no exception"); +                } +                catch (const LLErrorEvent& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    errordata = e.getData(); +                } +            } +            END +        } + +        void coroPumpsNoLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                result = waiter.waitWithLog(self); +            } +            END +        } + +        void coroPumpsLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                WrapLL_ERRS capture; +                try +                { +                    result = waiter.waitWithLog(self); +                    debug("no exception"); +                } +                catch (const WrapLL_ERRS::FatalException& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    threw = e.what(); +                } +            } +            END +        } + +        void coroPumpsPost(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                LLEventWithID pair(waiter.postAndWait(self, LLSD().insert("value", 23), +                                                      immediateAPI.getPump(), "reply", "error")); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsPost_1(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                LLEventWithID pair( +                    waiter.postAndWait(self, LLSD().insert("value", 23).insert("fail", LLSD()), +                                       immediateAPI.getPump(), "reply", "error")); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsPostNoEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                result = waiter.postAndWaitWithException(self, LLSD().insert("value", 8), +                                                         immediateAPI.getPump(), "reply", "error"); +            } +            END +        } + +        void coroPumpsPostEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                try +                { +                    result = waiter.postAndWaitWithException(self, +                        LLSD().insert("value", 9).insert("fail", LLSD()), +                        immediateAPI.getPump(), "reply", "error"); +                    debug("no exception"); +                } +                catch (const LLErrorEvent& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    errordata = e.getData(); +                } +            } +            END +        } + +        void coroPumpsPostNoLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                result = waiter.postAndWaitWithLog(self, LLSD().insert("value", 30), +                                                   immediateAPI.getPump(), "reply", "error"); +            } +            END +        } + +        void coroPumpsPostLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                WrapLL_ERRS capture; +                try +                { +                    result = waiter.postAndWaitWithLog(self, +                        LLSD().insert("value", 31).insert("fail", LLSD()), +                        immediateAPI.getPump(), "reply", "error"); +                    debug("no exception"); +                } +                catch (const WrapLL_ERRS::FatalException& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    threw = e.what(); +                } +            } +            END +        } + +        void ensure_done(coroutine_type& coro) +        { +            ensure("coroutine complete", ! coro); +        } + +        ImmediateAPI immediateAPI; +        std::string replyName, errorName, threw; +        LLSD result, errordata; +        int which; +    }; +    typedef test_group<coroutine_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); +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("explicit_wait"); +        DEBUG; + +        // Construct the coroutine instance that will run explicit_wait. +        // Pass the ctor a callable that accepts the coroutine_type::self +        // param passed by the library. +        coroutine_type coro(boost::bind(&coroutine_data::explicit_wait, this, _1)); +        // Start the coroutine +        coro(std::nothrow); +        // When the coroutine waits for the event pump, it returns here. +        debug("about to send"); +        // Satisfy the wait. +        LLEventPumps::instance().obtain("source").post("received"); +        // Now wait for the coroutine to complete. +        ensure_done(coro); +        // ensure the coroutine ran and woke up again with the intended result +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("waitForEventOn1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn1, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("source").post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("waitForEventOn2 reply"); +        { +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("reply").post("received"); +        debug("back from send"); +        ensure_done(coro); +        } +        ensure_equals(result.asString(), "received"); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("waitForEventOn2 error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("error").post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "badness"); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("coroPump"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPump, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<7>() +    { +        set_test_name("coroPumps reply"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<8>() +    { +        set_test_name("coroPumps error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "badness"); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<9>() +    { +        set_test_name("coroPumpsNoEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoEx, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<10>() +    { +        set_test_name("coroPumpsEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsEx, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_equals("got error", errordata.asString(), "badness"); +    } + +    template<> template<> +    void object::test<11>() +    { +        set_test_name("coroPumpsNoLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoLog, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<12>() +    { +        set_test_name("coroPumpsLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsLog, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_contains("got error", threw, "badness"); +    } + +    template<> template<> +    void object::test<13>() +    { +        set_test_name("postAndWait1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 18); +    } + +    template<> template<> +    void object::test<14>() +    { +        set_test_name("postAndWait2"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait2, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 19); +        ensure_equals(which, 0); +    } + +    template<> template<> +    void object::test<15>() +    { +        set_test_name("postAndWait2_1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait2_1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 19); +        ensure_equals(which, 1); +    } + +    template<> template<> +    void object::test<16>() +    { +        set_test_name("coroPumpPost"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpPost, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 18); +    } + +    template<> template<> +    void object::test<17>() +    { +        set_test_name("coroPumpsPost reply"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 24); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<18>() +    { +        set_test_name("coroPumpsPost error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost_1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 24); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<19>() +    { +        set_test_name("coroPumpsPostNoEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoEx, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 9); +    } + +    template<> template<> +    void object::test<20>() +    { +        set_test_name("coroPumpsPostEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostEx, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_equals("got error", errordata.asInteger(), 10); +    } + +    template<> template<> +    void object::test<21>() +    { +        set_test_name("coroPumpsPostNoLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoLog, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 31); +    } + +    template<> template<> +    void object::test<22>() +    { +        set_test_name("coroPumpsPostLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostLog, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_contains("got error", threw, "32"); +    } +} // namespace tut diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp new file mode 100644 index 0000000000..28b909298e --- /dev/null +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -0,0 +1,276 @@ +/** + * @file   lleventfilter_test.cpp + * @author Nat Goodspeed + * @date   2009-03-06 + * @brief  Test for lleventfilter. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" +#include "listener.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +*   Test classes +*****************************************************************************/ +// Strictly speaking, we're testing LLEventTimeoutBase rather than the +// production LLEventTimeout (using LLTimer) because we don't want every test +// run to pause for some number of seconds until we reach a real timeout. But +// as we've carefully put all functionality except actual LLTimer calls into +// LLEventTimeoutBase, that should suffice. We're not not not trying to test +// LLTimer here. +class TestEventTimeout: public LLEventTimeoutBase +{ +public: +    TestEventTimeout(): +        mElapsed(true) +    {} +    TestEventTimeout(LLEventPump& source): +        LLEventTimeoutBase(source), +        mElapsed(true) +    {} + +    // test hook +    void forceTimeout(bool timeout=true) { mElapsed = timeout; } + +protected: +    virtual void setCountdown(F32 seconds) { mElapsed = false; } +    virtual bool countdownElapsed() const { return mElapsed; } + +private: +    bool mElapsed; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct filter_data +    { +        // The resemblance between this test data and that in llevents_tut.cpp +        // is not coincidental. +        filter_data(): +            pumps(LLEventPumps::instance()), +            mainloop(pumps.obtain("mainloop")), +            listener0("first"), +            listener1("second") +        {} +        LLEventPumps& pumps; +        LLEventPump& mainloop; +        Listener listener0; +        Listener listener1; + +        void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) +        { +            ensure_equals(STRINGIZE(listener << ' ' << desc), +                          listener.getLastEvent(), got); +        } +    }; +    typedef test_group<filter_data> filter_group; +    typedef filter_group::object filter_object; +    filter_group filtergrp("lleventfilter"); + +    template<> template<> +    void filter_object::test<1>() +    { +        set_test_name("LLEventMatching"); +        LLEventPump& driver(pumps.obtain("driver")); +        listener0.reset(0); +        // Listener isn't derived from LLEventTrackable specifically to test +        // various connection-management mechanisms. But that means we have a +        // couple of transient Listener objects, one of which is listening to +        // a persistent LLEventPump. Capture those connections in local +        // LLTempBoundListener instances so they'll disconnect +        // on destruction. +        LLTempBoundListener temp1( +            listener0.listenTo(driver)); +        // Construct a pattern LLSD: desired Event must have a key "foo" +        // containing string "bar" +        LLEventMatching filter(driver, LLSD().insert("foo", "bar")); +        listener1.reset(0); +        LLTempBoundListener temp2( +            listener1.listenTo(filter)); +        driver.post(1); +        check_listener("direct", listener0, LLSD(1)); +        check_listener("filtered", listener1, LLSD(0)); +        // Okay, construct an LLSD map matching the pattern +        LLSD data; +        data["foo"] = "bar"; +        data["random"] = 17; +        driver.post(data); +        check_listener("direct", listener0, data); +        check_listener("filtered", listener1, data); +    } + +    template<> template<> +    void filter_object::test<2>() +    { +        set_test_name("LLEventTimeout::actionAfter()"); +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        // Use listener1.call() as the Action for actionAfter(), since it +        // already provides a way to sense the call +        listener1.reset(0); +        // driver --> filter --> listener0 +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener1, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener1, LLSD(0)); +        // Verify chained actionAfter() calls, that is, that a second +        // actionAfter() resets the timer established by the first +        // actionAfter(). +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Since our TestEventTimeout class isn't actually manipulating time +        // (quantities of seconds), only a bool "elapsed" flag, sense that by +        // forcing the flag between actionAfter() calls. +        filter.forceTimeout(); +        // Pumping mainloop here would result in a timeout (as we'll verify +        // below). This state simulates a ticking timer that has not yet timed +        // out. But now, before a mainloop event lets 'filter' recognize +        // timeout on the previous actionAfter() call, pretend we're pushing +        // that timeout farther into the future. +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Look ma, no timeout! +        mainloop.post(17); +        check_listener("no timeout 3", listener1, LLSD(0)); +        // Now let the updated actionAfter() timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        mainloop.post(17); +        check_listener("timeout", listener1, LLSD("timeout")); +        // Timing out cancels the timer. Verify that. +        listener1.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 4", listener1, LLSD(0)); +        // Reset the timer and then cancel() it. +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // neither expired nor satisified +        mainloop.post(17); +        check_listener("no timeout 5", listener1, LLSD(0)); +        // cancel +        filter.cancel(); +        // timeout! +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 6", listener1, LLSD(0)); +    } + +    template<> template<> +    void filter_object::test<3>() +    { +        set_test_name("LLEventTimeout::eventAfter()"); +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        filter.eventAfter(20, LLSD("timeout")); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener0, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener0, LLSD(1)); +        // Set timer again. +        filter.eventAfter(20, LLSD("timeout")); +        // Now let the timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        mainloop.post(17); +        check_listener("timeout", listener0, LLSD("timeout")); +        // Timing out cancels the timer. Verify that. +        listener0.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 3", listener0, LLSD(0)); +    } + +    template<> template<> +    void filter_object::test<4>() +    { +        set_test_name("LLEventTimeout::errorAfter()"); +        WrapLL_ERRS capture; +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        filter.errorAfter(20, "timeout"); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener0, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener0, LLSD(1)); +        // Set timer again. +        filter.errorAfter(20, "timeout"); +        // Now let the timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        std::string threw; +        try +        { +            mainloop.post(17); +        } +        catch (const WrapLL_ERRS::FatalException& e) +        { +            threw = e.what(); +        } +        ensure_contains("errorAfter() timeout exception", threw, "timeout"); +        // Timing out cancels the timer. Verify that. +        listener0.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 3", listener0, LLSD(0)); +    } +} // namespace tut + +/***************************************************************************** +*   Link dependencies +*****************************************************************************/ +#include "llsdutil.cpp" diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f13c69f1e3..6ab48ec34a 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -48,6 +48,18 @@  typedef U32 uint32_t;  #endif +std::vector<U8> string_to_vector(std::string str) +{ +	// bc LLSD can't... +	size_t len = (size_t)str.length(); +	std::vector<U8> v(len); +	for (size_t i = 0; i < len ; i++) +	{ +		v[i] = str[i]; +	} +	return v; +} +  namespace tut  {  	struct sd_xml_data @@ -107,7 +119,16 @@ namespace tut  		expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n";  		xml_test("date", expected); -		// *FIX: test binary +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		std::vector<U8> hello; +		hello.push_back('h'); +		hello.push_back('e'); +		hello.push_back('l'); +		hello.push_back('l'); +		hello.push_back('o'); +		mSD = hello; +		expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		xml_test("binary", expected);  	}  	template<> template<> @@ -199,6 +220,21 @@ namespace tut  		xml_test("2 element map", expected);  	} +	template<> template<> +	void sd_xml_object::test<6>() +	{ +		// tests with binary +		std::string expected; + +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		mSD = string_to_vector("hello"); +		expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		xml_test("binary", expected); + +		mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		xml_test("binary", expected); +	}  	class TestLLSDSerializeData  	{ @@ -637,6 +673,42 @@ namespace tut  			v.size() + 1);  	} +	template<> template<>  +	void TestLLSDXMLParsingObject::test<4>() +	{ +		// test handling of binary object in XML +		std::string xml; +		LLSD expected; + +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		expected = string_to_vector("hello"); +		xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		ensureParse( +			"the word 'hello' packed in binary encoded base64", +			xml, +			expected, +			1); + +		expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		ensureParse( +			"a common binary blob for object -> agent offline inv transfer", +			xml, +			expected, +			1); + +		expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n"; +		xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n"; +		xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n"; +		xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n"; +		xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		ensureParse( +			"a common binary blob for object -> agent offline inv transfer", +			xml, +			expected, +			1); +	}  	/*  	TODO:  		test XML parsing diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h new file mode 100644 index 0000000000..1001ebc466 --- /dev/null +++ b/indra/llcommon/tests/wrapllerrs.h @@ -0,0 +1,56 @@ +/** + * @file   wrapllerrs.h + * @author Nat Goodspeed + * @date   2009-03-11 + * @brief  Define a class useful for unit tests that engage llerrs (LL_ERRS) functionality + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRAPLLERRS_H) +#define LL_WRAPLLERRS_H + +#include "llerrorcontrol.h" + +struct WrapLL_ERRS +{ +    WrapLL_ERRS(): +        // Resetting Settings discards the default Recorder that writes to +        // stderr. Otherwise, expected llerrs (LL_ERRS) messages clutter the +        // console output of successful tests, potentially confusing things. +        mPriorErrorSettings(LLError::saveAndResetSettings()), +        // Save shutdown function called by LL_ERRS +        mPriorFatal(LLError::getFatalFunction()) +    { +        // Make LL_ERRS call our own operator() method +        LLError::setFatalFunction(boost::bind(&WrapLL_ERRS::operator(), this, _1)); +    } + +    ~WrapLL_ERRS() +    { +        LLError::setFatalFunction(mPriorFatal); +        LLError::restoreSettings(mPriorErrorSettings); +    } + +    struct FatalException: public std::runtime_error +    { +        FatalException(const std::string& what): std::runtime_error(what) {} +    }; + +    void operator()(const std::string& message) +    { +        // Save message for later in case consumer wants to sense the result directly +        error = message; +        // Also throw an appropriate exception since calling code is likely to +        // assume that control won't continue beyond LL_ERRS. +        throw FatalException(message); +    } + +    std::string error; +    LLError::Settings* mPriorErrorSettings; +    LLError::FatalFunction mPriorFatal; +}; + +#endif /* ! defined(LL_WRAPLLERRS_H) */ | 
