/** * @file lllogin_test.cpp * @author Mark Palange * @date 2009-02-26 * @brief Tests of lllogin.cpp. * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2009-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$ */ #if LL_WINDOWS #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally #endif // Precompiled header #include "linden_common.h" // associated header #include "../lllogin.h" // STL headers // std headers #include #include // external library headers // other Linden headers #include "../../../test/debug.h" #include "../../../test/lltestapp.h" #include "../../../test/lltut.h" #include "llevents.h" #include "lleventcoro.h" #include "llsd.h" #include "stringize.h" #if LL_WINDOWS #define skipwin(arg) skip(arg) #define skipmac(arg) #define skiplinux(arg) #elif LL_DARWIN #define skipwin(arg) #define skipmac(arg) skip(arg) #define skiplinux(arg) #elif LL_LINUX #define skipwin(arg) #define skipmac(arg) #define skiplinux(arg) skip(arg) #endif /***************************************************************************** * Helper classes *****************************************************************************/ // This is a listener to receive results from lllogin. class LoginListener: public LLEventTrackable { std::string mName; LLSD mLastEvent; size_t mCalls{ 0 }; Debug mDebug; public: LoginListener(const std::string& name) : mName(name), mDebug(stringize(*this)) {} bool call(const LLSD& event) { mDebug(STRINGIZE("LoginListener called!: " << event)); mLastEvent = event; ++mCalls; return false; } LLBoundListener listenTo(LLEventPump& pump) { return pump.listen(mName, boost::bind(&LoginListener::call, this, _1)); } LLSD lastEvent() const { return mLastEvent; } size_t getCalls() const { return mCalls; } // wait for arbitrary predicate to become true template LLSD waitFor(const std::string& desc, PRED&& pred, double seconds=2.0) const { // remember when we started waiting auto start = std::chrono::system_clock::now(); // Break loop when the passed predicate returns true while (! std::forward(pred)()) { // but if we've been spinning here too long, test failed // how long have we been here, anyway? auto now = std::chrono::system_clock::now(); // the default ratio for duration is seconds std::chrono::duration elapsed = (now - start); if (elapsed.count() > seconds) { tut::fail(STRINGIZE("LoginListener::waitFor() took more than " << seconds << " seconds waiting for " << desc)); } // haven't yet received the new call, nor have we timed out -- // just wait llcoro::suspend(); } // oh good, we've gotten at least one new call! Return its event. return lastEvent(); } // wait for any call() calls beyond prevcalls LLSD waitFor(size_t prevcalls, double seconds) const { return waitFor(STRINGIZE("more than " << prevcalls << " calls"), [this, prevcalls]()->bool{ return getCalls() > prevcalls; }, seconds); } friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener) { return out << "LoginListener(" << listener.mName << ')'; } }; class LLXMLRPCListener: public LLEventTrackable { std::string mName; LLSD mEvent; bool mImmediateResponse; LLSD mResponse; Debug mDebug; public: LLXMLRPCListener(const std::string& name, bool i = false, const LLSD& response = LLSD() ) : mName(name), mImmediateResponse(i), mResponse(response), mDebug(stringize(*this)) { if(mResponse.isUndefined()) { mResponse["status"] = "Complete"; // StatusComplete mResponse["errorcode"] = 0; mResponse["error"] = "dummy response"; mResponse["transfer_rate"] = 0; mResponse["responses"]["login"] = true; } } void setResponse(const LLSD& r) { mResponse = r; } bool handle_event(const LLSD& event) { mDebug(STRINGIZE("LLXMLRPCListener called!: " << event)); mEvent = event; if(mImmediateResponse) { sendReply(); } return false; } void sendReply() { LLEventPumps::instance().obtain(mEvent["reply"]).post(mResponse); } LLBoundListener listenTo(LLEventPump& pump) { return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1)); } friend std::ostream& operator<<(std::ostream& out, const LLXMLRPCListener& listener) { return out << "LLXMLRPCListener(" << listener.mName << ')'; } }; /***************************************************************************** * TUT *****************************************************************************/ namespace tut { struct llviewerlogin_data { llviewerlogin_data() : pumps(LLEventPumps::instance()) {} ~llviewerlogin_data() { pumps.clear(); } LLEventPumps& pumps; LLTestApp testApp; }; typedef test_group llviewerlogin_group; typedef llviewerlogin_group::object llviewerlogin_object; llviewerlogin_group llviewerlogingrp("LLViewerLogin"); template<> template<> void llviewerlogin_object::test<1>() { DEBUG; // Testing login with an immediate response from XMLPRC // The response will come before the post request exits. // This tests an edge case of the login state handling. LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump bool respond_immediately = true; // Have dummy XMLRPC respond immediately. LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately); LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "foo"; credentials["last"] = "bar"; credentials["passwd"] = "secret"; login.connect("login.bar.com", credentials); listener.waitFor( "online state", [&listener]()->bool{ return listener.lastEvent()["state"].asString() == "online"; }); } template<> template<> void llviewerlogin_object::test<2>() { DEBUG; // Test completed response, that fails to login. set_test_name("LLLogin valid response, failure (eg. bad credentials)"); // Testing normal login procedure. LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "who"; credentials["last"] = "what"; credentials["passwd"] = "badpasswd"; login.connect("login.bar.com", credentials); llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); auto prev = listener.getCalls(); // Send the failed auth request reponse LLSD data; data["status"] = "Complete"; data["errorcode"] = 0; data["error"] = "dummy response"; data["transfer_rate"] = 0; data["responses"]["login"] = "false"; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); // we happen to know LLLogin uses a 10-second timeout to try to sync // with SLVersionChecker -- allow at least that much time before // giving up listener.waitFor(prev, 11.0); ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } template<> template<> void llviewerlogin_object::test<3>() { DEBUG; // Test incomplete response, that end the attempt. set_test_name("LLLogin valid response, failure (eg. bad credentials)"); // Testing normal login procedure. LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump); LLLogin login; LoginListener listener("test_ear"); LLTempBoundListener conn2 = listener.listenTo(login.getEventPump()); LLSD credentials; credentials["first"] = "these"; credentials["last"] = "don't"; credentials["passwd"] = "matter"; login.connect("login.bar.com", credentials); llcoro::suspend(); ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); auto prev = listener.getCalls(); // Send the failed auth request reponse LLSD data; data["status"] = "OtherError"; data["errorcode"] = 0; data["error"] = "dummy response"; data["transfer_rate"] = 0; dummyXMLRPC.setResponse(data); dummyXMLRPC.sendReply(); // we happen to know LLLogin uses a 10-second timeout to try to sync // with SLVersionChecker -- allow at least that much time before // giving up listener.waitFor(prev, 11.0); ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); } }