/** * @file llmediadataclient_test.cpp * @brief LLMediaDatClient tests * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "../llviewerprecompiledheaders.h" #include <iostream> #include "../test/lltut.h" #include "llsdserialize.h" #include "llsdutil.h" #include "llerrorcontrol.h" #include "llhttpstatuscodes.h" #include "../llmediadataclient.h" #include "../llvovolume.h" #include "../../llprimitive/llmediaentry.cpp" #include "../../llprimitive/lltextureentry.cpp" #include "../../llmessage/tests/llcurl_stub.cpp" #if LL_WINDOWS #pragma warning (push) #pragma warning (disable : 4702) // boost::lexical_cast generates this warning #endif #include <boost/lexical_cast.hpp> #if LL_WINDOWS #pragma warning (pop) #endif #define VALID_OBJECT_ID "3607d5c4-644b-4a8a-871a-8b78471af2a2" #define VALID_OBJECT_ID_1 "11111111-1111-1111-1111-111111111111" #define VALID_OBJECT_ID_2 "22222222-2222-2222-2222-222222222222" #define VALID_OBJECT_ID_3 "33333333-3333-3333-3333-333333333333" #define VALID_OBJECT_ID_4 "44444444-4444-4444-4444-444444444444" #define FAKE_OBJECT_MEDIA_CAP_URL "foo_ObjectMedia" #define FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL "foo_ObjectMediaNavigate" #define FAKE_OBJECT_MEDIA_CAP_URL_503 "foo_ObjectMedia_503" #define FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR "foo_ObjectMediaNavigate_ERROR" #define MEDIA_DATA "\ <array> \ <string>foo</string> \ <string>bar</string> \ <string>baz</string> \ </array>" #define _DATA_URLS(ID,DIST,INT,URL1,URL2) " \ <llsd> \ <map> \ <key>uuid</key> \ <string>" ID "</string> \ <key>distance</key> \ <real>" DIST "</real> \ <key>interest</key> \ <real>" INT "</real> \ <key>cap_urls</key> \ <map> \ <key>ObjectMedia</key> \ <string>" URL1 "</string> \ <key>ObjectMediaNavigate</key> \ <string>" URL2 "</string> \ </map> \ <key>media_data</key> \ " MEDIA_DATA " \ </map> \ </llsd>" #define _DATA(ID,DIST,INT) _DATA_URLS(ID,DIST,INT,FAKE_OBJECT_MEDIA_CAP_URL,FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL) const char *DATA = _DATA(VALID_OBJECT_ID,"1.0","1.0"); #define STR(I) boost::lexical_cast<std::string>(I) #define LOG_TEST(N) LL_DEBUGS("LLMediaDataClient") << "\n" << \ "================================================================================\n" << \ "===================================== TEST " #N " ===================================\n" << \ "================================================================================\n" << LL_ENDL; LLSD *gPostRecords = NULL; // stubs: void LLHTTPClient::post( const std::string& url, const LLSD& body, LLHTTPClient::ResponderPtr responder, const LLSD& headers, const F32 timeout) { LLSD record; record["url"] = url; record["body"] = body; record["headers"] = headers; record["timeout"] = timeout; gPostRecords->append(record); // Magic URL that triggers a 503: if ( url == FAKE_OBJECT_MEDIA_CAP_URL_503 ) { responder->error(HTTP_SERVICE_UNAVAILABLE, "fake reason"); } else if (url == FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR) { LLSD result; LLSD error; error["code"] = LLObjectMediaNavigateClient::ERROR_PERMISSION_DENIED_CODE; result["error"] = error; responder->result(result); } else { responder->result(LLSD()); } } const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; class LLMediaDataClientObjectTest : public LLMediaDataClientObject { public: LLMediaDataClientObjectTest(const char *data) { std::istringstream d(data); LLSDSerialize::fromXML(mRep, d); mNumBounceBacks = 0; // std::cout << ll_pretty_print_sd(mRep) << std::endl; // std::cout << "ID: " << getID() << std::endl; } LLMediaDataClientObjectTest(const LLSD &rep) : mRep(rep), mNumBounceBacks(0) {} ~LLMediaDataClientObjectTest() { LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClientObjectTest" << LL_ENDL; } virtual U8 getMediaDataCount() const { return mRep["media_data"].size(); } virtual LLSD getMediaDataLLSD(U8 index) const { return mRep["media_data"][(LLSD::Integer)index]; } virtual LLUUID getID() const { return mRep["uuid"]; } virtual void mediaNavigateBounceBack(U8 index) { mNumBounceBacks++; } virtual bool hasMedia() const { return mRep.has("media_data"); } virtual void updateObjectMediaData(LLSD const &media_data_array) { mRep["media_data"] = media_data_array; } virtual F64 getDistanceFromAvatar() const { return (LLSD::Real)mRep["distance"]; } virtual F64 getTotalMediaInterest() const { return (LLSD::Real)mRep["interest"]; } virtual std::string getCapabilityUrl(const std::string &name) const { return mRep["cap_urls"][name]; } int getNumBounceBacks() const { return mNumBounceBacks; } private: LLSD mRep; int mNumBounceBacks; }; // This special timer delay should ensure that the timer will fire on the very // next pump, no matter what (though this does make an assumption about the // implementation of LLEventTimer::updateClass()): const F32 NO_PERIOD = -1000.0f; static void pump_timers() { LLEventTimer::updateClass(); } namespace tut { struct mediadataclient { mediadataclient() { gPostRecords = &mLLSD; //LLError::setDefaultLevel(LLError::LEVEL_DEBUG); //LLError::setClassLevel("LLMediaDataClient", LLError::LEVEL_DEBUG); //LLError::setTagLevel("MediaOnAPrim", LLError::LEVEL_DEBUG); } LLSD mLLSD; }; typedef test_group<mediadataclient> mediadataclient_t; typedef mediadataclient_t::object mediadataclient_object_t; tut::mediadataclient_t tut_mediadataclient("mediadataclient"); void ensure(const std::string &msg, int value, int expected) { std::string m = msg; m += " value: " + STR(value); m += ", expected: " + STR(expected); ensure(m, value == expected); } void ensure(const std::string &msg, const std::string & value, const std::string & expected) { std::string m = msg; m += " value: " + value; m += ", expected: " + expected; ensure(m, value == expected); } void ensure(const std::string &msg, const LLUUID & value, const LLUUID & expected) { std::string m = msg; m += " value: " + value.asString(); m += ", expected: " + expected.asString(); ensure(m, value == expected); } void ensure_llsd(const std::string &msg, const LLSD & value, const char *expected) { LLSD expected_llsd; std::istringstream e(expected); LLSDSerialize::fromXML(expected_llsd, e); std::string value_str = ll_pretty_print_sd(value); std::string expected_str = ll_pretty_print_sd(expected_llsd); std::string m = msg; m += " value: " + value_str; m += ", expected: " + expected_str; ensure(m, value_str == expected_str); } ////////////////////////////////////////////////////////////////////////////////////////// template<> template<> void mediadataclient_object_t::test<1>() { // // Test fetchMedia() // LOG_TEST(1); LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); int num_refs_start = o->getNumRefs(); { LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD); mdc->fetchMedia(o); // Make sure no posts happened yet... ensure("post records", gPostRecords->size(), 0); ::pump_timers(); ensure("post records", gPostRecords->size(), 1); ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_CAP_URL); ensure("post GET", (*gPostRecords)[0]["body"]["verb"], "GET"); ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); ensure("queue empty", mdc->isEmpty()); } // Make sure everyone's destroyed properly ensure("REF COUNT", o->getNumRefs(), num_refs_start); } ////////////////////////////////////////////////////////////////////////////////////////// template<> template<> void mediadataclient_object_t::test<2>() { // // Test updateMedia() // LOG_TEST(2); LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); { // queue time w/ no delay ensures that ::pump_timers() will hit the tick() LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD); mdc->updateMedia(o); ensure("post records", gPostRecords->size(), 0); ::pump_timers(); ensure("post records", gPostRecords->size(), 1); ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_CAP_URL); ensure("post UPDATE", (*gPostRecords)[0]["body"]["verb"], "UPDATE"); ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); ensure_llsd("post data llsd", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_MEDIA_DATA_KEY], "<llsd>" MEDIA_DATA "</llsd>"); ensure("queue empty", mdc->isEmpty()); } ensure("REF COUNT", o->getNumRefs(), 1); } ////////////////////////////////////////////////////////////////////////////////////////// template<> template<> void mediadataclient_object_t::test<3>() { // // Test navigate() // LOG_TEST(3); LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); { LLPointer<LLObjectMediaNavigateClient> mdc = new LLObjectMediaNavigateClient(NO_PERIOD,NO_PERIOD); const char *TEST_URL = "http://example.com"; mdc->navigate(o, 0, TEST_URL); ensure("post records", gPostRecords->size(), 0); ::pump_timers(); // ensure no bounce back ensure("bounce back", dynamic_cast<LLMediaDataClientObjectTest*>(static_cast<LLMediaDataClientObject*>(o))->getNumBounceBacks(), 0); ensure("post records", gPostRecords->size(), 1); ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL); ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); ensure("post data", (*gPostRecords)[0]["body"][LLTextureEntry::TEXTURE_INDEX_KEY], 0); ensure("post data", (*gPostRecords)[0]["body"][LLMediaEntry::CURRENT_URL_KEY], TEST_URL); ensure("queue empty", mdc->isEmpty()); } ensure("REF COUNT", o->getNumRefs(), 1); } ////////////////////////////////////////////////////////////////////////////////////////// template<> template<> void mediadataclient_object_t::test<4>() { // // Test queue ordering // LOG_TEST(4); LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest( _DATA(VALID_OBJECT_ID_1,"3.0","1.0")); LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest( _DATA(VALID_OBJECT_ID_2,"1.0","1.0")); LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest( _DATA(VALID_OBJECT_ID_3,"2.0","1.0")); { LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD); const char *ORDERED_OBJECT_IDS[] = { VALID_OBJECT_ID_2, VALID_OBJECT_ID_3, VALID_OBJECT_ID_1 }; mdc->fetchMedia(o1); mdc->fetchMedia(o2); mdc->fetchMedia(o3); // Make sure no posts happened yet... ensure("post records", gPostRecords->size(), 0); // tick 3 times... ::pump_timers(); ensure("post records", gPostRecords->size(), 1); ::pump_timers(); ensure("post records", gPostRecords->size(), 2); ::pump_timers(); ensure("post records", gPostRecords->size(), 3); for( int i=0; i < 3; i++ ) { ensure("[" + STR(i) + "] post url", (*gPostRecords)[i]["url"], FAKE_OBJECT_MEDIA_CAP_URL); ensure("[" + STR(i) + "] post GET", (*gPostRecords)[i]["body"]["verb"], "GET"); ensure("[" + STR(i) + "] post object id", (*gPostRecords)[i]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(ORDERED_OBJECT_IDS[i])); } ensure("queue empty", mdc->isEmpty()); } ensure("refcount of o1", o1->getNumRefs(), 1); ensure("refcount of o2", o2->getNumRefs(), 1); ensure("refcount of o3", o3->getNumRefs(), 1); } ////////////////////////////////////////////////////////////////////////////////////////// template<> template<> void mediadataclient_object_t::test<5>() { // // Test fetchMedia() getting a 503 error // LOG_TEST(5); LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest( _DATA_URLS(VALID_OBJECT_ID, "1.0", "1.0", FAKE_OBJECT_MEDIA_CAP_URL_503, FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL)); int num_refs_start = o->getNumRefs(); { const int NUM_RETRIES = 5; LLPointer<LLObjectMediaDataClient> mdc = new LLObjectMediaDataClient(NO_PERIOD,NO_PERIOD,NUM_RETRIES); // This should generate a retry mdc->fetchMedia(o); // Make sure no posts happened yet... ensure("post records before", gPostRecords->size(), 0); // Once, causes retry // Second, fires retry timer // Third, fires queue timer again for (int i=0; i<NUM_RETRIES; ++i) { ::pump_timers(); // Should pump (fire) the queue timer, causing a retry timer to be scheduled // XXX This ensure is not guaranteed, because scheduling a timer might actually get it pumped in the same loop //ensure("post records " + STR(i), gPostRecords->size(), i+1); ::pump_timers(); // Should pump (fire) the retry timer, scheduling the queue timer } // Do some extra pumps to make sure no other timer work occurs. ::pump_timers(); ::pump_timers(); ::pump_timers(); // Make sure there were 2 posts ensure("post records after", gPostRecords->size(), NUM_RETRIES); for (int i=0; i<NUM_RETRIES; ++i) { ensure("[" + STR(i) + "] post url", (*gPostRecords)[i]["url"], FAKE_OBJECT_MEDIA_CAP_URL_503); ensure("[" + STR(i) + "] post GET", (*gPostRecords)[i]["body"]["verb"], "GET"); ensure("[" + STR(i) + "] post object id", (*gPostRecords)[i]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); } ensure("queue empty", mdc->isEmpty()); } // Make sure everyone's destroyed properly ensure("REF COUNT", o->getNumRefs(), num_refs_start); } template<> template<> void mediadataclient_object_t::test<6>() { // // Test navigate() with a bounce back // LOG_TEST(6); LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest( _DATA_URLS(VALID_OBJECT_ID, "1.0", "1.0", FAKE_OBJECT_MEDIA_CAP_URL, FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR)); { LLPointer<LLObjectMediaNavigateClient> mdc = new LLObjectMediaNavigateClient(NO_PERIOD,NO_PERIOD); const char *TEST_URL = "http://example.com"; mdc->navigate(o, 0, TEST_URL); ensure("post records", gPostRecords->size(), 0); ::pump_timers(); // ensure bounce back ensure("bounce back", dynamic_cast<LLMediaDataClientObjectTest*>(static_cast<LLMediaDataClientObject*>(o))->getNumBounceBacks(), 1); ensure("post records", gPostRecords->size(), 1); ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR); ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); ensure("post data", (*gPostRecords)[0]["body"][LLTextureEntry::TEXTURE_INDEX_KEY], 0); ensure("post data", (*gPostRecords)[0]["body"][LLMediaEntry::CURRENT_URL_KEY], TEST_URL); ensure("queue empty", mdc->isEmpty()); } ensure("REF COUNT", o->getNumRefs(), 1); } }