diff options
Diffstat (limited to 'indra/llcommon')
107 files changed, 8045 insertions, 681 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 48e053b8ee..f785698612 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -4,12 +4,19 @@ project(llcommon) include(00-Common) include(LLCommon) +include(Linking) include(Boost) +include (Pth) + +if (WINDOWS) + include(CopyWinLibs) +endif (WINDOWS) include_directories( ${EXPAT_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} + ${PTH_INCLUDE_DIRS} ) # add_executable(lltreeiterators lltreeiterators.cpp) @@ -26,6 +33,7 @@ set(llcommon_SOURCE_FILES llbase32.cpp llbase64.cpp llcommon.cpp + llcoros.cpp llcrc.cpp llcriticaldamp.cpp llcursortypes.cpp @@ -34,11 +42,15 @@ set(llcommon_SOURCE_FILES llerror.cpp llerrorthread.cpp llevent.cpp + lleventcoro.cpp + lleventdispatcher.cpp + lleventfilter.cpp llevents.cpp llfasttimer.cpp llfile.cpp llfindlocale.cpp llfixedbuffer.cpp + llfoldertype.cpp llformat.cpp llframetimer.cpp llheartbeat.cpp @@ -64,6 +76,7 @@ set(llcommon_SOURCE_FILES llsdserialize_xml.cpp llsdutil.cpp llsecondlifeurls.cpp + llsingleton.cpp llstat.cpp llstacktrace.cpp llstreamtools.cpp @@ -106,6 +119,7 @@ set(llcommon_HEADER_FILES llchat.h llclickaction.h llcommon.h + llcoros.h llcrc.h llcriticaldamp.h llcursortypes.h @@ -127,6 +141,9 @@ set(llcommon_HEADER_FILES llerrorlegacy.h llerrorthread.h llevent.h + lleventcoro.h + lleventdispatcher.h + lleventfilter.h llevents.h lleventemitter.h llextendedstatus.h @@ -134,6 +151,7 @@ set(llcommon_HEADER_FILES llfile.h llfindlocale.h llfixedbuffer.h + llfoldertype.h llformat.h llframetimer.h llhash.h @@ -141,6 +159,7 @@ set(llcommon_HEADER_FILES llhttpstatuscodes.h llindexedqueue.h llinstancetracker.h + llinstancetracker.h llkeythrottle.h lllazy.h lllinkedqueue.h @@ -222,33 +241,81 @@ set_source_files_properties(${llcommon_HEADER_FILES} list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) -add_library (llcommon ${llcommon_SOURCE_FILES}) -target_link_libraries(llcommon +if(LLCOMMON_LINK_SHARED) + add_library (llcommon SHARED ${llcommon_SOURCE_FILES}) + + if(SHARED_LIB_STAGING_DIR) + # *FIX:Mani --- + # llcommon.dll get written to the DLL staging directory. + # Also this directory is shared with RunBuildTest.cmake, y'know, for the tests. + set_target_properties(llcommon PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${SHARED_LIB_STAGING_DIR}) + if(NOT WINDOWS) + get_target_property(LLCOMMON_PATH llcommon LOCATION) + get_filename_component(LLCOMMON_FILE ${LLCOMMON_PATH} NAME) + add_custom_command( + TARGET llcommon POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${LLCOMMON_PATH} + ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/${LLCOMMON_FILE} + COMMENT "Copying llcommon to the staging folder." + ) + endif(NOT WINDOWS) + endif(SHARED_LIB_STAGING_DIR) + + if (DARWIN) + set_target_properties(llcommon PROPERTIES + BUILD_WITH_INSTALL_RPATH 1 + INSTALL_NAME_DIR "@executable_path/../Resources" + ) + endif(DARWIN) + +else(LLCOMMON_LINK_SHARED) + add_library (llcommon ${llcommon_SOURCE_FILES}) +endif(LLCOMMON_LINK_SHARED) + +target_link_libraries( + llcommon ${APRUTIL_LIBRARIES} ${APR_LIBRARIES} ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES} + ${WINDOWS_LIBRARIES} ${BOOST_PROGRAM_OPTIONS_LIBRARY} ${BOOST_REGEX_LIBRARY} + ${PTH_LIBRARIES} ) -#add unit tests -INCLUDE(LLAddBuildTest) +add_dependencies(llcommon stage_third_party_libs) + +include(LLAddBuildTest) SET(llcommon_TEST_SOURCE_FILES # unit-testing llcommon is not possible right now as the test-harness *itself* depends upon llcommon, causing a circular dependency. Add your 'unit' tests as integration tests for now. ) LL_ADD_PROJECT_UNIT_TESTS(llcommon "${llcommon_TEST_SOURCE_FILES}") #set(TEST_DEBUG on) -set(test_libs llcommon ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) +set(test_libs llcommon ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES} ${GOOGLEMOCK_LIBRARIES}) LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}") +LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. #ADD_BUILD_TEST(llallocator llcommon) #ADD_BUILD_TEST(llallocator_heap_profile llcommon) #ADD_BUILD_TEST(llmemtype llcommon) + diff --git a/indra/llcommon/bitpack.cpp b/indra/llcommon/bitpack.cpp new file mode 100644 index 0000000000..8db403febf --- /dev/null +++ b/indra/llcommon/bitpack.cpp @@ -0,0 +1,36 @@ +/** + * @file bitpack.cpp + * @brief LLBitPack class implementation + * + * $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" + +// implementation is all in the header, this include dep ensures the unit test is rerun if the implementation changes. +#include "bitpack.h" diff --git a/indra/llcommon/indra_constants.h b/indra/llcommon/indra_constants.h index 4836d41fb3..6b75a720af 100644 --- a/indra/llcommon/indra_constants.h +++ b/indra/llcommon/indra_constants.h @@ -253,6 +253,10 @@ const U8 SIM_ACCESS_MAX = SIM_ACCESS_ADULT; // group constants const S32 MAX_AGENT_GROUPS = 25; +// attachment constants +const S32 MAX_AGENT_ATTACHMENTS = 38; +const U8 ATTACHMENT_ADD = 0x80; + // god levels const U8 GOD_MAINTENANCE = 250; const U8 GOD_FULL = 200; diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 8687a24655..c2eb867795 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -72,13 +72,7 @@ #ifdef LL_WINDOWS // Reenable warnings we disabled above #pragma warning (3 : 4702) // unreachable code, we like level 3, not 4 -// level 4 warnings that we need to disable: -#pragma warning (disable : 4100) // unreferenced formal parameter -#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) -#pragma warning (disable : 4244) // possible loss of data on conversions -#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template -#pragma warning (disable : 4512) // assignment operator could not be generated -#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) +// moved msvc warnings to llpreprocessor.h *TODO - delete this comment after merge conflicts are unlikely -brad #endif // LL_WINDOWS // Linden only libs in alpha-order other than stdtypes.h diff --git a/indra/llcommon/llallocator.h b/indra/llcommon/llallocator.h index 2b70fee0b8..50129b4526 100644 --- a/indra/llcommon/llallocator.h +++ b/indra/llcommon/llallocator.h @@ -38,7 +38,7 @@ #include "llmemtype.h" #include "llallocator_heap_profile.h" -class LLAllocator { +class LL_COMMON_API LLAllocator { friend class LLMemoryView; friend class LLMemType; diff --git a/indra/llcommon/llallocator_heap_profile.cpp b/indra/llcommon/llallocator_heap_profile.cpp index d82ee9ed81..0a807702d0 100644 --- a/indra/llcommon/llallocator_heap_profile.cpp +++ b/indra/llcommon/llallocator_heap_profile.cpp @@ -38,6 +38,7 @@ // disable warning about boost::lexical_cast returning uninitialized data // when it fails to parse the string #pragma warning (disable:4701) +#pragma warning (disable:4702) #endif #include <boost/algorithm/string/split.hpp> diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index e32a293f1c..1a052ce62d 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -62,7 +62,7 @@ public: }; #endif -class LLApp : public LLOptionInterface +class LL_COMMON_API LLApp : public LLOptionInterface { friend class LLErrorThread; public: diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 63130a89fc..a1fcd2cf8d 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -48,25 +48,25 @@ #include "apr_atomic.h" #include "llstring.h" -extern apr_thread_mutex_t* gLogMutexp; +extern LL_COMMON_API apr_thread_mutex_t* gLogMutexp; extern apr_thread_mutex_t* gCallStacksLogMutexp; /** * @brief initialize the common apr constructs -- apr itself, the * global pool, and a mutex. */ -void ll_init_apr(); +void LL_COMMON_API ll_init_apr(); /** * @brief Cleanup those common apr constructs. */ -void ll_cleanup_apr(); +void LL_COMMON_API ll_cleanup_apr(); // //LL apr_pool //manage apr_pool_t, destroy allocated apr_pool in the destruction function. // -class LLAPRPool +class LL_COMMON_API LLAPRPool { public: LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE) ; @@ -92,7 +92,7 @@ protected: //which clears memory automatically. //so it can not hold static data or data after memory is cleared // -class LLVolatileAPRPool : public LLAPRPool +class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool { public: LLVolatileAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE); @@ -118,7 +118,7 @@ private: * destructor handles the unlock. Instances of this class are * <b>not</b> thread safe. */ -class LLScopedLock : private boost::noncopyable +class LL_COMMON_API LLScopedLock : private boost::noncopyable { public: /** @@ -192,7 +192,7 @@ typedef LLAtomic32<S32> LLAtomicS32; // 1, a temperary pool passed to an APRFile function, which is used within this function and only once. // 2, a global pool. // -class LLAPRFile +class LL_COMMON_API LLAPRFile { private: apr_file_t* mFile ; @@ -250,10 +250,10 @@ public: * APR_SUCCESS. * @return Returns <code>true</code> if status is an error condition. */ -bool ll_apr_warn_status(apr_status_t status); +bool LL_COMMON_API ll_apr_warn_status(apr_status_t status); -void ll_apr_assert_status(apr_status_t status); +void LL_COMMON_API ll_apr_assert_status(apr_status_t status); -extern "C" apr_pool_t* gAPRPoolp; // Global APR memory pool +extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool #endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 5d7672b378..6d5b12d840 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -43,30 +43,21 @@ struct AssetEntry : public LLDictionaryEntry { AssetEntry(const char *desc_name, - const char *type_name, // 8 character limit! - const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one - const char *category_name, // used by llinventorymodel when creating new categories - EDragAndDropType dad_type, - bool can_link, // can you create a link to this type? - bool is_protected) // can the viewer change categories of this type? + const char *type_name, // 8 character limit! + const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one + bool can_link) // can you create a link to this type? : LLDictionaryEntry(desc_name), mTypeName(type_name), mHumanName(human_name), - mCategoryName(category_name), - mDadType(dad_type), - mCanLink(can_link), - mIsProtected(is_protected) + mCanLink(can_link) { llassert(strlen(mTypeName) <= 8); } const char *mTypeName; const char *mHumanName; - const char *mCategoryName; - EDragAndDropType mDadType; bool mCanLink; - bool mIsProtected; }; class LLAssetDictionary : public LLSingleton<LLAssetDictionary>, @@ -78,48 +69,32 @@ public: LLAssetDictionary::LLAssetDictionary() { - // DESCRIPTION TYPE NAME HUMAN NAME CATEGORY NAME DRAG&DROP CAN LINK? PROTECTED? - // |--------------------|-----------|-------------------|-------------------|---------------|-----------|-----------| - addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", "Textures", DAD_TEXTURE, TRUE, TRUE)); - addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", "Sounds", DAD_SOUND, TRUE, TRUE)); - addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", "Calling Cards", DAD_CALLINGCARD, TRUE, TRUE)); - addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", "Landmarks", DAD_LANDMARK, TRUE, TRUE)); - addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", "Scripts", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", "Clothing", DAD_CLOTHING, TRUE, TRUE)); - addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", "Objects", DAD_OBJECT, TRUE, TRUE)); - addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", "Notecards", DAD_NOTECARD, TRUE, TRUE)); - addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", "New Folder", DAD_CATEGORY, TRUE, TRUE)); - addEntry(LLAssetType::AT_ROOT_CATEGORY, new AssetEntry("ROOT_CATEGORY", "root", "root", "Inventory", DAD_ROOT_CATEGORY, TRUE, TRUE)); - addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", "Scripts", DAD_SCRIPT, TRUE, TRUE)); - addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", "Scripts", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", "Uncompressed Images", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", "Body Parts", DAD_BODYPART, TRUE, TRUE)); - addEntry(LLAssetType::AT_TRASH, new AssetEntry("TRASH", "trash", "trash", "Trash", DAD_NONE, FALSE, TRUE)); - addEntry(LLAssetType::AT_SNAPSHOT_CATEGORY, new AssetEntry("SNAPSHOT_CATEGORY", "snapshot", "snapshot", "Photo Album", DAD_NONE, FALSE, TRUE)); - addEntry(LLAssetType::AT_LOST_AND_FOUND, new AssetEntry("LOST_AND_FOUND", "lstndfnd", "lost and found", "Lost And Found", DAD_NONE, FALSE, TRUE)); - addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", "Uncompressed SoundS", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", "Uncompressed Images", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", "Uncompressed Images", DAD_NONE, TRUE, TRUE)); - addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", "Animations", DAD_ANIMATION, TRUE, TRUE)); - addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", "Gestures", DAD_GESTURE, TRUE, TRUE)); - addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", "New Folder", DAD_NONE, FALSE, TRUE)); - addEntry(LLAssetType::AT_FAVORITE, new AssetEntry("FAVORITE", "favorite", "favorite", "favorite", DAD_NONE, FALSE, TRUE)); - - addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "symbolic link", "Link", DAD_LINK, FALSE, TRUE)); - addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "symbolic folder link", "New Folder", DAD_LINK, FALSE, TRUE)); - - for (S32 ensemble_num = S32(LLAssetType::AT_FOLDER_ENSEMBLE_START); - ensemble_num <= S32(LLAssetType::AT_FOLDER_ENSEMBLE_END); - ensemble_num++) - { - addEntry(LLAssetType::EType(ensemble_num), new AssetEntry("ENSEMBLE", "ensemble", "ensemble", "New Folder", DAD_CATEGORY, TRUE, FALSE)); - } - - addEntry(LLAssetType::AT_CURRENT_OUTFIT, new AssetEntry("CURRENT", "current", "current outfit", "Current Outfit", DAD_CATEGORY, FALSE, TRUE)); - addEntry(LLAssetType::AT_OUTFIT, new AssetEntry("OUTFIT", "outfit", "outfit", "New Outfit", DAD_CATEGORY, TRUE, FALSE)); - addEntry(LLAssetType::AT_MY_OUTFITS, new AssetEntry("MY_OUTFITS", "my_otfts", "my outfits", "My Outfits", DAD_CATEGORY, FALSE, TRUE)); - - addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, "New Folder", DAD_NONE, FALSE, FALSE)); + // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? + // |--------------------|-----------|-------------------|-----------| + addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", FALSE)); + addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", FALSE)); + addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", FALSE)); + addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", FALSE)); + addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", FALSE)); + addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", TRUE)); + addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", TRUE)); + addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", FALSE)); + addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", TRUE)); + addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", FALSE)); + addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", FALSE)); + addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", FALSE)); + addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", TRUE)); + addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", FALSE)); + addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", FALSE)); + addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", FALSE)); + addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", FALSE)); + addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", TRUE)); + addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", FALSE)); + + addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "symbolic link", FALSE)); + addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "symbolic folder link", FALSE)); + + addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE)); }; // static @@ -140,8 +115,7 @@ const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) } else { - static const std::string error_string = "BAD TYPE"; - return error_string; + return badLookup(); } } @@ -156,7 +130,7 @@ const char *LLAssetType::lookup(LLAssetType::EType asset_type) } else { - return "-1"; + return badLookup().c_str(); } } @@ -166,6 +140,7 @@ LLAssetType::EType LLAssetType::lookup(const char* name) return lookup(ll_safe_string(name)); } +// static LLAssetType::EType LLAssetType::lookup(const std::string& type_name) { const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); @@ -193,7 +168,7 @@ const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) } else { - return NULL; + return badLookup().c_str(); } } @@ -203,6 +178,7 @@ LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) return lookupHumanReadable(ll_safe_string(name)); } +// static LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) { const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); @@ -211,7 +187,7 @@ LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_ iter++) { const AssetEntry *entry = iter->second; - if (readable_name == entry->mHumanName) + if (entry->mHumanName && (readable_name == entry->mHumanName)) { return iter->first; } @@ -220,32 +196,6 @@ LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_ } // static -const char *LLAssetType::lookupCategoryName(LLAssetType::EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCategoryName; - } - else - { - return "New Folder"; - } -} - -// static -EDragAndDropType LLAssetType::lookupDragAndDropType(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - return entry->mDadType; - else - return DAD_NONE; -} - -// static bool LLAssetType::lookupCanLink(EType asset_type) { const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); @@ -269,36 +219,9 @@ bool LLAssetType::lookupIsLinkType(EType asset_type) } // static -// Only ensembles and plain folders aren't protected. "Protected" means -// you can't change certain properties such as their type. -bool LLAssetType::lookupIsProtectedCategoryType(EType asset_type) +const std::string &LLAssetType::badLookup() { - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mIsProtected; - } - return true; -} + static const std::string sBadLookup = "llassettype_bad_lookup"; + return sBadLookup; -// static -bool LLAssetType::lookupIsEnsembleCategoryType(EType asset_type) -{ - return (asset_type >= AT_FOLDER_ENSEMBLE_START && - asset_type <= AT_FOLDER_ENSEMBLE_END); -} - -// static. Generate a good default description -void LLAssetType::generateDescriptionFor(LLAssetType::EType asset_type, - std::string& description) -{ - const S32 BUF_SIZE = 30; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - time_t now; - time(&now); - memset(time_str, '\0', BUF_SIZE); - strftime(time_str, BUF_SIZE - 1, "%Y-%m-%d %H:%M:%S ", localtime(&now)); - description.assign(time_str); - description.append(LLAssetType::lookupHumanReadable(asset_type)); } diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 5e51188541..ec2290d30e 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -37,7 +37,7 @@ #include "stdenums.h" // for EDragAndDropType -class LLAssetType +class LL_COMMON_API LLAssetType { public: enum EType @@ -94,18 +94,6 @@ public: AT_BODYPART = 13, // A collection of textures and parameters that can be worn by an avatar. - AT_TRASH = 14, - // Only to be used as a marker for a category preferred type. - // Using this, we can throw things in the trash before completely deleting. - - AT_SNAPSHOT_CATEGORY = 15, - // A marker for a folder meant for snapshots. - // No actual assets will be snapshots, though if there were, you - // could interpret them as textures. - - AT_LOST_AND_FOUND = 16, - // Used to stuff lost&found items into. - AT_SOUND_WAV = 17, // Uncompressed sound. @@ -126,37 +114,22 @@ public: AT_SIMSTATE = 22, // Simstate file. - AT_FAVORITE = 23, - // favorite items - AT_LINK = 24, // Inventory symbolic link AT_LINK_FOLDER = 25, // Inventory folder link - - AT_FOLDER_ENSEMBLE_START = 26, - AT_FOLDER_ENSEMBLE_END = 45, - // This range is reserved for special clothing folder types. - - AT_CURRENT_OUTFIT = 46, - // Current outfit - - AT_OUTFIT = 47, - // Predefined outfit ("look") - - AT_MY_OUTFITS = 48, - // Folder that holds your outfits. - - AT_COUNT = 49, + AT_COUNT = 26, + // +*********************************************************+ // | TO ADD AN ELEMENT TO THIS ENUM: | // +*********************************************************+ // | 1. INSERT BEFORE AT_COUNT | // | 2. INCREMENT AT_COUNT BY 1 | - // | 3. ADD TO LLAssetDictionary in LLAssetType.cpp | - // | 3. ADD TO DEFAULT_ASSET_FOR_INV in LLInventoryType.cpp | + // | 3. ADD TO LLAssetType.cpp | + // | 4. ADD TO LLViewerAssetType.cpp | + // | 5. ADD TO DEFAULT_ASSET_FOR_INV in LLInventoryType.cpp | // +*********************************************************+ AT_NONE = -1 @@ -172,33 +145,17 @@ public: static EType lookupHumanReadable(const std::string& readable_name); static const char* lookupHumanReadable(EType asset_type); - // Generate a good default description. You may want to add a verb - // or agent name after this depending on your application. - static void generateDescriptionFor(LLAssetType::EType asset_type, - std::string& description); - static EType getType(const std::string& desc_name); static const std::string& getDesc(EType asset_type); - static EDragAndDropType lookupDragAndDropType(EType asset_type); static bool lookupCanLink(EType asset_type); static bool lookupIsLinkType(EType asset_type); - static const char* lookupCategoryName(EType asset_type); - static bool lookupIsProtectedCategoryType(EType asset_type); - static bool lookupIsEnsembleCategoryType(EType asset_type); - - /* TODO: Change return types from "const char *" to "const std::string &". - This is fairly straightforward, but requires changing some calls to use .c_str(). - e.g.: - - fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); - + fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType).c_str()); - */ - -private: - // don't instantiate or derive one of these objects - LLAssetType( void ) {} - ~LLAssetType( void ) {} + static const std::string& badLookup(); // error string when a lookup fails + +protected: + LLAssetType() {} + ~LLAssetType() {} }; #endif // LL_LLASSETTYPE_H diff --git a/indra/llcommon/llbase32.h b/indra/llcommon/llbase32.h index 63a93e11ab..0697f7b8e2 100644 --- a/indra/llcommon/llbase32.h +++ b/indra/llcommon/llbase32.h @@ -32,9 +32,9 @@ */ #ifndef LLBASE32_H -#define LLBASE32_h +#define LLBASE32_H -class LLBase32 +class LL_COMMON_API LLBase32 { public: static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index 58414bba8b..c48fea2478 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -32,9 +32,9 @@ */ #ifndef LLBASE64_H -#define LLBASE64_h +#define LLBASE64_H -class LLBase64 +class LL_COMMON_API LLBase64 { public: static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index a1808e8a6c..b36471f9f8 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -37,7 +37,7 @@ #include "lltimer.h" #include "llfile.h" -class LLCommon +class LL_COMMON_API LLCommon { public: static void initClass(); diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp new file mode 100644 index 0000000000..377bfaa247 --- /dev/null +++ b/indra/llcommon/llcoros.cpp @@ -0,0 +1,137 @@ +/** + * @file llcoros.cpp + * @author Nat Goodspeed + * @date 2009-06-03 + * @brief Implementation for llcoros. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcoros.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "stringize.h" + +LLCoros::LLCoros() +{ + // Register our cleanup() method for "mainloop" ticks + LLEventPumps::instance().obtain("mainloop").listen( + "LLCoros", boost::bind(&LLCoros::cleanup, this, _1)); +} + +bool LLCoros::cleanup(const LLSD&) +{ + // Walk the mCoros map, checking and removing completed coroutines. + for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ) + { + // Has this coroutine exited (normal return, exception, exit() call) + // since last tick? + if (mi->second->exited()) + { + LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; + // The erase() call will invalidate its passed iterator value -- + // so increment mi FIRST -- but pass its original value to + // erase(). This is what postincrement is all about. + mCoros.erase(mi++); + } + else + { + // Still live, just skip this entry as if incrementing at the top + // of the loop as usual. + ++mi; + } + } + return false; +} + +std::string LLCoros::generateDistinctName(const std::string& prefix) const +{ + // Allowing empty name would make getName()'s not-found return ambiguous. + if (prefix.empty()) + { + LL_ERRS("LLCoros") << "LLCoros::launch(): pass non-empty name string" << LL_ENDL; + } + + // If the specified name isn't already in the map, just use that. + std::string name(prefix); + + // Find the lowest numeric suffix that doesn't collide with an existing + // entry. Start with 2 just to make it more intuitive for any interested + // parties: e.g. "joe", "joe2", "joe3"... + for (int i = 2; ; name = STRINGIZE(prefix << i++)) + { + if (mCoros.find(name) == mCoros.end()) + { + LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; + return name; + } + } +} + +bool LLCoros::kill(const std::string& name) +{ + CoroMap::iterator found = mCoros.find(name); + if (found == mCoros.end()) + { + return false; + } + // Because this is a boost::ptr_map, erasing the map entry also destroys + // the referenced heap object, in this case the boost::coroutine object, + // which will terminate the coroutine. + mCoros.erase(found); + return true; +} + +std::string LLCoros::getNameByID(const void* self_id) const +{ + // Walk the existing coroutines, looking for one from which the 'self_id' + // passed to us comes. + for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi) + { + namespace coro_private = boost::coroutines::detail; + if (static_cast<void*>(coro_private::coroutine_accessor::get_impl(const_cast<coro&>(*mi->second)).get()) + == self_id) + { + return mi->first; + } + } + return ""; +} + +/***************************************************************************** +* MUST BE LAST +*****************************************************************************/ +// Turn off MSVC optimizations for just LLCoros::launchImpl() -- see +// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it +// does for warning suppression, and we really don't want to force +// optimization ON for other code even in Debug or RelWithDebInfo builds. + +#if LL_MSVC +// work around broken optimizations +#pragma warning(disable: 4748) +#pragma optimize("", off) +#endif // LL_MSVC + +std::string LLCoros::launchImpl(const std::string& prefix, coro* newCoro) +{ + std::string name(generateDistinctName(prefix)); + mCoros.insert(name, newCoro); + /* Run the coroutine until its first wait, then return here */ + (*newCoro)(std::nothrow); + return name; +} + +#if LL_MSVC +// reenable optimizations +#pragma optimize("", on) +#endif // LL_MSVC diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h new file mode 100644 index 0000000000..141b0df43c --- /dev/null +++ b/indra/llcommon/llcoros.h @@ -0,0 +1,149 @@ +/** + * @file llcoros.h + * @author Nat Goodspeed + * @date 2009-06-02 + * @brief Manage running boost::coroutine instances + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCOROS_H) +#define LL_LLCOROS_H + +#include <boost/coroutine/coroutine.hpp> +#include "llsingleton.h" +#include <boost/ptr_container/ptr_map.hpp> +#include <string> +#include <boost/preprocessor/repetition/enum_params.hpp> +#include <boost/preprocessor/repetition/enum_binary_params.hpp> +#include <boost/preprocessor/iteration/local.hpp> +#include <stdexcept> + +/** + * Registry of named Boost.Coroutine instances + * + * The Boost.Coroutine library supports the general case of a coroutine + * accepting arbitrary parameters and yielding multiple (sets of) results. For + * such use cases, it's natural for the invoking code to retain the coroutine + * instance: the consumer repeatedly calls into the coroutine, perhaps passing + * new parameter values, prompting it to yield its next result. + * + * Our typical coroutine usage is different, though. For us, coroutines + * provide an alternative to the @c Responder pattern. Our typical coroutine + * has @c void return, invoked in fire-and-forget mode: the handler for some + * user gesture launches the coroutine and promptly returns to the main loop. + * The coroutine initiates some action that will take multiple frames (e.g. a + * capability request), waits for its result, processes it and silently steals + * away. + * + * This usage poses two (related) problems: + * + * # Who should own the coroutine instance? If it's simply local to the + * handler code that launches it, return from the handler will destroy the + * coroutine object, terminating the coroutine. + * # Once the coroutine terminates, in whatever way, who's responsible for + * cleaning up the coroutine object? + * + * LLCoros is a Singleton collection of currently-active coroutine instances. + * Each has a name. You ask LLCoros to launch a new coroutine with a suggested + * name prefix; from your prefix it generates a distinct name, registers the + * new coroutine and returns the actual name. + * + * The name can be used to kill off the coroutine prematurely, if needed. It + * can also provide diagnostic info: we can look up the name of the + * currently-running coroutine. + * + * Finally, the next frame ("mainloop" event) after the coroutine terminates, + * LLCoros will notice its demise and destroy it. + */ +class LL_COMMON_API LLCoros: public LLSingleton<LLCoros> +{ +public: + /// Canonical boost::coroutines::coroutine signature we use + typedef boost::coroutines::coroutine<void()> coro; + /// Canonical 'self' type + typedef coro::self self; + + /** + * Create and start running a new coroutine with specified name. The name + * string you pass is a suggestion; it will be tweaked for uniqueness. The + * actual name is returned to you. + * + * Usage looks like this, for (e.g.) two coroutine parameters: + * @code + * class MyClass + * { + * public: + * ... + * // Do NOT NOT NOT accept reference params other than 'self'! + * // Pass by value only! + * void myCoroutineMethod(LLCoros::self& self, std::string, LLSD); + * ... + * }; + * ... + * std::string name = LLCoros::instance().launch( + * "mycoro", boost::bind(&MyClass::myCoroutineMethod, this, _1, + * "somestring", LLSD(17)); + * @endcode + * + * Your function/method must accept LLCoros::self& as its first parameter. + * It can accept any other parameters you want -- but ONLY BY VALUE! + * Other reference parameters are a BAD IDEA! You Have Been Warned. See + * DEV-32777 comments for an explanation. + * + * Pass a callable that accepts the single LLCoros::self& parameter. It + * may work to pass a free function whose only parameter is 'self'; for + * all other cases use boost::bind(). Of course, for a non-static class + * method, the first parameter must be the class instance. Use the + * placeholder _1 for the 'self' parameter. Any other parameters should be + * passed via the bind() expression. + * + * launch() tweaks the suggested name so it won't collide with any + * existing coroutine instance, creates the coroutine instance, registers + * it with the tweaked name and runs it until its first wait. At that + * point it returns the tweaked name. + */ + template <typename CALLABLE> + std::string launch(const std::string& prefix, const CALLABLE& callable) + { + return launchImpl(prefix, new coro(callable)); + } + + /** + * Abort a running coroutine by name. Normally, when a coroutine either + * runs to completion or terminates with an exception, LLCoros quietly + * cleans it up. This is for use only when you must explicitly interrupt + * one prematurely. Returns @c true if the specified name was found and + * still running at the time. + */ + bool kill(const std::string& name); + + /** + * From within a coroutine, pass its @c self object to look up the + * (tweaked) name string by which this coroutine is registered. Returns + * the empty string if not found (e.g. if the coroutine was launched by + * hand rather than using LLCoros::launch()). + */ + template <typename COROUTINE_SELF> + std::string getName(const COROUTINE_SELF& self) const + { + return getNameByID(self.get_id()); + } + + /// getName() by self.get_id() + std::string getNameByID(const void* self_id) const; + +private: + friend class LLSingleton<LLCoros>; + LLCoros(); + std::string launchImpl(const std::string& prefix, coro* newCoro); + std::string generateDistinctName(const std::string& prefix) const; + bool cleanup(const LLSD&); + + typedef boost::ptr_map<std::string, coro> CoroMap; + CoroMap mCoros; +}; + +#endif /* ! defined(LL_LLCOROS_H) */ diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index 27fae7d269..74369062cc 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -50,7 +50,7 @@ // llinfos << "File crc: " << crc.getCRC() << llendl; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLCRC +class LL_COMMON_API LLCRC { protected: U32 mCurrent; diff --git a/indra/llcommon/llcriticaldamp.h b/indra/llcommon/llcriticaldamp.h index ad98284a6c..1ea5914b5b 100644 --- a/indra/llcommon/llcriticaldamp.h +++ b/indra/llcommon/llcriticaldamp.h @@ -38,7 +38,7 @@ #include "llframetimer.h" -class LLCriticalDamp +class LL_COMMON_API LLCriticalDamp { public: LLCriticalDamp(); diff --git a/indra/llcommon/llcursortypes.h b/indra/llcommon/llcursortypes.h index 35dbeaf16e..a1b8178bfe 100644 --- a/indra/llcommon/llcursortypes.h +++ b/indra/llcommon/llcursortypes.h @@ -71,6 +71,6 @@ enum ECursorType { UI_CURSOR_COUNT // Number of elements in this enum (NOT a cursor) }; -ECursorType getCursorFromString(const std::string& cursor_string); +LL_COMMON_API ECursorType getCursorFromString(const std::string& cursor_string); #endif // LL_LLCURSORTYPES_H diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index 2655a2d283..f8b2f2f163 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -46,7 +46,7 @@ * * The date class represents a point in time after epoch - 1970-01-01. */ -class LLDate +class LL_COMMON_API LLDate { public: /** @@ -156,14 +156,11 @@ private: }; // Helper function to stream out a date -std::ostream& operator<<(std::ostream& s, const LLDate& date); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLDate& date); // Helper function to stream in a date -std::istream& operator>>(std::istream& s, LLDate& date); +LL_COMMON_API std::istream& operator>>(std::istream& s, LLDate& date); -const static std::string weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; - -const static std::string months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; #endif // LL_LLDATE_H diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h index 82f53c6e17..e6229db834 100644 --- a/indra/llcommon/lldependencies.h +++ b/indra/llcommon/lldependencies.h @@ -81,7 +81,7 @@ struct instance_from_range: public TYPE * LLDependencies components that should not be reinstantiated for each KEY, * NODE specialization */ -class LLDependenciesBase +class LL_COMMON_API LLDependenciesBase { public: virtual ~LLDependenciesBase() {} diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 6794be4904..5a4c644859 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -131,7 +131,7 @@ namespace LLError class CallSite; - class Log + class LL_COMMON_API Log { public: static bool shouldLog(CallSite&); @@ -140,7 +140,7 @@ namespace LLError static void flush(std::ostringstream*, const CallSite&); }; - class CallSite + class LL_COMMON_API CallSite { // Represents a specific place in the code where a message is logged // This is public because it is used by the macros below. It is not @@ -189,7 +189,7 @@ namespace LLError //LLCallStacks is designed not to be thread-safe. //so try not to use it in multiple parallel threads at same time. //Used in a single thread at a time is fine. - class LLCallStacks + class LL_COMMON_API LLCallStacks { private: static char** sBuffer ; @@ -239,7 +239,7 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; */ #define lllog(level, broadTag, narrowTag, once) \ - { \ + do { \ static LLError::CallSite _site( \ level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), __FUNCTION__, broadTag, narrowTag, once);\ if (_site.shouldLog()) \ @@ -252,7 +252,7 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; LLError::End(); \ LLError::Log::flush(_out, _site); \ } \ - } + } while(0) // DEPRECATED: Use the new macros that allow tags and *look* like macros. #define lldebugs lllog(LLError::LEVEL_DEBUG, NULL, NULL, false) diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index fab0a1ef9f..233e9d3389 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -63,12 +63,12 @@ public: namespace LLError { - void initForServer(const std::string& identity); + LL_COMMON_API void initForServer(const std::string& identity); // resets all logging settings to defaults needed by server processes // logs to stderr, syslog, and windows debug log // the identity string is used for in the syslog - void initForApplication(const std::string& dir); + LL_COMMON_API void initForApplication(const std::string& dir); // resets all logging settings to defaults needed by applicaitons // logs to stderr and windows debug log // sets up log configuration from the file logcontrol.xml in dir @@ -79,14 +79,14 @@ namespace LLError Setting a level means log messages at that level or above. */ - void setPrintLocation(bool); - void setDefaultLevel(LLError::ELevel); - void setFunctionLevel(const std::string& function_name, LLError::ELevel); - void setClassLevel(const std::string& class_name, LLError::ELevel); - void setFileLevel(const std::string& file_name, LLError::ELevel); - void setTagLevel(const std::string& file_name, LLError::ELevel); + LL_COMMON_API void setPrintLocation(bool); + LL_COMMON_API void setDefaultLevel(LLError::ELevel); + LL_COMMON_API void setFunctionLevel(const std::string& function_name, LLError::ELevel); + LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel); + LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel); + LL_COMMON_API void setTagLevel(const std::string& file_name, LLError::ELevel); - void configure(const LLSD&); + LL_COMMON_API void configure(const LLSD&); // the LLSD can configure all of the settings // usually read automatically from the live errorlog.xml file @@ -96,21 +96,21 @@ namespace LLError */ typedef boost::function<void(const std::string&)> FatalFunction; - void crashAndLoop(const std::string& message); + LL_COMMON_API void crashAndLoop(const std::string& message); // Default fatal function: access null pointer and loops forever - void setFatalFunction(const FatalFunction&); + LL_COMMON_API void setFatalFunction(const FatalFunction&); // The fatal function will be called when an message of LEVEL_ERROR // is logged. Note: supressing a LEVEL_ERROR message from being logged // (by, for example, setting a class level to LEVEL_NONE), will keep // the that message from causing the fatal funciton to be invoked. - FatalFunction getFatalFunction(); + LL_COMMON_API FatalFunction getFatalFunction(); // Retrieve the previously-set FatalFunction /// temporarily override the FatalFunction for the duration of a /// particular scope, e.g. for unit tests - class OverrideFatalFunction + class LL_COMMON_API OverrideFatalFunction { public: OverrideFatalFunction(const FatalFunction& func): @@ -128,15 +128,15 @@ namespace LLError }; typedef std::string (*TimeFunction)(); - std::string utcTime(); + LL_COMMON_API std::string utcTime(); - void setTimeFunction(TimeFunction); + LL_COMMON_API void setTimeFunction(TimeFunction); // The function is use to return the current time, formatted for // display by those error recorders that want the time included. - class Recorder + class LL_COMMON_API Recorder { // An object that handles the actual output or error messages. public: @@ -150,17 +150,17 @@ namespace LLError // included in the text of the message }; - void addRecorder(Recorder*); - void removeRecorder(Recorder*); + LL_COMMON_API void addRecorder(Recorder*); + LL_COMMON_API void removeRecorder(Recorder*); // each error message is passed to each recorder via recordMessage() - void logToFile(const std::string& filename); - void logToFixedBuffer(LLLineBuffer*); + LL_COMMON_API void logToFile(const std::string& filename); + LL_COMMON_API void logToFixedBuffer(LLLineBuffer*); // Utilities to add recorders for logging to a file or a fixed buffer // A second call to the same function will remove the logger added // with the first. // Passing the empty string or NULL to just removes any prior. - std::string logFileName(); + LL_COMMON_API std::string logFileName(); // returns name of current logging file, empty string if none @@ -169,11 +169,11 @@ namespace LLError */ class Settings; - Settings* saveAndResetSettings(); - void restoreSettings(Settings *); + LL_COMMON_API Settings* saveAndResetSettings(); + LL_COMMON_API void restoreSettings(Settings *); - std::string abbreviateFile(const std::string& filePath); - int shouldLogCallCount(); + LL_COMMON_API std::string abbreviateFile(const std::string& filePath); + LL_COMMON_API int shouldLogCallCount(); }; diff --git a/indra/llcommon/llerrorthread.h b/indra/llcommon/llerrorthread.h index f1d6ffc34f..3121d29675 100644 --- a/indra/llcommon/llerrorthread.h +++ b/indra/llcommon/llerrorthread.h @@ -35,7 +35,7 @@ #include "llthread.h" -class LLErrorThread : public LLThread +class LL_COMMON_API LLErrorThread : public LLThread { public: LLErrorThread(); diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 2cc8577219..0ea7cf4ae8 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -47,7 +47,7 @@ class LLEventDispatcher; class LLObservable; // Abstract event. All events derive from LLEvent -class LLEvent : public LLThreadSafeRefCount +class LL_COMMON_API LLEvent : public LLThreadSafeRefCount { protected: virtual ~LLEvent(); @@ -75,7 +75,7 @@ private: }; // Abstract listener. All listeners derive from LLEventListener -class LLEventListener : public LLThreadSafeRefCount +class LL_COMMON_API LLEventListener : public LLThreadSafeRefCount { protected: virtual ~LLEventListener(); @@ -92,7 +92,7 @@ public: }; // A listener which tracks references to it and cleans up when it's deallocated -class LLSimpleListener : public LLEventListener +class LL_COMMON_API LLSimpleListener : public LLEventListener { public: void clearDispatchers(); @@ -117,7 +117,7 @@ struct LLListenerEntry // Base class for a dispatcher - an object which listens // to events being fired and relays them to their // appropriate destinations. -class LLEventDispatcher : public LLThreadSafeRefCount +class LL_COMMON_API LLEventDispatcher : public LLThreadSafeRefCount { protected: virtual ~LLEventDispatcher(); @@ -160,7 +160,7 @@ private: // In order for this class to work properly, it needs // an instance of an LLEventDispatcher to route events to their // listeners. -class LLObservable +class LL_COMMON_API LLObservable { public: // Initialize with the default Dispatcher diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp new file mode 100644 index 0000000000..d598f1cc4a --- /dev/null +++ b/indra/llcommon/lleventcoro.cpp @@ -0,0 +1,129 @@ +/** + * @file lleventcoro.cpp + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Implementation for lleventcoro. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventcoro.h" +// STL headers +#include <map> +// std headers +// external library headers +// other Linden headers +#include "llsdserialize.h" +#include "llerror.h" +#include "llcoros.h" + +std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id) +{ + // First, if this coroutine was launched by LLCoros::launch(), find that name. + std::string name(LLCoros::instance().getNameByID(self_id)); + if (! name.empty()) + { + return name; + } + // Apparently this coroutine wasn't launched by LLCoros::launch(). Check + // whether we have a memo for this self_id. + typedef std::map<const void*, std::string> MapType; + static MapType memo; + MapType::const_iterator found = memo.find(self_id); + if (found != memo.end()) + { + // this coroutine instance has called us before, reuse same name + return found->second; + } + // this is the first time we've been called for this coroutine instance + name = LLEventPump::inventName("coro"); + memo[self_id] = name; + LL_INFOS("LLEventCoro") << "listenerNameForCoroImpl(" << self_id << "): inventing coro name '" + << name << "'" << LL_ENDL; + return name; +} + +void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +{ + if (rawPath.isUndefined()) + { + // no-op case + return; + } + + // Arrange to treat rawPath uniformly as an array. If it's not already an + // array, store it as the only entry in one. + LLSD path; + if (rawPath.isArray()) + { + path = rawPath; + } + else + { + path.append(rawPath); + } + + // Need to indicate a current destination -- but that current destination + // needs to change as we step through the path array. Where normally we'd + // use an LLSD& to capture a subscripted LLSD lvalue, this time we must + // instead use a pointer -- since it must be reassigned. + LLSD* pdest = &dest; + + // Now loop through that array + for (LLSD::Integer i = 0; i < path.size(); ++i) + { + if (path[i].isString()) + { + // *pdest is an LLSD map + pdest = &((*pdest)[path[i].asString()]); + } + else if (path[i].isInteger()) + { + // *pdest is an LLSD array + pdest = &((*pdest)[path[i].asInteger()]); + } + else + { + // What do we do with Real or Array or Map or ...? + // As it's a coder error -- not a user error -- rub the coder's + // face in it so it gets fixed. + LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value + << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL; + } + } + + // Here *pdest is where we should store value. + *pdest = value; +} + +LLSD errorException(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), instead of + // returning it, deliver it via exception. + if (result.second) + { + throw LLErrorEvent(desc, result.first); + } + // That way, our caller knows a simple return must be from the reply + // pump (pump 0). + return result.first; +} + +LLSD errorLog(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), log it as a fatal + // error. + if (result.second) + { + LL_ERRS("errorLog") << desc << ":" << std::endl; + LLSDSerialize::toPrettyXML(result.first, LL_CONT); + LL_CONT << LL_ENDL; + } + // A simple return must therefore be from the reply pump (pump 0). + return result.first; +} diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h new file mode 100644 index 0000000000..c6d9de171d --- /dev/null +++ b/indra/llcommon/lleventcoro.h @@ -0,0 +1,549 @@ +/** + * @file lleventcoro.h + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Utilities to interface between coroutines and events. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTCORO_H) +#define LL_LLEVENTCORO_H + +#include <boost/coroutine/coroutine.hpp> +#include <boost/coroutine/future.hpp> +#include <boost/optional.hpp> +#include <string> +#include <stdexcept> +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a <tt>const LLEventPumpOrPumpName&</tt> and you can accept either an + * <tt>LLEventPump&</tt> or its string name. For a single parameter that could + * be either, it's not hard to overload the function -- but as soon as you + * want to accept two such parameters, this is cheaper than four overloads. + */ +class LLEventPumpOrPumpName +{ +public: + /// Pass an actual LLEventPump& + LLEventPumpOrPumpName(LLEventPump& pump): + mPump(pump) + {} + /// Pass the string name of an LLEventPump + LLEventPumpOrPumpName(const std::string& pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Pass string constant name of an LLEventPump. This override must be + /// explicit, since otherwise passing <tt>const char*</tt> to a function + /// accepting <tt>const LLEventPumpOrPumpName&</tt> would require two + /// different implicit conversions: <tt>const char*</tt> -> <tt>const + /// std::string&</tt> -> <tt>const LLEventPumpOrPumpName&</tt>. + LLEventPumpOrPumpName(const char* pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Unspecified: "I choose not to identify an LLEventPump." + LLEventPumpOrPumpName() {} + operator LLEventPump& () const { return *mPump; } + LLEventPump& getPump() const { return *mPump; } + operator bool() const { return mPump; } + bool operator!() const { return ! mPump; } + +private: + boost::optional<LLEventPump&> mPump; +}; + +/// This is an adapter for a signature like void LISTENER(const LLSD&), which +/// isn't a valid LLEventPump listener: such listeners should return bool. +template <typename LISTENER> +class LLVoidListener +{ +public: + LLVoidListener(const LISTENER& listener): + mListener(listener) + {} + bool operator()(const LLSD& event) + { + mListener(event); + // don't swallow the event, let other listeners see it + return false; + } +private: + LISTENER mListener; +}; + +/// LLVoidListener helper function to infer the type of the LISTENER +template <typename LISTENER> +LLVoidListener<LISTENER> voidlistener(const LISTENER& listener) +{ + return LLVoidListener<LISTENER>(listener); +} + +namespace LLEventDetail +{ + /** + * waitForEventOn() permits a coroutine to temporarily listen on an + * LLEventPump any number of times. We don't really want to have to ask + * the caller to label each such call with a distinct string; the whole + * point of waitForEventOn() is to present a nice sequential interface to + * the underlying LLEventPump-with-named-listeners machinery. So we'll use + * LLEventPump::inventName() to generate a distinct name for each + * temporary listener. On the other hand, because a given coroutine might + * call waitForEventOn() any number of times, we don't really want to + * consume an arbitrary number of generated inventName()s: that namespace, + * though large, is nonetheless finite. So we memoize an invented name for + * each distinct coroutine instance (each different 'self' object). We + * can't know the type of 'self', because it depends on the coroutine + * body's signature. So we cast its address to void*, looking for distinct + * pointer values. Yes, that means that an early coroutine could cache a + * value here, then be destroyed, only to be supplanted by a later + * coroutine (of the same or different type), and we'll end up + * "recognizing" the second one and reusing the listener name -- but + * that's okay, since it won't collide with any listener name used by the + * earlier coroutine since that earlier coroutine no longer exists. + */ + template <typename COROUTINE_SELF> + std::string listenerNameForCoro(COROUTINE_SELF& self) + { + return listenerNameForCoroImpl(self.get_id()); + } + + /// Implementation for listenerNameForCoro() + LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id); + + /** + * Implement behavior described for postAndWait()'s @a replyPumpNamePath + * parameter: + * + * * If <tt>path.isUndefined()</tt>, do nothing. + * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value + * into <tt>dest[path.asString()]</tt>. + * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a + * value into <tt>dest[path.asInteger()]</tt>. + * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step + * down through the structure of @a dest. The last array entry in @a + * path specifies the entry in the lowest-level structure in @a dest + * into which to store @a value. + * + * @note + * In the degenerate case in which @a path is an empty array, @a dest will + * @em become @a value rather than @em containing it. + */ + LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); +} // namespace LLEventDetail + +/** + * Post specified LLSD event on the specified LLEventPump, then wait for a + * response on specified other LLEventPump. This is more than mere + * convenience: the difference between this function and the sequence + * @code + * requestPump.post(myEvent); + * LLSD reply = waitForEventOn(self, replyPump); + * @endcode + * is that the sequence above fails if the reply is posted immediately on + * @a replyPump, that is, before <tt>requestPump.post()</tt> returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until <tt>requestPump.post()</tt> returns and @c waitForEventOn() is + * entered. Therefore, the coroutine completely misses an immediate reply + * event, making it wait indefinitely. + * + * By contrast, postAndWait() listens on the @a replyPump @em before posting + * the specified LLSD event on the specified @a requestPump. + * + * @param self The @c self object passed into a coroutine + * @param event LLSD data to be posted on @a requestPump + * @param requestPump an LLEventPump on which to post @a event. Pass either + * the LLEventPump& or its string name. However, if you pass a + * default-constructed @c LLEventPumpOrPumpName, we skip the post() call. + * @param replyPump an LLEventPump on which postAndWait() will listen for a + * reply. Pass either the LLEventPump& or its string name. The calling + * coroutine will wait until that reply arrives. (If you're concerned about a + * reply that might not arrive, please see also LLEventTimeout.) + * @param replyPumpNamePath specifies the location within @a event in which to + * store <tt>replyPump.getName()</tt>. This is a strictly optional convenience + * feature; obviously you can store the name in @a event "by hand" if desired. + * @a replyPumpNamePath can be specified in any of four forms: + * * @c isUndefined() (default-constructed LLSD object): do nothing. This is + * the default behavior if you omit @a replyPumpNamePath. + * * @c isInteger(): @a event is an array. Store <tt>replyPump.getName()</tt> + * in <tt>event[replyPumpNamePath.asInteger()]</tt>. + * * @c isString(): @a event is a map. Store <tt>replyPump.getName()</tt> in + * <tt>event[replyPumpNamePath.asString()]</tt>. + * * @c isArray(): @a event has several levels of structure, e.g. map of + * maps, array of arrays, array of maps, map of arrays, ... Store + * <tt>replyPump.getName()</tt> in + * <tt>event[replyPumpNamePath[0]][replyPumpNamePath[1]]...</tt> In other + * words, examine each array entry in @a replyPumpNamePath in turn. If it's an + * <tt>LLSD::String</tt>, the current level of @a event is a map; step down to + * that map entry. If it's an <tt>LLSD::Integer</tt>, the current level of @a + * event is an array; step down to that array entry. The last array entry in + * @a replyPumpNamePath specifies the entry in the lowest-level structure in + * @a event into which to store <tt>replyPump.getName()</tt>. + */ +template <typename SELF> +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ + // declare the future + boost::coroutines::future<LLSD> future(self); + // make a callback that will assign a value to the future, and listen on + // the specified LLEventPump with that callback + std::string listenerName(LLEventDetail::listenerNameForCoro(self)); + LLTempBoundListener connection( + replyPump.getPump().listen(listenerName, + voidlistener(boost::coroutines::make_callback(future)))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If replyPumpNamePath is non-empty, store the replyPump name in the + // request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " about to wait on LLEventPump " << replyPump.getPump().getName() + << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLSD value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; +} + +/// Wait for the next event on the specified LLEventPump. Pass either the +/// LLEventPump& or its string name. +template <typename SELF> +LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump) +{ + // This is now a convenience wrapper for postAndWait(). + return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump); +} + +/// return type for two-pump variant of waitForEventOn() +typedef std::pair<LLSD, int> LLEventWithID; + +namespace LLEventDetail +{ + /** + * This helper is specifically for the two-pump version of waitForEventOn(). + * We use a single future object, but we want to listen on two pumps with it. + * Since we must still adapt from (the callable constructed by) + * boost::coroutines::make_callback() (void return) to provide an event + * listener (bool return), we've adapted LLVoidListener for the purpose. The + * basic idea is that we construct a distinct instance of WaitForEventOnHelper + * -- binding different instance data -- for each of the pumps. Then, when a + * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine + * that LLSD with its discriminator to feed the future object. + */ + template <typename LISTENER> + class WaitForEventOnHelper + { + public: + WaitForEventOnHelper(const LISTENER& listener, int discriminator): + mListener(listener), + mDiscrim(discriminator) + {} + // this signature is required for an LLEventPump listener + bool operator()(const LLSD& event) + { + // our future object is defined to accept LLEventWithID + mListener(LLEventWithID(event, mDiscrim)); + // don't swallow the event, let other listeners see it + return false; + } + private: + LISTENER mListener; + const int mDiscrim; + }; + + /// WaitForEventOnHelper type-inference helper + template <typename LISTENER> + WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator) + { + return WaitForEventOnHelper<LISTENER>(listener, discriminator); + } +} // namespace LLEventDetail + +/** + * This function waits for a reply on either of two specified LLEventPumps. + * Otherwise, it closely resembles postAndWait(); please see the documentation + * for that function for detailed parameter info. + * + * While we could have implemented the single-pump variant in terms of this + * one, there's enough added complexity here to make it worthwhile to give the + * single-pump variant its own straightforward implementation. Conversely, + * though we could use preprocessor logic to generate n-pump overloads up to + * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump + * overload exists because certain event APIs are defined in terms of a reply + * LLEventPump and an error LLEventPump. + * + * The LLEventWithID return value provides not only the received event, but + * the index of the pump on which it arrived (0 or 1). + * + * @note + * I'd have preferred to overload the name postAndWait() for both signatures. + * But consider the following ambiguous call: + * @code + * postAndWait(self, LLSD(), requestPump, replyPump, "someString"); + * @endcode + * "someString" could be converted to either LLSD (@a replyPumpNamePath for + * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump + * function). + * + * It seems less burdensome to write postAndWait2() than to write either + * LLSD("someString") or LLEventOrPumpName("someString"). + */ +template <typename SELF> +LLEventWithID postAndWait2(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump0, + const LLEventPumpOrPumpName& replyPump1, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) +{ + // declare the future + boost::coroutines::future<LLEventWithID> future(self); + // either callback will assign a value to this future; listen on + // each specified LLEventPump with a callback + std::string name(LLEventDetail::listenerNameForCoro(self)); + LLTempBoundListener connection0( + replyPump0.getPump().listen(name + "a", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 0))); + LLTempBoundListener connection1( + replyPump1.getPump().listen(name + "b", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 1))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If either replyPumpNamePath is non-empty, store the corresponding + // replyPump name in the request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath, + replyPump0.getPump().getName()); + LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath, + replyPump1.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " about to wait on LLEventPumps " << replyPump0.getPump().getName() + << ", " << replyPump1.getPump().getName() << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLEventWithID value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name + << " resuming with (" << value.first << ", " << value.second << ")" + << LL_ENDL; + // returning should disconnect both connections + return value; +} + +/** + * Wait for the next event on either of two specified LLEventPumps. + */ +template <typename SELF> +LLEventWithID +waitForEventOn(SELF& self, + const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +{ + // This is now a convenience wrapper for postAndWait2(). + return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1); +} + +/** + * Helper for the two-pump variant of waitForEventOn(), e.g.: + * + * @code + * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump), + * "error response from login.cgi"); + * @endcode + * + * Examines an LLEventWithID, assuming that the second pump (pump 1) is + * listening for an error indication. If the incoming data arrived on pump 1, + * throw an LLErrorEvent exception. If the incoming data arrived on pump 0, + * just return it. Since a normal return can only be from pump 0, we no longer + * need the LLEventWithID's discriminator int; we can just return the LLSD. + * + * @note I'm not worried about introducing the (fairly generic) name + * errorException() into global namespace, because how many other overloads of + * the same name are going to accept an LLEventWithID parameter? + */ +LLSD errorException(const LLEventWithID& result, const std::string& desc); + +/** + * Exception thrown by errorException(). We don't call this LLEventError + * because it's not an error in event processing: rather, this exception + * announces an event that bears error information (for some other API). + */ +class LL_COMMON_API LLErrorEvent: public std::runtime_error +{ +public: + LLErrorEvent(const std::string& what, const LLSD& data): + std::runtime_error(what), + mData(data) + {} + virtual ~LLErrorEvent() throw() {} + + LLSD getData() const { return mData; } + +private: + LLSD mData; +}; + +/** + * Like errorException(), save that this trips a fatal error using LL_ERRS + * rather than throwing an exception. + */ +LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc); + +/** + * Certain event APIs require the name of an LLEventPump on which they should + * post results. While it works to invent a distinct name and let + * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton," + * in a certain sense it's more robust to instantiate a local LLEventPump and + * provide its name instead. This class packages the following idiom: + * + * 1. Instantiate a local LLCoroEventPump, with an optional name prefix. + * 2. Provide its actual name to the event API in question as the name of the + * reply LLEventPump. + * 3. Initiate the request to the event API. + * 4. Call your LLEventTempStream's wait() method to wait for the reply. + * 5. Let the LLCoroEventPump go out of scope. + */ +class LL_COMMON_API LLCoroEventPump +{ +public: + LLCoroEventPump(const std::string& name="coro"): + mPump(name, true) // allow tweaking the pump instance name + {} + /// It's typical to request the LLEventPump name to direct an event API to + /// send its response to this pump. + std::string getName() const { return mPump.getName(); } + /// Less typically, we'd request the pump itself for some reason. + LLEventPump& getPump() { return mPump; } + + /** + * Wait for an event on this LLEventPump. + * + * @note + * The other major usage pattern we considered was to bind @c self at + * LLCoroEventPump construction time, which would avoid passing the + * parameter to each wait() call. But if we were going to bind @c self as + * a class member, we'd need to specify a class template parameter + * indicating its type. The big advantage of passing it to the wait() call + * is that the type can be implicit. + */ + template <typename SELF> + LLSD wait(SELF& self) + { + return waitForEventOn(self, mPump); + } + + template <typename SELF> + LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPumpNamePath=LLSD()) + { + return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath); + } + +private: + LLEventStream mPump; +}; + +/** + * Other event APIs require the names of two different LLEventPumps: one for + * success response, the other for error response. Extend LLCoroEventPump + * for the two-pump use case. + */ +class LL_COMMON_API LLCoroEventPumps +{ +public: + LLCoroEventPumps(const std::string& name="coro", + const std::string& suff0="Reply", + const std::string& suff1="Error"): + mPump0(name + suff0, true), // allow tweaking the pump instance name + mPump1(name + suff1, true) + {} + /// request pump 0's name + std::string getName0() const { return mPump0.getName(); } + /// request pump 1's name + std::string getName1() const { return mPump1.getName(); } + /// request both names + std::pair<std::string, std::string> getNames() const + { + return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName()); + } + + /// request pump 0 + LLEventPump& getPump0() { return mPump0; } + /// request pump 1 + LLEventPump& getPump1() { return mPump1; } + + /// waitForEventOn(self, either of our two LLEventPumps) + template <typename SELF> + LLEventWithID wait(SELF& self) + { + return waitForEventOn(self, mPump0, mPump1); + } + + /// errorException(wait(self)) + template <typename SELF> + LLSD waitWithException(SELF& self) + { + return errorException(wait(self), std::string("Error event on ") + getName1()); + } + + /// errorLog(wait(self)) + template <typename SELF> + LLSD waitWithLog(SELF& self) + { + return errorLog(wait(self), std::string("Error event on ") + getName1()); + } + + template <typename SELF> + LLEventWithID postAndWait(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return postAndWait2(self, event, requestPump, mPump0, mPump1, + replyPump0NamePath, replyPump1NamePath); + } + + template <typename SELF> + LLSD postAndWaitWithException(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorException(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + + template <typename SELF> + LLSD postAndWaitWithLog(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorLog(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + +private: + LLEventStream mPump0, mPump1; +}; + +#endif /* ! defined(LL_LLEVENTCORO_H) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 0000000000..6b1413d054 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -0,0 +1,133 @@ +/** + * @file lleventdispatcher.cpp + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Implementation for lleventdispatcher. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/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 "lleventdispatcher.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "llsdutil.h" + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + mDesc(desc), + mKey(key) +{ +} + +LLEventDispatcher::~LLEventDispatcher() +{ +} + +/// Register a callable by name +void LLEventDispatcher::add(const std::string& name, const Callable& callable, const LLSD& required) +{ + mDispatch[name] = DispatchMap::mapped_type(callable, required); +} + +void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << classname << " is not a subclass " + << "of LLEventDispatcher" << LL_ENDL; +} + +/// Unregister a callable +bool LLEventDispatcher::remove(const std::string& name) +{ + DispatchMap::iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return false; + } + mDispatch.erase(found); + return true; +} + +/// Call a registered callable with an explicitly-specified name. If no +/// such callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +{ + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name + << "' not found" << LL_ENDL; + } +} + +/// Extract the @a key value from the incoming @a event, and call the +/// callable whose name is specified by that map @a key. If no such +/// callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const LLSD& event) const +{ + // This could/should be implemented in terms of the two-arg overload. + // However -- we can produce a more informative error message. + std::string name(event[mKey]); + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey + << " value '" << name << "'" << LL_ENDL; + } +} + +bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + // The reason we only return false, leaving it up to our caller to die + // with LL_ERRS, is that different callers have different amounts of + // available information. + return false; + } + // Found the name, so it's plausible to even attempt the call. But first, + // validate the syntax of the event itself. + std::string mismatch(llsd_matches(found->second.second, event)); + if (! mismatch.empty()) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name + << "': bad request: " << mismatch << LL_ENDL; + } + // Event syntax looks good, go for it! + (found->second.first)(event); + return true; // tell caller we were able to call +} + +LLEventDispatcher::Callable LLEventDispatcher::get(const std::string& name) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return Callable(); + } + return found->second.first; +} + +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): + LLEventDispatcher(pumpname, key), + mPump(pumpname, true), // allow tweaking for uniqueness + mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) +{ +} + +bool LLDispatchListener::process(const LLSD& event) +{ + (*this)(event); + return false; +} diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 0000000000..5a86b90bff --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -0,0 +1,130 @@ +/** + * @file lleventdispatcher.h + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Central mechanism for dispatching events by string name. This is + * useful when you have a single LLEventPump listener on which you can + * request different operations, vs. instantiating a different + * LLEventPump for each such operation. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTDISPATCHER_H) +#define LL_LLEVENTDISPATCHER_H + +#include <string> +#include <map> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <typeinfo> +#include "llevents.h" + +class LLSD; + +/** + * Given an LLSD map, examine a string-valued key and call a corresponding + * callable. This class is designed to be contained by an LLEventPump + * listener class that will register some of its own methods, though any + * callable can be used. + */ +class LL_COMMON_API LLEventDispatcher +{ +public: + LLEventDispatcher(const std::string& desc, const std::string& key); + virtual ~LLEventDispatcher(); + + /// Accept any C++ callable, typically a boost::bind() expression + typedef boost::function<void(const LLSD&)> Callable; + + /** + * Register a @a callable by @a name. The optional @a required parameter + * is used to validate the structure of each incoming event (see + * llsd_matches()). + */ + void add(const std::string& name, const Callable& callable, const LLSD& required=LLSD()); + + /** + * Special case: a subclass of this class can pass an unbound member + * function pointer without explicitly specifying the + * <tt>boost::bind()</tt> expression. + */ + template <class CLASS> + void add(const std::string& name, void (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod<CLASS>(name, method, required); + } + + /// Overload for both const and non-const methods + template <class CLASS> + void add(const std::string& name, void (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod<CLASS>(name, method, required); + } + + /// Unregister a callable + bool remove(const std::string& name); + + /// Call a registered callable with an explicitly-specified name. If no + /// such callable exists, die with LL_ERRS. If the @a event fails to match + /// the @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const std::string& name, const LLSD& event) const; + + /// Extract the @a key value from the incoming @a event, and call the + /// callable whose name is specified by that map @a key. If no such + /// callable exists, die with LL_ERRS. If the @a event fails to match the + /// @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const LLSD& event) const; + + /// Fetch the Callable for the specified name. If no such name was + /// registered, return an empty() Callable. + Callable get(const std::string& name) const; + +private: + template <class CLASS, typename METHOD> + void addMethod(const std::string& name, const METHOD& method, const LLSD& required) + { + CLASS* downcast = dynamic_cast<CLASS*>(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, boost::bind(method, downcast, _1), required); + } + } + void addFail(const std::string& name, const std::string& classname) const; + /// try to dispatch, return @c true if success + bool attemptCall(const std::string& name, const LLSD& event) const; + + std::string mDesc, mKey; + typedef std::map<std::string, std::pair<Callable, LLSD> > DispatchMap; + DispatchMap mDispatch; +}; + +/** + * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class + * that contains (or derives from) LLDispatchListener need only specify the + * LLEventPump name and dispatch key, and add() its methods. Incoming events + * will automatically be dispatched. + */ +class LL_COMMON_API LLDispatchListener: public LLEventDispatcher +{ +public: + LLDispatchListener(const std::string& pumpname, const std::string& key); + + std::string getPumpName() const { return mPump.getName(); } + +private: + bool process(const LLSD& event); + + LLEventStream mPump; + LLTempBoundListener mBoundListener; +}; + +#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp new file mode 100644 index 0000000000..74133781be --- /dev/null +++ b/indra/llcommon/lleventfilter.cpp @@ -0,0 +1,149 @@ +/** + * @file lleventfilter.cpp + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Implementation for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "llerror.h" // LL_ERRS +#include "llsdutil.h" // llsd_matches() + +LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): + LLEventStream(name, tweak) +{ + source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)); +} + +LLEventMatching::LLEventMatching(const LLSD& pattern): + LLEventFilter("matching"), + mPattern(pattern) +{ +} + +LLEventMatching::LLEventMatching(LLEventPump& source, const LLSD& pattern): + LLEventFilter(source, "matching"), + mPattern(pattern) +{ +} + +bool LLEventMatching::post(const LLSD& event) +{ + if (! llsd_matches(mPattern, event).empty()) + return false; + + return LLEventStream::post(event); +} + +LLEventTimeoutBase::LLEventTimeoutBase(): + LLEventFilter("timeout") +{ +} + +LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source): + LLEventFilter(source, "timeout") +{ +} + +void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) +{ + setCountdown(seconds); + mAction = action; + if (! mMainloop.connected()) + { + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); + } +} + +class ErrorAfter +{ +public: + ErrorAfter(const std::string& message): mMessage(message) {} + + void operator()() + { + LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL; + } + +private: + std::string mMessage; +}; + +void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message) +{ + actionAfter(seconds, ErrorAfter(message)); +} + +class EventAfter +{ +public: + EventAfter(LLEventPump& pump, const LLSD& event): + mPump(pump), + mEvent(event) + {} + + void operator()() + { + mPump.post(mEvent); + } + +private: + LLEventPump& mPump; + LLSD mEvent; +}; + +void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event) +{ + actionAfter(seconds, EventAfter(*this, event)); +} + +bool LLEventTimeoutBase::post(const LLSD& event) +{ + cancel(); + return LLEventStream::post(event); +} + +void LLEventTimeoutBase::cancel() +{ + mMainloop.disconnect(); +} + +bool LLEventTimeoutBase::tick(const LLSD&) +{ + if (countdownElapsed()) + { + cancel(); + mAction(); + } + return false; // show event to other listeners +} + +LLEventTimeout::LLEventTimeout() {} + +LLEventTimeout::LLEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source) +{ +} + +void LLEventTimeout::setCountdown(F32 seconds) +{ + mTimer.setTimerExpirySec(seconds); +} + +bool LLEventTimeout::countdownElapsed() const +{ + return mTimer.hasExpired(); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h new file mode 100644 index 0000000000..89f0c7ea43 --- /dev/null +++ b/indra/llcommon/lleventfilter.h @@ -0,0 +1,186 @@ +/** + * @file lleventfilter.h + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Define LLEventFilter: LLEventStream subclass with conditions + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTFILTER_H) +#define LL_LLEVENTFILTER_H + +#include "llevents.h" +#include "stdtypes.h" +#include "lltimer.h" +#include <boost/function.hpp> + +/** + * Generic base class + */ +class LL_COMMON_API LLEventFilter: public LLEventStream +{ +public: + /// construct a standalone LLEventFilter + LLEventFilter(const std::string& name="filter", bool tweak=true): + LLEventStream(name, tweak) + {} + /// construct LLEventFilter and connect it to the specified LLEventPump + LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true); + + /// Post an event to all listeners + virtual bool post(const LLSD& event) = 0; +}; + +/** + * Pass through only events matching a specified pattern + */ +class LLEventMatching: public LLEventFilter +{ +public: + /// Pass an LLSD map with keys and values the incoming event must match + LLEventMatching(const LLSD& pattern); + /// instantiate and connect + LLEventMatching(LLEventPump& source, const LLSD& pattern); + + /// Only pass through events matching the pattern + virtual bool post(const LLSD& event); + +private: + LLSD mPattern; +}; + +/** + * Wait for an event to be posted. If no such event arrives within a specified + * time, take a specified action. See LLEventTimeout for production + * implementation. + * + * @NOTE This is an abstract base class so that, for testing, we can use an + * alternate "timer" that doesn't actually consume real time. + */ +class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter +{ +public: + /// construct standalone + LLEventTimeoutBase(); + /// construct and connect + LLEventTimeoutBase(LLEventPump& source); + + /// Callable, can be constructed with boost::bind() + typedef boost::function<void()> Action; + + /** + * Start countdown timer for the specified number of @a seconds. Forward + * all events. If any event arrives before timer expires, cancel timer. If + * no event arrives before timer expires, take specified @a action. + * + * This is a one-shot timer. Once it has either expired or been canceled, + * it is inert until another call to actionAfter(). + * + * Calling actionAfter() while an existing timer is running cheaply + * replaces that original timer. Thus, a valid use case is to detect + * idleness of some event source by calling actionAfter() on each new + * event. A rapid sequence of events will keep the timer from expiring; + * the first gap in events longer than the specified timer will fire the + * specified Action. + * + * Any post() call cancels the timer. To be satisfied with only a + * particular event, chain on an LLEventMatching that only passes such + * events: + * + * @code + * event ultimate + * source ---> LLEventMatching ---> LLEventTimeout ---> listener + * @endcode + * + * @NOTE + * The implementation relies on frequent events on the LLEventPump named + * "mainloop". + */ + void actionAfter(F32 seconds, const Action& action); + + /** + * Like actionAfter(), but where the desired Action is LL_ERRS + * termination. Pass the timeout time and the desired LL_ERRS @a message. + * + * This method is useful when, for instance, some async API guarantees an + * event, whether success or failure, within a stated time window. + * Instantiate an LLEventTimeout listening to that API and call + * errorAfter() on each async request with a timeout comfortably longer + * than the API's time guarantee (much longer than the anticipated + * "mainloop" granularity). + * + * Then if the async API breaks its promise, the program terminates with + * the specified LL_ERRS @a message. The client of the async API can + * therefore assume the guarantee is upheld. + * + * @NOTE + * errorAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to errorAfter(). + */ + void errorAfter(F32 seconds, const std::string& message); + + /** + * Like actionAfter(), but where the desired Action is a particular event + * for all listeners. Pass the timeout time and the desired @a event data. + * + * Suppose the timeout should only be satisfied by a particular event, but + * the ultimate listener must see all other incoming events as well, plus + * the timeout @a event if any: + * + * @code + * some LLEventMatching LLEventMatching + * event ---> for particular ---> LLEventTimeout ---> for timeout + * source event event \ + * \ \ ultimate + * `-----------------------------------------------------> listener + * @endcode + * + * Since a given listener can listen on more than one LLEventPump, we can + * set things up so it sees the set union of events from LLEventTimeout + * and the original event source. However, as LLEventTimeout passes + * through all incoming events, the "particular event" that satisfies the + * left LLEventMatching would reach the ultimate listener twice. So we add + * an LLEventMatching that only passes timeout events. + * + * @NOTE + * eventAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to eventAfter(). + */ + void eventAfter(F32 seconds, const LLSD& event); + + /// Pass event through, canceling the countdown timer + virtual bool post(const LLSD& event); + + /// Cancel timer without event + void cancel(); + +protected: + virtual void setCountdown(F32 seconds) = 0; + virtual bool countdownElapsed() const = 0; + +private: + bool tick(const LLSD&); + + LLBoundListener mMainloop; + Action mAction; +}; + +/// Production implementation of LLEventTimoutBase +class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase +{ +public: + LLEventTimeout(); + LLEventTimeout(LLEventPump& source); + +protected: + virtual void setCountdown(F32 seconds); + virtual bool countdownElapsed() const; + +private: + LLTimer mTimer; +}; + +#endif /* ! defined(LL_LLEVENTFILTER_H) */ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index eb380ba7c8..4bdfe5a867 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -38,6 +38,12 @@ #pragma warning (pop) #endif // other Linden headers +#include "stringize.h" +#include "llerror.h" +#include "llsdutil.h" +#if LL_MSVC +#pragma warning (disable : 4702) +#endif /***************************************************************************** * queue_names: specify LLEventPump names that should be instantiated as @@ -56,14 +62,12 @@ const char* queue_names[] = /***************************************************************************** * If there's a "mainloop" pump, listen on that to flush all LLEventQueues *****************************************************************************/ -struct RegisterFlush +struct RegisterFlush : public LLEventTrackable { RegisterFlush(): - pumps(LLEventPumps::instance()), - mainloop(pumps.obtain("mainloop")), - name("flushLLEventQueues") + pumps(LLEventPumps::instance()) { - mainloop.listen(name, boost::bind(&RegisterFlush::flush, this, _1)); + pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1)); } bool flush(const LLSD&) { @@ -72,11 +76,9 @@ struct RegisterFlush } ~RegisterFlush() { - mainloop.stopListening(name); + // LLEventTrackable handles stopListening for us. } LLEventPumps& pumps; - LLEventPump& mainloop; - const std::string name; }; static RegisterFlush registerFlush; @@ -124,6 +126,16 @@ void LLEventPumps::flush() } } +void LLEventPumps::reset() +{ + // Reset every known LLEventPump instance. Leave it up to each instance to + // decide what to do with the reset() call. + for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) + { + pmi->second->reset(); + } +} + std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak) { std::pair<PumpMap::iterator, bool> inserted = @@ -240,6 +252,7 @@ LLEventPumps::~LLEventPumps() LLEventPump::LLEventPump(const std::string& name, bool tweak): // Register every new instance with LLEventPumps mName(LLEventPumps::instance().registerNew(*this, name, tweak)), + mSignal(new LLStandardSignal()), mEnabled(true) {} @@ -256,6 +269,19 @@ LLEventPump::~LLEventPump() // static data member const LLEventPump::NameList LLEventPump::empty; +std::string LLEventPump::inventName(const std::string& pfx) +{ + static long suffix = 0; + return STRINGIZE(pfx << suffix++); +} + +void LLEventPump::reset() +{ + mSignal.reset(); + mConnections.clear(); + //mDeps.clear(); +} + LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, const NameList& after, const NameList& before) @@ -397,7 +423,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL } // Now that newNode has a value that places it appropriately in mSignal, // connect it. - LLBoundListener bound = mSignal.connect(newNode, listener); + LLBoundListener bound = mSignal->connect(newNode, listener); mConnections[name] = bound; return bound; } @@ -437,7 +463,7 @@ bool LLEventStream::post(const LLSD& event) // Let caller know if any one listener handled the event. This is mostly // useful when using LLEventStream as a listener for an upstream // LLEventPump. - return mSignal(event); + return (*mSignal)(event); } /***************************************************************************** @@ -468,7 +494,7 @@ void LLEventQueue::flush() mEventQueue.clear(); for ( ; ! queue.empty(); queue.pop_front()) { - mSignal(queue.front()); + (*mSignal)(queue.front()); } } @@ -499,3 +525,26 @@ bool LLListenerOrPumpName::operator()(const LLSD& event) const } return (*mListener)(event); } + +void LLReqID::stamp(LLSD& response) const +{ + if (! (response.isUndefined() || response.isMap())) + { + // If 'response' was previously completely empty, it's okay to + // turn it into a map. If it was already a map, then it should be + // okay to add a key. But if it was anything else (e.g. a scalar), + // assigning a ["reqid"] key will DISCARD the previous value, + // replacing it with a map. That would be Bad. + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") leaving non-map response unmodified: " + << response << LL_ENDL; + return; + } + LLSD oldReqid(response["reqid"]); + if (! (oldReqid.isUndefined() || llsd_equals(oldReqid, mReqid))) + { + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") preserving existing [\"reqid\"] value " + << oldReqid << " in response: " << response << LL_ENDL; + return; + } + response["reqid"] = mReqid; +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 240adcdd41..192d79b27d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -19,7 +19,6 @@ #include <map> #include <set> #include <vector> -#include <list> #include <deque> #include <stdexcept> #if LL_WINDOWS @@ -37,13 +36,9 @@ #include <boost/enable_shared_from_this.hpp> #include <boost/utility.hpp> // noncopyable #include <boost/optional/optional.hpp> -#include <boost/ptr_container/ptr_vector.hpp> #include <boost/visit_each.hpp> #include <boost/ref.hpp> // reference_wrapper #include <boost/type_traits/is_pointer.hpp> -#include <boost/utility/addressof.hpp> -#include <boost/preprocessor/repetition/enum_params.hpp> -#include <boost/preprocessor/iteration/local.hpp> #include <boost/function.hpp> #include <boost/static_assert.hpp> #include "llsd.h" @@ -120,6 +115,9 @@ typedef LLStandardSignal::slot_type LLEventListener; /// Result of registering a listener, supports <tt>connected()</tt>, /// <tt>disconnect()</tt> and <tt>blocked()</tt> typedef boost::signals2::connection LLBoundListener; +/// Storing an LLBoundListener in LLTempBoundListener will disconnect the +/// referenced listener when the LLTempBoundListener instance is destroyed. +typedef boost::signals2::scoped_connection LLTempBoundListener; /** * A common idiom for event-based code is to accept either a callable -- @@ -136,7 +134,7 @@ typedef boost::signals2::connection LLBoundListener; * LLListenerOrPumpName::Empty. Test for this condition beforehand using * either <tt>if (param)</tt> or <tt>if (! param)</tt>. */ -class LLListenerOrPumpName +class LL_COMMON_API LLListenerOrPumpName { public: /// passing string name of LLEventPump @@ -189,7 +187,7 @@ class LLEventPump; * LLEventPumps is a Singleton manager through which one typically accesses * this subsystem. */ -class LLEventPumps: public LLSingleton<LLEventPumps> +class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps> { friend class LLSingleton<LLEventPumps>; public: @@ -204,6 +202,12 @@ public: */ void flush(); + /** + * Reset all known LLEventPump instances + * workaround for DEV-35406 crash on shutdown + */ + void reset(); + private: friend class LLEventPump; /** @@ -264,13 +268,61 @@ namespace LLEventDetail } // namespace LLEventDetail /***************************************************************************** +* LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1))</tt>. This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * <tt>LLEventPump::listen()</tt>. + * - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> + * runs, destroying state specific to the subclass. (For instance, a + * <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) + * - The listening method will not be disconnected until + * <tt>~LLEventTrackable()</tt> runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the <tt>Foo*</tt> pointer that was + * <tt>delete</tt>d but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with <tt>boost::shared_ptr</tt>. + * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression + * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + +/***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses LLEventStream and LLEventQueue. + * + * @NOTE + * LLEventPump derives from LLEventTrackable so that when you "chain" + * LLEventPump instances together, they will automatically disconnect on + * destruction. Please see LLEventTrackable documentation for situations in + * which this may be perilous across threads. */ -class LLEventPump: boost::noncopyable +class LL_COMMON_API LLEventPump: public LLEventTrackable { public: /** @@ -373,10 +425,22 @@ public: * themselves. listen() can throw any ListenError; see ListenError * subclasses. * - * If (as is typical) you pass a <tt>boost::bind()</tt> expression, - * listen() will inspect the components of that expression. If a bound - * object matches any of several cases, the connection will automatically - * be disconnected when that object is destroyed. + * The listener name must be unique among active listeners for this + * LLEventPump, else you get DupListenerName. If you don't care to invent + * a name yourself, use inventName(). (I was tempted to recognize e.g. "" + * and internally generate a distinct name for that case. But that would + * handle badly the scenario in which you want to add, remove, re-add, + * etc. the same listener: each new listen() call would necessarily + * perform a new dependency sort. Assuming you specify the same + * after/before lists each time, using inventName() when you first + * instantiate your listener, then passing the same name on each listen() + * call, allows us to optimize away the second and subsequent dependency + * sorts. + * + * If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a + * listener, listen() will inspect the components of that expression. If a + * bound object matches any of several cases, the connection will + * automatically be disconnected when that object is destroyed. * * * You bind a <tt>boost::weak_ptr</tt>. * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the @@ -438,11 +502,16 @@ public: /// query virtual bool enabled() const { return mEnabled; } + /// Generate a distinct name for a listener -- see listen() + static std::string inventName(const std::string& pfx="listener"); + private: friend class LLEventPumps; /// flush queued events virtual void flush() {} + virtual void reset(); + private: virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, const NameList& after, @@ -451,7 +520,8 @@ private: protected: /// implement the dispatching - LLStandardSignal mSignal; + boost::scoped_ptr<LLStandardSignal> mSignal; + /// valve open? bool mEnabled; /// Map of named listeners. This tracks the listeners that actually exist @@ -476,7 +546,7 @@ protected: * LLEventStream is a thin wrapper around LLStandardSignal. Posting an * event immediately calls all registered listeners. */ -class LLEventStream: public LLEventPump +class LL_COMMON_API LLEventStream: public LLEventPump { public: LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -493,7 +563,7 @@ public: * LLEventQueue isa LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ -class LLEventQueue: public LLEventPump +class LL_COMMON_API LLEventQueue: public LLEventPump { public: LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -512,47 +582,89 @@ private: }; /***************************************************************************** -* LLEventTrackable and underpinnings +* LLReqID *****************************************************************************/ /** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1))</tt>. This will implicitly disconnect when the object - * referenced by @c instance is destroyed. + * This class helps the implementer of a given event API to honor the + * ["reqid"] convention. By this convention, each event API stamps into its + * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if + * any, from the corresponding request. + * + * This supports an (atypical, but occasionally necessary) use case in which + * two or more asynchronous requests are multiplexed onto the same ["reply"] + * LLEventPump. Since the response events could arrive in arbitrary order, the + * caller must be able to demux them. It does so by matching the ["reqid"] + * value in each response with the ["reqid"] value in the corresponding + * request. + * + * It is the caller's responsibility to ensure distinct ["reqid"] values for + * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: + * the "namespace" of unique ["reqid"] values is simply the set of requests + * specifying the same ["reply"] LLEventPump name. + * + * Making a given event API echo the request's ["reqid"] into the response is + * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a + * place to put these comments. We hope that each time a coder implements a + * new event API based on some existing one, s/he will say, "Huh, what's an + * LLReqID?" and look up this material. + * + * The hardest part about the convention is deciding where to store the + * ["reqid"] value. Ironically, LLReqID can't help with that: you must store + * an LLReqID instance in whatever storage will persist until the reply is + * sent. For example, if the request ultimately ends up using a Responder + * subclass, storing an LLReqID instance in the Responder works. * * @note - * LLEventTrackable doesn't address a couple of cases: - * * Object destroyed during call - * - You enter a slot call in thread A. - * - Thread B destroys the object, which of course disconnects it from any - * future slot calls. - * - Thread A's call uses 'this', which now refers to a defunct object. - * Undefined behavior results. - * * Call during destruction - * - @c MySubclass is derived from LLEventTrackable. - * - @c MySubclass registers one of its own methods using - * <tt>LLEventPump::listen()</tt>. - * - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> - * runs, destroying state specific to the subclass. (For instance, a - * <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) - * - The listening method will not be disconnected until - * <tt>~LLEventTrackable()</tt> runs. - * - Before we get there, another thread posts data to the @c LLEventPump - * instance, calling the @c MySubclass method. - * - The method in question relies on valid @c MySubclass state. (For - * instance, it attempts to dereference the <tt>Foo*</tt> pointer that was - * <tt>delete</tt>d but not zeroed.) - * - Undefined behavior results. - * If you suspect you may encounter any such scenario, you're better off - * managing the lifespan of your object with <tt>boost::shared_ptr</tt>. - * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression - * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging - * thread-safe Boost.Signals2 machinery. + * The @em implementer of an event API must honor the ["reqid"] convention. + * However, the @em caller of an event API need only use it if s/he is sharing + * the same ["reply"] LLEventPump for two or more asynchronous event API + * requests. + * + * In most cases, it's far easier for the caller to instantiate a local + * LLEventStream and pass its name to the event API in question. Then it's + * perfectly reasonable not to set a ["reqid"] key in the request, ignoring + * the @c isUndefined() ["reqid"] value in the response. */ -typedef boost::signals2::trackable LLEventTrackable; +class LL_COMMON_API LLReqID +{ +public: + /** + * If you have the request in hand at the time you instantiate the + * LLReqID, pass that request to extract its ["reqid"]. + */ + LLReqID(const LLSD& request): + mReqid(request["reqid"]) + {} + /// If you don't yet have the request, use setFrom() later. + LLReqID() {} + + /// Extract and store the ["reqid"] value from an incoming request. + void setFrom(const LLSD& request) + { + mReqid = request["reqid"]; + } + + /// Set ["reqid"] key into a pending response LLSD object. + void stamp(LLSD& response) const; + + /// Make a whole new response LLSD object with our ["reqid"]. + LLSD makeResponse() const + { + LLSD response; + stamp(response); + return response; + } + /// Not really sure of a use case for this accessor... + LLSD getReqID() const { return mReqid; } + +private: + LLSD mReqid; +}; + +/***************************************************************************** +* Underpinnings +*****************************************************************************/ /** * We originally provided a suite of overloaded * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index c2f23f6ff6..45b84ea3ea 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -37,7 +37,75 @@ #define FAST_TIMER_ON 1 -U64 get_cpu_clock_count(); +#if LL_WINDOWS + +// shift off lower 8 bits for lower resolution but longer term timing +// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing +inline U32 get_cpu_clock_count_32() +{ + U32 ret_val; + __asm + { + _emit 0x0f + _emit 0x31 + shr eax,8 + shl edx,24 + or eax, edx + mov dword ptr [ret_val], eax + } + return ret_val; +} + +// return full timer value, still shifted by 8 bits +inline U64 get_cpu_clock_count_64() +{ + U64 ret_val; + __asm + { + _emit 0x0f + _emit 0x31 + mov eax,eax + mov edx,edx + mov dword ptr [ret_val+4], edx + mov dword ptr [ret_val], eax + } + return ret_val >> 8; +} + +#endif // LL_WINDOWS + +#if (LL_LINUX || LL_SOLARIS || LL_DARWIN) && (defined(__i386__) || defined(__amd64__)) +inline U32 get_cpu_clock_count_32() +{ + U64 x; + __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); + return (U32)x >> 8; +} + +inline U32 get_cpu_clock_count_64() +{ + U64 x; + __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); + return x >> 8; +} +#endif + +#if ( LL_DARWIN && !(defined(__i386__) || defined(__amd64__))) || (LL_SOLARIS && defined(__sparc__)) +// +// Mac PPC (deprecated) & Solaris SPARC implementation of CPU clock +// +// Just use gettimeofday implementation for now + +inline U32 get_cpu_clock_count_32() +{ + return (U32)get_clock_count(); +} + +inline U32 get_cpu_clock_count_64() +{ + return get_clock_count(); +} +#endif class LLMutex; @@ -45,20 +113,21 @@ class LLMutex; #include "llsd.h" -class LLFastTimer +class LL_COMMON_API LLFastTimer { public: // stores a "named" timer instance to be reused via multiple LLFastTimer stack instances - class NamedTimer + class LL_COMMON_API NamedTimer : public LLInstanceTracker<NamedTimer> { + friend class DeclareTimer; public: ~NamedTimer(); enum { HISTORY_NUM = 60 }; - const std::string& getName() { return mName; } - NamedTimer* getParent() { return mParent; } + const std::string& getName() const { return mName; } + NamedTimer* getParent() const { return mParent; } void setParent(NamedTimer* parent); S32 getDepth(); std::string getToolTip(S32 history_index = -1); @@ -71,11 +140,11 @@ public: void setCollapsed(bool collapsed) { mCollapsed = collapsed; } bool getCollapsed() const { return mCollapsed; } - U64 getCountAverage() const { return mCountAverage; } - U64 getCallAverage() const { return mCallAverage; } + U32 getCountAverage() const { return mCountAverage; } + U32 getCallAverage() const { return mCallAverage; } - U64 getHistoricalCount(S32 history_index = 0) const; - U64 getHistoricalCalls(S32 history_index = 0) const; + U32 getHistoricalCount(S32 history_index = 0) const; + U32 getHistoricalCalls(S32 history_index = 0) const; static NamedTimer& getRootNamedTimer(); @@ -83,8 +152,7 @@ public: { FrameState(NamedTimer* timerp); - U64 mSelfTimeCounter; - U64 mLastStartTime; // most recent time when this timer was started + U32 mSelfTimeCounter; U32 mCalls; FrameState* mParent; // info for caller timer FrameState* mLastCaller; // used to bootstrap tree construction @@ -93,11 +161,6 @@ public: bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame }; - FrameState& getFrameStateFast() const - { - return (*sTimerInfos)[mFrameStateIndex]; - } - S32 getFrameStateIndex() const { return mFrameStateIndex; } FrameState& getFrameState() const; @@ -122,10 +185,7 @@ public: static void resetFrame(); static void reset(); - typedef std::vector<FrameState> info_list_t; - static info_list_t& getFrameStateList(); - static void createFrameStateList(); // must call before any call to getFrameStateList() - + // // members // @@ -133,13 +193,13 @@ public: std::string mName; - U64 mTotalTimeCounter; + U32 mTotalTimeCounter; - U64 mCountAverage; - U64 mCallAverage; + U32 mCountAverage; + U32 mCallAverage; - U64* mCountHistory; - U64* mCallHistory; + U32* mCountHistory; + U32* mCallHistory; // tree structure NamedTimer* mParent; // NamedTimer of caller(parent) @@ -147,41 +207,46 @@ public: bool mCollapsed; // don't show children bool mNeedsSorting; // sort children whenever child added - static info_list_t* sTimerInfos; }; // used to statically declare a new named timer - class DeclareTimer + class LL_COMMON_API DeclareTimer + : public LLInstanceTracker<DeclareTimer> { public: DeclareTimer(const std::string& name, bool open); DeclareTimer(const std::string& name); + static void updateCachedPointers(); + // convertable to NamedTimer::FrameState for convenient usage of LLFastTimer(declared_timer) - operator NamedTimer::FrameState&() { return mNamedTimer.getFrameStateFast(); } + operator NamedTimer::FrameState&() { return *mFrameState; } private: - NamedTimer& mNamedTimer; + NamedTimer& mTimer; + NamedTimer::FrameState* mFrameState; }; public: - enum RootTimerMarker { ROOT }; - static LLMutex* sLogLock; static std::queue<LLSD> sLogQueue; static BOOL sLog; static BOOL sMetricLog; + typedef std::vector<NamedTimer::FrameState> info_list_t; + static info_list_t& getFrameStateList(); + + enum RootTimerMarker { ROOT }; LLFastTimer(RootTimerMarker); LLFastTimer(NamedTimer::FrameState& timer) : mFrameState(&timer) { #if FAST_TIMER_ON - NamedTimer::FrameState* frame_state = mFrameState; - U64 cur_time = get_cpu_clock_count(); - frame_state->mLastStartTime = cur_time; + NamedTimer::FrameState* frame_state = &timer; + U32 cur_time = get_cpu_clock_count_32(); mStartSelfTime = cur_time; + mStartTotalTime = cur_time; frame_state->mActiveCount++; frame_state->mCalls++; @@ -197,7 +262,7 @@ public: { #if FAST_TIMER_ON NamedTimer::FrameState* frame_state = mFrameState; - U64 cur_time = get_cpu_clock_count(); + U32 cur_time = get_cpu_clock_count_32(); frame_state->mSelfTimeCounter += cur_time - mStartSelfTime; frame_state->mActiveCount--; @@ -208,7 +273,7 @@ public: frame_state->mLastCaller = last_timer->mFrameState; // we are only tracking self time, so subtract our total time delta from parents - U64 total_time = cur_time - frame_state->mLastStartTime; + U32 total_time = cur_time - mStartTotalTime; last_timer->mStartSelfTime += total_time; #endif } @@ -240,9 +305,11 @@ private: static LLFastTimer* sCurTimer; static S32 sCurFrameIndex; static S32 sLastFrameIndex; + static U64 sLastFrameTime; + static info_list_t* sTimerInfos; - static F64 sCPUClockFrequency; - U64 mStartSelfTime; // start time + time of all child timers + U32 mStartSelfTime; // start time + time of all child timers + U32 mStartTotalTime; // start time + time of all child timers NamedTimer::FrameState* mFrameState; LLFastTimer* mLastTimer; }; diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index c6092f7b9c..fea5d3ed2b 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -70,7 +70,7 @@ typedef struct stat llstat; #include "llstring.h" // safe char* -> std::string conversion -class LLFile +class LL_COMMON_API LLFile { public: // All these functions take UTF8 path/filenames. @@ -95,7 +95,7 @@ public: #if USE_LLFILESTREAMS -class llifstream : public std::basic_istream < char , std::char_traits < char > > +class LL_COMMON_API llifstream : public std::basic_istream < char , std::char_traits < char > > { // input stream associated with a C stream public: @@ -136,7 +136,7 @@ private: }; -class llofstream : public std::basic_ostream< char , std::char_traits < char > > +class LL_COMMON_API llofstream : public std::basic_ostream< char , std::char_traits < char > > { public: typedef std::basic_ostream< char , std::char_traits < char > > _Myt; @@ -185,7 +185,7 @@ private: //#define llifstream std::ifstream //#define llofstream std::ofstream -class llifstream : public std::ifstream +class LL_COMMON_API llifstream : public std::ifstream { public: llifstream() : std::ifstream() @@ -203,7 +203,7 @@ public: }; -class llofstream : public std::ofstream +class LL_COMMON_API llofstream : public std::ofstream { public: llofstream() : std::ofstream() @@ -231,7 +231,7 @@ public: * and should only be used for config files and the like -- not in a * loop. */ -std::streamsize llifstream_size(llifstream& fstr); -std::streamsize llofstream_size(llofstream& fstr); +std::streamsize LL_COMMON_API llifstream_size(llifstream& fstr); +std::streamsize LL_COMMON_API llofstream_size(llofstream& fstr); #endif // not LL_LLFILE_H diff --git a/indra/llcommon/llfindlocale.h b/indra/llcommon/llfindlocale.h index f17c7740f3..b812a065db 100644 --- a/indra/llcommon/llfindlocale.h +++ b/indra/llcommon/llfindlocale.h @@ -59,8 +59,8 @@ typedef enum { /* This allocates/fills in a FL_Locale structure with pointers to strings (which should be treated as static), or NULL for inappropriate / undetected fields. */ -FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain); +LL_COMMON_API FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain); /* This should be used to free the struct written by FL_FindLocale */ -void FL_FreeLocale(FL_Locale **locale); +LL_COMMON_API void FL_FreeLocale(FL_Locale **locale); #endif /*__findlocale_h_*/ diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h index 01b46d327a..17fdef27d7 100644 --- a/indra/llcommon/llfixedbuffer.h +++ b/indra/llcommon/llfixedbuffer.h @@ -41,7 +41,7 @@ #include "llerrorcontrol.h" // fixed buffer implementation -class LLFixedBuffer : public LLLineBuffer +class LL_COMMON_API LLFixedBuffer : public LLLineBuffer { public: LLFixedBuffer(const U32 max_lines = 20); diff --git a/indra/llcommon/llfoldertype.cpp b/indra/llcommon/llfoldertype.cpp new file mode 100644 index 0000000000..9107b11597 --- /dev/null +++ b/indra/llcommon/llfoldertype.cpp @@ -0,0 +1,165 @@ +/** + * @file llfoldertype.cpp + * @brief Implementatino of LLFolderType functionality. + * + * $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 "llfoldertype.h" +#include "lldictionary.h" +#include "llmemory.h" +#include "llsingleton.h" + +///---------------------------------------------------------------------------- +/// Class LLFolderType +///---------------------------------------------------------------------------- +struct FolderEntry : public LLDictionaryEntry +{ + FolderEntry(const std::string &type_name, // 8 character limit! + bool is_protected) // can the viewer change categories of this type? + : + LLDictionaryEntry(type_name), + mIsProtected(is_protected) + { + llassert(type_name.length() <= 8); + } + + const bool mIsProtected; +}; + +class LLFolderDictionary : public LLSingleton<LLFolderDictionary>, + public LLDictionary<LLFolderType::EType, FolderEntry> +{ +public: + LLFolderDictionary(); +}; + +LLFolderDictionary::LLFolderDictionary() +{ + // TYPE NAME PROTECTED + // |-----------|---------| + addEntry(LLFolderType::FT_TEXTURE, new FolderEntry("texture", TRUE)); + addEntry(LLFolderType::FT_SOUND, new FolderEntry("sound", TRUE)); + addEntry(LLFolderType::FT_CALLINGCARD, new FolderEntry("callcard", TRUE)); + addEntry(LLFolderType::FT_LANDMARK, new FolderEntry("landmark", TRUE)); + addEntry(LLFolderType::FT_CLOTHING, new FolderEntry("clothing", TRUE)); + addEntry(LLFolderType::FT_OBJECT, new FolderEntry("object", TRUE)); + addEntry(LLFolderType::FT_NOTECARD, new FolderEntry("notecard", TRUE)); + addEntry(LLFolderType::FT_CATEGORY, new FolderEntry("category", TRUE)); + addEntry(LLFolderType::FT_ROOT_CATEGORY, new FolderEntry("root", TRUE)); + addEntry(LLFolderType::FT_LSL_TEXT, new FolderEntry("lsltext", TRUE)); + addEntry(LLFolderType::FT_BODYPART, new FolderEntry("bodypart", TRUE)); + addEntry(LLFolderType::FT_TRASH, new FolderEntry("trash", TRUE)); + addEntry(LLFolderType::FT_SNAPSHOT_CATEGORY, new FolderEntry("snapshot", TRUE)); + addEntry(LLFolderType::FT_LOST_AND_FOUND, new FolderEntry("lstndfnd", TRUE)); + addEntry(LLFolderType::FT_ANIMATION, new FolderEntry("animatn", TRUE)); + addEntry(LLFolderType::FT_GESTURE, new FolderEntry("gesture", TRUE)); + addEntry(LLFolderType::FT_FAVORITE, new FolderEntry("favorite", TRUE)); + + for (S32 ensemble_num = S32(LLFolderType::FT_ENSEMBLE_START); ensemble_num <= S32(LLFolderType::FT_ENSEMBLE_END); ensemble_num++) + { + addEntry(LLFolderType::EType(ensemble_num), new FolderEntry("ensemble", FALSE)); + } + + addEntry(LLFolderType::FT_CURRENT_OUTFIT, new FolderEntry("current", TRUE)); + addEntry(LLFolderType::FT_OUTFIT, new FolderEntry("outfit", FALSE)); + addEntry(LLFolderType::FT_MY_OUTFITS, new FolderEntry("my_otfts", TRUE)); + addEntry(LLFolderType::FT_INBOX, new FolderEntry("inbox", TRUE)); + + addEntry(LLFolderType::FT_NONE, new FolderEntry("-1", FALSE)); +}; + +// static +LLFolderType::EType LLFolderType::lookup(const std::string& name) +{ + return LLFolderDictionary::getInstance()->lookup(name); +} + +// static +const std::string &LLFolderType::lookup(LLFolderType::EType folder_type) +{ + const FolderEntry *entry = LLFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + return entry->mName; + } + else + { + return badLookup(); + } +} + +// static +// Only ensembles and plain folders aren't protected. "Protected" means +// you can't change certain properties such as their type. +bool LLFolderType::lookupIsProtectedType(EType folder_type) +{ + const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); + const FolderEntry *entry = dict->lookup(folder_type); + if (entry) + { + return entry->mIsProtected; + } + return true; +} + +// static +bool LLFolderType::lookupIsEnsembleType(EType folder_type) +{ + return (folder_type >= FT_ENSEMBLE_START && + folder_type <= FT_ENSEMBLE_END); +} + +// static +LLAssetType::EType LLFolderType::folderTypeToAssetType(LLFolderType::EType folder_type) +{ + if (LLAssetType::lookup(LLAssetType::EType(folder_type)) == LLAssetType::badLookup()) + { + llwarns << "Converting to unknown asset type " << folder_type << llendl; + } + return (LLAssetType::EType)folder_type; +} + +// static +LLFolderType::EType LLFolderType::assetTypeToFolderType(LLAssetType::EType asset_type) +{ + if (LLFolderType::lookup(LLFolderType::EType(asset_type)) == LLFolderType::badLookup()) + { + llwarns << "Converting to unknown folder type " << asset_type << llendl; + } + return (LLFolderType::EType)asset_type; +} + +// static +const std::string &LLFolderType::badLookup() +{ + static const std::string sBadLookup = "llfoldertype_bad_lookup"; + return sBadLookup; +} diff --git a/indra/llcommon/llfoldertype.h b/indra/llcommon/llfoldertype.h new file mode 100644 index 0000000000..5374ffd829 --- /dev/null +++ b/indra/llcommon/llfoldertype.h @@ -0,0 +1,123 @@ +/** + * @file llfoldertype.h + * @brief Declaration of LLFolderType. + * + * $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$ + */ + +#ifndef LL_LLFOLDERTYPE_H +#define LL_LLFOLDERTYPE_H + +#include <string> +#include "llassettype.h" + +// This class handles folder types (similar to assettype, except for folders) +// and operations on those. +class LL_COMMON_API LLFolderType +{ +public: + // ! BACKWARDS COMPATIBILITY ! Folder type enums must match asset type enums. + enum EType + { + FT_TEXTURE = 0, + + FT_SOUND = 1, + + FT_CALLINGCARD = 2, + + FT_LANDMARK = 3, + + // FT_SCRIPT = 4, + + FT_CLOTHING = 5, + + FT_OBJECT = 6, + + FT_NOTECARD = 7, + + FT_CATEGORY = 8, + + FT_ROOT_CATEGORY = 9, + + FT_LSL_TEXT = 10, + + // FT_LSL_BYTECODE = 11, + // FT_TEXTURE_TGA = 12, + + FT_BODYPART = 13, + + FT_TRASH = 14, + + FT_SNAPSHOT_CATEGORY = 15, + + FT_LOST_AND_FOUND = 16, + + // FT_SOUND_WAV = 17, + // FT_IMAGE_TGA = 18, + // FT_IMAGE_JPEG = 19, + + FT_ANIMATION = 20, + + FT_GESTURE = 21, + + // FT_SIMSTATE = 22, + + FT_FAVORITE = 23, + + FT_ENSEMBLE_START = 26, + FT_ENSEMBLE_END = 45, + // This range is reserved for special clothing folder types. + + FT_CURRENT_OUTFIT = 46, + FT_OUTFIT = 47, + FT_MY_OUTFITS = 48, + + FT_INBOX = 49, + + FT_COUNT = 50, + + FT_NONE = -1 + }; + + static EType lookup(const std::string& type_name); + static const std::string& lookup(EType folder_type); + + static bool lookupIsProtectedType(EType folder_type); + static bool lookupIsEnsembleType(EType folder_type); + + static LLAssetType::EType folderTypeToAssetType(LLFolderType::EType folder_type); + static LLFolderType::EType assetTypeToFolderType(LLAssetType::EType asset_type); + + static const std::string& badLookup(); // error string when a lookup fails + +protected: + LLFolderType() {} + ~LLFolderType() {} +}; + +#endif // LL_LLFOLDERTYPE_H diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h index 44c62d9710..dc64edb26d 100644 --- a/indra/llcommon/llformat.h +++ b/indra/llcommon/llformat.h @@ -40,6 +40,6 @@ // *NOTE: buffer limited to 1024, (but vsnprintf prevents overrun) // should perhaps be replaced with boost::format. -std::string llformat(const char *fmt, ...); +std::string LL_COMMON_API llformat(const char *fmt, ...); #endif // LL_LLFORMAT_H diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 8f51272af2..be2d9b0703 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -43,7 +43,7 @@ #include "lltimer.h" #include "timing.h" -class LLFrameTimer +class LL_COMMON_API LLFrameTimer { public: LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} diff --git a/indra/llcommon/llheartbeat.h b/indra/llcommon/llheartbeat.h index fecb5b1e54..6f7026970f 100644 --- a/indra/llcommon/llheartbeat.h +++ b/indra/llcommon/llheartbeat.h @@ -40,7 +40,7 @@ // Note: Win32 does not support the heartbeat/smackdown system; // heartbeat-delivery turns into a no-op there. -class LLHeartbeat +class LL_COMMON_API LLHeartbeat { public: // secs_between_heartbeat: after a heartbeat is successfully delivered, diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index 873f50a65e..7544ab1d11 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -118,6 +118,63 @@ public: THROTTLE_BLOCKED, // rate exceed, block key }; + F64 getActionCount(const T& id) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl<T>::getTime(); + } + else + { + now = LLKeyThrottleImpl<T>::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + + typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + } + + typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + return averageCount; + } + // call each time the key wants use State noteAction(const T& id, S32 weight = 1) { diff --git a/indra/llcommon/llliveappconfig.h b/indra/llcommon/llliveappconfig.h index a6ece6e8b3..73b3a23352 100644 --- a/indra/llcommon/llliveappconfig.h +++ b/indra/llcommon/llliveappconfig.h @@ -45,7 +45,7 @@ * loop. The traditional name for it is live_config. Be sure to call * <code>live_config.checkAndReload()</code> periodically. */ -class LLLiveAppConfig : public LLLiveFile +class LL_COMMON_API LLLiveAppConfig : public LLLiveFile { public: diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h index 89b5d95e44..2453d7a125 100644 --- a/indra/llcommon/lllivefile.h +++ b/indra/llcommon/lllivefile.h @@ -36,7 +36,7 @@ extern const F32 DEFAULT_CONFIG_FILE_REFRESH; -class LLLiveFile +class LL_COMMON_API LLLiveFile { public: LLLiveFile(const std::string& filename, const F32 refresh_period = 5.f); diff --git a/indra/llcommon/lllog.h b/indra/llcommon/lllog.h index 7ac6c8aa42..4b6777bb9c 100644 --- a/indra/llcommon/lllog.h +++ b/indra/llcommon/lllog.h @@ -41,7 +41,7 @@ class LLLogImpl; class LLApp; class LLSD; -class LLLog +class LL_COMMON_API LLLog { public: LLLog(LLApp* app); diff --git a/indra/llcommon/llmd5.h b/indra/llcommon/llmd5.h index d8bca03e4e..df9d7324ab 100644 --- a/indra/llcommon/llmd5.h +++ b/indra/llcommon/llmd5.h @@ -80,7 +80,7 @@ const int MD5RAW_BYTES = 16; const int MD5HEX_STR_SIZE = 33; // char hex[MD5HEX_STR_SIZE]; with null const int MD5HEX_STR_BYTES = 32; // message system fixed size -class LLMD5 { +class LL_COMMON_API LLMD5 { // first, some types: typedef unsigned int uint4; // assumes integer is 4 words long typedef unsigned short int uint2; // assumes short integer is 2 words long diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index f41da37ba6..1c6f64dd8b 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -41,7 +41,7 @@ extern S32 gDACount; extern void* ll_allocate (size_t size); extern void ll_release (void *p); -class LLMemory +class LL_COMMON_API LLMemory { public: static void initClass(); diff --git a/indra/llcommon/llmemorystream.h b/indra/llcommon/llmemorystream.h index f3486324c5..fa0f5d22f2 100644 --- a/indra/llcommon/llmemorystream.h +++ b/indra/llcommon/llmemorystream.h @@ -52,7 +52,7 @@ * be careful to always pass in a valid memory location that exists * for at least as long as this streambuf. */ -class LLMemoryStreamBuf : public std::streambuf +class LL_COMMON_API LLMemoryStreamBuf : public std::streambuf { public: LLMemoryStreamBuf(const U8* start, S32 length); @@ -74,7 +74,7 @@ protected: * be careful to always pass in a valid memory location that exists * for at least as long as this streambuf. */ -class LLMemoryStream : public std::istream +class LL_COMMON_API LLMemoryStream : public std::istream { public: LLMemoryStream(const U8* start, S32 length); diff --git a/indra/llcommon/llmemtype.h b/indra/llcommon/llmemtype.h index 12310fcdb4..677fad3034 100644 --- a/indra/llcommon/llmemtype.h +++ b/indra/llcommon/llmemtype.h @@ -48,14 +48,14 @@ #define MEM_TYPE_NEW(T) -class LLMemType +class LL_COMMON_API LLMemType { public: // class we'll initialize all instances of as // static members of MemType. Then use // to construct any new mem type. - class DeclareMemType + class LL_COMMON_API DeclareMemType { public: DeclareMemType(char const * st); diff --git a/indra/llcommon/llmetrics.h b/indra/llcommon/llmetrics.h index 1d91e8c8a2..f6f49eb456 100644 --- a/indra/llcommon/llmetrics.h +++ b/indra/llcommon/llmetrics.h @@ -38,7 +38,7 @@ class LLMetricsImpl; class LLSD; -class LLMetrics +class LL_COMMON_API LLMetrics { public: LLMetrics(); diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index fcda3df58e..27bd8cd9b5 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -35,7 +35,7 @@ #include "stdtypes.h" -class LLMortician +class LL_COMMON_API LLMortician { public: LLMortician() { mIsDead = FALSE; } diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index bb598a2be1..48244480b1 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -93,19 +93,8 @@ #endif -// Deal with the differeneces on Windows -#if LL_MSVC -namespace snprintf_hack -{ - int snprintf(char *str, size_t size, const char *format, ...); -} - -// #define snprintf safe_snprintf /* Flawfinder: ignore */ -using snprintf_hack::snprintf; -#endif // LL_MSVC - // Static linking with apr on windows needs to be declared. -#ifdef LL_WINDOWS +#if LL_WINDOWS && !LL_COMMON_LINK_SHARED #ifndef APR_DECLARE_STATIC #define APR_DECLARE_STATIC // For APR on Windows #endif @@ -137,6 +126,43 @@ using snprintf_hack::snprintf; #pragma warning( disable : 4503 ) // 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation. #pragma warning( disable : 4800 ) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) #pragma warning( disable : 4996 ) // warning: deprecated + +// level 4 warnings that we need to disable: +#pragma warning (disable : 4100) // unreferenced formal parameter +#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) +#pragma warning (disable : 4244) // possible loss of data on conversions +#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template +#pragma warning (disable : 4512) // assignment operator could not be generated +#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) + +#pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class +#pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class #endif // LL_MSVC +#if LL_WINDOWS +#define LL_DLLEXPORT __declspec(dllexport) +#define LL_DLLIMPORT __declspec(dllimport) +#elif LL_LINUX +#define LL_DLLEXPORT __attribute__ ((visibility("default"))) +#define LL_DLLIMPORT +#else +#define LL_DLLEXPORT +#define LL_DLLIMPORT +#endif // LL_WINDOWS + +#if LL_COMMON_LINK_SHARED +// CMake automagically defines llcommon_EXPORTS only when building llcommon +// sources, and only when llcommon is a shared library (i.e. when +// LL_COMMON_LINK_SHARED). We must still test LL_COMMON_LINK_SHARED because +// otherwise we can't distinguish between (non-llcommon source) and (llcommon +// not shared). +# if defined(llcommon_EXPORTS) +# define LL_COMMON_API LL_DLLEXPORT +# else //llcommon_EXPORTS +# define LL_COMMON_API LL_DLLIMPORT +# endif //llcommon_EXPORTS +#else // LL_COMMON_LINK_SHARED +# define LL_COMMON_API +#endif // LL_COMMON_LINK_SHARED + #endif // not LL_LINDEN_PREPROCESSOR_H diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h index a1b8e22691..880562157f 100644 --- a/indra/llcommon/llprocesslauncher.h +++ b/indra/llcommon/llprocesslauncher.h @@ -42,7 +42,7 @@ It also keeps track of whether the process is still running, and can kill it if required. */ -class LLProcessLauncher +class LL_COMMON_API LLProcessLauncher { LOG_CLASS(LLProcessLauncher); public: diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 3ba43e1e07..8bfa5632a1 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -47,7 +47,7 @@ // Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small // It is assumed that LLQueuedThreads are rarely created/destroyed. -class LLQueuedThread : public LLThread +class LL_COMMON_API LLQueuedThread : public LLThread { //------------------------------------------------------------------------ public: @@ -80,7 +80,7 @@ public: //------------------------------------------------------------------------ public: - class QueuedRequest : public LLSimpleHashEntry<handle_t> + class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t> { friend class LLQueuedThread; @@ -148,6 +148,7 @@ protected: } }; + //------------------------------------------------------------------------ public: diff --git a/indra/llcommon/llrand.h b/indra/llcommon/llrand.h index d12597bb53..30fec9b982 100644 --- a/indra/llcommon/llrand.h +++ b/indra/llcommon/llrand.h @@ -65,32 +65,32 @@ /** *@brief Generate a float from [0, RAND_MAX). */ -S32 ll_rand(); +S32 LL_COMMON_API ll_rand(); /** *@brief Generate a float from [0, val) or (val, 0]. */ -S32 ll_rand(S32 val); +S32 LL_COMMON_API ll_rand(S32 val); /** *@brief Generate a float from [0, 1.0). */ -F32 ll_frand(); +F32 LL_COMMON_API ll_frand(); /** *@brief Generate a float from [0, val) or (val, 0]. */ -F32 ll_frand(F32 val); +F32 LL_COMMON_API ll_frand(F32 val); /** *@brief Generate a double from [0, 1.0). */ -F64 ll_drand(); +F64 LL_COMMON_API ll_drand(); /** *@brief Generate a double from [0, val) or (val, 0]. */ -F64 ll_drand(F64 val); +F64 LL_COMMON_API ll_drand(F64 val); /** * @brief typedefs for good boost lagged fibonacci. diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index d3597b454c..9ab844eb22 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -39,9 +39,9 @@ // see llthread.h for LLThreadSafeRefCount //---------------------------------------------------------------------------- -class LLRefCount +class LL_COMMON_API LLRefCount { -protected: +private: LLRefCount(const LLRefCount& other); // no implementation private: LLRefCount& operator=(const LLRefCount&); // no implementation diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index 77b23d9051..1fc9925df9 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -48,7 +48,7 @@ class LLRunnable; * which are scheduled to run on a repeating or one time basis. * @see LLRunnable */ -class LLRunner +class LL_COMMON_API LLRunner { public: /** @@ -149,7 +149,7 @@ protected: * something useful. * @see LLRunner */ -class LLRunnable +class LL_COMMON_API LLRunnable { public: LLRunnable(); diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index d2845a3757..552bb57498 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -89,7 +89,7 @@ @nosubgrouping */ -class LLSD +class LL_COMMON_API LLSD { public: LLSD(); ///< initially Undefined @@ -387,7 +387,7 @@ struct llsd_select_string : public std::unary_function<LLSD, LLSD::String> } }; -std::ostream& operator<<(std::ostream& s, const LLSD& llsd); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLSD& llsd); /** QUESTIONS & TO DOS - Would Binary be more convenient as usigned char* buffer semantics? diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h index 7463d1e5dd..2f2b292189 100644 --- a/indra/llcommon/llsdserialize.h +++ b/indra/llcommon/llsdserialize.h @@ -44,7 +44,7 @@ * @class LLSDParser * @brief Abstract base class for LLSD parsers. */ -class LLSDParser : public LLRefCount +class LL_COMMON_API LLSDParser : public LLRefCount { protected: /** @@ -221,7 +221,7 @@ protected: * @class LLSDNotationParser * @brief Parser which handles the original notation format for LLSD. */ -class LLSDNotationParser : public LLSDParser +class LL_COMMON_API LLSDNotationParser : public LLSDParser { protected: /** @@ -294,7 +294,7 @@ private: * @class LLSDXMLParser * @brief Parser which handles XML format LLSD. */ -class LLSDXMLParser : public LLSDParser +class LL_COMMON_API LLSDXMLParser : public LLSDParser { protected: /** @@ -342,7 +342,7 @@ private: * @class LLSDBinaryParser * @brief Parser which handles binary formatted LLSD. */ -class LLSDBinaryParser : public LLSDParser +class LL_COMMON_API LLSDBinaryParser : public LLSDParser { protected: /** @@ -407,7 +407,7 @@ private: * @class LLSDFormatter * @brief Abstract base class for formatting LLSD. */ -class LLSDFormatter : public LLRefCount +class LL_COMMON_API LLSDFormatter : public LLRefCount { protected: /** @@ -479,7 +479,7 @@ protected: * @class LLSDNotationFormatter * @brief Formatter which outputs the original notation format for LLSD. */ -class LLSDNotationFormatter : public LLSDFormatter +class LL_COMMON_API LLSDNotationFormatter : public LLSDFormatter { protected: /** @@ -520,7 +520,7 @@ public: * @class LLSDXMLFormatter * @brief Formatter which outputs the LLSD as XML. */ -class LLSDXMLFormatter : public LLSDFormatter +class LL_COMMON_API LLSDXMLFormatter : public LLSDFormatter { protected: /** @@ -588,7 +588,7 @@ protected: * Map: '{' + 4 byte integer size every(key + value) + '}'<br> * map keys are serialized as 'k' + 4 byte integer size + string */ -class LLSDBinaryFormatter : public LLSDFormatter +class LL_COMMON_API LLSDBinaryFormatter : public LLSDFormatter { protected: /** @@ -638,9 +638,14 @@ protected: * params << "[{'version':i1}," << LLSDOStreamer<LLSDNotationFormatter>(sd) * << "]"; * </code> + * + * *NOTE - formerly this class inherited from its template parameter Formatter, + * but all insnatiations passed in LLRefCount subclasses. This conflicted with + * the auto allocation intended for this class template (demonstrated in the + * example above). -brad */ template <class Formatter> -class LLSDOStreamer : public Formatter +class LLSDOStreamer { public: /** @@ -661,7 +666,8 @@ public: std::ostream& str, const LLSDOStreamer<Formatter>& formatter) { - formatter.format(formatter.mSD, str, formatter.mOptions); + LLPointer<Formatter> f = new Formatter; + f->format(formatter.mSD, str, formatter.mOptions); return str; } @@ -677,7 +683,7 @@ typedef LLSDOStreamer<LLSDXMLFormatter> LLSDXMLStreamer; * @class LLSDSerialize * @brief Serializer / deserializer for the various LLSD formats */ -class LLSDSerialize +class LL_COMMON_API LLSDSerialize { public: enum ELLSD_Serialize diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index c12ca350de..7e1c2e35e0 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -37,6 +37,7 @@ #include <deque> #include "apr_base64.h" +#include <boost/regex.hpp> extern "C" { @@ -777,10 +778,17 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) case ELEMENT_BINARY: { - S32 len = apr_base64_decode_len(mCurrentContent.c_str()); + // Regex is expensive, but only fix for whitespace in base64, + // created by python and other non-linden systems - DEV-39358 + // Fortunately we have very little binary passing now, + // so performance impact shold be negligible. + poppy 2009-09-04 + boost::regex r; + r.assign("\\s"); + std::string stripped = boost::regex_replace(mCurrentContent, r, ""); + S32 len = apr_base64_decode_len(stripped.c_str()); std::vector<U8> data; data.resize(len); - len = apr_base64_decode_binary(&data[0], mCurrentContent.c_str()); + len = apr_base64_decode_binary(&data[0], stripped.c_str()); data.resize(len); value = data; break; diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 0202a033c3..c8d8030e87 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -46,6 +46,11 @@ #endif #include "llsdserialize.h" +#include "stringize.h" + +#include <map> +#include <set> +#include <boost/range.hpp> // U32 LLSD ll_sd_from_U32(const U32 val) @@ -313,3 +318,353 @@ BOOL compare_llsd_with_template( return TRUE; } + +/***************************************************************************** +* Helpers for llsd_matches() +*****************************************************************************/ +// raw data used for LLSD::Type lookup +struct Data +{ + LLSD::Type type; + const char* name; +} typedata[] = +{ +#define def(type) { LLSD::type, #type + 4 } + def(TypeUndefined), + def(TypeBoolean), + def(TypeInteger), + def(TypeReal), + def(TypeString), + def(TypeUUID), + def(TypeDate), + def(TypeURI), + def(TypeBinary), + def(TypeMap), + def(TypeArray) +#undef def +}; + +// LLSD::Type lookup class into which we load the above static data +class TypeLookup +{ + typedef std::map<LLSD::Type, std::string> MapType; + +public: + TypeLookup() + { + for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) + { + mMap[di->type] = di->name; + } + } + + std::string lookup(LLSD::Type type) const + { + MapType::const_iterator found = mMap.find(type); + if (found != mMap.end()) + { + return found->second; + } + return STRINGIZE("<unknown LLSD type " << type << ">"); + } + +private: + MapType mMap; +}; + +// static instance of the lookup class +static const TypeLookup sTypes; + +// describe a mismatch; phrasing may want tweaking +const std::string op(" required instead of "); + +// llsd_matches() wants to identify specifically where in a complex prototype +// structure the mismatch occurred. This entails passing a prefix string, +// empty for the top-level call. If the prototype contains an array of maps, +// and the mismatch occurs in the second map in a key 'foo', we want to +// decorate the returned string with: "[1]['foo']: etc." On the other hand, we +// want to omit the entire prefix -- including colon -- if the mismatch is at +// top level. This helper accepts the (possibly empty) recursively-accumulated +// prefix string, returning either empty or the original string with colon +// appended. +static std::string colon(const std::string& pfx) +{ + if (pfx.empty()) + return pfx; + return pfx + ": "; +} + +// param type for match_types +typedef std::vector<LLSD::Type> TypeVector; + +// The scalar cases in llsd_matches() use this helper. In most cases, we can +// accept not only the exact type specified in the prototype, but also other +// types convertible to the expected type. That implies looping over an array +// of such types. If the actual type doesn't match any of them, we want to +// provide a list of acceptable conversions as well as the exact type, e.g.: +// "Integer (or Boolean, Real, String) required instead of UUID". Both the +// implementation and the calling logic are simplified by separating out the +// expected type from the convertible types. +static std::string match_types(LLSD::Type expect, // prototype.type() + const TypeVector& accept, // types convertible to that type + LLSD::Type actual, // type we're checking + const std::string& pfx) // as for llsd_matches +{ + // Trivial case: if the actual type is exactly what we expect, we're good. + if (actual == expect) + return ""; + + // For the rest of the logic, build up a suitable error string as we go so + // we only have to make a single pass over the list of acceptable types. + // If we detect success along the way, we'll simply discard the partial + // error string. + std::ostringstream out; + out << colon(pfx) << sTypes.lookup(expect); + + // If there are any convertible types, append that list. + if (! accept.empty()) + { + out << " ("; + const char* sep = "or "; + for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); + ai != aend; ++ai, sep = ", ") + { + // Don't forget to return success if we match any of those types... + if (actual == *ai) + return ""; + out << sep << sTypes.lookup(*ai); + } + out << ')'; + } + // If we got this far, it's because 'actual' was not one of the acceptable + // types, so we must return an error. 'out' already contains colon(pfx) + // and the formatted list of acceptable types, so just append the mismatch + // phrase and the actual type. + out << op << sTypes.lookup(actual); + return out.str(); +} + +// see docstring in .h file +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) +{ + // An undefined prototype means that any data is valid. + // An undefined slot in an array or map prototype means that any data + // may fill that slot. + if (prototype.isUndefined()) + return ""; + // A prototype array must match a data array with at least as many + // entries. Moreover, every prototype entry must match the + // corresponding data entry. + if (prototype.isArray()) + { + if (! data.isArray()) + { + return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); + } + if (data.size() < prototype.size()) + { + return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op + << "Array size " << data.size()); + } + for (LLSD::Integer i = 0; i < prototype.size(); ++i) + { + std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A prototype map must match a data map. Every key in the prototype + // must have a corresponding key in the data map; every value in the + // prototype must match the corresponding key's value in the data. + if (prototype.isMap()) + { + if (! data.isMap()) + { + return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); + } + // If there are a number of keys missing from the data, it would be + // frustrating to a coder to discover them one at a time, with a big + // build each time. Enumerate all missing keys. + std::ostringstream out; + out << colon(pfx); + const char* init = "Map missing keys: "; + const char* sep = init; + for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) + { + if (! data.has(mi->first)) + { + out << sep << mi->first; + sep = ", "; + } + } + // So... are we missing any keys? + if (sep != init) + { + return out.str(); + } + // Good, the data block contains all the keys required by the + // prototype. Now match the prototype entries. + for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) + { + std::string match(llsd_matches(mi2->second, data[mi2->first], + STRINGIZE("['" << mi2->first << "']"))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A String prototype can match String, Boolean, Integer, Real, UUID, + // Date and URI, because any of these can be converted to String. + if (prototype.isString()) + { + static LLSD::Type accept[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeUUID, + LLSD::TypeDate, + LLSD::TypeURI + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // Boolean, Integer, Real match each other or String. TBD: ensure that + // a String value is numeric. + if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) + { + static LLSD::Type all[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeString + }; + // Funny business: shuffle the set of acceptable types to include all + // but the prototype's type. Get the acceptable types in a set. + std::set<LLSD::Type> rest(boost::begin(all), boost::end(all)); + // Remove the prototype's type because we pass that separately. + rest.erase(prototype.type()); + return match_types(prototype.type(), + TypeVector(rest.begin(), rest.end()), + data.type(), + pfx); + } + // UUID, Date and URI match themselves or String. + if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) + { + static LLSD::Type accept[] = + { + LLSD::TypeString + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // We don't yet know the conversion semantics associated with any new LLSD + // data type that might be added, so until we've been extended to handle + // them, assume it's strict: the new type matches only itself. (This is + // true of Binary, which is why we don't handle that case separately.) Too + // bad LLSD doesn't define isConvertible(Type to, Type from). + return match_types(prototype.type(), TypeVector(), data.type(), pfx); +} + +bool llsd_equals(const LLSD& lhs, const LLSD& rhs) +{ + // We're comparing strict equality of LLSD representation rather than + // performing any conversions. So if the types aren't equal, the LLSD + // values aren't equal. + if (lhs.type() != rhs.type()) + { + return false; + } + + // Here we know both types are equal. Now compare values. + switch (lhs.type()) + { + case LLSD::TypeUndefined: + // Both are TypeUndefined. There's nothing more to know. + return true; + +#define COMPARE_SCALAR(type) \ + case LLSD::Type##type: \ + /* LLSD::URI has operator!=() but not operator==() */ \ + /* rely on the optimizer for all others */ \ + return (! (lhs.as##type() != rhs.as##type())) + + COMPARE_SCALAR(Boolean); + COMPARE_SCALAR(Integer); + // The usual caveats about comparing floating-point numbers apply. This is + // only useful when we expect identical bit representation for a given + // Real value, e.g. for integer-valued Reals. + COMPARE_SCALAR(Real); + COMPARE_SCALAR(String); + COMPARE_SCALAR(UUID); + COMPARE_SCALAR(Date); + COMPARE_SCALAR(URI); + COMPARE_SCALAR(Binary); + +#undef COMPARE_SCALAR + + case LLSD::TypeArray: + { + LLSD::array_const_iterator + lai(lhs.beginArray()), laend(lhs.endArray()), + rai(rhs.beginArray()), raend(rhs.endArray()); + // Compare array elements, walking the two arrays in parallel. + for ( ; lai != laend && rai != raend; ++lai, ++rai) + { + // If any one array element is unequal, the arrays are unequal. + if (! llsd_equals(*lai, *rai)) + return false; + } + // Here we've reached the end of one or the other array. They're equal + // only if they're BOTH at end: that is, if they have equal length too. + return (lai == laend && rai == raend); + } + + case LLSD::TypeMap: + { + // Build a set of all rhs keys. + std::set<LLSD::String> rhskeys; + for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); + rmi != rmend; ++rmi) + { + rhskeys.insert(rmi->first); + } + // Now walk all the lhs keys. + for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); + lmi != lmend; ++lmi) + { + // Try to erase this lhs key from the set of rhs keys. If rhs has + // no such key, the maps are unequal. erase(key) returns count of + // items erased. + if (rhskeys.erase(lmi->first) != 1) + return false; + // Both maps have the current key. Compare values. + if (! llsd_equals(lmi->second, rhs[lmi->first])) + return false; + } + // We've now established that all the lhs keys have equal values in + // both maps. The maps are equal unless rhs contains a superset of + // those keys. + return rhskeys.empty(); + } + + default: + // We expect that every possible type() value is specifically handled + // above. Failing to extend this switch to support a new LLSD type is + // an error that must be brought to the coder's attention. + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): " + "unknown type " << lhs.type() << LL_ENDL; + return false; // pacify the compiler + } +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 501600f1d9..6a6c396687 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -35,62 +35,32 @@ #ifndef LL_LLSDUTIL_H #define LL_LLSDUTIL_H -#include "llsd.h" - -// vector3 -class LLVector3; -LLSD ll_sd_from_vector3(const LLVector3& vec); -LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector4 -class LLVector4; -LLSD ll_sd_from_vector4(const LLVector4& vec); -LLVector4 ll_vector4_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector3d (double) -class LLVector3d; -LLSD ll_sd_from_vector3d(const LLVector3d& vec); -LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector2 -class LLVector2; -LLSD ll_sd_from_vector2(const LLVector2& vec); -LLVector2 ll_vector2_from_sd(const LLSD& sd); - -// Quaternion -class LLQuaternion; -LLSD ll_sd_from_quaternion(const LLQuaternion& quat); -LLQuaternion ll_quaternion_from_sd(const LLSD& sd); - -// color4 -class LLColor4; -LLSD ll_sd_from_color4(const LLColor4& c); -LLColor4 ll_color4_from_sd(const LLSD& sd); +class LLSD; // U32 -LLSD ll_sd_from_U32(const U32); -U32 ll_U32_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U32(const U32); +LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); // U64 -LLSD ll_sd_from_U64(const U64); -U64 ll_U64_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U64(const U64); +LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); // IP Address -LLSD ll_sd_from_ipaddr(const U32); -U32 ll_ipaddr_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); +LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); // Binary to string -LLSD ll_string_from_binary(const LLSD& sd); +LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); //String to binary -LLSD ll_binary_from_string(const LLSD& sd); +LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); // Serializes sd to static buffer and returns pointer, useful for gdb debugging. -char* ll_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_print_sd(const LLSD& sd); // Serializes sd to static buffer and returns pointer, using "pretty printing" mode. -char* ll_pretty_print_sd_ptr(const LLSD* sd); -char* ll_pretty_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); +LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); //compares the structure of an LLSD to a template LLSD and stores the //"valid" values in a 3rd LLSD. Default values @@ -99,11 +69,69 @@ char* ll_pretty_print_sd(const LLSD& sd); //Returns false if the test is of same type but values differ in type //Otherwise, returns true -BOOL compare_llsd_with_template( +LL_COMMON_API BOOL compare_llsd_with_template( const LLSD& llsd_to_test, const LLSD& template_llsd, LLSD& resultant_llsd); +/** + * Recursively determine whether a given LLSD data block "matches" another + * LLSD prototype. The returned string is empty() on success, non-empty() on + * mismatch. + * + * This function tests structure (types) rather than data values. It is + * intended for when a consumer expects an LLSD block with a particular + * structure, and must succinctly detect whether the arriving block is + * well-formed. For instance, a test of the form: + * @code + * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) + * @endcode + * could instead be expressed by initializing a prototype LLSD map with the + * required keys and writing: + * @code + * if (! llsd_matches(prototype, data).empty()) + * @endcode + * + * A non-empty return value is an error-message fragment intended to indicate + * to (English-speaking) developers where in the prototype structure the + * mismatch occurred. + * + * * If a slot in the prototype isUndefined(), then anything is valid at that + * place in the real object. (Passing prototype == LLSD() matches anything + * at all.) + * * An array in the prototype must match a data array at least that large. + * (Additional entries in the data array are ignored.) Every isDefined() + * entry in the prototype array must match the corresponding entry in the + * data array. + * * A map in the prototype must match a map in the data. Every key in the + * prototype map must match a corresponding key in the data map. (Additional + * keys in the data map are ignored.) Every isDefined() value in the + * prototype map must match the corresponding key's value in the data map. + * * Scalar values in the prototype are tested for @em type rather than value. + * For instance, a String in the prototype matches any String at all. In + * effect, storing an Integer at a particular place in the prototype asserts + * that the caller intends to apply asInteger() to the corresponding slot in + * the data. + * * A String in the prototype matches String, Boolean, Integer, Real, UUID, + * Date and URI, because asString() applied to any of these produces a + * meaningful result. + * * Similarly, a Boolean, Integer or Real in the prototype can match any of + * Boolean, Integer or Real in the data -- or even String. + * * UUID matches UUID or String. + * * Date matches Date or String. + * * URI matches URI or String. + * * Binary in the prototype matches only Binary in the data. + * + * @TODO: when a Boolean, Integer or Real in the prototype matches a String in + * the data, we should examine the String @em value to ensure it can be + * meaningfully converted to the requested type. The same goes for UUID, Date + * and URI. + */ +LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); + +/// Deep equality +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs); + // Simple function to copy data out of input & output iterators if // there is no need for casting. template<typename Input> LLSD llsd_copy_array(Input iter, Input end) diff --git a/indra/llcommon/llsecondlifeurls.h b/indra/llcommon/llsecondlifeurls.h index a2e5f0b9c6..bd2f9f7604 100644 --- a/indra/llcommon/llsecondlifeurls.h +++ b/indra/llcommon/llsecondlifeurls.h @@ -34,49 +34,49 @@ #define LL_LLSECONDLIFEURLS_H /* // Account registration web page -extern const std::string CREATE_ACCOUNT_URL; +LL_COMMON_API extern const std::string CREATE_ACCOUNT_URL; // Manage Account -extern const std::string MANAGE_ACCOUNT; +LL_COMMON_API extern const std::string MANAGE_ACCOUNT; -extern const std::string AUCTION_URL; +LL_COMMON_API extern const std::string AUCTION_URL; -extern const std::string EVENTS_URL; +LL_COMMON_API extern const std::string EVENTS_URL; */ // Tier up to a new land level. -extern const std::string TIER_UP_URL; +LL_COMMON_API extern const std::string TIER_UP_URL; // Tier up to a new land level. -extern const std::string LAND_URL; +LL_COMMON_API extern const std::string LAND_URL; // How to get DirectX 9 -extern const std::string DIRECTX_9_URL; +LL_COMMON_API extern const std::string DIRECTX_9_URL; /* // Upgrade from basic membership to premium membership -extern const std::string UPGRADE_TO_PREMIUM_URL; +LL_COMMON_API extern const std::string UPGRADE_TO_PREMIUM_URL; // Out of date VIA chipset -extern const std::string VIA_URL; +LL_COMMON_API extern const std::string VIA_URL; // Support URL -extern const std::string SUPPORT_URL; +LL_COMMON_API extern const std::string SUPPORT_URL; // Linden Blogs page -extern const std::string BLOGS_URL; +LL_COMMON_API extern const std::string BLOGS_URL; // Currency page -extern const std::string BUY_CURRENCY_URL; +LL_COMMON_API extern const std::string BUY_CURRENCY_URL; // LSL script wiki -extern const std::string LSL_DOC_URL; +LL_COMMON_API extern const std::string LSL_DOC_URL; // SL KnowledgeBase page -extern const std::string SL_KB_URL; +LL_COMMON_API extern const std::string SL_KB_URL; // Release Notes Redirect URL for Server and Viewer -extern const std::string RELEASE_NOTES_BASE_URL; +LL_COMMON_API extern const std::string RELEASE_NOTES_BASE_URL; */ #endif diff --git a/indra/llcommon/llsimplehash.h b/indra/llcommon/llsimplehash.h index 0ba2a3014c..5df93b646e 100644 --- a/indra/llcommon/llsimplehash.h +++ b/indra/llcommon/llsimplehash.h @@ -64,7 +64,7 @@ public: }; template <typename HASH_KEY_TYPE, int TABLE_SIZE> -class LLSimpleHash +class LL_COMMON_API LLSimpleHash { public: LLSimpleHash() diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp new file mode 100644 index 0000000000..6b5feaf1c4 --- /dev/null +++ b/indra/llcommon/llsingleton.cpp @@ -0,0 +1,38 @@ +/** + * @file llsingleton.cpp + * @author Brad Kittenbrink + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009-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 "llsingleton.h" + +std::map<std::string, void *> * LLSingletonRegistry::sSingletonMap = NULL; + diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 2e7d845bf7..f55fafadd8 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -33,7 +33,41 @@ #include "llerror.h" // *TODO: eliminate this +#include <typeinfo> #include <boost/noncopyable.hpp> +#include <boost/any.hpp> + +/// @brief A global registry of all singletons to prevent duplicate allocations +/// across shared library boundaries +class LL_COMMON_API LLSingletonRegistry { + private: + typedef std::map<std::string, void *> TypeMap; + static TypeMap * sSingletonMap; + + static void checkInit() + { + if(sSingletonMap == NULL) + { + sSingletonMap = new TypeMap(); + } + } + + public: + template<typename T> static void * & get() + { + std::string name(typeid(T).name()); + + checkInit(); + + // the first entry of the pair returned by insert will be either the existing + // iterator matching our key, or the newly inserted NULL initialized entry + // see "Insert element" in http://www.sgi.com/tech/stl/UniqueAssociativeContainer.html + TypeMap::iterator result = + sSingletonMap->insert(std::make_pair(name, (void*)NULL)).first; + + return result->second; + } +}; // LLSingleton implements the getInstance() method part of the Singleton // pattern. It can't make the derived class constructors protected, though, so @@ -107,8 +141,17 @@ public: static SingletonInstanceData& getData() { - static SingletonInstanceData data; - return data; + // this is static to cache the lookup results + static void * & registry = LLSingletonRegistry::get<DERIVED_TYPE>(); + + // *TODO - look into making this threadsafe + if(NULL == registry) + { + static SingletonInstanceData data; + registry = &data; + } + + return *static_cast<SingletonInstanceData *>(registry); } static DERIVED_TYPE* getInstance() diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 4be91b5b11..6558df70a4 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -30,6 +30,7 @@ * $/LicenseInfo$ */ +#include "linden_common.h" #include "llstacktrace.h" #ifdef LL_WINDOWS diff --git a/indra/llcommon/llstacktrace.h b/indra/llcommon/llstacktrace.h index 609b934a97..9f857f0fd3 100644 --- a/indra/llcommon/llstacktrace.h +++ b/indra/llcommon/llstacktrace.h @@ -38,7 +38,7 @@ #include <vector> #include <string> -bool ll_get_stack_trace(std::vector<std::string>& lines); +LL_COMMON_API bool ll_get_stack_trace(std::vector<std::string>& lines); #endif diff --git a/indra/llcommon/llstat.cpp b/indra/llcommon/llstat.cpp index 90dae11793..0bd2609f4a 100644 --- a/indra/llcommon/llstat.cpp +++ b/indra/llcommon/llstat.cpp @@ -43,7 +43,7 @@ // statics -BOOL LLPerfBlock::sStatsEnabled = FALSE; // Flag for detailed information +S32 LLPerfBlock::sStatsFlags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS; // Control what is being recorded LLPerfBlock::stat_map_t LLPerfBlock::sStatMap; // Map full path string to LLStatTime objects, tracks all active objects std::string LLPerfBlock::sCurrentStatPath = ""; // Something like "/total_time/physics/physics step" LLStat::stat_map_t LLStat::sStatList; @@ -130,6 +130,7 @@ bool LLStatsConfigFile::loadFile() F32 duration = 0.f; F32 interval = 0.f; + S32 flags = LLPerfBlock::LLSTATS_BASIC_STATS; const char * w = "duration"; if (stats_config.has(w)) @@ -141,8 +142,18 @@ bool LLStatsConfigFile::loadFile() { interval = (F32)stats_config[w].asReal(); } + w = "flags"; + if (stats_config.has(w)) + { + flags = (S32)stats_config[w].asInteger(); + if (flags == LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS && + duration > 0) + { // No flags passed in, but have a duration, so reset to basic stats + flags = LLPerfBlock::LLSTATS_BASIC_STATS; + } + } - mStatsp->setReportPerformanceDuration( duration ); + mStatsp->setReportPerformanceDuration( duration, flags ); mStatsp->setReportPerformanceInterval( interval ); if ( duration > 0 ) @@ -254,13 +265,14 @@ void LLPerfStats::dumpIntervalPerformanceStats() } } -// Set length of performance stat recording -void LLPerfStats::setReportPerformanceDuration( F32 seconds ) +// Set length of performance stat recording. +// If turning stats on, caller must provide flags +void LLPerfStats::setReportPerformanceDuration( F32 seconds, S32 flags /* = LLSTATS_NO_OPTIONAL_STATS */ ) { if ( seconds <= 0.f ) { mReportPerformanceStatEnd = 0.0; - LLPerfBlock::setStatsEnabled( FALSE ); + LLPerfBlock::setStatsFlags(LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS); // Make sure all recording is off mFrameStatsFile.close(); LLPerfBlock::clearDynamicStats(); } @@ -269,8 +281,8 @@ void LLPerfStats::setReportPerformanceDuration( F32 seconds ) mReportPerformanceStatEnd = LLFrameTimer::getElapsedSeconds() + ((F64) seconds); // Clear failure flag to try and create the log file once mFrameStatsFileFailure = FALSE; - LLPerfBlock::setStatsEnabled( TRUE ); mSkipFirstFrameStats = TRUE; // Skip the first report (at the end of this frame) + LLPerfBlock::setStatsFlags(flags); } } @@ -612,11 +624,26 @@ LLPerfBlock::LLPerfBlock(LLStatTime* stat ) : mPredefinedStat(stat), mDynamicSta } } -// Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key. -// These are also turned on or off via the switch passed in -LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat(NULL), mDynamicStat(NULL) +// Use this constructor for normal, optional LLPerfBlock time slices +LLPerfBlock::LLPerfBlock( const char* key ) : mPredefinedStat(NULL), mDynamicStat(NULL) { - if (!sStatsEnabled) return; + if ((sStatsFlags & LLSTATS_BASIC_STATS) == 0) + { // These are off unless the base set is enabled + return; + } + + initDynamicStat(key); +} + + +// Use this constructor for dynamically created LLPerfBlock time slices +// that are only enabled by specific control flags +LLPerfBlock::LLPerfBlock( const char* key1, const char* key2, S32 flags ) : mPredefinedStat(NULL), mDynamicStat(NULL) +{ + if ((sStatsFlags & flags) == 0) + { + return; + } if (NULL == key2 || strlen(key2) == 0) { @@ -630,10 +657,12 @@ LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat } } +// Set up the result data map if dynamic stats are enabled void LLPerfBlock::initDynamicStat(const std::string& key) { // Early exit if dynamic stats aren't enabled. - if (!sStatsEnabled) return; + if (sStatsFlags == LLSTATS_NO_OPTIONAL_STATS) + return; mLastPath = sCurrentStatPath; // Save and restore current path sCurrentStatPath += "/" + key; // Add key to current path diff --git a/indra/llcommon/llstat.h b/indra/llcommon/llstat.h index bad18f46a0..bd73c9a6bb 100644 --- a/indra/llcommon/llstat.h +++ b/indra/llcommon/llstat.h @@ -52,7 +52,7 @@ class LLSD; // amounts of time with very low memory cost. // -class LLStatAccum +class LL_COMMON_API LLStatAccum { protected: LLStatAccum(bool use_frame_timer); @@ -116,7 +116,7 @@ public: F64 mLastSampleValue; }; -class LLStatMeasure : public LLStatAccum +class LL_COMMON_API LLStatMeasure : public LLStatAccum // gathers statistics about things that are measured // ex.: tempature, time dilation { @@ -131,7 +131,7 @@ public: }; -class LLStatRate : public LLStatAccum +class LL_COMMON_API LLStatRate : public LLStatAccum // gathers statistics about things that can be counted over time // ex.: LSL instructions executed, messages sent, simulator frames completed // renders it in terms of rate of thing per second @@ -147,7 +147,7 @@ public: }; -class LLStatTime : public LLStatAccum +class LL_COMMON_API LLStatTime : public LLStatAccum // gathers statistics about time spent in a block of code // measure average duration per second in the block { @@ -178,7 +178,7 @@ private: // Use this class on the stack to record statistics about an area of code -class LLPerfBlock +class LL_COMMON_API LLPerfBlock { public: struct StatEntry @@ -192,14 +192,23 @@ public: // Use this constructor for pre-defined LLStatTime objects LLPerfBlock(LLStatTime* stat); - // Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key - LLPerfBlock( const char* key1, const char* key2 = NULL); + // Use this constructor for normal, optional LLPerfBlock time slices + LLPerfBlock( const char* key ); + // Use this constructor for dynamically created LLPerfBlock time slices + // that are only enabled by specific control flags + LLPerfBlock( const char* key1, const char* key2, S32 flags = LLSTATS_BASIC_STATS ); ~LLPerfBlock(); - static void setStatsEnabled( BOOL enable ) { sStatsEnabled = enable; }; - static S32 getStatsEnabled() { return sStatsEnabled; }; + enum + { // Stats bitfield flags + LLSTATS_NO_OPTIONAL_STATS = 0x00, // No optional stats gathering, just pre-defined LLStatTime objects + LLSTATS_BASIC_STATS = 0x01, // Gather basic optional runtime stats + LLSTATS_SCRIPT_FUNCTIONS = 0x02, // Include LSL function calls + }; + static void setStatsFlags( S32 flags ) { sStatsFlags = flags; }; + static S32 getStatsFlags() { return sStatsFlags; }; static void clearDynamicStats(); // Reset maps to clear out dynamic objects static void addStatsToLLSDandReset( LLSD & stats, // Get current information and clear time bin @@ -213,14 +222,14 @@ private: LLStatTime * mPredefinedStat; // LLStatTime object to get data StatEntry * mDynamicStat; // StatEntryobject to get data - static BOOL sStatsEnabled; // Normally FALSE + static S32 sStatsFlags; // Control what is being recorded static stat_map_t sStatMap; // Map full path string to LLStatTime objects static std::string sCurrentStatPath; // Something like "frame/physics/physics step" }; // ---------------------------------------------------------------------------- -class LLPerfStats +class LL_COMMON_API LLPerfStats { public: LLPerfStats(const std::string& process_name = "unknown", S32 process_pid = 0); @@ -236,7 +245,7 @@ public: BOOL frameStatsIsRunning() { return (mReportPerformanceStatEnd > 0.); }; F32 getReportPerformanceInterval() const { return mReportPerformanceStatInterval; }; void setReportPerformanceInterval( F32 interval ) { mReportPerformanceStatInterval = interval; }; - void setReportPerformanceDuration( F32 seconds ); + void setReportPerformanceDuration( F32 seconds, S32 flags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS ); void setProcessName(const std::string& process_name) { mProcessName = process_name; } void setProcessPID(S32 process_pid) { mProcessPID = process_pid; } @@ -256,7 +265,7 @@ private: }; // ---------------------------------------------------------------------------- -class LLStat +class LL_COMMON_API LLStat { private: typedef std::multimap<std::string, LLStat*> stat_map_t; diff --git a/indra/llcommon/llstreamtools.h b/indra/llcommon/llstreamtools.h index a6dc4d51e2..f64e761409 100644 --- a/indra/llcommon/llstreamtools.h +++ b/indra/llcommon/llstreamtools.h @@ -39,23 +39,23 @@ // unless specifed otherwise these all return input_stream.good() // skips spaces and tabs -bool skip_whitespace(std::istream& input_stream); +LL_COMMON_API bool skip_whitespace(std::istream& input_stream); // skips whitespace and newlines -bool skip_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_emptyspace(std::istream& input_stream); // skips emptyspace and lines that start with a # -bool skip_comments_and_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_comments_and_emptyspace(std::istream& input_stream); // skips to character after next newline -bool skip_line(std::istream& input_stream); +LL_COMMON_API bool skip_line(std::istream& input_stream); // skips to beginning of next non-emptyspace -bool skip_to_next_word(std::istream& input_stream); +LL_COMMON_API bool skip_to_next_word(std::istream& input_stream); // skips to character after the end of next keyword // a 'keyword' is defined as the first word on a line -bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); +LL_COMMON_API bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); // skip_to_start_of_next_keyword() is disabled -- might tickle corruption bug // in windows iostream @@ -65,14 +65,14 @@ bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream // characters are pulled out of input_stream and appended to output_string // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream); -bool get_line(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream); // characters are pulled out of input_stream (up to a max of 'n') // and appended to output_string // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream, int n); -bool get_line(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream, int n); // unget_line() is disabled -- might tickle corruption bug in windows iostream //// backs up the input_stream by line_size + 1 characters @@ -82,28 +82,28 @@ bool get_line(std::string& output_string, std::istream& input_stream, int n); // removes the last char in 'line' if it matches 'c' // returns true if removed last char -bool remove_last_char(char c, std::string& line); +LL_COMMON_API bool remove_last_char(char c, std::string& line); // replaces escaped characters with the correct characters from left to right // "\\" ---> '\\' // "\n" ---> '\n' -void unescape_string(std::string& line); +LL_COMMON_API void unescape_string(std::string& line); // replaces unescaped characters with expanded equivalents from left to right // '\\' ---> "\\" // '\n' ---> "\n" -void escape_string(std::string& line); +LL_COMMON_API void escape_string(std::string& line); // replaces each '\n' character with ' ' -void replace_newlines_with_whitespace(std::string& line); +LL_COMMON_API void replace_newlines_with_whitespace(std::string& line); // erases any double-quote characters in line -void remove_double_quotes(std::string& line); +LL_COMMON_API void remove_double_quotes(std::string& line); // the 'keyword' is defined as the first word on a line // the 'value' is everything after the keyword on the same line // starting at the first non-whitespace and ending right before the newline -void get_keyword_and_value(std::string& keyword, +LL_COMMON_API void get_keyword_and_value(std::string& keyword, std::string& value, const std::string& line); @@ -111,13 +111,13 @@ void get_keyword_and_value(std::string& keyword, // read anymore or until we hit the count. Some istream // implimentations have a max that they will read. // Returns the number of bytes read. -std::streamsize fullread( +LL_COMMON_API std::streamsize fullread( std::istream& istr, char* buf, std::streamsize requested); -std::istream& operator>>(std::istream& str, const char *tocheck); +LL_COMMON_API std::istream& operator>>(std::istream& str, const char *tocheck); #endif diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f2edd5c559..c027aa7bdd 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -671,9 +671,9 @@ std::string ll_convert_wide_to_string(const wchar_t* in) } #endif // LL_WINDOWS -long LLStringOps::sltOffset; -long LLStringOps::localTimeOffset; -bool LLStringOps::daylightSavings; +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0; std::map<std::string, std::string> LLStringOps::datetimeToCodes; S32 LLStringOps::collate(const llwchar* a, const llwchar* b) @@ -700,11 +700,11 @@ void LLStringOps::setupDatetimeInfo (bool daylight) tmpT = gmtime (&nowT); gmtT = mktime (tmpT); - localTimeOffset = (long) (gmtT - localT); + sLocalTimeOffset = (long) (gmtT - localT); - daylightSavings = daylight; - sltOffset = (daylightSavings? 7 : 8 ) * 60 * 60; + sPacificDaylightTime = daylight; + sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; datetimeToCodes["wkday"] = "%a"; // Thu datetimeToCodes["weekday"] = "%A"; // Thursday @@ -957,36 +957,35 @@ bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, } else if (param != "utc") // slt { - secFromEpoch -= LLStringOps::getSltOffset(); + secFromEpoch -= LLStringOps::getPacificTimeOffset(); } // if never fell into those two ifs above, param must be utc if (secFromEpoch < 0) secFromEpoch = 0; - LLDate * datetime = new LLDate((F64)secFromEpoch); + LLDate datetime((F64)secFromEpoch); std::string code = LLStringOps::getDatetimeCode (token); // special case to handle timezone if (code == "%Z") { if (param == "utc") + { replacement = "GMT"; - else if (param == "slt") - replacement = "SLT"; - else if (param != "local") // *TODO Vadim: not local? then what? - replacement = LLStringOps::getDaylightSavings() ? "PDT" : "PST"; - - return true; - } - replacement = datetime->toHTTPDateString(code); - - if (code.empty()) - { - return false; - } - else - { + } + else if (param == "local") + { + replacement = ""; // user knows their own timezone + } + else + { + // "slt" = Second Life Time, which is deprecated. + // If not utc or user local time, fallback to Pacific time + replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; + } return true; } + replacement = datetime.toHTTPDateString(code); + return !code.empty(); } // LLStringUtil::format recogizes the following patterns. diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 8f70726a9e..31e70e0fe4 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -34,6 +34,7 @@ #define LL_LLSTRING_H #include <string> +#include <cstdio> #include <locale> #include <iomanip> #include "llsd.h" @@ -147,12 +148,12 @@ struct char_traits<U16> }; #endif -class LLStringOps +class LL_COMMON_API LLStringOps { private: - static long sltOffset; - static long localTimeOffset; - static bool daylightSavings; + static long sPacificTimeOffset; + static long sLocalTimeOffset; + static bool sPacificDaylightTime; static std::map<std::string, std::string> datetimeToCodes; public: @@ -183,10 +184,13 @@ public: static S32 collate(const char* a, const char* b) { return strcoll(a, b); } static S32 collate(const llwchar* a, const llwchar* b); - static void setupDatetimeInfo (bool daylight); - static long getSltOffset (void) {return sltOffset;} - static long getLocalTimeOffset (void) {return localTimeOffset;} - static bool getDaylightSavings (void) {return daylightSavings;} + static void setupDatetimeInfo(bool pacific_daylight_time); + static long getPacificTimeOffset(void) { return sPacificTimeOffset;} + static long getLocalTimeOffset(void) { return sLocalTimeOffset;} + // Is the Pacific time zone (aka server time zone) + // currently in daylight savings time? + static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} + static std::string getDatetimeCode (std::string key); }; @@ -194,8 +198,8 @@ public: * @brief Return a string constructed from in without crashing if the * pointer is NULL. */ -std::string ll_safe_string(const char* in); -std::string ll_safe_string(const char* in, S32 maxlen); +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); // Allowing assignments from non-strings into format_map_t is apparently @@ -230,13 +234,13 @@ public: static std::basic_string<T> null; typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t; - static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims); - static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals); - static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch); - static S32 format(std::basic_string<T>& s, const format_map_t& substitutions); - static S32 format(std::basic_string<T>& s, const LLSD& substitutions); - static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions); - static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions); + LL_COMMON_API static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims); + LL_COMMON_API static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals); + LL_COMMON_API static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch); + LL_COMMON_API static S32 format(std::basic_string<T>& s, const format_map_t& substitutions); + LL_COMMON_API static S32 format(std::basic_string<T>& s, const LLSD& substitutions); + LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions); + LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions); static void setLocale (std::string inLocale) {sLocale = inLocale;}; static std::string getLocale (void) {return sLocale;}; @@ -342,11 +346,11 @@ public: #ifdef _DEBUG - static void testHarness(); + LL_COMMON_API static void testHarness(); #endif private: - static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens); + LL_COMMON_API static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens); }; template<class T> std::basic_string<T> LLStringUtilBase<T>::null; @@ -400,7 +404,7 @@ inline std::string chop_tail_copy( * @brief This translates a nybble stored as a hex value from 0-f back * to a nybble in the low order bits of the return byte. */ -U8 hex_as_nybble(char hex); +LL_COMMON_API U8 hex_as_nybble(char hex); /** * @brief read the contents of a file into a string. @@ -411,8 +415,8 @@ U8 hex_as_nybble(char hex); * @param filename The full name of the file to read. * @return Returns true on success. If false, str is unmodified. */ -bool _read_file_into_string(std::string& str, const std::string& filename); -bool iswindividual(llwchar elem); +LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem); /** * Unicode support @@ -421,52 +425,52 @@ bool iswindividual(llwchar elem); // Make the incoming string a utf8 string. Replaces any unknown glyph // with the UNKOWN_CHARACTER. Once any unknown glph is found, the rest // of the data may not be recovered. -std::string rawstr_to_utf8(const std::string& raw); +LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); // // We should never use UTF16 except when communicating with Win32! // typedef std::basic_string<U16> llutf16string; -LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); -LLWString utf16str_to_wstring(const llutf16string &utf16str); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); -llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); -llutf16string wstring_to_utf16str(const LLWString &utf32str); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); -llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); -llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); -LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); -LLWString utf8str_to_wstring(const std::string &utf8str); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str); // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } // -S32 wchar_to_utf8chars(llwchar inchar, char* outchars); +LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); -std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); -std::string wstring_to_utf8str(const LLWString &utf32str); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); -std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); -std::string utf16str_to_utf8str(const llutf16string &utf16str); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); // Length of this UTF32 string in bytes when transformed to UTF8 -S32 wstring_utf8_length(const LLWString& wstr); +LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); // Length in bytes of this wide char in a UTF8 string -S32 wchar_utf8_length(const llwchar wc); +LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); -std::string utf8str_tolower(const std::string& utf8str); +LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); +LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); // Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); +LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); // Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); /** * @brief Properly truncate a utf8 string to a maximum byte count. @@ -478,11 +482,11 @@ S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset * @param max_len The maximum number of bytes in the return value. * @return Returns a valid utf8 string with byte count <= max_len. */ -std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); +LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); -std::string utf8str_trim(const std::string& utf8str); +LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); -S32 utf8str_compare_insensitive( +LL_COMMON_API S32 utf8str_compare_insensitive( const std::string& lhs, const std::string& rhs); @@ -493,17 +497,17 @@ S32 utf8str_compare_insensitive( * @param target_char The wchar to be replaced * @param replace_char The wchar which is written on replace */ -std::string utf8str_substChar( +LL_COMMON_API std::string utf8str_substChar( const std::string& utf8str, const llwchar target_char, const llwchar replace_char); -std::string utf8str_makeASCII(const std::string& utf8str); +LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); // Hack - used for evil notecards. -std::string mbcsstring_makeASCII(const std::string& str); +LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); -std::string utf8str_removeCRLF(const std::string& utf8str); +LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); #if LL_WINDOWS @@ -528,14 +532,21 @@ std::string utf8str_removeCRLF(const std::string& utf8str); * formatted string. * */ -int safe_snprintf(char* str, size_t size, const char* format, ...); + +// Deal with the differeneces on Windows +namespace snprintf_hack +{ + LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...); +} + +using snprintf_hack::snprintf; /** * @brief Convert a wide string to std::string * * This replaces the unsafe W2A macro from ATL. */ -std::string ll_convert_wide_to_string(const wchar_t* in); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); //@} #endif // LL_WINDOWS @@ -558,7 +569,7 @@ namespace LLStringFn * with zero non-printable characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_nonprintable_in_ascii( + LL_COMMON_API void replace_nonprintable_in_ascii( std::basic_string<char>& string, char replacement); @@ -572,7 +583,7 @@ namespace LLStringFn * with zero non-printable characters and zero pipe characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, + LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, char replacement); @@ -581,7 +592,7 @@ namespace LLStringFn * Returns a copy of the string with those characters removed. * Works with US ASCII and UTF-8 encoded strings. JC */ - std::string strip_invalid_xml(const std::string& input); + LL_COMMON_API std::string strip_invalid_xml(const std::string& input); /** @@ -592,7 +603,7 @@ namespace LLStringFn * with zero non-printable characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_ascii_controlchars( + LL_COMMON_API void replace_ascii_controlchars( std::basic_string<char>& string, char replacement); } diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index 888361b0b9..d40c9d8dfd 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -48,15 +48,17 @@ //# define STRING_TABLE_HASH_MAP 1 #endif -#if LL_WINDOWS -#include <hash_map> -#else -#include <ext/hash_map> +#if STRING_TABLE_HASH_MAP +# if LL_WINDOWS +# include <hash_map> +# else +# include <ext/hash_map> +# endif #endif const U32 MAX_STRINGS_LENGTH = 256; -class LLStringTableEntry +class LL_COMMON_API LLStringTableEntry { public: LLStringTableEntry(const char *str); @@ -69,7 +71,7 @@ public: S32 mCount; }; -class LLStringTable +class LL_COMMON_API LLStringTable { public: LLStringTable(int tablesize); @@ -103,7 +105,7 @@ public: #endif }; -extern LLStringTable gStringTable; +extern LL_COMMON_API LLStringTable gStringTable; //============================================================================ @@ -113,7 +115,7 @@ extern LLStringTable gStringTable; typedef const std::string* LLStdStringHandle; -class LLStdStringTable +class LL_COMMON_API LLStdStringTable { public: LLStdStringTable(S32 tablesize = 0) diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 4d03c4d40d..3652eeba72 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -124,12 +124,53 @@ LLOSInfo::LLOSInfo() : } else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) { + ///get native system info if available.. + typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo + SYSTEM_INFO si; //System Info object file contains architecture info + PGNSI pGNSI; //pointer object + ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information + pGNSI = (PGNSI) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function + if(NULL != pGNSI) //check if it has failed + pGNSI(&si); //success + else + GetSystemInfo(&si); //if it fails get regular system info + //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) + + //msdn microsoft finds 32 bit and 64 bit flavors this way.. + //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors + //of windows than this code does (in case it is needed for the future) + if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 ) //check for 64 bit + { + if(osvi.wProductType == VER_NT_WORKSTATION) + mOSStringSimple = "Microsoft Windows Vista 64-bit "; + else + mOSStringSimple = "Microsoft Windows Vista Server 64-bit "; + } + else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL ) + { + if(osvi.wProductType == VER_NT_WORKSTATION) + mOSStringSimple = "Microsoft Windows Vista 32-bit "; + else + mOSStringSimple = "Microsoft Windows Vista Server 32-bit "; + } + else // PROCESSOR_ARCHITECTURE_IA64 || PROCESSOR_ARCHITECTURE_UNKNOWN not checked + { + if(osvi.wProductType == VER_NT_WORKSTATION) + mOSStringSimple = "Microsoft Windows Vista "; + else + mOSStringSimple = "Microsoft Windows Vista Server "; + } + } + else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) + { if(osvi.wProductType == VER_NT_WORKSTATION) - mOSStringSimple = "Microsoft Windows Vista "; - else mOSStringSimple = "Microsoft Windows Vista Server "; + mOSStringSimple = "Microsoft Windows 7 "; + else mOSStringSimple = "Microsoft Windows 7 Server "; } else // Use the registry on early versions of Windows NT. { + mOSStringSimple = "Microsoft Windows (unrecognized) "; + HKEY hKey; WCHAR szProductType[80]; DWORD dwBufLen; diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 03f48ca018..c2c45bec9a 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -45,7 +45,7 @@ #include <iosfwd> #include <string> -class LLOSInfo +class LL_COMMON_API LLOSInfo { public: LLOSInfo(); @@ -70,7 +70,7 @@ private: }; -class LLCPUInfo +class LL_COMMON_API LLCPUInfo { public: LLCPUInfo(); @@ -99,7 +99,7 @@ private: // // CLASS LLMemoryInfo -class LLMemoryInfo +class LL_COMMON_API LLMemoryInfo /*! @brief Class to query the memory subsystem @@ -123,15 +123,15 @@ public: }; -std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); -std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); -std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); // gunzip srcfile into dstfile. Returns FALSE on error. -BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); // gzip srcfile into dstfile. Returns FALSE on error. -BOOL gzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); -extern LLCPUInfo gSysCPU; +extern LL_COMMON_API LLCPUInfo gSysCPU; #endif // LL_LLSYS_H diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index f25339f48d..c3d7650bd9 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -42,7 +42,7 @@ class LLThread; class LLMutex; class LLCondition; -class LLThread +class LL_COMMON_API LLThread { public: typedef enum e_thread_status @@ -130,7 +130,7 @@ protected: //============================================================================ -class LLMutex +class LL_COMMON_API LLMutex { public: LLMutex(apr_pool_t *apr_poolp); // NULL pool constructs a new pool for the mutex @@ -147,7 +147,7 @@ protected: }; // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). -class LLCondition : public LLMutex +class LL_COMMON_API LLCondition : public LLMutex { public: LLCondition(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well. @@ -194,7 +194,7 @@ void LLThread::unlockData() // see llmemory.h for LLPointer<> definition -class LLThreadSafeRefCount +class LL_COMMON_API LLThreadSafeRefCount { public: static void initThreadSafeRefCount(); // creates sMutex @@ -246,7 +246,7 @@ private: // Simple responder for self destructing callbacks // Pure virtual class -class LLResponder : public LLThreadSafeRefCount +class LL_COMMON_API LLResponder : public LLThreadSafeRefCount { protected: virtual ~LLResponder(); diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 0319bec45b..d009c0f5f7 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -55,7 +55,7 @@ const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; -class LLTimer +class LL_COMMON_API LLTimer { public: static LLTimer *sTimer; // global timer @@ -114,17 +114,17 @@ public: // // Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. // -U64 get_clock_count(); -F64 calc_clock_frequency(U32 msecs); -void update_clock_frequencies(); +LL_COMMON_API U64 get_clock_count(); +LL_COMMON_API F64 calc_clock_frequency(U32 msecs); +LL_COMMON_API void update_clock_frequencies(); // Sleep for milliseconds -void ms_sleep(U32 ms); -U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); +LL_COMMON_API void ms_sleep(U32 ms); +LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); // Returns the correct UTC time in seconds, like time(NULL). // Useful on the viewer, which may have its local clock set wrong. -time_t time_corrected(); +LL_COMMON_API time_t time_corrected(); static inline time_t time_min() { @@ -155,24 +155,24 @@ static inline time_t time_max() } // Correction factor used by time_corrected() above. -extern S32 gUTCOffset; +extern LL_COMMON_API S32 gUTCOffset; // Is the current computer (in its current time zone) // observing daylight savings time? -BOOL is_daylight_savings(); +LL_COMMON_API BOOL is_daylight_savings(); // Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time // Usage: // S32 utc_time; // utc_time = time_corrected(); // struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); -void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); -void secondsToTimecodeString(F32 current_time, std::string& tcstring); +LL_COMMON_API void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); +LL_COMMON_API void secondsToTimecodeString(F32 current_time, std::string& tcstring); // class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LLEventTimer : protected LLInstanceTracker<LLEventTimer> +class LL_COMMON_API LLEventTimer : protected LLInstanceTracker<LLEventTimer> { public: LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds @@ -190,4 +190,6 @@ protected: F32 mPeriod; }; +U64 LL_COMMON_API totalTime(); // Returns current system time in microseconds + #endif diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 8e46e2e89e..8e69e8558a 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -47,7 +47,7 @@ class LLApp; * See: http://www.ietf.org/rfc/rfc3986.txt * */ -class LLURI +class LL_COMMON_API LLURI { public: LLURI(); @@ -178,6 +178,6 @@ private: }; // this operator required for tut -bool operator!=(const LLURI& first, const LLURI& second); +LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); #endif // LL_LLURI_H diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 4b32138a06..c78fb12018 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -35,6 +35,7 @@ #include <iostream> #include <set> #include "stdtypes.h" +#include "llpreprocessor.h" const S32 UUID_BYTES = 16; const S32 UUID_WORDS = 4; @@ -47,7 +48,7 @@ struct uuid_time_t { U32 low; }; -class LLUUID +class LL_COMMON_API LLUUID { public: // @@ -106,8 +107,8 @@ public: LLUUID combine(const LLUUID& other) const; void combine(const LLUUID& other, LLUUID& result) const; - friend std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); - friend std::istream& operator>>(std::istream& s, LLUUID &uuid); + friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); + friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) void toString(std::string& out) const; @@ -323,7 +324,7 @@ typedef std::set<LLUUID, lluuid_less> uuid_list_t; */ typedef LLUUID LLAssetID; -class LLTransactionID : public LLUUID +class LL_COMMON_API LLTransactionID : public LLUUID { public: LLTransactionID() : LLUUID() { } diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h index 23e39ceb08..71c6fc0591 100644 --- a/indra/llcommon/llversionserver.h +++ b/indra/llcommon/llversionserver.h @@ -34,9 +34,9 @@ #define LL_LLVERSIONSERVER_H const S32 LL_VERSION_MAJOR = 1; -const S32 LL_VERSION_MINOR = 29; +const S32 LL_VERSION_MINOR = 31; const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 0; +const S32 LL_VERSION_BUILD = 3256; const char * const LL_CHANNEL = "Second Life Server"; diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index 2c3e9c7333..082d054ba2 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -36,7 +36,7 @@ const S32 LL_VERSION_MAJOR = 2; const S32 LL_VERSION_MINOR = 0; const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 0; +const S32 LL_VERSION_BUILD = 3256; const char * const LL_CHANNEL = "Second Life Developer"; diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 19407f4463..a12bd52a64 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -50,7 +50,7 @@ class LLWorkerClass; // Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small // It is assumed that LLWorkerThreads are rarely created/destroyed. -class LLWorkerThread : public LLQueuedThread +class LL_COMMON_API LLWorkerThread : public LLQueuedThread { public: class WorkRequest : public LLQueuedThread::QueuedRequest @@ -113,7 +113,7 @@ public: // Only one background task can be active at a time (per instance). // i.e. don't call addWork() if haveWork() returns true -class LLWorkerClass +class LL_COMMON_API LLWorkerClass { friend class LLWorkerThread; friend class LLWorkerThread::WorkRequest; diff --git a/indra/llcommon/metaclass.h b/indra/llcommon/metaclass.h index cc10f1675f..f38bcd2d57 100644 --- a/indra/llcommon/metaclass.h +++ b/indra/llcommon/metaclass.h @@ -43,7 +43,7 @@ class LLReflective; class LLMetaProperty; class LLMetaMethod; -class LLMetaClass +class LL_COMMON_API LLMetaClass { public: diff --git a/indra/llcommon/metaproperty.h b/indra/llcommon/metaproperty.h index e5ac35907c..6c016c56dd 100644 --- a/indra/llcommon/metaproperty.h +++ b/indra/llcommon/metaproperty.h @@ -41,7 +41,7 @@ class LLMetaClass; class LLReflective; -class LLMetaProperty +class LL_COMMON_API LLMetaProperty { public: LLMetaProperty(const std::string& name, const LLMetaClass& object_class); diff --git a/indra/llcommon/metapropertyt.h b/indra/llcommon/metapropertyt.h index 79a536a224..5ad230d1d5 100644 --- a/indra/llcommon/metapropertyt.h +++ b/indra/llcommon/metapropertyt.h @@ -94,6 +94,13 @@ inline const LLReflective* LLMetaPropertyT<LLUUID>::get(const LLReflective* obje } template <> +inline const LLReflective* LLMetaPropertyT<bool>::get(const LLReflective* object) const +{ + checkObjectClass(object); + return NULL; +} + +template <> inline LLSD LLMetaPropertyT<S32>::getLLSD(const LLReflective* object) const { return *(getProperty(object)); @@ -111,6 +118,12 @@ inline LLSD LLMetaPropertyT<LLUUID>::getLLSD(const LLReflective* object) const return *(getProperty(object)); } +template <> +inline LLSD LLMetaPropertyT<bool>::getLLSD(const LLReflective* object) const +{ + return *(getProperty(object)); +} + template<class TObject, class TProperty> class LLMetaPropertyTT : public LLMetaPropertyT<TProperty> { diff --git a/indra/llcommon/reflective.h b/indra/llcommon/reflective.h index e2c18ebc6d..a13537681d 100644 --- a/indra/llcommon/reflective.h +++ b/indra/llcommon/reflective.h @@ -36,7 +36,7 @@ #define LL_REFLECTIVE_H class LLMetaClass; -class LLReflective +class LL_COMMON_API LLReflective { public: LLReflective(); diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 1b2958020f..6399547f5e 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -13,6 +13,7 @@ #define LL_STRINGIZE_H #include <sstream> +#include <boost/lambda/lambda.hpp> /** * stringize(item) encapsulates an idiom we use constantly, using @@ -28,6 +29,17 @@ std::string stringize(const T& item) } /** + * stringize_f(functor) + */ +template <typename Functor> +std::string stringize_f(Functor const & f) +{ + std::ostringstream out; + f(out); + return out.str(); +} + +/** * STRINGIZE(item1 << item2 << item3 ...) effectively expands to the * following: * @code @@ -36,40 +48,43 @@ std::string stringize(const T& item) * return out.str(); * @endcode */ -#define STRINGIZE(EXPRESSION) (static_cast<std::ostringstream&>(Stringize() << EXPRESSION).str()) +#define STRINGIZE(EXPRESSION) (stringize_f(boost::lambda::_1 << EXPRESSION)) + /** - * Helper class for STRINGIZE() macro. Ideally the body of - * STRINGIZE(EXPRESSION) would look something like this: + * destringize(str) + * defined for symmetry with stringize + * *NOTE - this has distinct behavior from boost::lexical_cast<T> regarding + * leading/trailing whitespace and handling of bad_lexical_cast exceptions + */ +template <typename T> +T destringize(std::string const & str) +{ + T val; + std::istringstream in(str); + in >> val; + return val; +} + +/** + * destringize_f(str, functor) + */ +template <typename Functor> +void destringize_f(std::string const & str, Functor const & f) +{ + std::istringstream in(str); + f(in); +} + +/** + * DESTRINGIZE(str, item1 >> item2 >> item3 ...) effectively expands to the + * following: * @code - * (std::ostringstream() << EXPRESSION).str() + * std::istringstream in(str); + * in >> item1 >> item2 >> item3 ... ; * @endcode - * That doesn't work because each of the relevant operator<<() functions - * accepts a non-const std::ostream&, to which you can't pass a temp instance - * of std::ostringstream. Stringize plays the necessary const tricks to make - * the whole thing work. */ -class Stringize -{ -public: - /** - * This is the essence of Stringize. The leftmost << operator (the one - * coded in the STRINGIZE() macro) engages this operator<<() const method - * on the temp Stringize instance. Every other << operator (ones embedded - * in EXPRESSION) simply sees the std::ostream& returned by the first one. - * - * Finally, the STRINGIZE() macro downcasts that std::ostream& to - * std::ostringstream&. - */ - template <typename T> - std::ostream& operator<<(const T& item) const - { - mOut << item; - return mOut; - } +#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), (boost::lambda::_1 >> EXPRESSION))) -private: - mutable std::ostringstream mOut; -}; #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/bitpack_test.cpp b/indra/llcommon/tests/bitpack_test.cpp new file mode 100644 index 0000000000..09fd037f02 --- /dev/null +++ b/indra/llcommon/tests/bitpack_test.cpp @@ -0,0 +1,125 @@ +/** + * @file bitpack_test.cpp + * @author Adroit + * @date 2007-02 + * @brief llstreamtools test cases. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-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 "../bitpack.h" + +#include "../test/lltut.h" + + +namespace tut +{ + struct bit_pack + { + }; + typedef test_group<bit_pack> bit_pack_t; + typedef bit_pack_t::object bit_pack_object_t; + tut::bit_pack_t tut_bit_pack("bitpack"); + + // pack -> unpack + template<> template<> + void bit_pack_object_t::test<1>() + { + U8 packbuffer[255]; + U8 unpackbuffer[255]; + int pack_bufsize = 0; + int unpack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + + char str[] = "SecondLife is a 3D virtual world"; + int len = sizeof(str); + pack_bufsize = bitpack.bitPack((U8*) str, len*8); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, len*8); + ensure("bitPack: unpack size should be same as string size prior to pack", len == unpack_bufsize); + ensure_memory_matches("str->bitPack->bitUnpack should be equal to string", str, len, unpackbuffer, unpack_bufsize); + } + + // pack large, unpack in individual bytes + template<> template<> + void bit_pack_object_t::test<2>() + { + U8 packbuffer[255]; + U8 unpackbuffer[255]; + int pack_bufsize = 0; + int unpack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + + char str[] = "SecondLife"; + int len = sizeof(str); + pack_bufsize = bitpack.bitPack((U8*) str, len*8); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 0", unpackbuffer[0] == (U8) str[0]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 1", unpackbuffer[0] == (U8) str[1]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 2", unpackbuffer[0] == (U8) str[2]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 3", unpackbuffer[0] == (U8) str[3]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 4", unpackbuffer[0] == (U8) str[4]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 5", unpackbuffer[0] == (U8) str[5]); + unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, 8*4); // Life + ensure_memory_matches("bitPack: 4 bytes unpack:", unpackbuffer, 4, str+6, 4); + } + + // U32 packing + template<> template<> + void bit_pack_object_t::test<3>() + { + U8 packbuffer[255]; + int pack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + U32 num = 0x41fab67a; + pack_bufsize = bitpack.bitPack((U8*)&num, 8*sizeof(U32)); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + U32 res = 0; + // since packing and unpacking is done on same machine in the unit test run, + // endianness should not matter + bitunpack.bitUnpack((U8*) &res, sizeof(res)*8); + ensure("U32->bitPack->bitUnpack->U32 should be equal", num == res); + } +} diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h new file mode 100644 index 0000000000..fa12f944ef --- /dev/null +++ b/indra/llcommon/tests/listener.h @@ -0,0 +1,139 @@ +/** + * @file listener.h + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Useful for tests of the LLEventPump family of classes + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LISTENER_H) +#define LL_LISTENER_H + +#include "llsd.h" +#include <iostream> + +/***************************************************************************** +* test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +/// Bear in mind that this is strictly for testing +class Listener +{ +public: + /// Every Listener is instantiated with a name + Listener(const std::string& name): + mName(name) + { +// std::cout << *this << ": ctor\n"; + } +/*==========================================================================*| + // These methods are only useful when trying to track Listener instance + // lifespan + Listener(const Listener& that): + mName(that.mName), + mLastEvent(that.mLastEvent) + { + std::cout << *this << ": copy\n"; + } + virtual ~Listener() + { + std::cout << *this << ": dtor\n"; + } +|*==========================================================================*/ + /// You can request the name + std::string getName() const { return mName; } + /// This is a typical listener method that returns 'false' when done, + /// allowing subsequent listeners on the LLEventPump to process the + /// incoming event. + bool call(const LLSD& event) + { +// std::cout << *this << "::call(" << event << ")\n"; + mLastEvent = event; + return false; + } + /// This is an alternate listener that returns 'true' when done, which + /// stops processing of the incoming event. + bool callstop(const LLSD& event) + { +// std::cout << *this << "::callstop(" << event << ")\n"; + mLastEvent = event; + return true; + } + /// ListenMethod can represent either call() or callstop(). + typedef bool (Listener::*ListenMethod)(const LLSD&); + /** + * This helper method is only because our test code makes so many + * repetitive listen() calls to ListenerMethods. In real code, you should + * call LLEventPump::listen() directly so it can examine the specific + * object you pass to boost::bind(). + */ + LLBoundListener listenTo(LLEventPump& pump, + ListenMethod method=&Listener::call, + const LLEventPump::NameList& after=LLEventPump::empty, + const LLEventPump::NameList& before=LLEventPump::empty) + { + return pump.listen(getName(), boost::bind(method, this, _1), after, before); + } + /// Both call() and callstop() set mLastEvent. Retrieve it. + LLSD getLastEvent() const + { +// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; + return mLastEvent; + } + /// Reset mLastEvent to a known state. + void reset(const LLSD& to = LLSD()) + { +// std::cout << *this << "::reset(" << to << ")\n"; + mLastEvent = to; + } + +private: + std::string mName; + LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ + out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; + return out; +} + +/** + * This class tests the relative order in which various listeners on a given + * LLEventPump are called. Each listen() call binds a particular string, which + * we collect for later examination. The actual event is ignored. + */ +struct Collect +{ + bool add(const std::string& bound, const LLSD& event) + { + result.push_back(bound); + return false; + } + void clear() { result.clear(); } + typedef std::vector<std::string> StringList; + StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ + out << '('; + Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); + if (begin != end) + { + out << '"' << *begin << '"'; + while (++begin != end) + { + out << ", \"" << *begin << '"'; + } + } + out << ')'; + return out; +} + +#endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/llbase64_test.cpp b/indra/llcommon/tests/llbase64_test.cpp new file mode 100644 index 0000000000..6009788b22 --- /dev/null +++ b/indra/llcommon/tests/llbase64_test.cpp @@ -0,0 +1,83 @@ +/** + * @file llbase64_test.cpp + * @author James Cook + * @date 2007-02-04 + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-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 <string> + +#include "linden_common.h" + +#include "../llbase64.h" +#include "../lluuid.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct base64_data + { + }; + typedef test_group<base64_data> base64_test; + typedef base64_test::object base64_object; + tut::base64_test base64("base64"); + + template<> template<> + void base64_object::test<1>() + { + std::string result; + + result = LLBase64::encode(NULL, 0); + ensure("encode nothing", (result == "") ); + + LLUUID nothing; + result = LLBase64::encode(¬hing.mData[0], UUID_BYTES); + ensure("encode blank uuid", + (result == "AAAAAAAAAAAAAAAAAAAAAA==") ); + + LLUUID id("526a1e07-a19d-baed-84c4-ff08a488d15e"); + result = LLBase64::encode(&id.mData[0], UUID_BYTES); + ensure("encode random uuid", + (result == "UmoeB6Gduu2ExP8IpIjRXg==") ); + + } + + template<> template<> + void base64_object::test<2>() + { + std::string result; + + U8 blob[40] = { 115, 223, 172, 255, 140, 70, 49, 125, 236, 155, 45, 199, 101, 17, 164, 131, 230, 19, 80, 64, 112, 53, 135, 98, 237, 12, 26, 72, 126, 14, 145, 143, 118, 196, 11, 177, 132, 169, 195, 134 }; + result = LLBase64::encode(&blob[0], 40); + ensure("encode 40 bytes", + (result == "c9+s/4xGMX3smy3HZRGkg+YTUEBwNYdi7QwaSH4OkY92xAuxhKnDhg==") ); + } + +} diff --git a/indra/llcommon/tests/lldate_test.cpp b/indra/llcommon/tests/lldate_test.cpp index 1e36fdd119..c31259227a 100644 --- a/indra/llcommon/tests/lldate_test.cpp +++ b/indra/llcommon/tests/lldate_test.cpp @@ -1,5 +1,5 @@ /** - * @file lldate_tut.cpp + * @file lldate_test.cpp * @author Adroit * @date 2007-02 * @brief LLDate test cases. diff --git a/indra/llcommon/tests/lldependencies_test.cpp b/indra/llcommon/tests/lldependencies_test.cpp new file mode 100644 index 0000000000..f3c25de8b5 --- /dev/null +++ b/indra/llcommon/tests/lldependencies_test.cpp @@ -0,0 +1,275 @@ +/** + * @file lldependencies_tut.cpp + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief Test of lldependencies.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// STL headers +#include <iostream> +#include <string> +// std headers +// external library headers +#include <boost/assign/list_of.hpp> +// Precompiled header +#include "linden_common.h" +// associated header +#include "../lldependencies.h" +// other Linden headers +#include "../test/lltut.h" + +using boost::assign::list_of; + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +typedef LLDependencies<> StringDeps; +typedef StringDeps::KeyList StringList; + +// We use the very cool boost::assign::list_of() construct to specify vectors +// of strings inline. For reasons on which I'm not entirely clear, though, it +// needs a helper function. You can use list_of() to construct an implicit +// StringList (std::vector<std::string>) by conversion, e.g. for a function +// parameter -- but if you simply write StringList(list_of("etc.")), you get +// ambiguity errors. Shrug! +template<typename CONTAINER> +CONTAINER make(const CONTAINER& data) +{ + return data; +} + +// Display an arbitary value as itself... +template<typename T> +std::ostream& display(std::ostream& out, const T& value) +{ + out << value; + return out; +} + +// ...but display std::string enclosed in double quotes. +template<> +std::ostream& display(std::ostream& out, const std::string& value) +{ + out << '"' << value << '"'; + return out; +} + +// display any sequence compatible with Boost.Range +template<typename SEQUENCE> +std::ostream& display_seq(std::ostream& out, + const std::string& open, const SEQUENCE& seq, const std::string& close) +{ + out << open; + typename boost::range_const_iterator<SEQUENCE>::type + sli = boost::begin(seq), + slend = boost::end(seq); + if (sli != slend) + { + display(out, *sli); + while (++sli != slend) + { + out << ", "; + display(out, *sli); + } + } + out << close; + return out; +} + +// helper to dump a StringList to std::cout if needed +template<typename ENTRY> +std::ostream& operator<<(std::ostream& out, const std::vector<ENTRY>& list) +{ + display_seq(out, "(", list, ")"); + return out; +} + +template<typename ENTRY> +std::ostream& operator<<(std::ostream& out, const std::set<ENTRY>& set) +{ + display_seq(out, "{", set, "}"); + return out; +} + +const std::string& extract_key(const LLDependencies<>::value_type& entry) +{ + return entry.first; +} + +// helper to return a StringList of keys from LLDependencies::sort() +StringList sorted_keys(LLDependencies<>& deps) +{ + // 1. Call deps.sort(), returning a value_type range of (key, node) pairs. + // 2. Use make_transform_range() to obtain a range of just keys. + // 3. Use instance_from_range to instantiate a StringList from that range. + // 4. Return by value "slices" instance_from_range<StringList> (a subclass + // of StringList) to its base class StringList. + return instance_from_range<StringList>(make_transform_range(deps.sort(), extract_key)); +} + +template<typename RANGE> +bool is_empty(const RANGE& range) +{ + return boost::begin(range) == boost::end(range); +} + +/***************************************************************************** +* tut test group +*****************************************************************************/ +namespace tut +{ + struct deps_data + { + }; + typedef test_group<deps_data> deps_group; + typedef deps_group::object deps_object; + tut::deps_group depsgr("lldependencies"); + + template<> template<> + void deps_object::test<1>() + { + StringDeps deps; + StringList empty; + // The quick brown fox jumps over the lazy yellow dog. + // (note, "The" and "the" are distinct, else this test wouldn't work) + deps.add("lazy"); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy"))); + deps.add("jumps"); + ensure("found lazy", deps.get("lazy")); + ensure("not found dog.", ! deps.get("dog.")); + // NOTE: Maybe it's overkill to test each of these intermediate + // results before all the interdependencies have been specified. My + // thought is simply that if the order changes, I'd like to know why. + // A change to the implementation of boost::topological_sort() would + // be an acceptable reason, and you can simply update the expected + // test output. + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps"))); + deps.add("The", 0, empty, list_of("fox")("dog.")); + // Test key accessors + ensure("empty before deps for missing key", is_empty(deps.get_before_range("bogus"))); + ensure("empty before deps for jumps", is_empty(deps.get_before_range("jumps"))); + ensure_equals(instance_from_range< std::set<std::string> >(deps.get_before_range("The")), + make< std::set<std::string> >(list_of("dog.")("fox"))); + // resume building dependencies + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps")("The"))); + deps.add("the", 0, list_of("The")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps")("The")("the"))); + deps.add("fox", 0, list_of("The"), list_of("jumps")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); + deps.add("the", 0, list_of("The")); // same, see if cache works + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); + deps.add("jumps", 0, empty, list_of("over")); // update jumps deps + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); +/*==========================================================================*| + // It drives me nuts that this test doesn't work in the test + // framework, because -- for reasons unknown -- running the test + // framework on Mac OS X 10.5 Leopard and Windows XP Pro, the catch + // clause below doesn't catch the exception. Something about the TUT + // test framework?!? The identical code works fine in a standalone + // test program. Commenting out the test for now, in hopes that our + // real builds will be able to catch Cycle exceptions... + try + { + // We've already specified fox -> jumps and jumps -> over. Try an + // impossible constraint. + deps.add("over", 0, empty, list_of("fox")); + } + catch (const StringDeps::Cycle& e) + { + std::cout << "Cycle detected: " << e.what() << '\n'; + // It's legal to add() an impossible constraint because we don't + // detect the cycle until sort(). So sort() can't know the minimum set + // of nodes to remove to make the StringDeps object valid again. + // Therefore we must break the cycle by hand. + deps.remove("over"); + } +|*==========================================================================*/ + deps.add("dog.", 0, list_of("yellow")("lazy")); + ensure_equals(instance_from_range< std::set<std::string> >(deps.get_after_range("dog.")), + make< std::set<std::string> >(list_of("lazy")("yellow"))); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps")("dog."))); + deps.add("quick", 0, list_of("The"), list_of("fox")("brown")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("quick")("fox")("jumps")("dog."))); + deps.add("over", 0, list_of("jumps"), list_of("yellow")("the")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("quick")("fox")("jumps")("over")("the")("dog."))); + deps.add("yellow", 0, list_of("the"), list_of("lazy")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("The")("quick")("fox")("jumps")("over")("the")("yellow")("lazy")("dog."))); + deps.add("brown"); + // By now the dependencies are pretty well in place. A change to THIS + // order should be viewed with suspicion. + ensure_equals(sorted_keys(deps), make<StringList>(list_of("The")("quick")("brown")("fox")("jumps")("over")("the")("yellow")("lazy")("dog."))); + + StringList keys(make<StringList>(list_of("The")("brown")("dog.")("fox")("jumps")("lazy")("over")("quick")("the")("yellow"))); + ensure_equals(instance_from_range<StringList>(deps.get_key_range()), keys); +#if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) + // This is the succinct way, works on modern compilers + ensure_equals(instance_from_range<StringList>(make_transform_range(deps.get_range(), extract_key)), keys); +#else // gcc 3.3 + StringDeps::range got_range(deps.get_range()); + StringDeps::iterator kni = got_range.begin(), knend = got_range.end(); + StringList::iterator ki = keys.begin(), kend = keys.end(); + for ( ; kni != knend && ki != kend; ++kni, ++ki) + { + ensure_equals(kni->first, *ki); + } + ensure("get_range() returns proper length", kni == knend && ki == kend); +#endif // gcc 3.3 + // blow off get_node_range() because they're all LLDependenciesEmpty instances + } + + template<> template<> + void deps_object::test<2>() + { + typedef LLDependencies<std::string, int> NameIndexDeps; + NameIndexDeps nideps; + const NameIndexDeps& const_nideps(nideps); + nideps.add("def", 2, list_of("ghi")); + nideps.add("ghi", 3); + nideps.add("abc", 1, list_of("def")); + NameIndexDeps::range range(nideps.get_range()); + ensure_equals(range.begin()->first, "abc"); + ensure_equals(range.begin()->second, 1); + range.begin()->second = 0; + range.begin()->second = 1; + NameIndexDeps::const_range const_range(const_nideps.get_range()); + NameIndexDeps::const_iterator const_iterator(const_range.begin()); + ++const_iterator; + ensure_equals(const_iterator->first, "def"); + ensure_equals(const_iterator->second, 2); + NameIndexDeps::node_range node_range(nideps.get_node_range()); + ensure_equals(instance_from_range<std::vector<int> >(node_range), make< std::vector<int> >(list_of(1)(2)(3))); + *node_range.begin() = 0; + *node_range.begin() = 1; + NameIndexDeps::const_node_range const_node_range(const_nideps.get_node_range()); + ensure_equals(instance_from_range<std::vector<int> >(const_node_range), make< std::vector<int> >(list_of(1)(2)(3))); + NameIndexDeps::const_key_range const_key_range(const_nideps.get_key_range()); + ensure_equals(instance_from_range<StringList>(const_key_range), make<StringList>(list_of("abc")("def")("ghi"))); + NameIndexDeps::sorted_range sorted(const_nideps.sort()); + NameIndexDeps::sorted_iterator sortiter(sorted.begin()); + ensure_equals(sortiter->first, "ghi"); + ensure_equals(sortiter->second, 3); + + // test all iterator-flavored versions of get_after_range() + StringList def(make<StringList>(list_of("def"))); + ensure("empty abc before list", is_empty(nideps.get_before_range(nideps.get_range().begin()))); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(const_nideps.get_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_node_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(const_nideps.get_node_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_key_range().begin())), + def); + // advance from "ghi" to "def", which must come after "ghi" + ++sortiter; + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(sortiter)), + make<StringList>(list_of("ghi"))); + } +} // namespace tut diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp new file mode 100644 index 0000000000..1558df231a --- /dev/null +++ b/indra/llcommon/tests/llerror_test.cpp @@ -0,0 +1,768 @@ +/** + * @file llerror_test.cpp + * @date December 2006 + * @brief error unit tests + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-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 <vector> + +#include "linden_common.h" + +#include "../llerror.h" + +#include "../llerrorcontrol.h" +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace +{ + void test_that_error_h_includes_enough_things_to_compile_a_message() + { + llinfos << "!" << llendl; + } +} + +namespace +{ + static bool fatalWasCalled; + void fatalCall(const std::string&) { fatalWasCalled = true; } + + class TestRecorder : public LLError::Recorder + { + public: + TestRecorder() : mWantsTime(false) { } + ~TestRecorder() { LLError::removeRecorder(this); } + + void recordMessage(LLError::ELevel level, + const std::string& message) + { + mMessages.push_back(message); + } + + int countMessages() { return (int) mMessages.size(); } + void clearMessages() { mMessages.clear(); } + + void setWantsTime(bool t) { mWantsTime = t; } + bool wantsTime() { return mWantsTime; } + + std::string message(int n) + { + std::ostringstream test_name; + test_name << "testing message " << n << ", not enough messages"; + + tut::ensure(test_name.str(), n < countMessages()); + return mMessages[n]; + } + + private: + typedef std::vector<std::string> MessageVector; + MessageVector mMessages; + + bool mWantsTime; + }; +} + +namespace tut +{ + struct ErrorTestData + { + TestRecorder mRecorder; + LLError::Settings* mPriorErrorSettings; + + ErrorTestData() + { + fatalWasCalled = false; + + mPriorErrorSettings = LLError::saveAndResetSettings(); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setFatalFunction(fatalCall); + LLError::addRecorder(&mRecorder); + } + + ~ErrorTestData() + { + LLError::removeRecorder(&mRecorder); + LLError::restoreSettings(mPriorErrorSettings); + } + + void ensure_message_count(int expectedCount) + { + ensure_equals("message count", mRecorder.countMessages(), expectedCount); + } + + void ensure_message_contains(int n, const std::string& expectedText) + { + std::ostringstream test_name; + test_name << "testing message " << n; + + ensure_contains(test_name.str(), mRecorder.message(n), expectedText); + } + + void ensure_message_does_not_contain(int n, const std::string& expectedText) + { + std::ostringstream test_name; + test_name << "testing message " << n; + + ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText); + } + }; + + typedef test_group<ErrorTestData> ErrorTestGroup; + typedef ErrorTestGroup::object ErrorTestObject; + + ErrorTestGroup errorTestGroup("error"); + + template<> template<> + void ErrorTestObject::test<1>() + // basic test of output + { + llinfos << "test" << llendl; + llinfos << "bob" << llendl; + + ensure_message_contains(0, "test"); + ensure_message_contains(1, "bob"); + } +} + +namespace +{ + void writeSome() + { + lldebugs << "one" << llendl; + llinfos << "two" << llendl; + llwarns << "three" << llendl; + llerrs << "four" << llendl; + // fatal messages write out and addtional "error" message + } +}; + +namespace tut +{ + template<> template<> + void ErrorTestObject::test<2>() + // messages are filtered based on default level + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + writeSome(); + ensure_message_contains(0, "one"); + ensure_message_contains(1, "two"); + ensure_message_contains(2, "three"); + ensure_message_contains(3, "error"); + ensure_message_contains(4, "four"); + ensure_message_count(5); + + LLError::setDefaultLevel(LLError::LEVEL_INFO); + writeSome(); + ensure_message_contains(5, "two"); + ensure_message_contains(6, "three"); + ensure_message_contains(7, "error"); + ensure_message_contains(8, "four"); + ensure_message_count(9); + + LLError::setDefaultLevel(LLError::LEVEL_WARN); + writeSome(); + ensure_message_contains(9, "three"); + ensure_message_contains(10, "error"); + ensure_message_contains(11, "four"); + ensure_message_count(12); + + LLError::setDefaultLevel(LLError::LEVEL_ERROR); + writeSome(); + ensure_message_contains(12, "error"); + ensure_message_contains(13, "four"); + ensure_message_count(14); + + LLError::setDefaultLevel(LLError::LEVEL_NONE); + writeSome(); + ensure_message_count(14); + } + + template<> template<> + void ErrorTestObject::test<3>() + // error type string in output + { + writeSome(); + ensure_message_contains(0, "DEBUG: "); + ensure_message_contains(1, "INFO: "); + ensure_message_contains(2, "WARNING: "); + ensure_message_does_not_contain(3, "ERROR"); + ensure_message_contains(4, "ERROR: "); + ensure_message_count(5); + } + + template<> template<> + void ErrorTestObject::test<4>() + // file abbreviation + { + std::string thisFile = __FILE__; + std::string abbreviateFile = LLError::abbreviateFile(thisFile); + + ensure_ends_with("file name abbreviation", + abbreviateFile, + "llcommon/tests/llerror_test.cpp" + ); + ensure_does_not_contain("file name abbreviation", + abbreviateFile, "indra"); + + std::string someFile = +#if LL_WINDOWS + "C:/amy/bob/cam.cpp" +#else + "/amy/bob/cam.cpp" +#endif + ; + std::string someAbbreviation = LLError::abbreviateFile(someFile); + + ensure_equals("non-indra file abbreviation", + someAbbreviation, someFile); + } +} + +namespace +{ + std::string locationString(int line) + { + std::ostringstream location; + location << LLError::abbreviateFile(__FILE__) + << "(" << line << ") : "; + + return location.str(); + } + + std::string writeReturningLocation() + { + llinfos << "apple" << llendl; int this_line = __LINE__; + return locationString(this_line); + } + + std::string writeReturningLocationAndFunction() + { + llinfos << "apple" << llendl; int this_line = __LINE__; + return locationString(this_line) + __FUNCTION__; + } + + std::string errorReturningLocation() + { + llerrs << "die" << llendl; int this_line = __LINE__; + return locationString(this_line); + } +} + +namespace tut +{ + template<> template<> + void ErrorTestObject::test<5>() + // file and line information in log messages + { + std::string location = writeReturningLocation(); + // expecting default to not print location information + + LLError::setPrintLocation(true); + writeReturningLocation(); + + LLError::setPrintLocation(false); + writeReturningLocation(); + + ensure_message_does_not_contain(0, location); + ensure_message_contains(1, location); + ensure_message_does_not_contain(2, location); + } +} + +/* The following helper functions and class members all log a simple message + from some particular function scope. Each function takes a bool argument + that indicates if it should log its own name or not (in the manner that + existing log messages often do.) The functions all return their C++ + name so that test can be substantial mechanized. + */ + +std::string logFromGlobal(bool id) +{ + llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl; + return "logFromGlobal"; +} + +static std::string logFromStatic(bool id) +{ + llinfos << (id ? "logFromStatic: " : "") << "hi" << llendl; + return "logFromStatic"; +} + +namespace +{ + std::string logFromAnon(bool id) + { + llinfos << (id ? "logFromAnon: " : "") << "hi" << llendl; + return "logFromAnon"; + } +} + +namespace Foo { + std::string logFromNamespace(bool id) + { + llinfos << (id ? "Foo::logFromNamespace: " : "") << "hi" << llendl; + //return "Foo::logFromNamespace"; + // there is no standard way to get the namespace name, hence + // we won't be testing for it + return "logFromNamespace"; + } +} + +namespace +{ + class ClassWithNoLogType { + public: + std::string logFromMember(bool id) + { + llinfos << (id ? "ClassWithNoLogType::logFromMember: " : "") << "hi" << llendl; + return "ClassWithNoLogType::logFromMember"; + } + static std::string logFromStatic(bool id) + { + llinfos << (id ? "ClassWithNoLogType::logFromStatic: " : "") << "hi" << llendl; + return "ClassWithNoLogType::logFromStatic"; + } + }; + + class ClassWithLogType { + LOG_CLASS(ClassWithLogType); + public: + std::string logFromMember(bool id) + { + llinfos << (id ? "ClassWithLogType::logFromMember: " : "") << "hi" << llendl; + return "ClassWithLogType::logFromMember"; + } + static std::string logFromStatic(bool id) + { + llinfos << (id ? "ClassWithLogType::logFromStatic: " : "") << "hi" << llendl; + return "ClassWithLogType::logFromStatic"; + } + }; + + std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); } + std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); } + std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); } + std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); } + std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); } + + void ensure_has(const std::string& message, + const std::string& actual, const std::string& expected) + { + std::string::size_type n1 = actual.find(expected); + if (n1 == std::string::npos) + { + std::stringstream ss; + ss << message << ": " << "expected to find a copy of " << expected + << " in actual " << actual; + throw tut::failure(ss.str().c_str()); + } + } + + typedef std::string (*LogFromFunction)(bool); + void testLogName(TestRecorder& recorder, LogFromFunction f, + const std::string& class_name = "") + { + recorder.clearMessages(); + std::string name = f(false); + f(true); + + std::string messageWithoutName = recorder.message(0); + std::string messageWithName = recorder.message(1); + + ensure_has(name + " logged without name", + messageWithoutName, name); + ensure_has(name + " logged with name", + messageWithName, name); + + if (!class_name.empty()) + { + ensure_has(name + "logged without name", + messageWithoutName, class_name); + ensure_has(name + "logged with name", + messageWithName, class_name); + } + } +} + +namespace tut +{ + template<> template<> + // class/function information in output + void ErrorTestObject::test<6>() + { + testLogName(mRecorder, logFromGlobal); + testLogName(mRecorder, logFromStatic); + testLogName(mRecorder, logFromAnon); + testLogName(mRecorder, logFromNamespace); + //testLogName(mRecorder, logFromClassWithNoLogTypeMember, "ClassWithNoLogType"); + //testLogName(mRecorder, logFromClassWithNoLogTypeStatic, "ClassWithNoLogType"); + // XXX: figure out what the exepcted response is for these + testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType"); + testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType"); + } +} + +namespace +{ + std::string innerLogger() + { + llinfos << "inside" << llendl; + return "moo"; + } + + std::string outerLogger() + { + llinfos << "outside(" << innerLogger() << ")" << llendl; + return "bar"; + } + + void uberLogger() + { + llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl; + } + + class LogWhileLogging + { + public: + void print(std::ostream& out) const + { + llinfos << "logging" << llendl; + out << "baz"; + } + }; + + std::ostream& operator<<(std::ostream& out, const LogWhileLogging& l) + { l.print(out); return out; } + + void metaLogger() + { + LogWhileLogging l; + llinfos << "meta(" << l << ")" << llendl; + } + +} + +namespace tut +{ + template<> template<> + // handle nested logging + void ErrorTestObject::test<7>() + { + outerLogger(); + ensure_message_contains(0, "inside"); + ensure_message_contains(1, "outside(moo)"); + ensure_message_count(2); + + uberLogger(); + ensure_message_contains(2, "inside"); + ensure_message_contains(3, "inside"); + ensure_message_contains(4, "outside(moo)"); + ensure_message_contains(5, "uber(bar,moo)"); + ensure_message_count(6); + + metaLogger(); + ensure_message_contains(6, "logging"); + ensure_message_contains(7, "meta(baz)"); + ensure_message_count(8); + } + + template<> template<> + // special handling of llerrs calls + void ErrorTestObject::test<8>() + { + LLError::setPrintLocation(false); + std::string location = errorReturningLocation(); + + ensure_message_contains(0, location + "error"); + ensure_message_contains(1, "die"); + ensure_message_count(2); + + ensure("fatal callback called", fatalWasCalled); + } +} + +namespace +{ + std::string roswell() + { + return "1947-07-08T03:04:05Z"; + } + + void ufoSighting() + { + llinfos << "ufo" << llendl; + } +} + +namespace tut +{ + template<> template<> + // time in output (for recorders that need it) + void ErrorTestObject::test<9>() + { + LLError::setTimeFunction(roswell); + + mRecorder.setWantsTime(false); + ufoSighting(); + ensure_message_contains(0, "ufo"); + ensure_message_does_not_contain(0, roswell()); + + mRecorder.setWantsTime(true); + ufoSighting(); + ensure_message_contains(1, "ufo"); + ensure_message_contains(1, roswell()); + } + + template<> template<> + // output order + void ErrorTestObject::test<10>() + { + LLError::setPrintLocation(true); + LLError::setTimeFunction(roswell); + mRecorder.setWantsTime(true); + std::string locationAndFunction = writeReturningLocationAndFunction(); + + ensure_equals("order is time type location function message", + mRecorder.message(0), + roswell() + " INFO: " + locationAndFunction + ": apple"); + } + + template<> template<> + // multiple recorders + void ErrorTestObject::test<11>() + { + TestRecorder altRecorder; + LLError::addRecorder(&altRecorder); + + llinfos << "boo" << llendl; + + ensure_message_contains(0, "boo"); + ensure_equals("alt recorder count", altRecorder.countMessages(), 1); + ensure_contains("alt recorder message 0", altRecorder.message(0), "boo"); + + LLError::setTimeFunction(roswell); + + TestRecorder anotherRecorder; + anotherRecorder.setWantsTime(true); + LLError::addRecorder(&anotherRecorder); + + llinfos << "baz" << llendl; + + std::string when = roswell(); + + ensure_message_does_not_contain(1, when); + ensure_equals("alt recorder count", altRecorder.countMessages(), 2); + ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when); + ensure_equals("another recorder count", anotherRecorder.countMessages(), 1); + ensure_contains("another recorder message 0", anotherRecorder.message(0), when); + } +} + +class TestAlpha +{ + LOG_CLASS(TestAlpha); +public: + static void doDebug() { lldebugs << "add dice" << llendl; } + static void doInfo() { llinfos << "any idea" << llendl; } + static void doWarn() { llwarns << "aim west" << llendl; } + static void doError() { llerrs << "ate eels" << llendl; } + static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } +}; + +class TestBeta +{ + LOG_CLASS(TestBeta); +public: + static void doDebug() { lldebugs << "bed down" << llendl; } + static void doInfo() { llinfos << "buy iron" << llendl; } + static void doWarn() { llwarns << "bad word" << llendl; } + static void doError() { llerrs << "big easy" << llendl; } + static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } +}; + +namespace tut +{ + template<> template<> + // filtering by class + void ErrorTestObject::test<12>() + { + LLError::setDefaultLevel(LLError::LEVEL_WARN); + LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO); + + TestAlpha::doAll(); + TestBeta::doAll(); + + ensure_message_contains(0, "aim west"); + ensure_message_contains(1, "error"); + ensure_message_contains(2, "ate eels"); + ensure_message_contains(3, "buy iron"); + ensure_message_contains(4, "bad word"); + ensure_message_contains(5, "error"); + ensure_message_contains(6, "big easy"); + ensure_message_count(7); + } + + template<> template<> + // filtering by function, and that it will override class filtering + void ErrorTestObject::test<13>() + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN); + LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG); + LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE); + + TestBeta::doAll(); + ensure_message_contains(0, "buy iron"); + ensure_message_contains(1, "bad word"); + ensure_message_count(2); + } + + template<> template<> + // filtering by file + // and that it is overridden by both class and function filtering + void ErrorTestObject::test<14>() + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setFileLevel(LLError::abbreviateFile(__FILE__), + LLError::LEVEL_WARN); + LLError::setClassLevel("TestAlpha", LLError::LEVEL_INFO); + LLError::setFunctionLevel("TestAlpha::doError", + LLError::LEVEL_NONE); + LLError::setFunctionLevel("TestBeta::doError", + LLError::LEVEL_NONE); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(0, "any idea"); + ensure_message_contains(1, "aim west"); + ensure_message_contains(2, "bad word"); + ensure_message_count(3); + } + + template<> template<> + // proper cached, efficient lookup of filtering + void ErrorTestObject::test<15>() + { + LLError::setDefaultLevel(LLError::LEVEL_NONE); + + TestAlpha::doInfo(); + ensure_message_count(0); + ensure_equals("first check", LLError::shouldLogCallCount(), 1); + TestAlpha::doInfo(); + ensure_message_count(0); + ensure_equals("second check", LLError::shouldLogCallCount(), 1); + + LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG); + TestAlpha::doInfo(); + ensure_message_count(1); + ensure_equals("third check", LLError::shouldLogCallCount(), 2); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("fourth check", LLError::shouldLogCallCount(), 2); + + LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("fifth check", LLError::shouldLogCallCount(), 3); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("sixth check", LLError::shouldLogCallCount(), 3); + } + + template<> template<> + // configuration from LLSD + void ErrorTestObject::test<16>() + { + std::string this_file = LLError::abbreviateFile(__FILE__); + LLSD config; + config["print-location"] = true; + config["default-level"] = "DEBUG"; + + LLSD set1; + set1["level"] = "WARN"; + set1["files"][0] = this_file; + + LLSD set2; + set2["level"] = "INFO"; + set2["classes"][0] = "TestAlpha"; + + LLSD set3; + set3["level"] = "NONE"; + set3["functions"][0] = "TestAlpha::doError"; + set3["functions"][1] = "TestBeta::doError"; + + config["settings"][0] = set1; + config["settings"][1] = set2; + config["settings"][2] = set3; + + LLError::configure(config); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(0, "any idea"); + ensure_message_contains(0, this_file); + ensure_message_contains(1, "aim west"); + ensure_message_contains(2, "bad word"); + ensure_message_count(3); + + // make sure reconfiguring works + LLSD config2; + config2["default-level"] = "WARN"; + + LLError::configure(config2); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(3, "aim west"); + ensure_message_does_not_contain(3, this_file); + ensure_message_contains(4, "error"); + ensure_message_contains(5, "ate eels"); + ensure_message_contains(6, "bad word"); + ensure_message_contains(7, "error"); + ensure_message_contains(8, "big easy"); + ensure_message_count(9); + } +} + +/* Tests left: + handling of classes without LOG_CLASS + + live update of filtering from file + + syslog recorder + file recorder + cerr/stderr recorder + fixed buffer recorder + windows recorder + + mutex use when logging (?) + strange careful about to crash handling (?) +*/ diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp new file mode 100644 index 0000000000..3a2cda7735 --- /dev/null +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -0,0 +1,782 @@ +/** + * @file coroutine_test.cpp + * @author Nat Goodspeed + * @date 2009-04-22 + * @brief Test for coroutine. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/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/coroutine/coroutine.hpp> +// Normally, lleventcoro.h obviates future.hpp. We only include this because +// we implement a "by hand" test of future functionality. +#include <boost/coroutine/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 "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::coroutines; +using coroutines::coroutine; + +template<typename Iter> +bool match(Iter first, Iter last, std::string match) { + std::string::iterator i = match.begin(); + i != match.end(); + 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::coroutines::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::coroutines::future<LLSD> future(self); + // tell the future what to wait for + LLTempBoundListener connection( + LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::coroutines::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, + LLSD().insert("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, + LLSD().insert("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, + LLSD().insert("value", 18).insert("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, LLSD().insert("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(); + WrapLL_ERRS capture; + try + { + result = waiter.waitWithLog(self); + debug("no exception"); + } + catch (const WrapLL_ERRS::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, LLSD().insert("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, LLSD().insert("value", 23).insert("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, LLSD().insert("value", 8), + immediateAPI.getPump(), "reply", "error"); + } + END + } + + void coroPumpsPostEx(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + try + { + result = waiter.postAndWaitWithException(self, + LLSD().insert("value", 9).insert("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, LLSD().insert("value", 30), + immediateAPI.getPump(), "reply", "error"); + } + END + } + + void coroPumpsPostLog(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + WrapLL_ERRS capture; + try + { + result = waiter.postAndWaitWithLog(self, + LLSD().insert("value", 31).insert("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + } + catch (const WrapLL_ERRS::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"); + } +} // namespace tut diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp new file mode 100644 index 0000000000..28b909298e --- /dev/null +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -0,0 +1,276 @@ +/** + * @file lleventfilter_test.cpp + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Test for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" +#include "listener.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +* Test classes +*****************************************************************************/ +// Strictly speaking, we're testing LLEventTimeoutBase rather than the +// production LLEventTimeout (using LLTimer) because we don't want every test +// run to pause for some number of seconds until we reach a real timeout. But +// as we've carefully put all functionality except actual LLTimer calls into +// LLEventTimeoutBase, that should suffice. We're not not not trying to test +// LLTimer here. +class TestEventTimeout: public LLEventTimeoutBase +{ +public: + TestEventTimeout(): + mElapsed(true) + {} + TestEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source), + mElapsed(true) + {} + + // test hook + void forceTimeout(bool timeout=true) { mElapsed = timeout; } + +protected: + virtual void setCountdown(F32 seconds) { mElapsed = false; } + virtual bool countdownElapsed() const { return mElapsed; } + +private: + bool mElapsed; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct filter_data + { + // The resemblance between this test data and that in llevents_tut.cpp + // is not coincidental. + filter_data(): + pumps(LLEventPumps::instance()), + mainloop(pumps.obtain("mainloop")), + listener0("first"), + listener1("second") + {} + LLEventPumps& pumps; + LLEventPump& mainloop; + Listener listener0; + Listener listener1; + + void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) + { + ensure_equals(STRINGIZE(listener << ' ' << desc), + listener.getLastEvent(), got); + } + }; + typedef test_group<filter_data> filter_group; + typedef filter_group::object filter_object; + filter_group filtergrp("lleventfilter"); + + template<> template<> + void filter_object::test<1>() + { + set_test_name("LLEventMatching"); + LLEventPump& driver(pumps.obtain("driver")); + listener0.reset(0); + // Listener isn't derived from LLEventTrackable specifically to test + // various connection-management mechanisms. But that means we have a + // couple of transient Listener objects, one of which is listening to + // a persistent LLEventPump. Capture those connections in local + // LLTempBoundListener instances so they'll disconnect + // on destruction. + LLTempBoundListener temp1( + listener0.listenTo(driver)); + // Construct a pattern LLSD: desired Event must have a key "foo" + // containing string "bar" + LLEventMatching filter(driver, LLSD().insert("foo", "bar")); + listener1.reset(0); + LLTempBoundListener temp2( + listener1.listenTo(filter)); + driver.post(1); + check_listener("direct", listener0, LLSD(1)); + check_listener("filtered", listener1, LLSD(0)); + // Okay, construct an LLSD map matching the pattern + LLSD data; + data["foo"] = "bar"; + data["random"] = 17; + driver.post(data); + check_listener("direct", listener0, data); + check_listener("filtered", listener1, data); + } + + template<> template<> + void filter_object::test<2>() + { + set_test_name("LLEventTimeout::actionAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + // Use listener1.call() as the Action for actionAfter(), since it + // already provides a way to sense the call + listener1.reset(0); + // driver --> filter --> listener0 + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener1, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener1, LLSD(0)); + // Verify chained actionAfter() calls, that is, that a second + // actionAfter() resets the timer established by the first + // actionAfter(). + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Since our TestEventTimeout class isn't actually manipulating time + // (quantities of seconds), only a bool "elapsed" flag, sense that by + // forcing the flag between actionAfter() calls. + filter.forceTimeout(); + // Pumping mainloop here would result in a timeout (as we'll verify + // below). This state simulates a ticking timer that has not yet timed + // out. But now, before a mainloop event lets 'filter' recognize + // timeout on the previous actionAfter() call, pretend we're pushing + // that timeout farther into the future. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Look ma, no timeout! + mainloop.post(17); + check_listener("no timeout 3", listener1, LLSD(0)); + // Now let the updated actionAfter() timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener1, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener1.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 4", listener1, LLSD(0)); + // Reset the timer and then cancel() it. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // neither expired nor satisified + mainloop.post(17); + check_listener("no timeout 5", listener1, LLSD(0)); + // cancel + filter.cancel(); + // timeout! + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 6", listener1, LLSD(0)); + } + + template<> template<> + void filter_object::test<3>() + { + set_test_name("LLEventTimeout::eventAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.eventAfter(20, LLSD("timeout")); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.eventAfter(20, LLSD("timeout")); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener0, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } + + template<> template<> + void filter_object::test<4>() + { + set_test_name("LLEventTimeout::errorAfter()"); + WrapLL_ERRS capture; + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.errorAfter(20, "timeout"); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.errorAfter(20, "timeout"); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + std::string threw; + try + { + mainloop.post(17); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("errorAfter() timeout exception", threw, "timeout"); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } +} // namespace tut + +/***************************************************************************** +* Link dependencies +*****************************************************************************/ +#include "llsdutil.cpp" diff --git a/indra/llcommon/tests/llframetimer_test.cpp b/indra/llcommon/tests/llframetimer_test.cpp new file mode 100644 index 0000000000..1d047e41f8 --- /dev/null +++ b/indra/llcommon/tests/llframetimer_test.cpp @@ -0,0 +1,118 @@ +/** + * @file lltiming_test.cpp + * @date 2006-07-23 + * @brief Tests the timers. + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-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 "../llframetimer.h" +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct frametimer_test + { + frametimer_test() + { + LLFrameTimer::updateFrameTime(); + } + }; + typedef test_group<frametimer_test> frametimer_group_t; + typedef frametimer_group_t::object frametimer_object_t; + tut::frametimer_group_t frametimer_instance("frametimer"); + + template<> template<> + void frametimer_object_t::test<1>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + F64 expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry", + expires_at, + seconds_since_epoch, + 0.001); + } + + template<> template<> + void frametimer_object_t::test<2>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + seconds_since_epoch += 10.0; + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + F64 expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry 1", + expires_at, + seconds_since_epoch, + 0.001); + seconds_since_epoch += 10.0; + timer.setExpiryAt(seconds_since_epoch); + expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry 2", + expires_at, + seconds_since_epoch, + 0.001); + } + template<> template<> + void frametimer_object_t::test<3>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + seconds_since_epoch += 2.0; + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + ensure("timer not expired on create", !timer.hasExpired()); + int ii; + for(ii = 0; ii < 10; ++ii) + { + ms_sleep(150); + LLFrameTimer::updateFrameTime(); + } + ensure("timer not expired after a bit", !timer.hasExpired()); + for(ii = 0; ii < 10; ++ii) + { + ms_sleep(100); + LLFrameTimer::updateFrameTime(); + } + ensure("timer expired", timer.hasExpired()); + } +/* + template<> template<> + void frametimer_object_t::test<4>() + { + } +*/ +} diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index e5100e51dc..1f178d6fc9 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -1,5 +1,5 @@ /** - * @file llrandom_tut.cpp + * @file llrandom_test.cpp * @author Phoenix * @date 2007-01-25 * diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 662b599c55..6ab48ec34a 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1,5 +1,5 @@ /** - * @file llsdserialize_tut.cpp + * @file llsdserialize_test.cpp * @date 2006-04 * @brief LLSDSerialize unit tests * @@ -48,6 +48,18 @@ typedef U32 uint32_t; #endif +std::vector<U8> string_to_vector(std::string str) +{ + // bc LLSD can't... + size_t len = (size_t)str.length(); + std::vector<U8> v(len); + for (size_t i = 0; i < len ; i++) + { + v[i] = str[i]; + } + return v; +} + namespace tut { struct sd_xml_data @@ -107,7 +119,16 @@ namespace tut expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n"; xml_test("date", expected); - // *FIX: test binary + // Generated by: echo -n 'hello' | openssl enc -e -base64 + std::vector<U8> hello; + hello.push_back('h'); + hello.push_back('e'); + hello.push_back('l'); + hello.push_back('l'); + hello.push_back('o'); + mSD = hello; + expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + xml_test("binary", expected); } template<> template<> @@ -199,6 +220,21 @@ namespace tut xml_test("2 element map", expected); } + template<> template<> + void sd_xml_object::test<6>() + { + // tests with binary + std::string expected; + + // Generated by: echo -n 'hello' | openssl enc -e -base64 + mSD = string_to_vector("hello"); + expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + xml_test("binary", expected); + + mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + xml_test("binary", expected); + } class TestLLSDSerializeData { @@ -637,6 +673,42 @@ namespace tut v.size() + 1); } + template<> template<> + void TestLLSDXMLParsingObject::test<4>() + { + // test handling of binary object in XML + std::string xml; + LLSD expected; + + // Generated by: echo -n 'hello' | openssl enc -e -base64 + expected = string_to_vector("hello"); + xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + ensureParse( + "the word 'hello' packed in binary encoded base64", + xml, + expected, + 1); + + expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + ensureParse( + "a common binary blob for object -> agent offline inv transfer", + xml, + expected, + 1); + + expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n"; + xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n"; + xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n"; + xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n"; + xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + ensureParse( + "a common binary blob for object -> agent offline inv transfer", + xml, + expected, + 1); + } /* TODO: test XML parsing diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 6a2ebc61f5..beba55416a 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -32,6 +32,7 @@ * $/LicenseInfo$ */ +#include "linden_common.h" #include "../test/lltut.h" #include "../llstring.h" diff --git a/indra/llcommon/tests/lltreeiterators_test.cpp b/indra/llcommon/tests/lltreeiterators_test.cpp new file mode 100644 index 0000000000..d6d9f68110 --- /dev/null +++ b/indra/llcommon/tests/lltreeiterators_test.cpp @@ -0,0 +1,1222 @@ +/** + * @file lltreeiterators.cpp + * @author Nat Goodspeed + * @date 2008-08-20 + * @brief Test of lltreeiterators.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-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$ + */ + +// Precompiled header +#include "linden_common.h" + +#if LL_WINDOWS +#pragma warning (disable : 4180) // qualifier applied to function type has no meaning; ignored +#endif + +// STL headers +// std headers +#include <iostream> +#include <sstream> +#include <string> +// external library headers +#include <boost/bind.hpp> +#include <boost/range/iterator_range.hpp> +#include <boost/foreach.hpp> + +// associated header +#include "../lltreeiterators.h" +#include "../llpointer.h" + +#include "../test/lltut.h" + +/***************************************************************************** +* tut test group +*****************************************************************************/ +namespace tut +{ + struct iter_data + { + }; + typedef test_group<iter_data> iter_group; + typedef iter_group::object iter_object; + tut::iter_group ig("lltreeiterators"); +} // namespace tut + +/***************************************************************************** +* boost::get_pointer() specialization for LLPointer<> +*****************************************************************************/ +// This specialization of boost::get_pointer() undoubtedly belongs in +// llmemory.h. It's used by boost::bind() so that you can pass an +// LLPointer<Foo> as well as a Foo* to a functor such as +// boost::bind(&Foo::method, _1). +//namespace boost +//{ + template <class NODE> + NODE* get_pointer(const LLPointer<NODE>& ptr) { return ptr.get(); } +//}; + +/***************************************************************************** +* ScopeLabel +*****************************************************************************/ +class ScopeLabel +{ +public: + ScopeLabel(const std::string& label): mLabel(label) + { + std::cout << "Entering " << mLabel << '\n'; + } + ~ScopeLabel() + { + std::cout << "Leaving " << mLabel << '\n'; + } +private: + std::string mLabel; +}; + +/***************************************************************************** +* Cleanup +*****************************************************************************/ +// Yes, we realize this is redundant with auto_ptr and LLPointer and all +// kinds of better mechanisms. But in this particular source file, we need to +// test nodes managed with plain old dumb pointers as well as nodes managed +// with LLPointer, so we introduce this mechanism. +// +// In the general case, when we declare a Cleanup for some pointer, delete the +// pointer when the Cleanup goes out of scope. +template <typename PTRTYPE> +struct Cleanup +{ + Cleanup(const PTRTYPE& ptr): mPtr(ptr) {} + ~Cleanup() + { + delete mPtr; + } + PTRTYPE mPtr; +}; + +// But when the pointer is an LLPointer<something>, Cleanup is a no-op: +// LLPointer will handle the cleanup automagically. +template <typename NODE> +struct Cleanup< LLPointer<NODE> > +{ + Cleanup(const LLPointer<NODE>& ptr) {} + ~Cleanup() {} +}; + +/***************************************************************************** +* Expected +*****************************************************************************/ +// Expected is a base class used to capture the expected results -- a sequence +// of string node names -- from one of our traversals of this example data. +// Its subclasses initialize it with a pair of string iterators. It's not +// strictly necessary to customize Expected to model Boost.Range, it's just +// convenient. +struct Expected +{ + template <typename ITER> + Expected(ITER begin, ITER end): + strings(begin, end) + {} + /*------ The following are to make Expected work with Boost.Range ------*/ + typedef std::vector<std::string> container_type; + typedef container_type::iterator iterator; + typedef container_type::const_iterator const_iterator; + typedef container_type::size_type size_type; + container_type strings; + iterator begin() { return strings.begin(); } + iterator end() { return strings.end(); } + size_type size() { return strings.size(); } + const_iterator begin() const { return strings.begin(); } + const_iterator end() const { return strings.end(); } +}; + +// We have a couple of generic Expected template subclasses. This list of +// strings is used for the "else" case when all specializations fail. +const char* bad_strings[] = { "FAIL" }; + +/***************************************************************************** +* verify() +*****************************************************************************/ +// test function: given (an object modeling) a Boost.Range of tree nodes, +// compare the sequence of visited node names with a range of expected name +// strings. Report success both with std::cout output and a bool return. The +// string desc parameter is to identify the different tests. +template <typename NODERANGE, typename STRINGRANGE> +bool verify(const std::string& desc, NODERANGE noderange, STRINGRANGE expected) +{ + typename boost::range_iterator<NODERANGE>::type + nri = boost::begin(noderange), + nrend = boost::end(noderange); + typename boost::range_iterator<STRINGRANGE>::type + sri = boost::begin(expected), + srend = boost::end(expected); + // We choose to loop over both sequences explicitly rather than using + // std::equal() or std::lexicographical_compare(). The latter tells you + // whether one sequence is *less* than the other -- it doesn't tell you + // equality. std::equal() needs you to verify the sequence lengths ahead + // of time. Anyway, comparing explicitly allows us to report much more + // information about any sequence mismatch. + for ( ; nri != nrend && sri != srend; ++nri, ++sri) + { + if ((*nri)->name() != *sri) + { + std::cout << desc << " mismatch: " + << "expected " << *sri << ", got " << (*nri)->name() << "\n"; + return false; + } + } + if (nri != nrend) + { + std::cout << desc << " produced too many items:\n"; + for ( ; nri != nrend; ++nri) + { + std::cout << " " << (*nri)->name() << '\n'; + } + return false; + } + if (sri != srend) + { + std::cout << desc << " produced too few items, omitting:\n"; + for ( ; sri != srend; ++sri) + { + std::cout << " " << *sri << '\n'; + } + return false; + } +// std::cout << desc << " test passed\n"; + return true; +} + +/***************************************************************************** +* PlainNode: LLLinkIter, non-refcounted +*****************************************************************************/ +class PlainNode +{ +public: + PlainNode(const std::string& name, PlainNode* next=NULL): + mName(name), + mNext(next) + {} + ~PlainNode() + { + delete mNext; + } + std::string name() const { return mName; } + PlainNode* next() const { return mNext; } +public: // if this were 'private', couldn't bind mNext + PlainNode* mNext; +private: + std::string mName; +}; + +namespace tut +{ + template<> template<> + void iter_object::test<1>() + { +// set_test_name("LLLinkedIter -- non-refcounted class"); + PlainNode* last(new PlainNode("c")); + PlainNode* second(new PlainNode("b", last)); + PlainNode* first(new PlainNode("a", second)); + Cleanup<PlainNode*> cleanup(first); + static const char* cseq[] = { "a", "b", "c" }; + Expected seq(boost::begin(cseq), boost::end(cseq)); + std::string desc1("Iterate by public link member"); +// std::cout << desc1 << ":\n"; + // Try instantiating an iterator with NULL. This test is less about + // "did we iterate once?" than "did we avoid blowing up?" + for (LLLinkedIter<PlainNode> pni(NULL, boost::bind(&PlainNode::mNext, _1)), end; + pni != end; ++pni) + { +// std::cout << (*pni)->name() << '\n'; + ensure("LLLinkedIter<PlainNode>(NULL)", false); + } + ensure(desc1, + verify(desc1, + boost::make_iterator_range(LLLinkedIter<PlainNode>(first, + boost::bind(&PlainNode::mNext, _1)), + LLLinkedIter<PlainNode>()), + seq)); + std::string desc2("Iterate by next() method"); +// std::cout << desc2 << ":\n"; +// for (LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); ! (pni == end); ++pni) +// std::cout << (**pni).name() << '\n'; + ensure(desc2, + verify(desc2, + boost::make_iterator_range(LLLinkedIter<PlainNode>(first, + boost::bind(&PlainNode::next, _1)), + LLLinkedIter<PlainNode>()), + seq)); + { +// LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); +// std::cout << "First is " << (*pni++)->name() << '\n'; +// std::cout << "Second is " << (*pni )->name() << '\n'; + } + { + LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); + ensure_equals("first", (*pni++)->name(), "a"); + ensure_equals("second", (*pni )->name(), "b"); + } + } +} // tut + +/***************************************************************************** +* RCNode: LLLinkIter, refcounted +*****************************************************************************/ +class RCNode; +typedef LLPointer<RCNode> RCNodePtr; + +class RCNode: public LLRefCount +{ +public: + RCNode(const std::string& name, const RCNodePtr& next=RCNodePtr()): + mName(name), + mNext(next) + { +// std::cout << "New RCNode(" << mName << ")\n"; + } + RCNode(const RCNode& that): + mName(that.mName), + mNext(that.mNext) + { +// std::cout << "Copy RCNode(" << mName << ")\n"; + } + virtual ~RCNode(); + std::string name() const { return mName; } + RCNodePtr next() const { return mNext; } +public: // if this were 'private', couldn't bind mNext + RCNodePtr mNext; +private: + std::string mName; +}; + +std::ostream& operator<<(std::ostream& out, const RCNode& node) +{ + out << "RCNode(" << node.name() << ')'; + return out; +} + +// This string contains the node name of the last RCNode destroyed. We use it +// to validate that LLLinkedIter<RCNode> in fact contains LLPointer<RCNode>, +// and that therefore an outstanding LLLinkedIter to an instance of a +// refcounted class suffices to keep that instance alive. +std::string last_RCNode_destroyed; + +RCNode::~RCNode() +{ +// std::cout << "Kill " << *this << "\n"; + last_RCNode_destroyed = mName; +} + +namespace tut +{ + template<> template<> + void iter_object::test<2>() + { +// set_test_name("LLLinkedIter -- refcounted class"); + LLLinkedIter<RCNode> rcni, end2; + { +// ScopeLabel label("inner scope"); + RCNodePtr head(new RCNode("x", new RCNode("y", new RCNode("z")))); +// for (rcni = LLLinkedIter<RCNode>(head, boost::bind(&RCNode::mNext, _1)); rcni != end2; ++rcni) +// std::cout << **rcni << '\n'; + rcni = LLLinkedIter<RCNode>(head, boost::bind(&RCNode::next, _1)); + } +// std::cout << "Now the LLLinkedIter<RCNode> is the only remaining reference to RCNode chain\n"; + ensure_equals(last_RCNode_destroyed, ""); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "x"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "x"); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "y"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "y"); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "z"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "z"); + ensure(rcni == end2); + } +} + +/***************************************************************************** +* TreeNode +*****************************************************************************/ +class TreeNode; +typedef LLPointer<TreeNode> TreeNodePtr; + +/** + * TreeNode represents a refcounted tree-node class that hasn't (yet) been + * modified to incorporate LLTreeIter methods. This illustrates how you can + * use tree iterators either standalone, or with free functions. + */ +class TreeNode: public LLRefCount +{ +public: + typedef std::vector<TreeNodePtr> list_type; + typedef list_type::const_iterator child_iterator; + + // To avoid cycles, use a "weak" raw pointer for the parent link + TreeNode(const std::string& name, TreeNode* parent=0): + mParent(parent), + mName(name) + {} + TreeNodePtr newChild(const std::string& name) + { + TreeNodePtr child(new TreeNode(name, this)); + mChildren.push_back(child); + return child; + } + std::string name() const { return mName; } + TreeNodePtr getParent() const { return mParent; } + child_iterator child_begin() const { return mChildren.begin(); } + child_iterator child_end() const { return mChildren.end(); } +private: + std::string mName; + // To avoid cycles, use a "weak" raw pointer for the parent link + TreeNode* mParent; + list_type mChildren; +}; + +/** + * This is an example of a helper function to facilitate iterating from a + * TreeNode up to the root or down from the root (see LLTreeIter::RootIter). + * + * Example: + * @code + * BOOST_FOREACH(TreeNodePtr node, getRootRange<LLTreeIter::UP>(somenode)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::RootIter DISCRIM> +boost::iterator_range< LLTreeRootIter<DISCRIM, TreeNode> > +getRootRange(const TreeNodePtr& node) +{ + typedef LLTreeRootIter<DISCRIM, TreeNode> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, boost::bind(&TreeNode::getParent, _1)), + iter_type()); +} + +/** + * This is an example of a helper function to facilitate walking a given + * TreeNode's subtree in any supported order (see LLTreeIter::WalkIter). + * + * Example: + * @code + * BOOST_FOREACH(TreeNodePtr node, getWalkRange<LLTreeIter::DFS_PRE>(root)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::WalkIter DISCRIM> +boost::iterator_range< LLTreeWalkIter<DISCRIM, TreeNode, TreeNode::child_iterator> > +getWalkRange(const TreeNodePtr& node) +{ + typedef LLTreeWalkIter<DISCRIM, TreeNode, TreeNode::child_iterator> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, + boost::bind(&TreeNode::child_begin, _1), + boost::bind(&TreeNode::child_end, _1)), + iter_type()); +} + +/***************************************************************************** +* EnhancedTreeNode +*****************************************************************************/ +class EnhancedTreeNode; +typedef LLPointer<EnhancedTreeNode> EnhancedTreeNodePtr; + +/** + * More typically, you enhance the tree-node class itself with template + * methods like the above. This EnhancedTreeNode class illustrates the + * technique. Normally, of course, you'd simply add these methods to TreeNode; + * we put them in a separate class to preserve the undecorated TreeNode class + * to illustrate (and test) the use of plain tree iterators and standalone + * helper functions. + * + * We originally implemented EnhancedTreeNode as a subclass of TreeNode -- but + * because TreeNode stores and manipulates TreeNodePtrs and TreeNode*s, + * reusing its methods required so much ugly downcast logic that we gave up + * and restated the whole class. Bear in mind that logically these aren't two + * separate classes; logically they're two snapshots of the @em same class at + * different moments in time. + */ +class EnhancedTreeNode: public LLRefCount +{ +public: + /*-------------- The following is restated from TreeNode ---------------*/ + typedef std::vector<EnhancedTreeNodePtr> list_type; + typedef list_type::const_iterator child_iterator; + + // To avoid cycles, use a "weak" raw pointer for the parent link + EnhancedTreeNode(const std::string& name, EnhancedTreeNode* parent=0): + mParent(parent), + mName(name) + {} + EnhancedTreeNodePtr newChild(const std::string& name) + { + EnhancedTreeNodePtr child(new EnhancedTreeNode(name, this)); + mChildren.push_back(child); + return child; + } + std::string name() const { return mName; } + EnhancedTreeNodePtr getParent() const { return mParent; } + child_iterator child_begin() const { return mChildren.begin(); } + child_iterator child_end() const { return mChildren.end(); } + +private: + std::string mName; + // To avoid cycles, use a "weak" raw pointer for the parent link + EnhancedTreeNode* mParent; + list_type mChildren; +public: + /*----- End of TreeNode; what follows is new with EnhancedTreeNode -----*/ + + /** + * Because the type of the iterator range returned by getRootRange() + * depends on the discriminator enum value, instead of a simple typedef we + * use a templated struct. Example usage: + * + * @code + * for (EnhancedTreeNode::root_range<LLTreeIter::UP>::type range = + * somenode->getRootRange<LLTreeIter::UP>(); + * range.first != range.second; ++range.first) + * { + * std::cout << (*range.first)->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::RootIter DISCRIM> + struct root_range + { + typedef boost::iterator_range< LLTreeRootIter<DISCRIM, EnhancedTreeNode> > type; + }; + + /** + * Helper method for walking up to (or down from) the tree root. See + * LLTreeIter::RootIter. + * + * Example usage: + * @code + * BOOST_FOREACH(EnhancedTreeNodePtr node, somenode->getRootRange<LLTreeIter::UP>()) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::RootIter DISCRIM> + typename root_range<DISCRIM>::type getRootRange() const + { + typedef typename root_range<DISCRIM>::type range_type; + typedef typename range_type::iterator iter_type; + return range_type(iter_type(const_cast<EnhancedTreeNode*>(this), + boost::bind(&EnhancedTreeNode::getParent, _1)), + iter_type()); + } + + /** + * Because the type of the iterator range returned by getWalkRange() + * depends on the discriminator enum value, instead of a simple typedef we + * use a templated stuct. Example usage: + * + * @code + * for (EnhancedTreeNode::walk_range<LLTreeIter::DFS_PRE>::type range = + * somenode->getWalkRange<LLTreeIter::DFS_PRE>(); + * range.first != range.second; ++range.first) + * { + * std::cout << (*range.first)->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::WalkIter DISCRIM> + struct walk_range + { + typedef boost::iterator_range< LLTreeWalkIter<DISCRIM, + EnhancedTreeNode, + EnhancedTreeNode::child_iterator> > type; + }; + + /** + * Helper method for walking a given node's subtree in any supported + * order (see LLTreeIter::WalkIter). + * + * Example usage: + * @code + * BOOST_FOREACH(EnhancedTreeNodePtr node, somenode->getWalkRange<LLTreeIter::DFS_PRE>()) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::WalkIter DISCRIM> + typename walk_range<DISCRIM>::type getWalkRange() const + { + typedef typename walk_range<DISCRIM>::type range_type; + typedef typename range_type::iterator iter_type; + return range_type(iter_type(const_cast<EnhancedTreeNode*>(this), + boost::bind(&EnhancedTreeNode::child_begin, _1), + boost::bind(&EnhancedTreeNode::child_end, _1)), + iter_type()); + } +}; + +/***************************************************************************** +* PlainTree +*****************************************************************************/ +struct PlainTree +{ + PlainTree(const std::string& name, PlainTree* parent=0): + mName(name), + mParent(parent), + mNextSibling(0), + mFirstChild(0) + { + mLastChildLink = &mFirstChild; + } + ~PlainTree() + { + delete mNextSibling; + delete mFirstChild; + } + PlainTree* newChild(const std::string& name) + { + PlainTree* child(new PlainTree(name, this)); + *mLastChildLink = child; + mLastChildLink = &child->mNextSibling; + return child; + } + std::string name() const { return mName; } + + std::string mName; + PlainTree* mParent; + PlainTree* mNextSibling; + PlainTree* mFirstChild; + PlainTree** mLastChildLink; +}; + +// This "classic" tree tracks each node's children with a linked list anchored +// at the parent's mFirstChild and linked through each child's mNextSibling. +// LLTreeDFSIter<> and LLTreeBFSIter<> need functors to return begin()/end() +// iterators over a given node's children. But because this tree's children +// aren't stored in an STL container, we can't just export that container's +// begin()/end(). Instead we'll use LLLinkedIter<> to view the hand-maintained +// linked list as an iterator range. The straightforward way to do that would +// be to add child_begin() and child_end() methods. But let's say (for the +// sake of argument) that this struct is so venerable we don't dare modify it +// even to add new methods. Well, we can use free functions (or functors) too. +LLLinkedIter<PlainTree> PlainTree_child_begin(PlainTree* node) +{ + return LLLinkedIter<PlainTree>(node->mFirstChild, boost::bind(&PlainTree::mNextSibling, _1)); +} + +LLLinkedIter<PlainTree> PlainTree_child_end(PlainTree* node) +{ + return LLLinkedIter<PlainTree>(); +} + +/** + * This is an example of a helper function to facilitate iterating from a + * PlainTree up to the root or down from the root (see LLTreeIter::RootIter). + * Note that we're simply overloading the same getRootRange() helper function + * name we used for TreeNode. + * + * Example: + * @code + * BOOST_FOREACH(PlainTree* node, getRootRange<LLTreeIter::UP>(somenode)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::RootIter DISCRIM> +boost::iterator_range< LLTreeRootIter<DISCRIM, PlainTree> > +getRootRange(PlainTree* node) +{ + typedef LLTreeRootIter<DISCRIM, PlainTree> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, boost::bind(&PlainTree::mParent, _1)), + iter_type()); +} + +/** + * This is an example of a helper function to facilitate walking a given + * PlainTree's subtree in any supported order (see LLTreeIter::WalkIter). Note + * that we're simply overloading the same getWalkRange() helper function name + * we used for TreeNode. + * + * Example: + * @code + * BOOST_FOREACH(PlainTree* node, getWalkRange<LLTreeIter::DFS_PRE>(root)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::WalkIter DISCRIM> +boost::iterator_range< LLTreeWalkIter<DISCRIM, PlainTree, LLLinkedIter<PlainTree> > > +getWalkRange(PlainTree* node) +{ + typedef LLTreeWalkIter<DISCRIM, PlainTree, LLLinkedIter<PlainTree> > iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, + PlainTree_child_begin, + PlainTree_child_end), + iter_type()); +} + +// We could go through the exercise of writing EnhancedPlainTree containing +// root_range, getRootRange(), walk_range and getWalkRange() members -- but we +// won't. See EnhancedTreeNode for examples. + +/***************************************************************************** +* Generic tree test data +*****************************************************************************/ +template <class NODE> +typename LLPtrTo<NODE>::type example_tree() +{ + typedef typename LLPtrTo<NODE>::type NodePtr; + NodePtr root(new NODE("root")); + NodePtr A(root->newChild("A")); + NodePtr A1(A->newChild("A1")); +/* NodePtr A1a*/(A1->newChild("A1a")); +/* NodePtr A1b*/(A1->newChild("A1b")); +/* NodePtr A1c*/(A1->newChild("A1c")); + NodePtr A2(A->newChild("A2")); +/* NodePtr A2a*/(A2->newChild("A2a")); +/* NodePtr A2b*/(A2->newChild("A2b")); +/* NodePtr A2c*/(A2->newChild("A2c")); + NodePtr A3(A->newChild("A3")); +/* NodePtr A3a*/(A3->newChild("A3a")); +/* NodePtr A3b*/(A3->newChild("A3b")); +/* NodePtr A3c*/(A3->newChild("A3c")); + NodePtr B(root->newChild("B")); + NodePtr B1(B->newChild("B1")); +/* NodePtr B1a*/(B1->newChild("B1a")); +/* NodePtr B1b*/(B1->newChild("B1b")); +/* NodePtr B1c*/(B1->newChild("B1c")); + NodePtr B2(B->newChild("B2")); +/* NodePtr B2a*/(B2->newChild("B2a")); +/* NodePtr B2b*/(B2->newChild("B2b")); +/* NodePtr B2c*/(B2->newChild("B2c")); + NodePtr B3(B->newChild("B3")); +/* NodePtr B3a*/(B3->newChild("B3a")); +/* NodePtr B3b*/(B3->newChild("B3b")); +/* NodePtr B3c*/(B3->newChild("B3c")); + NodePtr C(root->newChild("C")); + NodePtr C1(C->newChild("C1")); +/* NodePtr C1a*/(C1->newChild("C1a")); +/* NodePtr C1b*/(C1->newChild("C1b")); +/* NodePtr C1c*/(C1->newChild("C1c")); + NodePtr C2(C->newChild("C2")); +/* NodePtr C2a*/(C2->newChild("C2a")); +/* NodePtr C2b*/(C2->newChild("C2b")); +/* NodePtr C2c*/(C2->newChild("C2c")); + NodePtr C3(C->newChild("C3")); +/* NodePtr C3a*/(C3->newChild("C3a")); +/* NodePtr C3b*/(C3->newChild("C3b")); +/* NodePtr C3c*/(C3->newChild("C3c")); + return root; +} + +// WalkExpected<WalkIter> is the list of string node names we expect from a +// WalkIter traversal of our example_tree() data. +template <LLTreeIter::WalkIter DISCRIM> +struct WalkExpected: public Expected +{ + // Initialize with bad_strings: we don't expect to use this generic case, + // only the specializations. Note that for a classic C-style array we must + // pass a pair of iterators rather than extracting boost::begin() and + // boost::end() within the target constructor: a template ctor accepts + // these classic C-style arrays as char** rather than char*[length]. Oh well. + WalkExpected(): Expected(boost::begin(bad_strings), boost::end(bad_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in +// DFS_PRE order +const char* dfs_pre_strings[] = +{ + "root", + "A", + "A1", + "A1a", + "A1b", + "A1c", + "A2", + "A2a", + "A2b", + "A2c", + "A3", + "A3a", + "A3b", + "A3c", + "B", + "B1", + "B1a", + "B1b", + "B1c", + "B2", + "B2a", + "B2b", + "B2c", + "B3", + "B3a", + "B3b", + "B3c", + "C", + "C1", + "C1a", + "C1b", + "C1c", + "C2", + "C2a", + "C2b", + "C2c", + "C3", + "C3a", + "C3b", + "C3c" +}; + +// specialize WalkExpected<DFS_PRE> with the expected strings +template <> +struct WalkExpected<LLTreeIter::DFS_PRE>: public Expected +{ + WalkExpected(): Expected(boost::begin(dfs_pre_strings), boost::end(dfs_pre_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in +// DFS_POST order +const char* dfs_post_strings[] = +{ + "A1a", + "A1b", + "A1c", + "A1", + "A2a", + "A2b", + "A2c", + "A2", + "A3a", + "A3b", + "A3c", + "A3", + "A", + "B1a", + "B1b", + "B1c", + "B1", + "B2a", + "B2b", + "B2c", + "B2", + "B3a", + "B3b", + "B3c", + "B3", + "B", + "C1a", + "C1b", + "C1c", + "C1", + "C2a", + "C2b", + "C2c", + "C2", + "C3a", + "C3b", + "C3c", + "C3", + "C", + "root" +}; + +// specialize WalkExpected<DFS_POST> with the expected strings +template <> +struct WalkExpected<LLTreeIter::DFS_POST>: public Expected +{ + WalkExpected(): Expected(boost::begin(dfs_post_strings), boost::end(dfs_post_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in BFS order +const char* bfs_strings[] = +{ + "root", + "A", + "B", + "C", + "A1", + "A2", + "A3", + "B1", + "B2", + "B3", + "C1", + "C2", + "C3", + "A1a", + "A1b", + "A1c", + "A2a", + "A2b", + "A2c", + "A3a", + "A3b", + "A3c", + "B1a", + "B1b", + "B1c", + "B2a", + "B2b", + "B2c", + "B3a", + "B3b", + "B3c", + "C1a", + "C1b", + "C1c", + "C2a", + "C2b", + "C2c", + "C3a", + "C3b", + "C3c" +}; + +// specialize WalkExpected<BFS> with the expected strings +template <> +struct WalkExpected<LLTreeIter::BFS>: public Expected +{ + WalkExpected(): Expected(boost::begin(bfs_strings), boost::end(bfs_strings)) {} +}; + +// extract a particular "arbitrary" node from the example_tree() data: the +// second (middle) node at each child level +template <class NODE, typename CHILDITER> +typename LLPtrTo<NODE>::type +get_B2b(const typename LLPtrTo<NODE>::type& root, + const boost::function<CHILDITER(const typename LLPtrTo<NODE>::type&)>& child_begin) +{ + typedef typename LLPtrTo<NODE>::type NodePtr; + CHILDITER Bi(child_begin(root)); + ++Bi; + NodePtr B(*Bi); + CHILDITER B2i(child_begin(B)); + ++B2i; + NodePtr B2(*B2i); + CHILDITER B2bi(child_begin(B2)); + ++B2bi; + NodePtr B2b(*B2bi); + return B2b; +} + +// RootExpected<RootIter> is the list of string node names we expect from a +// RootIter traversal of our example_tree() data. +template <LLTreeIter::RootIter DISCRIM> +struct RootExpected: public Expected +{ + // Initialize with bad_strings: we don't expect to use this generic case, + // only the specializations. + RootExpected(): Expected(boost::begin(bad_strings), boost::end(bad_strings)) {} +}; + +// list of string node names we expect from traversing UP from +// example_tree()'s B2b node +const char* up_from_B2b[] = +{ + "B2b", + "B2", + "B", + "root" +}; + +// specialize RootExpected<UP> with the expected strings +template <> +struct RootExpected<LLTreeIter::UP>: public Expected +{ + RootExpected(): Expected(boost::begin(up_from_B2b), boost::end(up_from_B2b)) {} +}; + +// list of string node names we expect from traversing DOWN to +// example_tree()'s B2b node +const char* down_to_B2b[] = +{ + "root", + "B", + "B2", + "B2b" +}; + +// specialize RootExpected<DOWN> with the expected strings +template <> +struct RootExpected<LLTreeIter::DOWN>: public Expected +{ + RootExpected(): Expected(boost::begin(down_to_B2b), boost::end(down_to_B2b)) {} +}; + +/***************************************************************************** +* Generic tree test functions +*****************************************************************************/ +template<LLTreeIter::RootIter DISCRIM, class NODE, typename PARENTFUNC> +bool LLTreeRootIter_test(const std::string& itername, const std::string& nodename, + const typename LLPtrTo<NODE>::type& node, + PARENTFUNC parentfunc) +{ + std::ostringstream desc; + desc << itername << '<' << nodename << "> from " << node->name(); + if (! verify(desc.str(), + boost::make_iterator_range(LLTreeRootIter<DISCRIM, NODE>(node, parentfunc), + LLTreeRootIter<DISCRIM, NODE>()), + RootExpected<DISCRIM>())) + return false; +// std::cout << desc.str() << '\n'; + // Try instantiating an iterator with NULL (that is, a default-constructed + // node pointer). This test is less about "did we iterate once?" than "did + // we avoid blowing up?" + for (LLTreeRootIter<DISCRIM, NODE> hri = LLTreeRootIter<DISCRIM, NODE>(typename LLPtrTo<NODE>::type(), parentfunc), hrend; + hri != hrend; /* ++hri */) // incrementing is moot, and MSVC complains + { +// std::cout << nodename << '(' << (*hri)->name() << ")\n"; + std::cout << itername << '<' << nodename << ">(NULL)\n"; + return false; + } + return true; +} + +template<class NODE, typename CHILDITER, typename PARENTFUNC, typename CHILDFUNC> +bool LLTreeUpIter_test(const std::string& nodename, PARENTFUNC parentfunc, CHILDFUNC childfunc) +{ + bool success = true; + typedef typename LLPtrTo<NODE>::type ptr_type; + ptr_type root(example_tree<NODE>()); + Cleanup<ptr_type> cleanup(root); + ptr_type B2b(get_B2b<NODE, CHILDITER>(root, childfunc)); + if (! LLTreeRootIter_test<LLTreeIter::UP, NODE>("LLTreeUpIter", nodename, B2b, parentfunc)) + success = false; + if (! LLTreeRootIter_test<LLTreeIter::DOWN, NODE>("LLTreeDownIter", nodename, B2b, parentfunc)) + success = false; + return success; +} + +template <LLTreeIter::WalkIter DISCRIM, class NODE, typename CHILDITER, + typename CHILDBEGINFUNC, typename CHILDENDFUNC> +bool LLTreeWalkIter_test(const std::string& itername, const std::string& nodename, + CHILDBEGINFUNC childbegin, CHILDENDFUNC childend) +{ + typename LLPtrTo<NODE>::type root(example_tree<NODE>()); + Cleanup<typename LLPtrTo<NODE>::type> cleanup(root); + std::ostringstream desc; + desc << itername << '<' << nodename << "> from " << root->name(); + if (! verify(desc.str(), + boost::make_iterator_range(LLTreeWalkIter<DISCRIM, NODE, CHILDITER>(root, + childbegin, + childend), + LLTreeWalkIter<DISCRIM, NODE, CHILDITER>()), + WalkExpected<DISCRIM>())) + return false; + // Try instantiating an iterator with NULL (that is, a default-constructed + // node pointer). This test is less about "did we iterate once?" than "did + // we avoid blowing up?" + for (LLTreeWalkIter<DISCRIM, NODE, CHILDITER> twi = LLTreeWalkIter<DISCRIM, NODE, CHILDITER>(typename LLPtrTo<NODE>::type(), + childbegin, + childend), + twend; + twi != twend; /* ++twi */) // incrementing is moot, and MSVC complains + { + std::cout << itername << '<' << nodename << ">(NULL)\n"; + return false; + } + return true; +} + +template <class NODE, typename CHILDITER, + typename PARENTFUNC, typename CHILDBEGINFUNC, typename CHILDENDFUNC> +bool LLTreeIter_tests(const std::string& nodename, + PARENTFUNC parentfunc, CHILDBEGINFUNC childbegin, CHILDENDFUNC childend) +{ + bool success = true; + if (! LLTreeUpIter_test<NODE, CHILDITER>(nodename, parentfunc, childbegin)) + success = false; +/*==========================================================================*| + LLTreeIter_test<NODE, LLTreeDFSIter<NODE, CHILDITER> >("LLTreeDFSIter", nodename, + childbegin, childend); + LLTreeIter_test<NODE, LLTreeDFSPostIter<NODE, CHILDITER> >("LLTreeDFSPostIter", nodename, + childbegin, childend); + LLTreeIter_test<NODE, LLTreeBFSIter<NODE, CHILDITER> >("LLTreeBFSIter", nodename, + childbegin, childend); +|*==========================================================================*/ + if (! LLTreeWalkIter_test<LLTreeIter::DFS_PRE, NODE, CHILDITER>("LLTreeDFSIter", nodename, + childbegin, childend)) + success = false; + if (! LLTreeWalkIter_test<LLTreeIter::DFS_POST, NODE, CHILDITER>("LLTreeDFSPostIter", nodename, + childbegin, childend)) + success = false; + if (! LLTreeWalkIter_test<LLTreeIter::BFS, NODE, CHILDITER>("LLTreeBFSIter", nodename, + childbegin, childend)) + success = false; + return success; +} + +namespace tut +{ + template<> template<> + void iter_object::test<3>() + { +// set_test_name("LLTreeIter tests"); + ensure(LLTreeIter_tests<TreeNode, TreeNode::child_iterator> + ("TreeNode", + boost::bind(&TreeNode::getParent, _1), + boost::bind(&TreeNode::child_begin, _1), + boost::bind(&TreeNode::child_end, _1))); + ensure(LLTreeIter_tests<PlainTree, LLLinkedIter<PlainTree> > + ("PlainTree", + boost::bind(&PlainTree::mParent, _1), + PlainTree_child_begin, + PlainTree_child_end)); + } + + template<> template<> + void iter_object::test<4>() + { +// set_test_name("getRootRange() tests"); + // This test function illustrates the looping techniques described in the + // comments for the getRootRange() free function, the + // EnhancedTreeNode::root_range template and the + // EnhancedTreeNode::getRootRange() method. Obviously the BOOST_FOREACH() + // forms are more succinct. + TreeNodePtr tnroot(example_tree<TreeNode>()); + TreeNodePtr tnB2b(get_B2b<TreeNode, TreeNode::child_iterator> + (tnroot, boost::bind(&TreeNode::child_begin, _1))); + + std::string desc1("BOOST_FOREACH(TreeNodePr, getRootRange<LLTreeIter::UP>(tnB2b))"); +// std::cout << desc1 << "\n"; + // Although we've commented out the output statement, ensure that the + // loop construct is still valid, as promised by the getRootRange() + // documentation. + BOOST_FOREACH(TreeNodePtr node, getRootRange<LLTreeIter::UP>(tnB2b)) + { +// std::cout << node->name() << '\n'; + } + ensure(desc1, + verify(desc1, getRootRange<LLTreeIter::UP>(tnB2b), RootExpected<LLTreeIter::UP>())); + + EnhancedTreeNodePtr etnroot(example_tree<EnhancedTreeNode>()); + EnhancedTreeNodePtr etnB2b(get_B2b<EnhancedTreeNode, EnhancedTreeNode::child_iterator> + (etnroot, boost::bind(&EnhancedTreeNode::child_begin, _1))); + +// std::cout << "EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type range =\n" +// << " etnB2b->getRootRange<LLTreeIter::DOWN>();\n" +// << "for (EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type::iterator ri = range.begin();\n" +// << " ri != range.end(); ++ri)\n"; + EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type range = + etnB2b->getRootRange<LLTreeIter::DOWN>(); + for (EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type::iterator ri = range.begin(); + ri != range.end(); ++ri) + { +// std::cout << (*ri)->name() << '\n'; + } + + std::string desc2("BOOST_FOREACH(EnhancedTreeNodePtr node, etnB2b->getRootRange<LLTreeIter::UP>())"); +// std::cout << desc2 << '\n'; + BOOST_FOREACH(EnhancedTreeNodePtr node, etnB2b->getRootRange<LLTreeIter::UP>()) + { +// std::cout << node->name() << '\n'; + } + ensure(desc2, + verify(desc2, etnB2b->getRootRange<LLTreeIter::UP>(), RootExpected<LLTreeIter::UP>())); + } + + template<> template<> + void iter_object::test<5>() + { +// set_test_name("getWalkRange() tests"); + // This test function doesn't illustrate the looping permutations for + // getWalkRange(); see getRootRange_tests() for such examples. This + // function simply verifies that they all work. + + // TreeNode, using helper function + TreeNodePtr tnroot(example_tree<TreeNode>()); + std::string desc_tnpre("getWalkRange<LLTreeIter::DFS_PRE>(tnroot)"); + ensure(desc_tnpre, + verify(desc_tnpre, + getWalkRange<LLTreeIter::DFS_PRE>(tnroot), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_tnpost("getWalkRange<LLTreeIter::DFS_POST>(tnroot)"); + ensure(desc_tnpost, + verify(desc_tnpost, + getWalkRange<LLTreeIter::DFS_POST>(tnroot), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_tnb("getWalkRange<LLTreeIter::BFS>(tnroot)"); + ensure(desc_tnb, + verify(desc_tnb, + getWalkRange<LLTreeIter::BFS>(tnroot), + WalkExpected<LLTreeIter::BFS>())); + + // EnhancedTreeNode, using method + EnhancedTreeNodePtr etnroot(example_tree<EnhancedTreeNode>()); + std::string desc_etnpre("etnroot->getWalkRange<LLTreeIter::DFS_PRE>()"); + ensure(desc_etnpre, + verify(desc_etnpre, + etnroot->getWalkRange<LLTreeIter::DFS_PRE>(), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_etnpost("etnroot->getWalkRange<LLTreeIter::DFS_POST>()"); + ensure(desc_etnpost, + verify(desc_etnpost, + etnroot->getWalkRange<LLTreeIter::DFS_POST>(), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_etnb("etnroot->getWalkRange<LLTreeIter::BFS>()"); + ensure(desc_etnb, + verify(desc_etnb, + etnroot->getWalkRange<LLTreeIter::BFS>(), + WalkExpected<LLTreeIter::BFS>())); + + // PlainTree, using helper function + PlainTree* ptroot(example_tree<PlainTree>()); + Cleanup<PlainTree*> cleanup(ptroot); + std::string desc_ptpre("getWalkRange<LLTreeIter::DFS_PRE>(ptroot)"); + ensure(desc_ptpre, + verify(desc_ptpre, + getWalkRange<LLTreeIter::DFS_PRE>(ptroot), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_ptpost("getWalkRange<LLTreeIter::DFS_POST>(ptroot)"); + ensure(desc_ptpost, + verify(desc_ptpost, + getWalkRange<LLTreeIter::DFS_POST>(ptroot), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_ptb("getWalkRange<LLTreeIter::BFS>(ptroot)"); + ensure(desc_ptb, + verify(desc_ptb, + getWalkRange<LLTreeIter::BFS>(ptroot), + WalkExpected<LLTreeIter::BFS>())); + } +} // tut diff --git a/indra/llcommon/tests/lluri_test.cpp b/indra/llcommon/tests/lluri_test.cpp new file mode 100644 index 0000000000..0a7c37d4b9 --- /dev/null +++ b/indra/llcommon/tests/lluri_test.cpp @@ -0,0 +1,370 @@ +/** + * @file lluri_test.cpp + * @brief LLURI unit tests + * @date September 2006 + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-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 "../llsd.h" +#include "../lluri.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct URITestData { + void checkParts(const LLURI& u, + const char* expectedScheme, + const char* expectedOpaque, + const char* expectedAuthority, + const char* expectedPath, + const char* expectedQuery = "") + { + ensure_equals("scheme", u.scheme(), expectedScheme); + ensure_equals("opaque", u.opaque(), expectedOpaque); + ensure_equals("authority", u.authority(), expectedAuthority); + ensure_equals("path", u.path(), expectedPath); + ensure_equals("query", u.query(), expectedQuery); + } + + void escapeRoundTrip(const std::string& uri_raw_1) + { + std::string uri_esc_1(LLURI::escape(uri_raw_1)); + std::string uri_raw_2(LLURI::unescape(uri_esc_1)); + ensure_equals("escape/unescape raw", uri_raw_2, uri_raw_1); + std::string uri_esc_2(LLURI::escape(uri_raw_2)); + ensure_equals("escape/unescape escaped", uri_esc_2, uri_esc_1); + } + }; + + typedef test_group<URITestData> URITestGroup; + typedef URITestGroup::object URITestObject; + + URITestGroup uriTestGroup("LLURI"); + + template<> template<> + void URITestObject::test<1>() + { + LLURI u("http://abc.com/def/ghi?x=37&y=hello"); + + ensure_equals("scheme", u.scheme(), "http"); + ensure_equals("authority", u.authority(), "abc.com"); + ensure_equals("path", u.path(), "/def/ghi"); + ensure_equals("query", u.query(), "x=37&y=hello"); + + ensure_equals("host name", u.hostName(), "abc.com"); + ensure_equals("host port", u.hostPort(), 80); + + LLSD query = u.queryMap(); + ensure_equals("query x", query["x"].asInteger(), 37); + ensure_equals("query y", query["y"].asString(), "hello"); + + query = LLURI::queryMap("x=22.23&y=https://lindenlab.com/"); + ensure_equals("query x", query["x"].asReal(), 22.23); + ensure_equals("query y", query["y"].asURI().asString(), "https://lindenlab.com/"); + } + + template<> template<> + void URITestObject::test<2>() + { + // empty string + checkParts(LLURI(""), "", "", "", ""); + } + + template<> template<> + void URITestObject::test<3>() + { + // no scheme + checkParts(LLURI("foo"), "", "foo", "", ""); + checkParts(LLURI("foo%3A"), "", "foo:", "", ""); + } + + template<> template<> + void URITestObject::test<4>() + { + // scheme w/o paths + checkParts(LLURI("mailto:zero@ll.com"), + "mailto", "zero@ll.com", "", ""); + checkParts(LLURI("silly://abc/def?foo"), + "silly", "//abc/def?foo", "", ""); + } + + template<> template<> + void URITestObject::test<5>() + { + // authority section + checkParts(LLURI("http:///"), + "http", "///", "", "/"); + + checkParts(LLURI("http://abc"), + "http", "//abc", "abc", ""); + + checkParts(LLURI("http://a%2Fb/cd"), + "http", "//a/b/cd", "a/b", "/cd"); + + checkParts(LLURI("http://host?"), + "http", "//host?", "host", ""); + } + + template<> template<> + void URITestObject::test<6>() + { + // path section + checkParts(LLURI("http://host/a/b/"), + "http", "//host/a/b/", "host", "/a/b/"); + + checkParts(LLURI("http://host/a%3Fb/"), + "http", "//host/a?b/", "host", "/a?b/"); + + checkParts(LLURI("http://host/a:b/"), + "http", "//host/a:b/", "host", "/a:b/"); + } + + template<> template<> + void URITestObject::test<7>() + { + // query string + checkParts(LLURI("http://host/?"), + "http", "//host/?", "host", "/", ""); + + checkParts(LLURI("http://host/?x"), + "http", "//host/?x", "host", "/", "x"); + + checkParts(LLURI("http://host/??"), + "http", "//host/??", "host", "/", "?"); + + checkParts(LLURI("http://host/?%3F"), + "http", "//host/??", "host", "/", "?"); + } + + template<> template<> + void URITestObject::test<8>() + { + LLSD path; + path.append("x"); + path.append("123"); + checkParts(LLURI::buildHTTP("host", path), + "http", "//host/x/123", "host", "/x/123"); + + LLSD query; + query["123"] = "12"; + query["abcd"] = "abc"; + checkParts(LLURI::buildHTTP("host", path, query), + "http", "//host/x/123?123=12&abcd=abc", + "host", "/x/123", "123=12&abcd=abc"); + } + + template<> template<> + void URITestObject::test<9>() + { + // test unescaped path components + LLSD path; + path.append("x@*//*$&^"); + path.append("123"); + checkParts(LLURI::buildHTTP("host", path), + "http", "//host/x@*//*$&^/123", "host", "/x@*//*$&^/123"); + } + + template<> template<> + void URITestObject::test<10>() + { + // test unescaped query components + LLSD path; + path.append("x"); + path.append("123"); + LLSD query; + query["123"] = "?&*#//"; + query["**@&?//"] = "abc"; + checkParts(LLURI::buildHTTP("host", path, query), + "http", "//host/x/123?**@&?//=abc&123=?&*#//", + "host", "/x/123", "**@&?//=abc&123=?&*#//"); + } + + template<> template<> + void URITestObject::test<11>() + { + // test unescaped host components + LLSD path; + path.append("x"); + path.append("123"); + LLSD query; + query["123"] = "12"; + query["abcd"] = "abc"; + checkParts(LLURI::buildHTTP("hi123*33--}{:portstuffs", path, query), + "http", "//hi123*33--}{:portstuffs/x/123?123=12&abcd=abc", + "hi123*33--}{:portstuffs", "/x/123", "123=12&abcd=abc"); + } + + template<> template<> + void URITestObject::test<12>() + { + // test funky host_port values that are actually prefixes + + checkParts(LLURI::buildHTTP("http://example.com:8080", LLSD()), + "http", "//example.com:8080", + "example.com:8080", ""); + + checkParts(LLURI::buildHTTP("http://example.com:8080/", LLSD()), + "http", "//example.com:8080/", + "example.com:8080", "/"); + + checkParts(LLURI::buildHTTP("http://example.com:8080/a/b", LLSD()), + "http", "//example.com:8080/a/b", + "example.com:8080", "/a/b"); + } + + template<> template<> + void URITestObject::test<13>() + { + const std::string unreserved = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + // test escape + ensure_equals("escaping", LLURI::escape("abcdefg", "abcdef"), "abcdef%67"); + ensure_equals("escaping", LLURI::escape("|/&\\+-_!@", ""), "%7C%2F%26%5C%2B%2D%5F%21%40"); + ensure_equals("escaping as query variable", + LLURI::escape("http://10.0.1.4:12032/agent/god/agent-id/map/layer/?resume=http://station3.ll.com:12032/agent/203ad6df-b522-491d-ba48-4e24eb57aeff/send-postcard", unreserved + ":@!$'()*+,="), + "http:%2F%2F10.0.1.4:12032%2Fagent%2Fgod%2Fagent-id%2Fmap%2Flayer%2F%3Fresume=http:%2F%2Fstation3.ll.com:12032%2Fagent%2F203ad6df-b522-491d-ba48-4e24eb57aeff%2Fsend-postcard"); + // French cedilla (C with squiggle, like in the word Francais) is UTF-8 C3 A7 + +#if LL_WINDOWS +#pragma warning(disable: 4309) +#endif + + std::string cedilla; + cedilla.push_back( (char)0xC3 ); + cedilla.push_back( (char)0xA7 ); + ensure_equals("escape UTF8", LLURI::escape( cedilla, unreserved), "%C3%A7"); + } + + + template<> template<> + void URITestObject::test<14>() + { + // make sure escape and unescape of empty strings return empty + // strings. + std::string uri_esc(LLURI::escape("")); + ensure("escape string empty", uri_esc.empty()); + std::string uri_raw(LLURI::unescape("")); + ensure("unescape string empty", uri_raw.empty()); + } + + template<> template<> + void URITestObject::test<15>() + { + // do some round-trip tests + escapeRoundTrip("http://secondlife.com"); + escapeRoundTrip("http://secondlife.com/url with spaces"); + escapeRoundTrip("http://bad[domain]name.com/"); + escapeRoundTrip("ftp://bill.gates@ms/micro$oft.com/c:\\autoexec.bat"); + escapeRoundTrip(""); + } + + template<> template<> + void URITestObject::test<16>() + { + // Test the default escaping + // yes -- this mangles the url. This is expected behavior + std::string simple("http://secondlife.com"); + ensure_equals( + "simple http", + LLURI::escape(simple), + "http%3A%2F%2Fsecondlife.com"); + ensure_equals( + "needs escape", + LLURI::escape("http://get.secondlife.com/windows viewer"), + "http%3A%2F%2Fget.secondlife.com%2Fwindows%20viewer"); + } + + template<> template<> + void URITestObject::test<17>() + { + // do some round-trip tests with very long strings. + escapeRoundTrip("Welcome to Second Life.We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun.The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community. Behavioral Guidelines - The Big Six"); + escapeRoundTrip( + "'asset_data':b(12100){'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444921\n\ttotal_crc\t323\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.368634403\t0.00781063363\t-0.569040775\n\toldpos\t150.117996\t25.8658009\t8.19664001\n\trotation\t-0.06293071806430816650390625\t-0.6995697021484375\t-0.7002241611480712890625\t0.1277817934751510620117188\n\tchildpos\t-0.00499999989\t-0.0359999985\t0.307999998\n\tchildrot\t-0.515492737293243408203125\t-0.46601200103759765625\t0.529055416584014892578125\t0.4870323240756988525390625\n\tscale" + "\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tf" + "aces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204" + "\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061088050622956\n\treztime\t1094866329019785\n\tparceltime\t1133568981980596\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':u61fa7364-e151-0597-774c-523312dae31b}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffff" + "ff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444922\n\ttotal_crc\t324\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.367110789\t0.00780026987\t-0.566269755\n\toldpos\t150.115005\t25.8479004\t8.18669987\n\trotation\t0.47332942485809326171875\t-0.380102097988128662109375\t-0.5734078884124755859375\t0.550168216228485107421875\n\tchildpos\t-0.00499999989\t-0.0370000005\t0.305000007\n\tchildrot\t-0.736649334430694580078125\t-0.03042060509324073791503906\t-0.02784589119255542755126953\t0.67501628398895263671875\n\tscale\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t" + "0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t" + "\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t" + "\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087839248891\n\treztime\t1094866329020800\n\tparceltime\t1133568981981983\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ub8d68643-7dd8-57af-0d24-8790032aed0c}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreat" + "or_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444923\n\ttotal_crc\t235\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.120029509\t-0.00284469454\t-0.0302077383\n\toldpos\t150.710999\t25.8584995\t8.19172001\n\trotation\t0.145459949970245361328125\t-0.1646589934825897216796875\t0.659558117389678955078125\t-0.718826770782470703125\n\tchildpos\t0\t-0.182999998\t-0.26699999\n\tchildrot\t0.991444766521453857421875\t3.271923924330621957778931e-05\t-0.0002416197530692443251609802\t0.1305266767740249633789062\n\tscale\t0.0382982\t0.205957\t0.368276\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundra" + "dius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0" + "\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087534454174\n\treztime\t1094866329021741\n\tparceltime\t1133568981982889\n\ttax_rate\t1.00326\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ue4b19200-9d33-962f-c8c5-6f" + "25be3a3fd0}\n{\n\tname\tApotheosis_Immolaine_tail|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444924\n\ttotal_crc\t675\n\ttype\t1\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.34780401\t-0.00968400016\t-0.260098994\n\toldpos\t0\t0\t0\n\trotation\t0.73164522647857666015625\t-0.67541944980621337890625\t-0.07733880728483200073242188\t0.05022468417882919311523438\n\tvelocity\t0\t0\t0\n\tangvel\t0\t0\t0\n\tscale\t0.0382982\t0.32228\t0.383834\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000" + "000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1" + ".57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087463950186\n\treztime\t1094866329022555\n\tparceltime\t1133568981984359\n\tdescription\t(No Description)|\n\ttax_rate\t1.01736\n\tnamevalue\tAttachPt U32 RW S 10\n\tnamevalue\tAttachmentOrientation VEC3 RW DS -3.110088, -0.182018, 1.493795\n\tnamevalue\tAttachmentOffset VEC3 RW DS -0.347804, -0.009684, -0.260099\n\tnamevalue\tAttachItemI" + "D STRING RW SV 20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t8747acbc-d391-1e59-69f1-41d06830e6c0\n\torig_item_id\t20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tfrom_task_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tlinked\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n"); + } + + + template<> template<> + void URITestObject::test<18>() + { + LLURI u("secondlife:///app/login?first_name=Testert4&last_name=Tester&web_login_key=test"); + // if secondlife is the scheme, LLURI should parse /app/login as path, with no authority + ensure_equals("scheme", u.scheme(), "secondlife"); + ensure_equals("authority", u.authority(), ""); + ensure_equals("path", u.path(), "/app/login"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "app"); + ensure_equals("pathmap", u.pathArray()[1].asString(), "login"); + ensure_equals("query", u.query(), "first_name=Testert4&last_name=Tester&web_login_key=test"); + ensure_equals("query map element", u.queryMap()["last_name"].asString(), "Tester"); + + u = LLURI("secondlife://Da Boom/128/128/128"); + // if secondlife is the scheme, LLURI should parse /128/128/128 as path, with Da Boom as authority + ensure_equals("scheme", u.scheme(), "secondlife"); + ensure_equals("authority", u.authority(), "Da Boom"); + ensure_equals("path", u.path(), "/128/128/128"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "128"); + ensure_equals("pathmap", u.pathArray()[1].asString(), "128"); + ensure_equals("pathmap", u.pathArray()[2].asString(), "128"); + ensure_equals("query", u.query(), ""); + } + + template<> template<> + void URITestObject::test<19>() + { + // Parse about: schemes + LLURI u("about:blank?redirect-http-hack=secondlife%3A%2F%2F%2Fapp%2Flogin%3Ffirst_name%3DCallum%26last_name%3DLinden%26location%3Dspecify%26grid%3Dvaak%26region%3D%2FMorris%2F128%2F128%26web_login_key%3Defaa4795-c2aa-4c58-8966-763c27931e78"); + ensure_equals("scheme", u.scheme(), "about"); + ensure_equals("authority", u.authority(), ""); + ensure_equals("path", u.path(), "blank"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "blank"); + ensure_equals("query", u.query(), "redirect-http-hack=secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + ensure_equals("query map element", u.queryMap()["redirect-http-hack"].asString(), "secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + } +} + + diff --git a/indra/llcommon/tests/reflection_test.cpp b/indra/llcommon/tests/reflection_test.cpp new file mode 100644 index 0000000000..5263e7fa64 --- /dev/null +++ b/indra/llcommon/tests/reflection_test.cpp @@ -0,0 +1,226 @@ +/** + * @file reflection_test.cpp + * @date May 2006 + * @brief Reflection unit tests. + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-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 "../reflective.h" +#include "../metaclasst.h" +#include "../metapropertyt.h" +#include "../stdtypes.h" + +#include "../test/lltut.h" + +namespace tut +{ + class TestAggregatedData : public LLReflective + { + public: + TestAggregatedData() {;} + virtual const LLMetaClass& getMetaClass() const; + + private: + }; + + class TestReflectionData : public LLReflective + { + public: + TestReflectionData() : mInt(42), mString("foo"), mNullPtr(NULL), mPtr(new TestAggregatedData()), mRef(*(new TestAggregatedData)) {;} + virtual ~TestReflectionData() {delete mPtr;} + virtual const LLMetaClass& getMetaClass() const; + + static U32 getPropertyCount() {return 5;} + + private: + + friend class LLMetaClassT<TestReflectionData>; + S32 mInt; + std::string mString; + TestAggregatedData* mNullPtr; + TestAggregatedData* mPtr; + TestAggregatedData mObj; + TestAggregatedData& mRef; + }; +} + +template <> +void LLMetaClassT<tut::TestReflectionData>::reflectProperties(LLMetaClass& meta_class) +{ + reflectProperty(meta_class, "mInt", &tut::TestReflectionData::mInt); + reflectProperty(meta_class, "mString", &tut::TestReflectionData::mString); + reflectPtrProperty(meta_class, "mNullPtr", &tut::TestReflectionData::mNullPtr); + reflectPtrProperty(meta_class, "mPtr", &tut::TestReflectionData::mPtr); + reflectProperty(meta_class, "mObj", &tut::TestReflectionData::mObj); + //reflectProperty(meta_class, "mRef", &tut::TestReflectionData::mRef); // AARGH! +} + +namespace tut +{ + // virtual + const LLMetaClass& TestReflectionData::getMetaClass() const + { + return LLMetaClassT<TestReflectionData>::instance(); + } + + const LLMetaClass& TestAggregatedData::getMetaClass() const + { + return LLMetaClassT<TestAggregatedData>::instance(); + } +} + +namespace tut +{ + typedef tut::test_group<TestReflectionData> TestReflectionGroup; + typedef TestReflectionGroup::object TestReflectionObject; + TestReflectionGroup gTestReflectionGroup("reflection"); + + template<> template<> + void TestReflectionObject::test<1>() + { + // Check properties can be found. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLMetaProperty* null = NULL; + ensure_not_equals(meta_class.findProperty("mInt"), null); + ensure_not_equals(meta_class.findProperty("mString"), null); + } + + template<> template<> + void TestReflectionObject::test<2>() + { + // Check non-existent property cannot be found. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLMetaProperty* null = NULL; + ensure_equals(meta_class.findProperty("foo"), null); + } + + template<> template<> + void TestReflectionObject::test<3>() + { + // Check integer property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.findProperty("mInt")->getLLSD(this).asInteger(), 42); + } + + template<> template<> + void TestReflectionObject::test<4>() + { + // Check string property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.findProperty("mString")->getLLSD(this).asString(), std::string("foo")); + } + + template<> template<> + void TestReflectionObject::test<5>() + { + // Check NULL reference property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + ensure_equals(meta_class.findProperty("mNullPtr")->get(this), null); + } + + template<> template<> + void TestReflectionObject::test<6>() + { + // Check reference property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + const LLReflective* ref = meta_class.findProperty("mPtr")->get(this); + ensure_not_equals(ref, null); + } + + template<> template<> + void TestReflectionObject::test<7>() + { + // Check reflective property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + const LLReflective* ref = meta_class.findProperty("mObj")->get(this); + ensure_not_equals(ref, null); + } + + template<> template<> + void TestReflectionObject::test<8>() + { + // Check property count. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.getPropertyCount(), TestReflectionData::getPropertyCount()); + } + + template<> template<> + void TestReflectionObject::test<9>() + { + // Check property iteration. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + U32 count = 0; + LLMetaClass::PropertyIterator iter; + for(iter = meta_class.beginProperties(); iter != meta_class.endProperties(); ++iter) + { + ++count; + } + ensure_equals(count, TestReflectionData::getPropertyCount()); + } + + template<> template<> + void TestReflectionObject::test<10>() + { + // Check meta classes of different types do not compare equal. + const LLMetaClass* reflection_data_meta_class = &(LLMetaClassT<TestReflectionData>::instance()); + const LLMetaClass* aggregated_data_meta_class = &(LLMetaClassT<TestAggregatedData>::instance()); + ensure_not_equals(reflection_data_meta_class, aggregated_data_meta_class); + } + + template<> template<> + void TestReflectionObject::test<11>() + { + // Check class cast checks. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + TestAggregatedData* aggregated_data = new TestAggregatedData(); + LLMetaClass::PropertyIterator iter; + U32 exception_count = 0; + for(iter = meta_class.beginProperties(); iter != meta_class.endProperties(); ++iter) + { + try + { + const LLMetaProperty* property = (*iter).second; + const LLReflective* reflective = property->get(aggregated_data); // Wrong reflective type, should throw exception. + + // useless op to get rid of compiler warning. + reflective = NULL; + } + catch(...) + { + ++exception_count; + } + } + ensure_equals(exception_count, getPropertyCount()); + + } +} diff --git a/indra/llcommon/tests/stringize_test.cpp b/indra/llcommon/tests/stringize_test.cpp new file mode 100644 index 0000000000..dd69787a1c --- /dev/null +++ b/indra/llcommon/tests/stringize_test.cpp @@ -0,0 +1,110 @@ +/** + * @file stringize_test.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Test of stringize.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-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$ + */ + +/*==========================================================================*| +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif +|*==========================================================================*/ + +// STL headers +#include <iomanip> + +// Precompiled header +#include "linden_common.h" + +// associated header +#include "../stringize.h" + +// std headers +// external library headers +// other Linden headers +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct stringize_data + { + stringize_data(): + c('c'), + s(17), + i(34), + l(68), + f(3.14159265358979f), + d(3.14159265358979), + // Including a space differentiates this from + // boost::lexical_cast<std::string>, which doesn't handle embedded + // spaces so well. + abc("abc def") + { + llsd["i"] = i; + llsd["d"] = d; + llsd["abc"] = abc; + } + + char c; + short s; + int i; + long l; + float f; + double d; + std::string abc; + LLSD llsd; + }; + typedef test_group<stringize_data> stringize_group; + typedef stringize_group::object stringize_object; + tut::stringize_group strzgrp("stringize"); + + template<> template<> + void stringize_object::test<1>() + { + ensure_equals(stringize(c), "c"); + ensure_equals(stringize(s), "17"); + ensure_equals(stringize(i), "34"); + ensure_equals(stringize(l), "68"); + ensure_equals(stringize(f), "3.14159"); + ensure_equals(stringize(d), "3.14159"); + ensure_equals(stringize(abc), "abc def"); + ensure_equals(stringize(llsd), "{'abc':'abc def','d':r3.14159,'i':i34}"); + } + + template<> template<> + void stringize_object::test<2>() + { + ensure_equals(STRINGIZE("c is " << c), "c is c"); + ensure_equals(STRINGIZE(std::setprecision(4) << d), "3.142"); + } +} // namespace tut diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h new file mode 100644 index 0000000000..1001ebc466 --- /dev/null +++ b/indra/llcommon/tests/wrapllerrs.h @@ -0,0 +1,56 @@ +/** + * @file wrapllerrs.h + * @author Nat Goodspeed + * @date 2009-03-11 + * @brief Define a class useful for unit tests that engage llerrs (LL_ERRS) functionality + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRAPLLERRS_H) +#define LL_WRAPLLERRS_H + +#include "llerrorcontrol.h" + +struct WrapLL_ERRS +{ + WrapLL_ERRS(): + // Resetting Settings discards the default Recorder that writes to + // stderr. Otherwise, expected llerrs (LL_ERRS) messages clutter the + // console output of successful tests, potentially confusing things. + mPriorErrorSettings(LLError::saveAndResetSettings()), + // Save shutdown function called by LL_ERRS + mPriorFatal(LLError::getFatalFunction()) + { + // Make LL_ERRS call our own operator() method + LLError::setFatalFunction(boost::bind(&WrapLL_ERRS::operator(), this, _1)); + } + + ~WrapLL_ERRS() + { + LLError::setFatalFunction(mPriorFatal); + LLError::restoreSettings(mPriorErrorSettings); + } + + struct FatalException: public std::runtime_error + { + FatalException(const std::string& what): std::runtime_error(what) {} + }; + + void operator()(const std::string& message) + { + // Save message for later in case consumer wants to sense the result directly + error = message; + // Also throw an appropriate exception since calling code is likely to + // assume that control won't continue beyond LL_ERRS. + throw FatalException(message); + } + + std::string error; + LLError::Settings* mPriorErrorSettings; + LLError::FatalFunction mPriorFatal; +}; + +#endif /* ! defined(LL_WRAPLLERRS_H) */ diff --git a/indra/llcommon/timing.h b/indra/llcommon/timing.h index 2b9f60adad..140ce1fcaa 100644 --- a/indra/llcommon/timing.h +++ b/indra/llcommon/timing.h @@ -43,7 +43,6 @@ const F32 SEC_TO_MICROSEC = 1000000.f; const U64 SEC_TO_MICROSEC_U64 = 1000000; const U32 SEC_PER_DAY = 86400; -// This is just a stub, implementation in lltimer.cpp. This file will be deprecated in the future. -U64 totalTime(); // Returns current system time in microseconds +// functionality has been moved lltimer.{cpp,h}. This file will be deprecated in the future. #endif diff --git a/indra/llcommon/u64.h b/indra/llcommon/u64.h index 09a6b3e18d..eb51131e94 100644 --- a/indra/llcommon/u64.h +++ b/indra/llcommon/u64.h @@ -39,14 +39,14 @@ * @param str The string to parse. * @return Returns the first U64 value found in the string or 0 on failure. */ -U64 str_to_U64(const std::string& str); +LL_COMMON_API U64 str_to_U64(const std::string& str); /** * @brief Given a U64 value, return a printable representation. * @param value The U64 to turn into a printable character array. * @return Returns the result string. */ -std::string U64_to_str(U64 value); +LL_COMMON_API std::string U64_to_str(U64 value); /** * @brief Given a U64 value, return a printable representation. @@ -65,16 +65,16 @@ std::string U64_to_str(U64 value); * @param result_size The size of the buffer allocated. Use U64_BUF. * @return Returns the result pointer. */ -char* U64_to_str(U64 value, char* result, S32 result_size); +LL_COMMON_API char* U64_to_str(U64 value, char* result, S32 result_size); /** * @brief Convert a U64 to the closest F64 value. */ -F64 U64_to_F64(const U64 value); +LL_COMMON_API F64 U64_to_F64(const U64 value); /** * @brief Helper function to wrap strtoull() which is not available on windows. */ -U64 llstrtou64(const char* str, char** end, S32 base); +LL_COMMON_API U64 llstrtou64(const char* str, char** end, S32 base); #endif |