/** * @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 #include #include #include #include #include "linden_common.h" #include #include #include #include "../test/lltut.h" #include "../test/lltestapp.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" #include "llcoros.h" #include "lleventfilter.h" #include "lleventcoro.h" #include "../test/debug.h" #include "../test/sync.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(Sync& sync): mPump("immediate", true), mSync(sync) { 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 response. bool operator()(const LLSD& event) const { mSync.bump(); LLSD::Integer value(event["value"]); LLEventPumps::instance().obtain(event["reply"]).post(value + 1); return false; } private: LLEventStream mPump; Sync& mSync; }; /***************************************************************************** * TUT *****************************************************************************/ namespace tut { struct test_data { Sync mSync; ImmediateAPI immediateAPI{mSync}; std::string replyName, errorName, threw, stringdata; LLSD result, errordata; int which; LLTestApp testApp; void explicit_wait(boost::shared_ptr>& cbp); void waitForEventOn1(); void coroPump(); void postAndWait1(); void coroPumpPost(); }; typedef test_group coroutine_group; typedef coroutine_group::object object; coroutine_group coroutinegrp("coroutine"); void test_data::explicit_wait(boost::shared_ptr>& cbp) { BEGIN { mSync.bump(); // The point of this test is to verify / illustrate suspending a // coroutine for something other than an LLEventPump. In other // words, this shows how to adapt to any async operation that // provides a callback-style notification (and prove that it // works). // 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::Future future = LLCoros::getFuture(*cbp); // calling get() on the future causes us to suspend debug("about to suspend"); stringdata = future.get(); mSync.bump(); ensure_equals("Got it", stringdata, "received"); } END } template<> template<> void object::test<1>() { set_test_name("explicit_wait"); DEBUG; // Construct the coroutine instance that will run explicit_wait. boost::shared_ptr> respond; LLCoros::instance().launch("test<1>", [this, &respond](){ explicit_wait(respond); }); mSync.bump(); // When the coroutine waits for the future, it returns here. debug("about to respond"); // Now we're the I/O subsystem delivering a result. This should make // the coroutine ready. respond->set_value("received"); // but give it a chance to wake up mSync.yield(); // ensure the coroutine ran and woke up again with the intended result ensure_equals(stringdata, "received"); } void test_data::waitForEventOn1() { BEGIN { mSync.bump(); result = suspendUntilEventOn("source"); mSync.bump(); } END } template<> template<> void object::test<2>() { set_test_name("waitForEventOn1"); DEBUG; LLCoros::instance().launch("test<2>", [this](){ waitForEventOn1(); }); mSync.bump(); debug("about to send"); LLEventPumps::instance().obtain("source").post("received"); // give waitForEventOn1() a chance to run mSync.yield(); debug("back from send"); ensure_equals(result.asString(), "received"); } void test_data::coroPump() { BEGIN { mSync.bump(); LLCoroEventPump waiter; replyName = waiter.getName(); result = waiter.suspend(); mSync.bump(); } END } template<> template<> void object::test<3>() { set_test_name("coroPump"); DEBUG; LLCoros::instance().launch("test<3>", [this](){ coroPump(); }); mSync.bump(); debug("about to send"); LLEventPumps::instance().obtain(replyName).post("received"); // give coroPump() a chance to run mSync.yield(); debug("back from send"); ensure_equals(result.asString(), "received"); } void test_data::postAndWait1() { BEGIN { mSync.bump(); result = postAndSuspend(LLSDMap("value", 17), // request event immediateAPI.getPump(), // requestPump "reply1", // replyPump "reply"); // request["reply"] = name mSync.bump(); } END } template<> template<> void object::test<4>() { set_test_name("postAndWait1"); DEBUG; LLCoros::instance().launch("test<4>", [this](){ postAndWait1(); }); ensure_equals(result.asInteger(), 18); } void test_data::coroPumpPost() { BEGIN { mSync.bump(); LLCoroEventPump waiter; result = waiter.postAndSuspend(LLSDMap("value", 17), immediateAPI.getPump(), "reply"); mSync.bump(); } END } template<> template<> void object::test<5>() { set_test_name("coroPumpPost"); DEBUG; LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); }); ensure_equals(result.asInteger(), 18); } template void test() { PUMP pump(typeid(PUMP).name()); bool running{false}; LLSD data{LLSD::emptyArray()}; // start things off by posting once before even starting the listener // coro LL_DEBUGS() << "test() posting first" << LL_ENDL; LLSD first{LLSDMap("desc", "first")("value", 0)}; bool consumed = pump.post(first); ensure("should not have consumed first", ! consumed); // now launch the coro LL_DEBUGS() << "test() launching listener coro" << LL_ENDL; running = true; LLCoros::instance().launch( "listener", [&pump, &running, &data](){ // important for this test that we consume posted values LLCoros::instance().set_consuming(true); // should immediately retrieve 'first' without waiting LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL; data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); // Don't use ensure() from within the coro -- ensure() failure // throws tut::fail, which won't propagate out to the main // test driver, which will result in an odd failure. // Wait for 'second' because it's not already pending. LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL; data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); // and wait for 'third', which should involve no further waiting LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL; data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD())); LL_DEBUGS() << "listener coro done" << LL_ENDL; running = false; }); // back from coro at the point where it's waiting for 'second' LL_DEBUGS() << "test() posting second" << LL_ENDL; LLSD second{llsd::map("desc", "second", "value", 1)}; consumed = pump.post(second); ensure("should have consumed second", consumed); // This is a key point: even though we've post()ed the value for which // the coroutine is waiting, it's actually still suspended until we // pause for some other reason. The coroutine will only pick up one // value at a time from our 'pump'. It's important to exercise the // case when we post() two values before it picks up either. LL_DEBUGS() << "test() posting third" << LL_ENDL; LLSD third{llsd::map("desc", "third", "value", 2)}; consumed = pump.post(third); ensure("should NOT yet have consumed third", ! consumed); // now just wait for coro to finish -- which it eventually will, given // that all its suspend calls have short timeouts. while (running) { LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL; llcoro::suspendUntilTimeout(0.1); } // okay, verify expected results ensure_equals("should have received three values", data, llsd::array(first, second, third)); LL_DEBUGS() << "test() done" << LL_ENDL; } template<> template<> void object::test<6>() { set_test_name("LLEventMailDrop"); tut::test(); } template<> template<> void object::test<7>() { set_test_name("LLEventLogProxyFor"); tut::test< LLEventLogProxyFor >(); } }