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

263 lines
8.1 KiB
C++

/**
* @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$
*/
#define BOOST_RESULT_OF_USE_TR1 1
#include <boost/bind.hpp>
#include <boost/range.hpp>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include "linden_common.h"
#include <iostream>
#include <string>
#include "../test/lltut.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "../test/debug.h"
using namespace llcoro;
/*****************************************************************************
* 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);
// give listener a chance to process
llcoro::suspend();
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");
// 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::Promise<std::string>>& 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).
// Perhaps we would send a request to a remote server and arrange
// for cbp->set_value() to be called on response.
// For test purposes, instead of handing 'callback' (or an
// adapter) off to some I/O subsystem, we'll just pass it back to
// our caller.
cbp = boost::make_shared<LLCoros::Promise<std::string>>();
LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp);
// calling get() on the future causes us to suspend
debug("about to suspend");
stringdata = future.get();
ensure_equals("Got it", stringdata, "received");
}
END
}
template<> template<>
void object::test<1>()
{
clear();
set_test_name("explicit_wait");
DEBUG;
// Construct the coroutine instance that will run explicit_wait.
boost::shared_ptr<LLCoros::Promise<std::string>> respond;
LLCoros::instance().launch("test<1>",
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 should make
// the coroutine ready.
respond->set_value("received");
// but give it a chance to wake up
llcoro::suspend();
// 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<2>()
{
clear();
set_test_name("waitForEventOn1");
DEBUG;
LLCoros::instance().launch("test<2>", waitForEventOn1);
debug("about to send");
LLEventPumps::instance().obtain("source").post("received");
// give waitForEventOn1() a chance to run
llcoro::suspend();
debug("back from send");
ensure_equals(result.asString(), "received");
}
void coroPump()
{
BEGIN
{
LLCoroEventPump waiter;
replyName = waiter.getName();
result = waiter.suspend();
}
END
}
template<> template<>
void object::test<3>()
{
clear();
set_test_name("coroPump");
DEBUG;
LLCoros::instance().launch("test<3>", coroPump);
debug("about to send");
LLEventPumps::instance().obtain(replyName).post("received");
// give coroPump() a chance to run
llcoro::suspend();
debug("back from send");
ensure_equals(result.asString(), "received");
}
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<4>()
{
clear();
set_test_name("postAndWait1");
DEBUG;
LLCoros::instance().launch("test<4>", postAndWait1);
// give postAndWait1() a chance to run
llcoro::suspend();
ensure_equals(result.asInteger(), 18);
}
void coroPumpPost()
{
BEGIN
{
LLCoroEventPump waiter;
result = waiter.postAndSuspend(LLSDMap("value", 17),
immediateAPI.getPump(), "reply");
}
END
}
template<> template<>
void object::test<5>()
{
clear();
set_test_name("coroPumpPost");
DEBUG;
LLCoros::instance().launch("test<5>", coroPumpPost);
// give coroPumpPost() a chance to run
llcoro::suspend();
ensure_equals(result.asInteger(), 18);
}
}