phoenix-firestorm/indra/llcommon/tests/lleventcoro_test.cpp

814 lines
27 KiB
C++
Executable File

/**
* @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)
/*****************************************************************************/
// 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>
// Normally, lleventcoro.h obviates future.hpp. We only include this because
// we implement a "by hand" test of future functionality.
#include <boost/dcoroutine/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 "llsdutil.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::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
*****************************************************************************/
// I suspect this will be typical of coroutines used in Linden software
typedef boost::dcoroutines::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::dcoroutines::future<LLSD> future(self);
// tell the future what to wait for
LLTempBoundListener connection(
LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::dcoroutines::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,
LLSDMap("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,
LLSDMap("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,
LLSDMap("value", 18)("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, LLSDMap("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();
WrapLLErrs capture;
try
{
result = waiter.waitWithLog(self);
debug("no exception");
}
catch (const WrapLLErrs::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, LLSDMap("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, LLSDMap("value", 23)("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, LLSDMap("value", 8),
immediateAPI.getPump(), "reply", "error");
}
END
}
void coroPumpsPostEx(coroutine_type::self& self)
{
BEGIN
{
LLCoroEventPumps waiter;
try
{
result = waiter.postAndWaitWithException(self,
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
}
void coroPumpsPostNoLog(coroutine_type::self& self)
{
BEGIN
{
LLCoroEventPumps waiter;
result = waiter.postAndWaitWithLog(self, LLSDMap("value", 30),
immediateAPI.getPump(), "reply", "error");
}
END
}
void coroPumpsPostLog(coroutine_type::self& self)
{
BEGIN
{
LLCoroEventPumps waiter;
WrapLLErrs capture;
try
{
result = waiter.postAndWaitWithLog(self,
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
}
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");
}
}
/*==========================================================================*|
#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
|*==========================================================================*/