Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
lleventcoro_test.cpp 24.51 KiB
/**
 * @file   coroutine_test.cpp
 * @author Nat Goodspeed
 * @date   2009-04-22
 * @brief  Test for coroutine.
 * 
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/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 "linden_common.h"

#include <iostream>
#include <string>

#include "../test/lltut.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "tests/wrapllerrs.h"
#include "stringize.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "../test/debug.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
/// initial request, rather than after some delay. This is the case that
/// distinguishes postAndSuspend() from calling post(), then calling
/// suspendUntilEventOn().
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 {};
    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);
    }

    // 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)
    {
        BEGIN
        {
            // 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
            // 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));

            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));
        }
        END
    }

    template<> template<>
    void object::test<2>()
    {
        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)));
        // 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");
        // ensure the coroutine ran and woke up again with the intended result
        ensure_equals(stringdata, "received");
    }

    void waitForEventOn1()
    {
        BEGIN
        {
            result = suspendUntilEventOn("source");
        }
        END
    }

    template<> template<>
    void object::test<3>()
    {
        clear();
        set_test_name("waitForEventOn1");
        DEBUG;
        LLCoros::instance().launch("test<3>", waitForEventOn1);
        debug("about to send");
        LLEventPumps::instance().obtain("source").post("received");
        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()
    {
        BEGIN
        {
            LLCoroEventPump waiter;
            replyName = waiter.getName();
            result = waiter.suspend();
        }
        END
    }

    template<> template<>
    void object::test<6>()
    {
        clear();
        set_test_name("coroPump");
        DEBUG;
        LLCoros::instance().launch("test<6>", coroPump);
        debug("about to send");
        LLEventPumps::instance().obtain(replyName).post("received");
        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;
            threw = capture.catch_llerrs([&waiter, &debug](){
                    result = waiter.suspendWithLog();
                    debug("no exception");
                });
        }
        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()
    {
        BEGIN
        {
            result = postAndSuspend(LLSDMap("value", 17),       // request event
                                 immediateAPI.getPump(),     // requestPump
                                 "reply1",                   // replyPump
                                 "reply");                   // request["reply"] = name
        }
        END
    }

    template<> template<>
    void object::test<13>()
    {
        clear();
        set_test_name("postAndWait1");
        DEBUG;
        LLCoros::instance().launch("test<13>", 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()
    {
        BEGIN
        {
            LLCoroEventPump waiter;
            result = waiter.postAndSuspend(LLSDMap("value", 17),
                                        immediateAPI.getPump(), "reply");
        }
        END
    }

    template<> template<>
    void object::test<16>()
    {
        clear();
        set_test_name("coroPumpPost");
        DEBUG;
        LLCoros::instance().launch("test<16>", 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
        {
            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();
            }
        }
        END
    }

    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()
    {
        BEGIN
        {
            LLCoroEventPumps waiter;
            WrapLLErrs capture;
            threw = capture.catch_llerrs(
                [&waiter, &debug](){
                    result = waiter.postAndSuspendWithLog(
                        LLSDMap("value", 31)("fail", LLSD()),
                        immediateAPI.getPump(), "reply", "error");
                    debug("no exception");
                });
        }
        END
    }

    template<> template<>
    void object::test<22>()
    {
        clear();
        set_test_name("coroPumpsPostLog");
        DEBUG;
        LLCoros::instance().launch("test<22>", coroPumpsPostLog);
        ensure("no result", result.isUndefined());
        ensure_contains("got error", threw, "32");
    }
}

/*==========================================================================*|
#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
|*==========================================================================*/