diff options
Diffstat (limited to 'indra/llcommon')
83 files changed, 5522 insertions, 2588 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 32226a62e6..bff3c66924 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -22,7 +22,6 @@ set(llcommon_SOURCE_FILES llapr.cpp llassettype.cpp llatomic.cpp - llbase32.cpp llbase64.cpp llbitpack.cpp llcallbacklist.cpp @@ -69,7 +68,7 @@ set(llcommon_SOURCE_FILES llmortician.cpp llmutex.cpp llpointer.cpp - llptrto.cpp + llptrto.cpp llpredicate.cpp llprocess.cpp llprocessor.cpp @@ -86,7 +85,6 @@ set(llcommon_SOURCE_FILES llsdutil.cpp llsingleton.cpp llstacktrace.cpp - llstreamqueue.cpp llstreamtools.cpp llstring.cpp llstringtable.cpp @@ -110,7 +108,7 @@ set(llcommon_SOURCE_FILES workqueue.cpp StackWalker.cpp ) - + set(llcommon_HEADER_FILES CMakeLists.txt @@ -119,8 +117,6 @@ set(llcommon_HEADER_FILES chrono.h classic_callback.h commoncontrol.h - ctype_workaround.h - fix_macros.h fsyspath.h function_types.h indra_constants.h @@ -131,7 +127,6 @@ set(llcommon_HEADER_FILES llapr.h llassettype.h llatomic.h - llbase32.h llbase64.h llbitpack.h llboost.h @@ -219,7 +214,6 @@ set(llcommon_HEADER_FILES llsingleton.h llstacktrace.h llstl.h - llstreamqueue.h llstreamtools.h llstrider.h llstring.h @@ -252,20 +246,33 @@ set(llcommon_HEADER_FILES threadpool.h threadpool_fwd.h threadsafeschedule.h - timer.h tuple.h u64.h workqueue.h StackWalker.h ) - + if (DARWIN) list(APPEND llcommon_HEADER_FILES llsys_objc.h) list(APPEND llcommon_SOURCE_FILES llsys_objc.mm) + + set_source_files_properties( + llsys_objc.mm + PROPERTIES + SKIP_PRECOMPILE_HEADERS TRUE + ) endif (DARWIN) if (USE_TRACY) list(APPEND llcommon_SOURCE_FILES llprofiler.cpp) + + # Skip precompiled headers on these files due to special compilation needs + set_source_files_properties( + llfasttimer.cpp + llprofiler.cpp + PROPERTIES + SKIP_PRECOMPILE_HEADERS TRUE + ) endif () list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) @@ -283,8 +290,12 @@ target_link_libraries( ll::sse2neon ) -target_include_directories(llcommon INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(llcommon PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(llcommon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(llcommon PRIVATE ${CMAKE_SOURCE_DIR}/llmath) + +if (USE_PRECOMPILED_HEADERS) + target_precompile_headers(llcommon REUSE_FROM llprecompiled) +endif () add_dependencies(llcommon stage_third_party_libs) @@ -302,16 +313,21 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llapp "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lldoubledispatch "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llevents "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llfile "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llhttpdate "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") @@ -321,9 +337,11 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llsd "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llsdutil "" "llcommon;llmath") LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llstreamtools "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") @@ -339,4 +357,11 @@ if (LL_TESTS) ## throwing and catching exceptions. ##LL_ADD_INTEGRATION_TEST(llexception "" "${test_libs}") + # Skip precompiled header for tests that need special handling + set_source_files_properties( + tests/llsd_test.cpp + PROPERTIES + SKIP_PRECOMPILE_HEADERS TRUE + ) + endif (LL_TESTS) diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index e9ae1723fb..a281fe83f6 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -785,6 +785,8 @@ private: case 8: //SymVirtual: szSymType = "Virtual"; break; + case NumSymTypes: + break; } } LPCSTR pdbName = Module.LoadedImageName; @@ -902,7 +904,7 @@ bool StackWalker::LoadModules() SetLastError(ERROR_DLL_INIT_FAILED); return false; } - if (m_modulesLoaded != FALSE) + if (m_modulesLoaded) return true; // Build the sym-path: diff --git a/indra/llcommon/classic_callback.h b/indra/llcommon/classic_callback.h index 009c25d67c..8a6ac7f7ff 100644 --- a/indra/llcommon/classic_callback.h +++ b/indra/llcommon/classic_callback.h @@ -184,8 +184,8 @@ protected: template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()> auto makeClassicCallback(CALLABLE&& callable) { - return std::move(ClassicCallback<SIGNATURE, USERDATA, CALLABLE> - (std::forward<CALLABLE>(callable))); + return ClassicCallback<SIGNATURE, USERDATA, CALLABLE> + (std::forward<CALLABLE>(callable)); } /***************************************************************************** diff --git a/indra/llcommon/ctype_workaround.h b/indra/llcommon/ctype_workaround.h deleted file mode 100644 index 89a47fe3db..0000000000 --- a/indra/llcommon/ctype_workaround.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file ctype_workaround.h - * @brief The workaround is to create some legacy symbols that point - * to the correct symbols, which avoids link errors. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef _CTYPE_WORKAROUND_H_ -#define _CTYPE_WORKAROUND_H_ - -/** - * the CTYPE_WORKAROUND is needed for linux dev stations that don't - * have the broken libc6 packages needed by our out-of-date static - * libs (such as libcrypto and libcurl). - * - * -- Leviathan 20060113 -*/ - -#include <cctype> - -__const unsigned short int *__ctype_b; -__const __int32_t *__ctype_tolower; -__const __int32_t *__ctype_toupper; - -// call this function at the beginning of main() -void ctype_workaround() -{ - __ctype_b = *(__ctype_b_loc()); - __ctype_toupper = *(__ctype_toupper_loc()); - __ctype_tolower = *(__ctype_tolower_loc()); -} - -#endif - diff --git a/indra/llcommon/fix_macros.h b/indra/llcommon/fix_macros.h deleted file mode 100644 index ed6c26a371..0000000000 --- a/indra/llcommon/fix_macros.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file fix_macros.h - * @author Nat Goodspeed - * @date 2012-11-16 - * @brief The Mac system headers seem to #define macros with obnoxiously - * generic names, preventing any library from using those names. We've - * had to fix these in so many places that it's worth making a header - * file to handle it. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Copyright (c) 2012, Linden Research, Inc. - * $/LicenseInfo$ - */ - -// DON'T use an #include guard: every time we encounter this header, #undef -// these macros all over again. - -// who injects MACROS with such generic names?! Grr. -#ifdef check -#undef check -#endif diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h index 2c900c02a7..237d99ae86 100644 --- a/indra/llcommon/fsyspath.h +++ b/indra/llcommon/fsyspath.h @@ -57,7 +57,7 @@ class fsyspath: public std::filesystem::path public: // default - fsyspath() {} + fsyspath() = default; // construct from UTF-8 encoded string fsyspath(const std::string& path): fsyspath(std::string_view(path)) {} fsyspath(const char* path): fsyspath(std::string_view(path)) {} @@ -66,8 +66,10 @@ public: {} // construct from existing path fsyspath(const super& path): super(path) {} + fsyspath(super&& path) : super(std::move(path)) {} fsyspath& operator=(const super& p) { super::operator=(p); return *this; } + fsyspath& operator=(super&& p) { super::operator=(std::move(p)); return *this; } fsyspath& operator=(const std::string& p) { return (*this) = std::string_view(p); } fsyspath& operator=(const char* p) { return (*this) = std::string_view(p); } fsyspath& operator=(std::string_view p) diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h index ab5ba2b16d..ab5ba2b16d 100755..100644 --- a/indra/llcommon/hexdump.h +++ b/indra/llcommon/hexdump.h diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index f92bb98ba6..02d6da989a 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -37,7 +37,7 @@ #endif #include "llcommon.h" -#include "llapr.h" + #include "llerrorcontrol.h" #include "llframetimer.h" #include "lllivefile.h" @@ -53,19 +53,13 @@ // // Signal handling #ifndef LL_WINDOWS +#include "apr_signal.h" + # include <signal.h> # include <unistd.h> // for fork() void setup_signals(); void default_unix_signal_handler(int signum, siginfo_t *info, void *); -#if LL_LINUX -#else -// Called by breakpad exception handler after the minidump has been generated. -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded); -#endif - # if LL_DARWIN /* OSX doesn't support SIGRT* */ S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; @@ -721,47 +715,4 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) } } -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded) -{ - // Copy minidump file path into fixed buffer in the app instance to avoid - // heap allocations in a crash handler. - - // path format: <dump_dir>/<minidump_id>.dmp - auto dirPathLength = strlen(dump_dir); - auto idLength = strlen(minidump_id); - - // The path must not be truncated. - llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH); - - char * path = LLApp::instance()->getMiniDumpFilename(); - auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH; - strncpy(path, dump_dir, remaining); - remaining -= dirPathLength; - path += dirPathLength; - if (remaining > 0 && dirPathLength > 0 && path[-1] != '/') - { - *path++ = '/'; - --remaining; - } - if (remaining > 0) - { - strncpy(path, minidump_id, remaining); - remaining -= idLength; - path += idLength; - strncpy(path, ".dmp", remaining); - } - - LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL; - LLApp::runErrorHandler(); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - clear_signals(); - return false; -#else - return true; -#endif -} #endif // !WINDOWS - diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 06eb4c389f..e1d80c5dea 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -230,7 +230,6 @@ bool LLVolatileAPRPool::isFull() bool _ll_apr_warn_status(apr_status_t status, const char* file, int line) { if(APR_SUCCESS == status) return false; -#if !LL_LINUX char buf[MAX_STRING]; apr_strerror(status, buf, sizeof(buf)); @@ -260,7 +259,6 @@ bool _ll_apr_warn_status(apr_status_t status, const char* file, int line) #else LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL; #endif -#endif return true; } @@ -552,226 +550,6 @@ S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) } } -//static -S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) -{ - LL_PROFILE_ZONE_SCOPED; - //***************************************** - LLAPRFilePoolScope scope(pool); - apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY); - //***************************************** - if (!file_handle) - { - return 0; - } - - llassert(offset >= 0); - - if (offset > 0) - offset = LLAPRFile::seek(file_handle, APR_SET, offset); - - apr_size_t bytes_read; - if (offset < 0) - { - bytes_read = 0; - } - else - { - bytes_read = nbytes ; - apr_status_t s = apr_file_read(file_handle, buf, &bytes_read); - if (s != APR_SUCCESS) - { - LL_WARNS("APR") << " Attempting to read filename: " << filename << LL_ENDL; - ll_apr_warn_status(s); - bytes_read = 0; - } - else - { - llassert_always(bytes_read <= 0x7fffffff); - } - } - - //***************************************** - close(file_handle) ; - //***************************************** - return (S32)bytes_read; -} - -//static -S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) -{ - LL_PROFILE_ZONE_SCOPED; - apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; - if (offset < 0) - { - flags |= APR_APPEND; - offset = 0; - } - - //***************************************** - LLAPRFilePoolScope scope(pool); - apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), flags); - //***************************************** - if (!file_handle) - { - return 0; - } - - if (offset > 0) - { - offset = LLAPRFile::seek(file_handle, APR_SET, offset); - } - - apr_size_t bytes_written; - if (offset < 0) - { - bytes_written = 0; - } - else - { - bytes_written = nbytes ; - apr_status_t s = apr_file_write(file_handle, buf, &bytes_written); - if (s != APR_SUCCESS) - { - LL_WARNS("APR") << "Attempting to write filename: " << filename << LL_ENDL; - if (APR_STATUS_IS_ENOSPC(s)) - { - LLApp::notifyOutOfDiskSpace(); - } - ll_apr_warn_status(s); - bytes_written = 0; - } - else - { - llassert_always(bytes_written <= 0x7fffffff); - } - } - - //***************************************** - LLAPRFile::close(file_handle); - //***************************************** - - return (S32)bytes_written; -} - -//static -bool LLAPRFile::remove(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(filename.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_rename(filename.c_str(), newname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to rename filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::isExist(const std::string& filename, LLVolatileAPRPool* pool, apr_int32_t flags) -{ - apr_file_t* apr_file; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), flags, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return false; - } - else - { - apr_file_close(apr_file) ; - return true; - } -} - -//static -S32 LLAPRFile::size(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_file_t* apr_file; - apr_finfo_t info; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), APR_READ, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return 0; - } - else - { - apr_status_t s = apr_file_info_get(&info, APR_FINFO_SIZE, apr_file); - - apr_file_close(apr_file) ; - - if (s == APR_SUCCESS) - { - return (S32)info.size; - } - else - { - return 0; - } - } -} - -//static -bool LLAPRFile::makeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_dir_make(dirname.c_str(), APR_FPROT_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to make directory: " << dirname << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::removeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(dirname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove directory: " << dirname << LL_ENDL; - return false; - } - return true; -} // //end of static components of LLAPRFile //******************************************************************************************************************************* diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 11e474b5dd..8795a0f2c4 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -35,12 +35,8 @@ #include "llwin32headers.h" #include "apr_thread_proc.h" -#include "apr_getopt.h" -#include "apr_signal.h" -#include "llstring.h" - -#include "mutex.h" +#include <mutex> struct apr_dso_handle_t; /** @@ -184,20 +180,7 @@ private: static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags); static apr_status_t close(apr_file_t* file) ; static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset); -public: - // returns false if failure: - static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL); - static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ); - static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - - // Returns bytes read/written, 0 if read/write fails: - static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); - static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append //******************************************************************************************************************************* }; - #endif // LL_LLAPR_H diff --git a/indra/llcommon/llatomic.cpp b/indra/llcommon/llatomic.cpp index d200bb0406..cff5e01225 100644 --- a/indra/llcommon/llatomic.cpp +++ b/indra/llcommon/llatomic.cpp @@ -23,6 +23,8 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "llatomic.h" //============================================================================ diff --git a/indra/llcommon/llbase32.cpp b/indra/llcommon/llbase32.cpp deleted file mode 100644 index 979114fe86..0000000000 --- a/indra/llcommon/llbase32.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @file llbase32.cpp - * @brief base32 encoding that returns a std::string - * @author James Cook - * - * Based on code from bitter - * http://ghostwhitecrab.com/bitter/ - * - * Some parts of this file are: - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Copyright (c) 2006 Christian Biere <christianbiere@gmx.de> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the authors nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "linden_common.h" - -#include "llbase32.h" - -#include <string> - -// bitter - base32.c starts here - -/* - * See RFC 3548 for details about Base 32 encoding: - * http://www.faqs.org/rfcs/rfc3548.html - */ - -static const char base32_alphabet[32] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', '2', '3', '4', '5', '6', '7' -}; - -size_t -base32_encode(char *dst, size_t size, const void *data, size_t len) -{ - size_t i = 0; - const U8 *p = (const U8*)data; - const char *end = &dst[size]; - char *q = dst; - - do { - size_t j, k; - U8 x[5]; - char s[8]; - - switch (len - i) { - case 4: k = 7; break; - case 3: k = 5; break; - case 2: k = 3; break; - case 1: k = 2; break; - default: - k = 8; - } - - for (j = 0; j < 5; j++) - x[j] = i < len ? p[i++] : 0; - -/* - +-------+-----------+--------+ - | target| source | source | - | byte | bits | byte | - +-------+-----------+--------+ - | 0 | 7 6 5 4 3 | 0 | - | 1 | 2 1 0 7 6 | 0-1 | - | 2 | 5 4 3 2 1 | 1 | - | 3 | 0 7 6 5 4 | 1-2 | - | 4 | 3 2 1 0 7 | 2-3 | - | 5 | 6 5 4 3 2 | 3 | - | 6 | 1 0 7 6 5 | 3-4 | - | 7 | 4 3 2 1 0 | 4 | - +-------+-----------+--------+ - -*/ - - s[0] = (x[0] >> 3); - s[1] = ((x[0] & 0x07) << 2) | (x[1] >> 6); - s[2] = (x[1] >> 1) & 0x1f; - s[3] = ((x[1] & 0x01) << 4) | (x[2] >> 4); - s[4] = ((x[2] & 0x0f) << 1) | (x[3] >> 7); - s[5] = (x[3] >> 2) & 0x1f; - s[6] = ((x[3] & 0x03) << 3) | (x[4] >> 5); - s[7] = x[4] & 0x1f; - - for (j = 0; j < k && q != end; j++) - *q++ = base32_alphabet[(U8) s[j]]; - - } while (i < len); - - return q - dst; -} - -/* *TODO: Implement base32 encode. - -static inline int -ascii_toupper(int c) -{ - return c >= 97 && c <= 122 ? c - 32 : c; -} - -static inline int -ascii_tolower(int c) -{ - return c >= 65 && c <= 90 ? c + 32 : c; -} - - -static char base32_map[(unsigned char) -1]; - -size_t -base32_decode(char *dst, size_t size, const void *data, size_t len) -{ - const char *end = &dst[size]; - const unsigned char *p = data; - char *q = dst; - size_t i; - unsigned max_pad = 3; - - if (0 == base32_map[0]) { - for (i = 0; i < LL_ARRAY_SIZE(base32_map); i++) { - const char *x; - - x = memchr(base32_alphabet, ascii_toupper(i), sizeof base32_alphabet); - base32_map[i] = x ? (x - base32_alphabet) : (unsigned char) -1; - } - } - - for (i = 0; i < len && max_pad > 0; i++) { - unsigned char c; - char s[8]; - size_t j; - - c = p[i]; - if ('=' == c) { - max_pad--; - c = 0; - } else { - c = base32_map[c]; - if ((unsigned char) -1 == c) { - return -1; - } - } - - j = i % LL_ARRAY_SIZE(s); - s[j] = c; - - if (7 == j) { - char b[5]; - - b[0] = ((s[0] << 3) & 0xf8) | ((s[1] >> 2) & 0x07); - b[1] = ((s[1] & 0x03) << 6) | ((s[2] & 0x1f) << 1) | ((s[3] >> 4) & 1); - b[2] = ((s[3] & 0x0f) << 4) | ((s[4] >> 1) & 0x0f); - b[3] = ((s[4] & 1) << 7) | ((s[5] & 0x1f) << 2) | ((s[6] >> 3) & 0x03); - b[4] = ((s[6] & 0x07) << 5) | (s[7] & 0x1f); - - for (j = 0; j < LL_ARRAY_SIZE(b); j++) { - if (q != end) - *q = b[j]; - q++; - } - } - } - - return q - dst; -} -*/ - - -// The following is -// Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. -// static -std::string LLBase32::encode(const U8* input, size_t input_size) -{ - std::string output; - if (input) - { - // Each 5 byte chunk of input is represented by an - // 8 byte chunk of output. - size_t input_chunks = (input_size + 4) / 5; - size_t output_size = input_chunks * 8; - - output.resize(output_size); - - size_t encoded = base32_encode(&output[0], output_size, input, input_size); - - LL_INFOS() << "encoded " << encoded << " into buffer of size " - << output_size << LL_ENDL; - } - return output; -} diff --git a/indra/llcommon/llbase32.h b/indra/llcommon/llbase32.h deleted file mode 100644 index 8cfbd7ef53..0000000000 --- a/indra/llcommon/llbase32.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @file llbase32.h - * @brief base32 encoding that returns a std::string - * @author James Cook - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLBASE32_H -#define LLBASE32_H - -class LL_COMMON_API LLBase32 -{ -public: - static std::string encode(const U8* input, size_t input_size); -}; - -#endif diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 3d5d30bd90..8fb7823f04 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -24,6 +24,8 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "llcallbacklist.h" #include "lleventtimer.h" #include "llerrorlegacy.h" diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 7a22eaf203..bf8801c46b 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -47,30 +47,13 @@ void* ll_tracy_new(size_t size) return ptr; } -void* ll_tracy_aligned_new(size_t size, size_t alignment) -{ - void* ptr = ll_aligned_malloc_fallback(size, alignment); - if (!ptr) - { - throw std::bad_alloc(); - } - LL_PROFILE_ALLOC(ptr, size); - return ptr; -} - void ll_tracy_delete(void* ptr) { LL_PROFILE_FREE(ptr); (free)(ptr); } -void ll_tracy_aligned_delete(void* ptr) -{ - LL_PROFILE_FREE(ptr); - ll_aligned_free_fallback(ptr); -} - -void* operator new(size_t size) +void* operator new(std::size_t size) { return ll_tracy_new(size); } @@ -80,24 +63,41 @@ void* operator new[](std::size_t count) return ll_tracy_new(count); } -void* operator new(size_t size, std::align_val_t align) +void operator delete(void *ptr) noexcept { - return ll_tracy_aligned_new(size, (size_t)align); + ll_tracy_delete(ptr); } -void* operator new[](std::size_t count, std::align_val_t align) +void operator delete[](void* ptr) noexcept { - return ll_tracy_aligned_new(count, (size_t)align); + ll_tracy_delete(ptr); } -void operator delete(void *ptr) noexcept +#if !defined(LL_LINUX) + +void* ll_tracy_aligned_new(size_t size, size_t alignment) { - ll_tracy_delete(ptr); + void* ptr = ll_aligned_malloc_fallback(size, alignment); + if (!ptr) + { + throw std::bad_alloc(); + } + return ptr; } -void operator delete[](void* ptr) noexcept +void ll_tracy_aligned_delete(void* ptr) { - ll_tracy_delete(ptr); + ll_aligned_free_fallback(ptr); +} + +void* operator new(size_t size, std::align_val_t align) +{ + return ll_tracy_aligned_new(size, (size_t)align); +} + +void* operator new[](std::size_t count, std::align_val_t align) +{ + return ll_tracy_aligned_new(count, (size_t)align); } void operator delete(void *ptr, std::align_val_t align) noexcept @@ -110,6 +110,8 @@ void operator delete[](void* ptr, std::align_val_t align) noexcept ll_tracy_aligned_delete(ptr); } +#endif + #endif // TRACY_ENABLE && !LL_PROFILER_ENABLE_TRACY_OPENGL //static diff --git a/indra/llcommon/llcond.h b/indra/llcommon/llcond.h index 2df1719941..b72ea33dea 100644 --- a/indra/llcommon/llcond.h +++ b/indra/llcommon/llcond.h @@ -17,7 +17,7 @@ #include "llunits.h" #include "llcoros.h" #include LLCOROS_MUTEX_HEADER -#include "mutex.h" +#include <mutex> #include <chrono> /** diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 9df52b6ed5..602c65b9ba 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -34,10 +34,10 @@ #include <boost/fiber/future/future.hpp> #include <boost/fiber/future/promise.hpp> #include <boost/fiber/recursive_mutex.hpp> -#include "mutex.h" #include "llsingleton.h" #include "llinstancetracker.h" #include <functional> +#include <mutex> #include <string> #include <exception> #include <queue> diff --git a/indra/llcommon/llcrc.cpp b/indra/llcommon/llcrc.cpp index d79d06e2a2..33bf01a29b 100644 --- a/indra/llcommon/llcrc.cpp +++ b/indra/llcommon/llcrc.cpp @@ -166,7 +166,7 @@ void LLCRC::update(const std::string& filename) return; } - FILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ + FILE* fp = LLFile::fopen(filename, LLFILE_MODE("rb")); /* Flawfinder: ignore */ if (fp) { diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index a3bde47780..2ea5fbc3b0 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -36,7 +36,7 @@ // example (don't try this at work kids): // // LLCRC crc; -// FILE* fp = LLFile::fopen(filename,"rb"); +// FILE* fp = LLFile::fopen(filename,LLFILE_MODE("rb")); // while(!feof(fp)) { // crc.update(fgetc(fp)); // } diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp index 5545bb71af..7f65c0d715 100644 --- a/indra/llcommon/lldate.cpp +++ b/indra/llcommon/lldate.cpp @@ -134,11 +134,7 @@ void LLDate::toStream(std::ostream& s) const } s << std::dec << std::setfill('0'); -#if( LL_WINDOWS || __GNUC__ > 2) s << std::right; -#else - s.setf(ios::right); -#endif s << std::setw(4) << (exp_time.tm_year + 1900) << '-' << std::setw(2) << (exp_time.tm_mon + 1) << '-' << std::setw(2) << (exp_time.tm_mday) diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp index f9c14d7c24..45c01eb84a 100644 --- a/indra/llcommon/lldeadmantimer.cpp +++ b/indra/llcommon/lldeadmantimer.cpp @@ -25,6 +25,7 @@ * $/LicenseInfo$ */ +#include "linden_common.h" #include "lldeadmantimer.h" diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index 232987da14..0481e45351 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -28,6 +28,7 @@ #define LL_LLDEFS_H #include "stdtypes.h" +#include <cassert> #include <type_traits> // Often used array indices @@ -169,6 +170,38 @@ constexpr U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 + // llclampb(a) // clamps a to [0 .. 255] // +// llless(d0, d1) safely compares d0 < d1 even if one is signed and the other +// is unsigned. A simple (d0 < d1) expression converts the signed operand to +// unsigned before comparing. If the signed operand is negative, that flips +// the negative value to a huge positive value, producing the wrong answer! +// llless() specifically addresses that case. +template <typename T0, typename T1> +constexpr bool llless(T0 d0, T1 d1) +{ + if constexpr (std::is_signed_v<T0> && ! std::is_signed_v<T1>) + { + // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 + if (d0 < 0) + return true; + // both are non-negative: explicitly cast to avoid C4018 + return std::make_unsigned_t<T0>(d0) < d1; + } + else if constexpr (! std::is_signed_v<T0> && std::is_signed_v<T1>) + { + // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 + if (d1 < 0) + return false; + // both are non-negative: explicitly cast to avoid C4018 + return d0 < std::make_unsigned_t<T1>(d1); + } + else + { + // both T0 and T1 are signed, or both are unsigned: + // straightforward comparison works + return d0 < d1; + } +} + // recursion tail template <typename T> constexpr auto llmax(T data) @@ -180,7 +213,7 @@ template <typename T0, typename T1, typename... Ts> constexpr auto llmax(T0 d0, T1 d1, Ts... rest) { auto maxrest = llmax(d1, rest...); - return (d0 > maxrest)? d0 : maxrest; + return llless(maxrest, d0)? d0 : maxrest; } // recursion tail @@ -194,12 +227,28 @@ template <typename T0, typename T1, typename... Ts> constexpr auto llmin(T0 d0, T1 d1, Ts... rest) { auto minrest = llmin(d1, rest...); - return (d0 < minrest) ? d0 : minrest; + return llless(d0, minrest) ? d0 : minrest; } template <typename A, typename MIN, typename MAX> constexpr A llclamp(A a, MIN minval, MAX maxval) { + // The only troublesome case is if A is unsigned and either minval or + // maxval is both signed and negative. Casting a negative number to + // unsigned flips it to a huge positive number, making this llclamp() call + // ineffective. + if constexpr (! std::is_signed_v<A>) + { + if constexpr (std::is_signed_v<MIN>) + { + assert(minval >= 0); + } + if constexpr (std::is_signed_v<MAX>) + { + assert(maxval >= 0); + } + } + A aminval{ static_cast<A>(minval) }, amaxval{ static_cast<A>(maxval) }; if ( a < aminval ) { @@ -225,7 +274,7 @@ constexpr LLDATATYPE llclampb(LLDATATYPE a) } template <class LLDATATYPE> -inline void llswap(LLDATATYPE& lhs, LLDATATYPE& rhs) +constexpr void llswap(LLDATATYPE& lhs, LLDATATYPE& rhs) { std::swap(lhs, rhs); } diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 6f5e57c3de..78101e21af 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -133,6 +133,7 @@ namespace { RecordToFile(const std::string& filename): mName(filename) { + showMultiline(true); mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app); if (!mFile) { @@ -344,6 +345,51 @@ namespace { } }; #endif + +#if LL_PROFILER_CONFIGURATION >= LL_PROFILER_CONFIG_TRACY + class RecordToTracy : public LLError::Recorder + { + public: + RecordToTracy() + { + this->showMultiline(true); + this->showTags(false); + this->showLocation(false); + } + + virtual bool enabled() override { return LLError::getEnabledLogTypesMask() & 0x12; } + + virtual void recordMessage(LLError::ELevel level, const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; + switch (level) + { + case LLError::LEVEL_DEBUG: + { + TracyMessageC(message.c_str(), message.size(), tracy::Color::Turquoise); + break; + } + default: + case LLError::LEVEL_NONE: + case LLError::LEVEL_INFO: + { + TracyMessageC(message.c_str(), message.size(), tracy::Color::White); + break; + } + case LLError::LEVEL_WARN: + { + TracyMessageC(message.c_str(), message.size(), tracy::Color::Yellow); + break; + } + case LLError::LEVEL_ERROR: + { + TracyMessageC(message.c_str(), message.size(), tracy::Color::Red); + break; + } + } + } + }; +#endif } @@ -435,13 +481,9 @@ namespace std::string file = user_dir + "/logcontrol-dev.xml"; - llstat stat_info; - if (LLFile::stat(file, &stat_info)) { - // NB: stat returns non-zero if it can't read the file, for example - // if it doesn't exist. LLFile has no better abstraction for - // testing for file existence. - - file = app_dir + "/logcontrol.xml"; + if (!LLFile::isfile(file)) + { + file = app_dir + "/logcontrol.xml"; } return * new LogControlFile(file); // NB: This instance is never freed @@ -759,6 +801,11 @@ namespace LLError::addRecorder(recordToWinDebug); #endif +#if LL_PROFILER_CONFIGURATION >= LL_PROFILER_CONFIG_TRACY + LLError::RecorderPtr recordToTracy(new RecordToTracy()); + LLError::addRecorder(recordToTracy); +#endif + LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir); // NOTE: We want to explicitly load the file before we add it to the event timer diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 0083865cf7..a06c299c8d 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -91,13 +91,8 @@ const int LL_ERR_NOERR = 0; #define llverify(func) do {if (func) {}} while(0) #endif -#ifdef LL_WINDOWS #define LL_STATIC_ASSERT(func, msg) static_assert(func, msg) -#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) static_assert(false, msg) -#else -#define LL_STATIC_ASSERT(func, msg) BOOST_STATIC_ASSERT(func) -#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) BOOST_STATIC_ASSERT(sizeof(type) != 0 && false); -#endif +#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) static_assert(sizeof(type) != 0 && false, msg) /** Error Logging Facility diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp index 1c5fe9d2f5..c5a7a0e56b 100644 --- a/indra/llcommon/llfasttimer.cpp +++ b/indra/llcommon/llfasttimer.cpp @@ -387,6 +387,11 @@ void BlockTimer::logStats() } +#if defined(LL_GNUC) && GCC_VERSION >= 130000 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnonnull" +#endif + //static void BlockTimer::dumpCurTimes() { @@ -418,8 +423,12 @@ void BlockTimer::dumpCurTimes() << num_calls << " calls"; LL_INFOS() << out_str.str() << LL_ENDL; + } } -} + +#if defined(LL_GNUC) && GCC_VERSION >= 130000 +# pragma GCC diagnostic push +#endif //static void BlockTimer::writeLog(std::ostream& os) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index f752e31563..114df29f6e 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -29,22 +29,17 @@ #include "linden_common.h" #include "llfile.h" -#include "llstring.h" #include "llerror.h" #include "stringize.h" #if LL_WINDOWS -#include "llwin32headers.h" -#include <vector> +#include <fcntl.h> #else #include <errno.h> +#include <sys/file.h> #endif -using namespace std; - -static std::string empty; - -// Many of the methods below use OS-level functions that mess with errno. Wrap +// Some of the methods below use OS-level functions that mess with errno. Wrap // variants of strerror() to report errors. #if LL_WINDOWS @@ -79,6 +74,7 @@ static errentry const errtable[] { ERROR_CURRENT_DIRECTORY, EACCES }, // 16 { ERROR_NOT_SAME_DEVICE, EXDEV }, // 17 { ERROR_NO_MORE_FILES, ENOENT }, // 18 + { ERROR_SHARING_VIOLATION, EACCES }, // 32 { ERROR_LOCK_VIOLATION, EACCES }, // 33 { ERROR_BAD_NETPATH, ENOENT }, // 53 { ERROR_NETWORK_ACCESS_DENIED, EACCES }, // 65 @@ -109,22 +105,25 @@ static errentry const errtable[] { ERROR_NOT_ENOUGH_QUOTA, ENOMEM } // 1816 }; -static int set_errno_from_oserror(unsigned long oserr) +static int get_errno_from_oserror(int oserr) { if (!oserr) return 0; // Check the table for the Windows OS error code - for (const struct errentry &entry : errtable) + for (const struct errentry& entry : errtable) { if (oserr == entry.oserr) { - _set_errno(entry.errcode); - return -1; + return entry.errcode; } } + return EINVAL; +} - _set_errno(EINVAL); +static int set_errno_from_oserror(unsigned long oserr) +{ + _set_errno(get_errno_from_oserror(oserr)); return -1; } @@ -136,90 +135,8 @@ std::string strerr(int errn) return buffer; } -inline bool is_slash(wchar_t const c) -{ - return c == L'\\' || c == L'/'; -} - -static std::wstring utf8path_to_wstring(const std::string& utf8path) -{ - if (utf8path.size() >= MAX_PATH) - { - // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names - std::wstring utf16path = L"\\\\?\\" + ll_convert<std::wstring>(utf8path); - // We need to make sure that the path does not contain forward slashes as above - // prefix does bypass the path normalization that replaces slashes with backslashes - // before passing the path to kernel mode APIs - std::replace(utf16path.begin(), utf16path.end(), L'/', L'\\'); - return utf16path; - } - return ll_convert<std::wstring>(utf8path); -} - -static unsigned short get_fileattr(const std::wstring& utf16path, bool dontFollowSymLink = false) -{ - unsigned long flags = FILE_FLAG_BACKUP_SEMANTICS; - if (dontFollowSymLink) - { - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - HANDLE file_handle = CreateFileW(utf16path.c_str(), FILE_READ_ATTRIBUTES, - FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_EXISTING, flags, nullptr); - if (file_handle != INVALID_HANDLE_VALUE) - { - FILE_ATTRIBUTE_TAG_INFO attribute_info; - if (GetFileInformationByHandleEx(file_handle, FileAttributeTagInfo, &attribute_info, sizeof(attribute_info))) - { - // A volume path alone (only drive letter) is not recognized as directory while it technically is - bool is_directory = (attribute_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) || - (iswalpha(utf16path[0]) && utf16path[1] == ':' && - (!utf16path[2] || (is_slash(utf16path[2]) && !utf16path[3]))); - unsigned short st_mode = is_directory ? S_IFDIR : - (attribute_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? S_IFLNK : S_IFREG); - st_mode |= (attribute_info.FileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE; - // we do not try to guess executable flag - - // propagate user bits to group/other fields: - st_mode |= (st_mode & 0700) >> 3; - st_mode |= (st_mode & 0700) >> 6; - - CloseHandle(file_handle); - return st_mode; - } - // Retrieve last error before calling CloseHandle() - DWORD last_error = GetLastError(); - CloseHandle(file_handle); - set_errno_from_oserror(last_error); - } - else - { - set_errno_from_oserror(GetLastError()); - } - - // If CreateFileW approach failed (e.g., exFAT), try the simpler GetFileAttributesW() - // GetFileAttributesW() always follows symlinks, so we skip this fallback when dontFollowSymLink is true. - if (!dontFollowSymLink) - { - DWORD attributes = GetFileAttributesW(utf16path.c_str()); - if (attributes != INVALID_FILE_ATTRIBUTES) - { - bool is_directory = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - unsigned short st_mode = is_directory ? S_IFDIR : S_IFREG; - st_mode |= (attributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE; - - // propagate user bits to group/other fields: - st_mode |= (st_mode & 0700) >> 3; - st_mode |= (st_mode & 0700) >> 6; - - return st_mode; - } - set_errno_from_oserror(GetLastError()); - } - return 0; -} - #else + // On Posix we want to call strerror_r(), but alarmingly, there are two // different variants. The one that returns int always populates the passed // buffer (except in case of error), whereas the other one always returns a @@ -266,9 +183,50 @@ std::string strerr(int errn) return message_from(errn, buffer, sizeof(buffer), strerror_r(errn, buffer, sizeof(buffer))); } + #endif // ! LL_WINDOWS -static int warnif(const std::string& desc, const std::string& filename, int rc, int accept = 0) +#if LL_WINDOWS && 0 // turn on to debug file-locking problems +#define PROCESS_LOCKING_CHECK 1 +static void find_locking_process(const std::string& filename) +{ + // Only do any of this stuff (before LL_ENDL) if it will be logged. + LL_DEBUGS("LLFile") << ""; + // wrong way + std::string TEMP = LLFile::tmpdir(); + if (TEMP.empty()) + { + LL_CONT << "No $TEMP, not running 'handle'"; + } + else + { + std::string tf(TEMP); + tf += "handle.tmp"; + // http://technet.microsoft.com/en-us/sysinternals/bb896655 + std::string cmd(STRINGIZE("handle \"" << filename + // "openfiles /query /v | fgrep -i \"" << filename + << "\" > \"" << tf << '"')); + LL_CONT << cmd; + if (system(cmd.c_str()) != 0) + { + LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655"; + } + else + { + std::ifstream inf(tf); + std::string line; + while (std::getline(inf, line)) + { + LL_CONT << '\n' << line; + } + } + LLFile::remove(tf); + } + LL_CONT << LL_ENDL; +} +#endif // LL_WINDOWS hack to identify processes holding file open + +static int warnif(std::string_view desc, const std::filesystem::path& filename, int rc, int suppress_warning = 0) { if (rc < 0) { @@ -277,322 +235,840 @@ static int warnif(const std::string& desc, const std::string& filename, int rc, // For certain operations, a particular errno value might be // acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit - // EEXIST. Don't warn if caller explicitly says this errno is okay. - if (errn != accept) + // EEXIST. Don't log a warning if caller explicitly says this errno is okay. + if (errn != suppress_warning) { - LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename - << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; } -#if 0 && LL_WINDOWS // turn on to debug file-locking problems +#if PROCESS_LOCKING_CHECK // If the problem is "Permission denied," maybe it's because another // process has the file open. Try to find out. - if (errn == EACCES) // *not* EPERM + if (errn == EACCES) // *not* EPERM { - // Only do any of this stuff (before LL_ENDL) if it will be logged. - LL_DEBUGS("LLFile") << empty; - // would be nice to use LLDir for this, but dependency goes the - // wrong way - const char* TEMP = LLFile::tmpdir(); - if (! (TEMP && *TEMP)) - { - LL_CONT << "No $TEMP, not running 'handle'"; - } - else - { - std::string tf(TEMP); - tf += "\\handle.tmp"; - // http://technet.microsoft.com/en-us/sysinternals/bb896655 - std::string cmd(STRINGIZE("handle \"" << filename - // "openfiles /query /v | fgrep -i \"" << filename - << "\" > \"" << tf << '"')); - LL_CONT << cmd; - if (system(cmd.c_str()) != 0) - { - LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655"; - } - else - { - std::ifstream inf(tf); - std::string line; - while (std::getline(inf, line)) - { - LL_CONT << '\n' << line; - } - } - LLFile::remove(tf); - } - LL_CONT << LL_ENDL; + find_locking_process(filename); } -#endif // LL_WINDOWS hack to identify processes holding file open +#endif } return rc; } -// static -int LLFile::mkdir(const std::string& dirname, int perms) +static int warnif(std::string_view desc, const std::filesystem::path& filename, const std::error_code& ec, int suppress_warning = 0) { - // We often use mkdir() to ensure the existence of a directory that might - // already exist. There is no known case in which we want to call out as - // an error the requested directory already existing. + if (ec) + { + // get Posix errno from the std::error_code so we can compare it to the suppress_warning parameter + // to see when a caller wants us to not generate a warning for a particular error code +#if LL_WINDOWS + int errn = get_errno_from_oserror(ec.value()); +#else + int errn = ec.value(); +#endif + // For certain operations, a particular errno value might be acceptable + // Don't warn if caller explicitly says this errno is okay. + if (errn != suppress_warning) + { + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << ec.message() << LL_ENDL; + } +#if PROCESS_LOCKING_CHECK + // Try to detect locked files by other processes + if (ec.value() == ERROR_SHARING_VIOLATION || ec.value() == ERROR_LOCK_VIOLATION) + { + find_locking_process(filename); + } +#endif + return -1; + } + return 0; +} + #if LL_WINDOWS - // permissions are ignored on Windows - int rc = 0; - std::wstring utf16dirname = utf8path_to_wstring(dirname); - if (!CreateDirectoryW(utf16dirname.c_str(), nullptr)) + +inline int set_ec_from_system_error(std::error_code& ec, DWORD error) +{ + ec.assign(error, std::system_category()); + return -1; +} + +static int set_ec_from_system_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, GetLastError()); +} + +inline int set_ec_to_parameter_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ERROR_INVALID_PARAMETER); +} + +inline int set_ec_to_outofmemory_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ERROR_NOT_ENOUGH_MEMORY); +} + +inline DWORD decode_access_mode(std::ios_base::openmode omode) +{ + switch (omode & (LLFile::in | LLFile::out)) + { + case LLFile::in: + return GENERIC_READ; + case LLFile::out: + return GENERIC_WRITE; + case static_cast<std::ios_base::openmode>(LLFile::in | LLFile::out): + return GENERIC_READ | GENERIC_WRITE; + } + if (omode & LLFile::app) + { + return GENERIC_WRITE; + } + return 0; +} + +inline DWORD decode_open_create_flags(std::ios_base::openmode omode) +{ + if (omode & LLFile::noreplace) + { + return CREATE_NEW; // create if it does not exist, otherwise fail + } + if (omode & LLFile::trunc) { - // Only treat other errors than an already existing file as a real error - unsigned long oserr = GetLastError(); - if (oserr != ERROR_ALREADY_EXISTS) + if (!(omode & LLFile::out)) { - rc = set_errno_from_oserror(oserr); + return TRUNCATE_EXISTING; // open and truncate if it exists, otherwise fail } + return CREATE_ALWAYS; // open and truncate if it exists, otherwise create it + } + if (!(omode & LLFile::out)) + { + return OPEN_EXISTING; // open if it exists, otherwise fail + } + // LLFile::app or (LLFile::out and (!LLFile::trunc or !LLFile::noreplace)) + return OPEN_ALWAYS; // open if it exists, otherwise create it +} + +inline DWORD decode_share_mode(int omode) +{ + if (omode & LLFile::exclusive) + { + return 0; // allow no other access + } + if (omode & LLFile::shared) + { + return FILE_SHARE_READ; // allow read access + } + return FILE_SHARE_READ | FILE_SHARE_WRITE; // allow read and write access to others +} + +inline DWORD decode_attributes(std::ios_base::openmode omode, int perm) +{ + return (perm & S_IWRITE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY; +} + +// Under Windows the values for the std::ios_base::seekdir constants match the according FILE_BEGIN +// and other constants but we do a programmatic translation for now to be sure +static DWORD seek_mode_from_dir(std::ios_base::seekdir seekdir) +{ + switch (seekdir) + { + case LLFile::beg: + return FILE_BEGIN; + case LLFile::cur: + return FILE_CURRENT; + case LLFile::end: + return FILE_END; } + return FILE_BEGIN; +} + #else - int rc = ::mkdir(dirname.c_str(), (mode_t)perms); - if (rc < 0 && errno == EEXIST) + +inline int set_ec_from_system_error(std::error_code& ec, int error) +{ + ec.assign(error, std::system_category()); + return -1; +} + +static int set_ec_from_system_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, errno); +} + +inline int set_ec_to_parameter_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, EINVAL); +} + +inline int set_ec_to_outofmemory_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ENOMEM); +} + +inline int decode_access_mode(std::ios_base::openmode omode) +{ + if (omode & LLFile::out) { - // this is not the error you want, move along - return 0; + if (omode & LLFile::in) + { + return O_RDWR; + } + return O_WRONLY; + } + return O_RDONLY; + + /*switch (omode & (LLFile::in | LLFile::out)) + { + case LLFile::out: + return O_WRONLY; + case static_cast<std::ios_base::openmode>(LLFile::in | LLFile::out): + return O_RDWR; + } + return O_RDONLY;*/ +} + +inline int decode_open_mode(std::ios_base::openmode omode) +{ + int flags = O_CREAT | decode_access_mode(omode); + if (omode & LLFile::app) + { + flags |= O_APPEND; } + if (omode & LLFile::trunc) + { + flags |= O_TRUNC; + } + if (omode & LLFile::binary) + { + // Not a thing under *nix + } + if (omode & LLFile::noreplace) + { + flags |= O_EXCL; + } + return flags; +} + +inline int decode_lock_mode(std::ios_base::openmode omode) +{ + int lmode = omode & LLFile::noblock ? LOCK_NB : 0; + if (omode & LLFile::lock_mask) + { + if (omode & LLFile::exclusive) + { + return lmode | LOCK_EX; + } + return lmode | LOCK_SH; + } + return lmode | LOCK_UN; +} + +// Under Linux and Mac the values for the std::ios_base::seekdir constants match the according SEEK_SET +// and other constants but we do a programmatic translation for now to be sure +inline int seek_mode_from_dir(std::ios_base::seekdir seekdir) +{ + switch (seekdir) + { + case LLFile::beg: + return SEEK_SET; + case LLFile::cur: + return SEEK_CUR; + case LLFile::end: + return SEEK_END; + } + return SEEK_SET; +} + #endif - // anything else might be a problem - return warnif("mkdir", dirname, rc); + +inline int clear_error(std::error_code& ec) +{ + ec.clear(); + return 0; } -// static -int LLFile::rmdir(const std::string& dirname, int suppress_error) +inline bool are_open_mode_flags_invalid(std::ios_base::openmode omode) +{ + // at least one of input or output needs to be specified + if (!(omode & (LLFile::in | LLFile::out))) + { + return true; + } + // output must be possible for any of the extra options + if (!(omode & LLFile::out) && (omode & (LLFile::trunc | LLFile::app | LLFile::noreplace))) + { + return true; + } + // invalid combination, mutually exclusive + if ((omode & LLFile::app) && (omode & (LLFile::trunc | LLFile::noreplace))) + { + return true; + } + return false; +} + +//---------------------------------------------------------------------------------------- +// class member functions +//---------------------------------------------------------------------------------------- +int LLFile::open(const std::filesystem::path& file_path, std::ios_base::openmode omode, std::error_code& ec, int perm) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + close(ec); + if (are_open_mode_flags_invalid(omode)) + { + return set_ec_to_parameter_error(ec); + } #if LL_WINDOWS - std::wstring utf16dirname = utf8path_to_wstring(dirname); - int rc = _wrmdir(utf16dirname.c_str()); + DWORD access = decode_access_mode(omode), + share = decode_share_mode(omode), + create = decode_open_create_flags(omode), + attributes = decode_attributes(omode, perm); + + mHandle = CreateFileW(file_path.native().c_str(), access, share, nullptr, create, attributes, nullptr); + // The dwShareMode = share parameter takes care of locking the file for other processes if indicated, + // no need to do anything else for file locking here #else - int rc = ::rmdir(dirname.c_str()); + int oflags = decode_open_mode(omode); + int lmode = omode & LLFile::lock_mask; + mHandle = ::open(file_path.native().c_str(), oflags, perm); + if (mHandle != InvalidHandle && lmode && lock(lmode | LLFile::noblock, ec) != 0) + { + close(); + return -1; + } #endif - return warnif("rmdir", dirname, rc, suppress_error); + if (mHandle == InvalidHandle) + { + return set_ec_from_system_error(ec); + } + + if (omode & LLFile::ate && seek(0, LLFile::end, ec) != 0) + { + close(); + return -1; + } + mOpen = omode; + return clear_error(ec); } -// static -LLFILE* LLFile::fopen(const std::string& filename, const char* mode) +S64 LLFile::size(std::error_code& ec) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; #if LL_WINDOWS - std::wstring utf16filename = utf8path_to_wstring(filename); - std::wstring utf16mode = ll_convert<std::wstring>(std::string(mode)); - return _wfopen(utf16filename.c_str(), utf16mode.c_str()); + LARGE_INTEGER value = { 0 }; + if (GetFileSizeEx(mHandle, &value)) + { + clear_error(ec); + return value.QuadPart; + } #else - return ::fopen(filename.c_str(),mode); + struct stat statval; + if (fstat(mHandle, &statval) == 0) + { + clear_error(ec); + return statval.st_size; + } #endif + set_ec_from_system_error(ec); + return 0; } -// static -int LLFile::close(LLFILE * file) +S64 LLFile::tell(std::error_code& ec) { - int ret_value = 0; - if (file) + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; +#if LL_WINDOWS + LARGE_INTEGER value = { 0 }; + if (SetFilePointerEx(mHandle, value, &value, FILE_CURRENT)) { - ret_value = fclose(file); + clear_error(ec); + return value.QuadPart; } - return ret_value; +#else + off_t offset = lseek(mHandle, 0, SEEK_CUR); + if (offset != -1) + { + clear_error(ec); + return offset; + } +#endif + return set_ec_from_system_error(ec); } -// static -std::string LLFile::getContents(const std::string& filename) +int LLFile::seek(S64 pos, std::error_code& ec) { - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (fp) + return seek(pos, LLFile::beg, ec); +} + +int LLFile::seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + S64 newOffset = 0; +#if LL_WINDOWS + DWORD seekdir = seek_mode_from_dir(dir); + LARGE_INTEGER value; + value.QuadPart = offset; + if (SetFilePointerEx(mHandle, value, (PLARGE_INTEGER)&newOffset, seekdir)) +#else + newOffset = lseek(mHandle, offset, seek_mode_from_dir(dir)); + if (newOffset != -1) +#endif { - fseek(fp, 0, SEEK_END); - U32 length = ftell(fp); - fseek(fp, 0, SEEK_SET); + return clear_error(ec); + } + return set_ec_from_system_error(ec); +} - std::vector<char> buffer(length); - size_t nread = fread(buffer.data(), 1, length, fp); - fclose(fp); +#if LL_WINDOWS +inline DWORD next_buffer_size(S64 nbytes) +{ + return nbytes > 0x80000000 ? 0x80000000 : (DWORD)nbytes; +} +#endif - return std::string(buffer.data(), nread); +S64 LLFile::read(void* buffer, S64 nbytes, std::error_code& ec) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + if (nbytes == 0) + { + // Nothing to do + return clear_error(ec); } + else if (!buffer || nbytes < 0) + { + return set_ec_to_parameter_error(ec); + } +#if LL_WINDOWS + S64 totalBytes = 0; + char *ptr = (char*)buffer; + DWORD bytesRead, bytesToRead = next_buffer_size(nbytes); - return LLStringUtil::null; + // Read in chunks to support >4GB which the S64 nbytes value makes possible + while (ReadFile(mHandle, ptr, bytesToRead, &bytesRead, nullptr)) + { + totalBytes += bytesRead; + if (nbytes <= totalBytes || // requested amount read + bytesRead < bytesToRead) // ReadFile encountered eof + { + clear_error(ec); + return totalBytes; + } + ptr += bytesRead; + bytesToRead = next_buffer_size(nbytes - totalBytes); + } +#else + ssize_t bytesRead = ::read(mHandle, buffer, nbytes); + if (bytesRead != -1) + { + clear_error(ec); + return bytesRead; + } +#endif + return set_ec_from_system_error(ec); } -// static -int LLFile::remove(const std::string& filename, int suppress_error) +S64 LLFile::write(const void* buffer, S64 nbytes, std::error_code& ec) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + if (nbytes == 0) + { + // Nothing to do here + return clear_error(ec); + } + else if (!buffer || nbytes < 0) + { + return set_ec_to_parameter_error(ec); + } #if LL_WINDOWS - // Posix remove() works on both files and directories although on Windows - // remove() and its wide char variant _wremove() only removes files just - // as its siblings unlink() and _wunlink(). - // If we really only want to support files we should instead use - // unlink() in the non-Windows part below too - int rc = -1; - std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode = get_fileattr(utf16filename); - if (S_ISDIR(st_mode)) + // If this was opened in append mode, we emulate it on Windows + if (mOpen & LLFile::app && seek(0, LLFile::end, ec) != 0) { - rc = _wrmdir(utf16filename.c_str()); + return -1; } - else if (S_ISREG(st_mode)) + + S64 totalBytes = 0; + char* ptr = (char*)buffer; + DWORD bytesWritten, bytesToWrite = next_buffer_size(nbytes); + + // Write in chunks to support >4GB which the S64 nbytes value makes possible + while (WriteFile(mHandle, ptr, bytesToWrite, &bytesWritten, nullptr)) { - rc = _wunlink(utf16filename.c_str()); + totalBytes += bytesWritten; + if (nbytes <= totalBytes) + { + clear_error(ec); + return totalBytes; + } + ptr += bytesWritten; + bytesToWrite = next_buffer_size(nbytes - totalBytes); + } +#else + ssize_t bytesWritten = ::write(mHandle, buffer, nbytes); + if (bytesWritten != -1) + { + clear_error(ec); + return bytesWritten; + } +#endif + return set_ec_from_system_error(ec); +} + +S64 LLFile::printf(const char* fmt, ...) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + va_list args1; + va_start(args1, fmt); + va_list args2; + va_copy(args2, args1); + int length = vsnprintf(nullptr, 0, fmt, args1); + va_end(args1); + if (length < 0) + { + va_end(args2); + return -1; } - else if (st_mode) + void* buffer = malloc(length + 1); + if (!buffer) { - // it is something else than a file or directory - // this should not really happen as long as we do not allow for symlink - // detection in the optional parameter to get_fileattr() - rc = set_errno_from_oserror(ERROR_INVALID_PARAMETER); + va_end(args2); + return -1; + } + length = vsnprintf((char*)buffer, length + 1, fmt, args2); + va_end(args2); + std::error_code ec; + S64 written = write(buffer, length, ec); + free(buffer); + return written; +} + +int LLFile::lock(int mode, std::error_code& ec) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; +#if LL_WINDOWS + if (!(mode & LLFile::lock_mask)) + { + if (UnlockFile(mHandle, 0, 0, MAXDWORD, MAXDWORD)) + { + return clear_error(ec); + } } else { - // get_fileattr() failed and already set errno, preserve it for correct error reporting + OVERLAPPED overlapped = { 0 }; + DWORD flags = (mode & LLFile::noblock) ? LOCKFILE_FAIL_IMMEDIATELY : 0; + if (mode & LLFile::exclusive) + { + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + // We lock the maximum range, since flock only supports locking the entire file too + if (LockFileEx(mHandle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) + { + return clear_error(ec); + } } #else - int rc = ::remove(filename.c_str()); + if (flock(mHandle, decode_lock_mode(static_cast<std::ios_base::openmode>(mode))) == 0) + { + return clear_error(ec); + } #endif - return warnif("remove", filename, rc, suppress_error); + return set_ec_from_system_error(ec); +} + +int LLFile::close(std::error_code& ec) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + if (mHandle != InvalidHandle) + { + llfile_handle_t handle = InvalidHandle; + std::swap(handle, mHandle); +#if LL_WINDOWS + if (!CloseHandle(handle)) +#else + if (::close(handle)) +#endif + { + return set_ec_from_system_error(ec); + } + } + return clear_error(ec); +} + +int LLFile::close() +{ + std::error_code ec; + return close(ec); } +//---------------------------------------------------------------------------------------- +// static member functions +//---------------------------------------------------------------------------------------- + // static -int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_error) +LLFILE* LLFile::fopen(const std::filesystem::path& file_path, const fopen_flags_t* mode, int lmode) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + LLFILE* file; #if LL_WINDOWS - // Posix rename() will gladly overwrite a file at newname if it exists, the Windows - // rename(), respectively _wrename(), will bark on that. Instead call directly the Windows - // API MoveFileEx() and use its flags to specify that overwrite is allowed. - std::wstring utf16filename = utf8path_to_wstring(filename); - std::wstring utf16newname = utf8path_to_wstring(newname); - int rc = 0; - if (!MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + int shflag = _SH_DENYNO; + switch (lmode) { - rc = set_errno_from_oserror(GetLastError()); + case LLFile::exclusive: + shflag = _SH_DENYRW; + break; + case LLFile::shared: + shflag = _SH_DENYWR; + break; } + file = _wfsopen(file_path.native().c_str(), mode, shflag); #else - int rc = ::rename(filename.c_str(),newname.c_str()); + file = ::fopen(file_path.native().c_str(), mode); + if (file && (lmode & (LLFile::lock_mask))) + { + // Rather fail on a sharing conflict than block + if (flock(fileno(file), decode_lock_mode(static_cast<std::ios_base::openmode>(lmode | LLFile::noblock)))) + { + ::fclose(file); + file = nullptr; + } + } #endif - return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, suppress_error); + return file; } -// Make this a define rather than using magic numbers multiple times in the code -#define LLFILE_COPY_BUFFER_SIZE 16384 +// static +int LLFile::close(LLFILE* file) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + int ret_value = 0; + if (file) + { + ret_value = ::fclose(file); + } + return ret_value; +} // static -bool LLFile::copy(const std::string& from, const std::string& to) +std::string LLFile::getContents(const std::filesystem::path& file_path, std::error_code& ec) { - bool copied = false; - LLFILE* in = LLFile::fopen(from, "rb"); - if (in) + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::string buffer; + LLFile file(file_path, LLFile::in | LLFile::binary, ec); + if (file) { - LLFILE* out = LLFile::fopen(to, "wb"); - if (out) + S64 length = file.size(ec); + if (!ec && length > 0) { - char buf[LLFILE_COPY_BUFFER_SIZE]; - size_t readbytes; - bool write_ok = true; - while (write_ok && (readbytes = fread(buf, 1, LLFILE_COPY_BUFFER_SIZE, in))) - { - if (fwrite(buf, 1, readbytes, out) != readbytes) - { - LL_WARNS("LLFile") << "Short write" << LL_ENDL; - write_ok = false; - } - } - if ( write_ok ) + buffer = std::string(length, 0); + file.read(&buffer[0], length, ec); + if (ec) { - copied = true; + buffer.clear(); } - fclose(out); } - fclose(in); } - return copied; + return buffer; } // static -int LLFile::stat(const std::string& filename, llstat* filestatus, int suppress_error) +int LLFile::mkdir(const std::filesystem::path& file_path) { -#if LL_WINDOWS - std::wstring utf16filename = utf8path_to_wstring(filename); - int rc = _wstat64(utf16filename.c_str(), filestatus); -#else - int rc = ::stat(filename.c_str(), filestatus); -#endif - return warnif("stat", filename, rc, suppress_error); + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::error_code ec; + // We often use mkdir() to ensure the existence of a directory that might + // already exist. There is no known case in which we want to call out as + // an error the requested directory already existing. + std::filesystem::create_directory(file_path, ec); + // The return value is only true if the directory was actually created. + // But if it already existed, ec still indicates success. + return warnif("mkdir", file_path, ec); } // static -unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLink, int suppress_error) +int LLFile::remove(const std::filesystem::path& file_path, int suppress_warning) { -#if LL_WINDOWS - // _wstat64() is a bit heavyweight on Windows, use a more lightweight API - // to just get the attributes - int rc = -1; - std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode = get_fileattr(utf16filename, dontFollowSymLink); - if (st_mode) + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::error_code ec; + std::filesystem::remove(file_path, ec); + return warnif("remove", file_path, ec, suppress_warning); +} + +// static +int LLFile::rename(const std::filesystem::path& file_path, const std::filesystem::path& new_path, int suppress_warning) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::error_code ec; + std::filesystem::rename(file_path, new_path, ec); + return warnif("rename", file_path, ec, suppress_warning); +} + +// static +S64 LLFile::read(const std::filesystem::path& file_path, void* buf, S64 offset, S64 nbytes, std::error_code& ec) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + // if number of bytes is 0 or less there is nothing to do here + if (nbytes <= 0) { - return st_mode; + clear_error(ec); + return 0; } -#else - llstat filestatus; - int rc = dontFollowSymLink ? ::lstat(filename.c_str(), &filestatus) : ::stat(filename.c_str(), &filestatus); - if (rc == 0) + + if (!buf || offset < 0) { - return filestatus.st_mode; + set_ec_to_parameter_error(ec); } -#endif - warnif("getattr", filename, rc, suppress_error); - return 0; + else + { + std::ios_base::openmode omode = LLFile::in | LLFile::binary; + + LLFile file(file_path, omode, ec); + if (!ec && (bool)file) + { + if (offset > 0) + { + file.seek(offset, ec); + } + // else (offset == 0) file was just opened and should already be at 0. + if (!ec) + { + S64 bytes_read = file.read(buf, nbytes, ec); + if (!ec) + { + return bytes_read; + } + } + } + } + return warnif("read from file failed", file_path, ec); } // static -bool LLFile::isdir(const std::string& filename) +S64 LLFile::write(const std::filesystem::path& file_path, const void* buf, S64 offset, S64 nbytes, std::error_code& ec) { - return S_ISDIR(getattr(filename)); + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + // if number of bytes is 0 or less there is nothing to do here + if (nbytes <= 0) + { + clear_error(ec); + return 0; + } + + if (!buf) + { + set_ec_to_parameter_error(ec); + } + else + { + std::ios_base::openmode omode = LLFile::out | LLFile::binary; + if (offset < 0) + { + omode |= LLFile::app; + } + + LLFile file(file_path, omode, ec); + if (!ec && (bool)file) + { + if (offset > 0) + { + file.seek(offset, ec); + } + // else (offset == 0) we are not appending, file was just opened and should already be at 0. + if (!ec) + { + S64 bytes_written = file.write(buf, nbytes, ec); + if (!ec) + { + return bytes_written; + } + } + } + } + return warnif("write to file failed", file_path, ec); } // static -bool LLFile::isfile(const std::string& filename) +bool LLFile::copy(const std::filesystem::path& source_path, const std::filesystem::path& target_path, std::filesystem::copy_options options, std::error_code& ec) { - return S_ISREG(getattr(filename)); + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + bool copied = std::filesystem::copy_file(source_path, target_path, options, ec); + if (!copied) + { + warnif(STRINGIZE("copy failed, to '" << target_path << "' from"), source_path, ec); + } + return copied; } // static -bool LLFile::islink(const std::string& filename) +int LLFile::stat(const std::filesystem::path& file_path, llstat* filestatus, const char *fname, int suppress_warning) { - return S_ISLNK(getattr(filename, true)); + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; +#if LL_WINDOWS + int rc = _wstat64(file_path.native().c_str(), filestatus); +#else + int rc = ::stat(file_path.native().c_str(), filestatus); +#endif + return warnif(fname ? fname : "stat", file_path, rc, suppress_warning); } // static -const char *LLFile::tmpdir() +S64 LLFile::size(const std::filesystem::path& file_path, int suppress_warning) { - static std::string utf8path; + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::error_code ec; + std::intmax_t size = static_cast<std::intmax_t>(std::filesystem::file_size(file_path, ec)); + if (ec) + { + warnif("size", file_path, ec, suppress_warning); + return 0; + } + return size; +} - if (utf8path.empty()) +// static +std::filesystem::file_status LLFile::getStatus(const std::filesystem::path& file_path, bool dontFollowSymLink, int suppress_warning) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + std::error_code ec; + std::filesystem::file_status status; + if (dontFollowSymLink) { - char sep; -#if LL_WINDOWS - sep = '\\'; + status = std::filesystem::symlink_status(file_path, ec); + } + else + { + status = std::filesystem::status(file_path, ec); + } + warnif("getStatus()", file_path, ec, suppress_warning); + return status; +} - std::vector<wchar_t> utf16path(MAX_PATH + 1); - GetTempPathW(static_cast<DWORD>(utf16path.size()), &utf16path[0]); - utf8path = ll_convert_wide_to_string(&utf16path[0]); +// static +const std::string& LLFile::tmpdir() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE; + static std::string temppath; + if (temppath.empty()) + { +#if LL_WINDOWS + temppath = ll_convert<std::string>(std::filesystem::temp_directory_path().native()); + char sep = '\\'; #else - sep = '/'; - - utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/"); + temppath = std::filesystem::temp_directory_path().string(); + char sep = '/'; #endif - if (utf8path[utf8path.size() - 1] != sep) + if (temppath[temppath.size() - 1] != sep) { - utf8path += sep; + temppath += sep; } } - return utf8path.c_str(); + + return temppath; } #if LL_WINDOWS /************** input file stream ********************************/ -llifstream::llifstream() {} +// explicit +llifstream::llifstream(const char* _Filename, ios_base::openmode _Mode) : + std::ifstream(ll_convert<std::wstring>(_Filename).c_str(), _Mode | ios_base::in) +{ +} // explicit llifstream::llifstream(const std::string& _Filename, ios_base::openmode _Mode): @@ -601,17 +1077,37 @@ llifstream::llifstream(const std::string& _Filename, ios_base::openmode _Mode): { } +// explicit +llifstream::llifstream(const std::filesystem::path& _Filepath, ios_base::openmode _Mode) : + std::ifstream(_Filepath, _Mode | ios_base::in) +{ +} + +void llifstream::open(const char* _Filename, ios_base::openmode _Mode) +{ + std::ifstream::open(ll_convert<std::wstring>(_Filename).c_str(), + _Mode | ios_base::in); +} + void llifstream::open(const std::string& _Filename, ios_base::openmode _Mode) { std::ifstream::open(ll_convert<std::wstring>(_Filename).c_str(), _Mode | ios_base::in); } +void llifstream::open(const std::filesystem::path& _Filepath, ios_base::openmode _Mode) +{ + std::ifstream::open(_Filepath, + _Mode | ios_base::in); +} /************** output file stream ********************************/ - -llofstream::llofstream() {} +// explicit +llofstream::llofstream(const char* _Filename, ios_base::openmode _Mode) : + std::ofstream(ll_convert<std::wstring>(_Filename).c_str(), _Mode | ios_base::out) +{ +} // explicit llofstream::llofstream(const std::string& _Filename, ios_base::openmode _Mode): @@ -620,36 +1116,27 @@ llofstream::llofstream(const std::string& _Filename, ios_base::openmode _Mode): { } -void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode) +// explicit +llofstream::llofstream(const std::filesystem::path& _Filepath, ios_base::openmode _Mode) : + std::ofstream(_Filepath, _Mode | ios_base::out) { - std::ofstream::open(ll_convert<std::wstring>( _Filename ).c_str(), - _Mode | ios_base::out); } -/************** helper functions ********************************/ +void llofstream::open(const char* _Filename, ios_base::openmode _Mode) +{ + std::ofstream::open(ll_convert<std::wstring>(_Filename).c_str(), _Mode | ios_base::out); +} -std::streamsize llifstream_size(llifstream& ifstr) +void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode) { - if(!ifstr.is_open()) return 0; - std::streampos pos_old = ifstr.tellg(); - ifstr.seekg(0, ios_base::beg); - std::streampos pos_beg = ifstr.tellg(); - ifstr.seekg(0, ios_base::end); - std::streampos pos_end = ifstr.tellg(); - ifstr.seekg(pos_old, ios_base::beg); - return pos_end - pos_beg; + std::ofstream::open(ll_convert<std::wstring>( _Filename ).c_str(), + _Mode | ios_base::out); } -std::streamsize llofstream_size(llofstream& ofstr) +void llofstream::open(const std::filesystem::path& _Filepath, ios_base::openmode _Mode) { - if(!ofstr.is_open()) return 0; - std::streampos pos_old = ofstr.tellp(); - ofstr.seekp(0, ios_base::beg); - std::streampos pos_beg = ofstr.tellp(); - ofstr.seekp(0, ios_base::end); - std::streampos pos_end = ofstr.tellp(); - ofstr.seekp(pos_old, ios_base::beg); - return pos_end - pos_beg; + std::ofstream::open(_Filepath, + _Mode | ios_base::out); } #endif // LL_WINDOWS diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 04a2946ac4..8de3f5c9b4 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -35,133 +35,712 @@ * Attempts to mostly mirror the POSIX style IO functions. */ -typedef FILE LLFILE; +#include "stdtypes.h" #include <fstream> +#include <filesystem> #include <sys/stat.h> #if LL_WINDOWS +#include <windows.h> // The Windows version of stat function and stat data structure are called _stat64 // We use _stat64 here to support 64-bit st_size and time_t values -typedef struct _stat64 llstat; +typedef struct _stat64 llstat; #else -typedef struct stat llstat; #include <sys/types.h> +typedef struct stat llstat; #endif -#ifndef S_ISREG -# define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) -#endif - -#ifndef S_ISDIR -# define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) -#endif +typedef FILE LLFILE; -// Windows C runtime library does not define this and does not support symlink detection in the -// stat functions but we do in our getattr() function -#ifndef S_IFLNK -#define S_IFLNK 0xA000 /* symlink */ -#endif +#include "fsyspath.h" +#include "llstring.h" // safe char* -> std::string conversion -#ifndef S_ISLNK -#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) +#if LL_WINDOWS +#define LLFILE_MODE(flags) L##flags +#else +#define LLFILE_MODE(flags) flags #endif -#include "llstring.h" // safe char* -> std::string conversion - -/// LLFile is a class of static functions operating on paths +/// This class provides a selection of functions to operate on files through names and +/// a class implementation to represent a file for reading and writing to it /// All the functions with a path string input take UTF8 path/filenames -class LL_COMMON_API LLFile +/// +/// @nosubgrouping +/// +class LLFile { public: + // ================================================================================ + /// @name Constants + /// + ///@{ + /** These can be passed to the omode parameter of LLFile::open() and its constructor + + This is similar to the openmode flags for std:fstream but not exactly the same + std::fstream open() does not allow to open a file for writing without either + forcing the file to be truncated on open or all write operations being always + appended to the end of the file or failing to open when the file does not exist. + But to allow implementing the LLAPRFile::writeEx() functionality we need to be + able to write at a random position to the file without truncating it on open. + + any other combinations than listed here are not allowed and will cause an error + + bin in out trunc app noreplace File exists File doesn't exist + ---------------------------------------------------------------------------------- + - + - - - - Open at begin Failure to open + + + - - - - " " + - - + - - - " Create new + + - + - - - " " + - + + - - - " " + + + + - - - " " + ---------------------------------------------------------------------------------- + - - + + - - Destroy contents Create new + + - + + - - " " + - + + + - - " " + + + + + - - " " + ---------------------------------------------------------------------------------- + - - + x - + Failure to open Create new + + - + x - + " " + - + + x - + " " + + + + x - + " " + ---------------------------------------------------------------------------------- + - - + - + - Write to end Create new + + - + - + - " " + - + + - + - " " + + + + - + - " " + ---------------------------------------------------------------------------------- + */ + static const std::ios_base::openmode app = static_cast<std::ios_base::openmode>(1 << 1); // append to end + static const std::ios_base::openmode ate = static_cast<std::ios_base::openmode>(1 << 2); // initialize to end + static const std::ios_base::openmode binary = static_cast<std::ios_base::openmode>(1 << 3); // binary mode + static const std::ios_base::openmode in = static_cast<std::ios_base::openmode>(1 << 4); // for reading + static const std::ios_base::openmode out = static_cast<std::ios_base::openmode>(1 << 5); // for writing // for writing and reading + static const std::ios_base::openmode trunc = static_cast<std::ios_base::openmode>(1 << 6); // truncate on open + static const std::ios_base::openmode noreplace = static_cast<std::ios_base::openmode>(1 << 7); // no replace if it exists + + /// Additional optional flags to omode in open() and lmode in fopen() or lock() + /// to indicate which sort of lock if any to attempt to get + /// + /// NOTE: there is a fundamental difference between platforms. + /// On Windows this lock is mandatory as it is part of the API to open a file handle and other + /// processes can not avoid it. If a file was opened denying other processes read and/or write + /// access, trying to open the same file in another process with that access will fail. + /// On Mac and Linux it is only an advisory lock implemented through the flock() system call. + /// This means that any other application needs to also attempt to at least acquire a shared + /// lock on the file in order to notice that the file is actually already locked. It can + /// therefore not be used to prevent random other applications from accessing the file, but it + /// works for other viewer processes when they use either the LLFile::open() or LLFile::fopen() + /// functions with the appropriate lock flags to open a file. + static const std::ios_base::openmode exclusive = static_cast<std::ios_base::openmode>(1 << 16); + static const std::ios_base::openmode shared = static_cast<std::ios_base::openmode>(1 << 17); + + /// Additional lmode flag to indicate to rather fail instead of blocking when trying + /// to acquire a lock with LLFile::lock() + static const std::ios_base::openmode noblock = static_cast<std::ios_base::openmode>(1 << 18); + + /// The mask value for the lock mask bits + static const std::ios_base::openmode lock_mask = static_cast<std::ios_base::openmode>(exclusive | shared); + + /// One of these can be passed to the dir parameter of LLFile::seek() + static const std::ios_base::seekdir beg = std::ios_base::beg; + static const std::ios_base::seekdir cur = std::ios_base::cur; + static const std::ios_base::seekdir end = std::ios_base::end; + ///@} + + // ================================================================================ + /// @name constructor/deconstructor + /// + ///@{ + /// default constructor + LLFile() : mHandle(InvalidHandle) {} + + /// no copy constructor + LLFile(const LLFile&) = delete; + + /// move constructor + LLFile(LLFile&& other) noexcept + { + mHandle = other.mHandle; + other.mHandle = InvalidHandle; + } + + /// constructor opening the file + explicit LLFile(const char* filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) : + mHandle(InvalidHandle) + { + open(filename, omode, ec, perm); + } + + explicit LLFile(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) : + mHandle(InvalidHandle) + { + open(filename, omode, ec, perm); + } + + explicit LLFile(const std::filesystem::path& file_path, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) : + mHandle(InvalidHandle) + { + open(file_path, omode, ec, perm); + } + + /// destructor always attempts to close the file + ~LLFile() { close(); } + ///@} + + // ================================================================================ + /// @name operators + /// + ///@{ + /// copy assignment deleted + LLFile& operator=(const LLFile&) = delete; + + /// move assignment + LLFile& operator=(LLFile&& other) noexcept + { + close(); + std::swap(mHandle, other.mHandle); + return *this; + } + + // detect whether the wrapped file descriptor/handle is open or not + explicit operator bool() const { return (mHandle != InvalidHandle); } + bool operator!() { return (mHandle == InvalidHandle); } + ///@} + + /// ================================================================================ + /// @name class member methods + /// + /// These methods provide read and write support as well as additional functionality to query the size of + /// the file, change the position of the current file pointer or query it. + /// + /// Most of these functions take as one of their parameters a std::error_code object which can be used to + /// determine in more detail what error occurred if required + ///@{ + + /// Open a file with the specific open mode flags + inline int open(const char* filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) + { + std::filesystem::path file_path = fsyspath(filename); + return open(file_path, omode, ec, perm); + } + inline int open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) + { + std::filesystem::path file_path = fsyspath(filename); + return open(file_path, omode, ec, perm); + } + int open(const std::filesystem::path& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666); + ///< @returns 0 on success, -1 on failure + + /// Determine the size of the opened file + S64 size(std::error_code& ec); + ///< @returns the number of bytes in the file or 0 on failure + + /// Query the position of the current file pointer in the file + S64 tell(std::error_code& ec); + ///< @returns the absolute offset of the file pointer in bytes relative to the start of the file or -1 on failure + + /// Move the file pointer to the specified absolute position relative to the start of the file + int seek(S64 pos, std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Move the file pointer to the specified position relative to dir + int seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Read the specified number of bytes into the buffer starting at the current file pointer + S64 read(void* buffer, S64 nbytes, std::error_code& ec); + ///< If the file ends before the requested amount of bytes could be read, the function succeeds and + /// returns the bytes up to the end of the file. The return value indicates the number of actually + /// read bytes and can be therefore smaller than the requested amount. + /// @returns the number of bytes read from the file or -1 on failure + + /// Write the specified number of bytes to the file starting at the current file pointer + S64 write(const void* buffer, S64 nbytes, std::error_code& ec); + ///< @returns the number of bytes written to the file or -1 on failure + + /// Write into the file starting at the current file pointer using printf style format and + /// additional optional parameters as specified in the fmt string + S64 printf(const char* fmt, ...); + ///< @returns the number of bytes written to the file or -1 on failure + + /// Attempt to acquire or release a lock on the file + int lock(int lmode, std::error_code& ec); + ///< lmode can be one of LLFile::exclusive or LLFile::shared to acquire the according lock + /// or 0 to give up an earlier acquired lock. Adding LLFile::noblock together with one of + /// the lock requests will cause the function to fail if the lock can not be acquired, + /// otherwise the function will block until the lock can be acquired. + /// @returns 0 on success, -1 on failure + + /// close the file explicitly + int close(std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Convenience function to close the file without additional parameters + int close(); + ///< @returns 0 on success, -1 on failure + ///@} + + /// ================================================================================ + /// @name static member functions + /// + /// These functions are static and operate with UTF8 filenames as one of their parameters. + /// + ///@{ /// open a file with the specified access mode - static LLFILE* fopen(const std::string& filename, const char* accessmode); /* Flawfinder: ignore */ + /// +#if LL_WINDOWS + using fopen_flags_t = wchar_t; +#else + using fopen_flags_t = char; +#endif + inline static LLFILE* fopen(const char* filename, const fopen_flags_t* accessmode, int lmode = 0) + { + std::filesystem::path file_path = fsyspath(filename); + return LLFile::fopen(file_path, accessmode, lmode); + } + inline static LLFILE* fopen(const std::string& filename, const fopen_flags_t* accessmode, int lmode = 0) + { + std::filesystem::path file_path = fsyspath(filename); + return LLFile::fopen(file_path, accessmode, lmode); + } + static LLFILE* fopen(const std::filesystem::path& file_path, const fopen_flags_t* accessmode, int lmode = 0); ///< 'accessmode' follows the rules of the Posix fopen() mode parameter - /// "r" open the file for reading only and positions the stream at the beginning - /// "r+" open the file for reading and writing and positions the stream at the beginning - /// "w" open the file for reading and writing and truncate it to zero length - /// "w+" open or create the file for reading and writing and truncate to zero length if it existed - /// "a" open the file for reading and writing and position the stream at the end of the file - /// "a+" open or create the file for reading and writing and position the stream at the end of the file + /// "r" open the file for reading only and positions the stream at the beginning + /// "r+" open the file for reading and writing and positions the stream at the beginning + /// "w" open the file for reading and writing and truncate it to zero length + /// "w+" open or create the file for reading and writing and truncate to zero length if it existed + /// "a" open the file for reading and writing and before every write position the stream at the end of the file + /// "a+" open or create the file for reading and writing and before every write position the stream at the end of the file + /// + /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac + /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not + /// try to translate line endings. Windows also allows to append "t" to indicate text mode. If neither + /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. + /// This means that it is always a good idea to append "b" specifically for binary file access to + /// avoid corruption of the binary consistency of the data stream when reading or writing + /// Other characters in 'accessmode', while possible on some platforms (Windows), will usually + /// cause an error on other platforms as fopen will verify this parameter /// - /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac - /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not - /// try to translate line endings. Windows also allows to append "t" to indicate text mode. If neither - /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. - /// This means that it is always a good idea to append "b" specifically for binary file access to - /// avoid corruption of the binary consistency of the data stream when reading or writing - /// Other characters in 'accessmode' will usually cause an error as fopen will verify this parameter - /// @returns a valid LLFILE* pointer on success or NULL on failure + /// lmode is optional and allows to lock the file for other processes either as a shared lock or an + /// exclusive lock. If the requested lock conflicts with an already existing lock, the open fails. + /// Pass either LLFIle::exclusive or LLFile::shared to this parameter if you want to prevent other + /// processes from reading (exclusive lock) or writing (shared lock) to the file. It will always use + /// LLFile::noblock, meaning the open will immediately fail if it conflicts with an existing lock on the + /// file. + /// + /// @returns a valid LLFILE* pointer on success that can be passed to the fread() and fwrite() functions + /// and some other f<something> functions in the Standard C library that accept a FILE* as parameter + /// or NULL on failure + /// Close a file handle opened with fopen() above static int close(LLFILE * file); - - /// retrieve the content of a file into a string - static std::string getContents(const std::string& filename); - ///< @returns the content of the file or an empty string on failure + ///< @returns 0 on success and -1 on failure. /// create a directory - static int mkdir(const std::string& filename, int perms = 0700); - ///< perms is a permissions mask like 0777 or 0700. In most cases it will be - /// overridden by the user's umask. It is ignored on Windows. - /// mkdir() considers "directory already exists" to be not an error. - /// @returns 0 on success and -1 on failure. - - //// remove a directory - static int rmdir(const std::string& filename, int suppress_error = 0); - ///< pass ENOENT in the optional 'suppress_error' parameter - /// if you don't want a warning in the log when the directory does not exist + inline static int mkdir(const char* dirname) + { + std::filesystem::path dir_path = fsyspath(dirname); + return mkdir(dir_path); + } + inline static int mkdir(const std::string& dirname) + { + std::filesystem::path dir_path = fsyspath(dirname); + return mkdir(dir_path); + } + static int mkdir(const std::filesystem::path& dirname); + ///< mkdir() considers "directory already exists" to be not an error. /// @returns 0 on success and -1 on failure. /// remove a file or directory - static int remove(const std::string& filename, int suppress_error = 0); - ///< pass ENOENT in the optional 'suppress_error' parameter - /// if you don't want a warning in the log when the directory does not exist + inline static int remove(const char* filename, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + return remove(file_path, suppress_warning); + } + inline static int remove(const std::string& filename, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + return remove(file_path, suppress_warning); + } + static int remove(const std::filesystem::path& file_path, int suppress_warning = 0); + ///< pass an errno value (e.g., ENOENT) in the optional 'suppress_warning' parameter if you want to + /// suppress a warning in the log when the failure matches that errno (e.g., suppress warning if + /// the file or directory does not exist) /// @returns 0 on success and -1 on failure. /// rename a file - static int rename(const std::string& filename, const std::string& newname, int suppress_error = 0); + inline static int rename(const char* filename, const char* newname, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + std::filesystem::path new_path = fsyspath(newname); + return rename(file_path, new_path, suppress_warning); + } + inline static int rename(const std::string& filename, const char* newname, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + std::filesystem::path new_path = fsyspath(newname); + return rename(file_path, new_path, suppress_warning); + } + inline static int rename(const char* filename, const std::string& newname, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + std::filesystem::path new_path = fsyspath(newname); + return rename(file_path, new_path, suppress_warning); + } + inline static int rename(const std::string& filename, const std::string& newname, int suppress_warning = 0) + { + std::filesystem::path file_path = fsyspath(filename); + std::filesystem::path new_path = fsyspath(newname); + return rename(file_path, new_path, suppress_warning); + } + static int rename(const std::filesystem::path& file_path, const std::filesystem::path& new_path, int suppress_warning = 0); ///< it will silently overwrite newname if it exists without returning an error /// Posix guarantees that if newname already exists, then there will be no moment /// in which for other processes newname does not exist. There is no such guarantee - /// under Windows at this time. It may do it in the same way but the used Windows API - /// does not make such guarantees. + /// under Windows at this time. It may do it in the same way but the used Windows + /// APIs do not make such guarantees. /// @returns 0 on success and -1 on failure. + /// copy the contents of the file from 'source' to 'target' + inline static bool copy(const char* source, const char* target) + { + std::error_code ec; + return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec); + } + inline static bool copy(const char* source, const std::string& target) + { + std::error_code ec; + return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec); + } + inline static bool copy(const std::string& source, const char* target) + { + std::error_code ec; + return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec); + } + inline static bool copy(const std::string& source, const std::string& target) + { + std::error_code ec; + return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec); + } + inline static bool copy(const std::filesystem::path& source_path, const std::filesystem::path& target_path) + { + std::error_code ec; + return copy(source_path, target_path, std::filesystem::copy_options::overwrite_existing, ec); + } + ///< Copies the contents of the file 'source' to the file 'target', overwriting 'target' if it already + /// existed. + /// This is a convenience function that implements the previous behavior of silently overwriting an + /// already existing target file. Consider using the function below if you desire a different + /// behavior when the target file already exists + /// @returns true on success and false on failure. + + /// copy the contents of the file from 'from' to 'to' + inline static bool copy(const char* source, const char* target, std::filesystem::copy_options options, std::error_code& ec) + { + std::filesystem::path source_path = fsyspath(source); + std::filesystem::path target_path = fsyspath(target); + return copy(source_path, target_path, options, ec); + } + inline static bool copy(const char* source, const std::string& target, std::filesystem::copy_options options, std::error_code& ec) + { + std::filesystem::path source_path = fsyspath(source); + std::filesystem::path target_path = fsyspath(target); + return copy(source_path, target_path, options, ec); + } + inline static bool copy(const std::string& source, const char* target, std::filesystem::copy_options options, std::error_code& ec) + { + std::filesystem::path source_path = fsyspath(source); + std::filesystem::path target_path = fsyspath(target); + return copy(source_path, target_path, options, ec); + } + inline static bool copy(const std::string& source, const std::string& target, std::filesystem::copy_options options, std::error_code& ec) + { + std::filesystem::path source_path = fsyspath(source); + std::filesystem::path target_path = fsyspath(target); + return copy(source_path, target_path, options, ec); + } + static bool copy(const std::filesystem::path& source_path, const std::filesystem::path& target_path, std::filesystem::copy_options options, std::error_code& ec); + ///< Copies the contents of the file 'source' to the file 'target'. The options parameter allows to + /// specify what should happen if the "target" file already exists: + /// std::filesystem::copy_options::none - return an error in ec and fail + /// std::filesystem::copy_options::skip_existing - skip the operation and do not overwrite file + /// std::filesystem::copy_options::overwrite_existing - overwrite the file + /// std::filesystem::copy_options::update_existing - overwrite the file only if it is older than the file being copied + /// @returns true on success and false on failure. + + /// retrieve the content of a file into a string + inline static std::string getContents(const char* filename) + { + std::error_code ec; + return getContents(filename, ec); + } + inline static std::string getContents(const std::string& filename) + { + std::error_code ec; + return getContents(filename, ec); + } + inline static std::string getContents(const std::filesystem::path& file_path) + { + std::error_code ec; + return getContents(file_path, ec); + } + inline static std::string getContents(const char* filename, std::error_code& ec) + { + std::filesystem::path file_path = fsyspath(filename); + return getContents(file_path, ec); + } + inline static std::string getContents(const std::string& filename, std::error_code& ec) + { + std::filesystem::path file_path = fsyspath(filename); + return getContents(file_path, ec); + } + static std::string getContents(const std::filesystem::path& file_path, std::error_code& ec); + ///< @returns the entire content of the file as std::string or an empty string on failure + + /// read nBytes from the file into the buffer, starting at offset in the file + inline static S64 read(const char* filename, void* buf, S64 offset, S64 nbytes) + { + std::error_code ec; + return read(filename, buf, offset, nbytes, ec); + } + inline static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes) + { + std::error_code ec; + return read(filename, buf, offset, nbytes, ec); + } + inline static S64 read(const std::filesystem::path& file_path, void* buf, S64 offset, S64 nbytes) + { + std::error_code ec; + return read(file_path, buf, offset, nbytes, ec); + } + inline static S64 read(const char* filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec) + { + std::filesystem::path file_path = fsyspath(filename); + return read(file_path, buf, offset, nbytes, ec); + } + inline static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec) + { + std::filesystem::path file_path = fsyspath(filename); + return read(file_path, buf, offset, nbytes, ec); + } + static S64 read(const std::filesystem::path& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec); + ///< @returns bytes read on success, or -1 on failure - /// copy the contents of file from 'from' to 'to' filename - static bool copy(const std::string& from, const std::string& to); - ///< @returns true on success and false on failure. + /// write nBytes from the buffer into the file, starting at offset in the file + inline static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes) + { + std::error_code ec; + std::filesystem::path file_path = fsyspath(filename); + return write(file_path, buf, offset, nbytes, ec); + } + inline static S64 write(const std::filesystem::path& filename, const void* buf, S64 offset, S64 nbytes) + { + std::error_code ec; + std::filesystem::path file_path = fsyspath(filename); + return write(file_path, buf, offset, nbytes, ec); + } + inline static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec) + { + std::filesystem::path file_path = fsyspath(filename); + return write(file_path, buf, offset, nbytes, ec); + } + static S64 write(const std::filesystem::path& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec); + ///< If a negative offset is provided, the file is opened in append mode and the + /// write will be appended to the end of the file. + /// @returns bytes written on success, or -1 on failure /// return the file stat structure for filename - static int stat(const std::string& filename, llstat* file_status, int suppress_error = ENOENT); + inline static int stat(const char* filename, llstat* file_status, const char* operation = nullptr, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return stat(file_path, file_status, operation, suppress_warning); + } + inline static int stat(const std::string& filename, llstat* file_status, const char* operation = nullptr, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return stat(file_path, file_status, operation, suppress_warning); + } + static int stat(const std::filesystem::path& file_path, llstat* file_status, const char *operation = nullptr, int suppress_warning = ENOENT); ///< for compatibility with existing uses of LL_File::stat() we use ENOENT as default in the - /// optional 'suppress_error' parameter to avoid spamming the log with warnings when the API + /// optional 'suppress_warning' parameter to avoid spamming the log with warnings when the API /// is used to detect if a file exists /// @returns 0 on success and -1 on failure. - /// get the file or directory attributes for filename - static unsigned short getattr(const std::string& filename, bool dontFollowSymLink = false, int suppress_error = ENOENT); - ///< a more lightweight function on Windows to stat, that just returns the file attribute flags - /// dontFollowSymLinks set to true returns the attributes of the symlink if it is one, rather than resolving it - /// we pass by default ENOENT in the optional 'suppress_error' parameter to not spam the log with - /// warnings when the file or directory does not exist - /// @returns 0 on failure and a st_mode value with either S_IFDIR or S_IFREG set otherwise - /// together with the three access bits which under Windows only the write bit is relevant. + /// get the std::filesystem::file_status for filename + inline static std::filesystem::file_status getStatus(const char* filename, bool dontFollowSymLink = false, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return getStatus(file_path, dontFollowSymLink, suppress_warning); + } + inline static std::filesystem::file_status getStatus(const std::string& filename, bool dontFollowSymLink = false, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return getStatus(file_path, dontFollowSymLink, suppress_warning); + } + static std::filesystem::file_status getStatus(const std::filesystem::path& file_path, bool dontFollowSymLink = false, int suppress_warning = ENOENT); + ///< dontFollowSymLinks set to true returns the std::filesystem::file_status of the symlink if it + /// is one, rather than resolving it. We pass by default ENOENT in the optional 'suppress_warning' + /// parameter to not spam the log with warnings when the file or directory does not exist + /// @returns a std::filesystem::file_status value that can be passed to the appropriate std::filesystem::exists() + /// and other APIs accepting a file_status. + + /// get the size of a file in bytes + inline static S64 size(const char* filename, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return size(file_path, suppress_warning); + } + inline static S64 size(const std::string& filename, int suppress_warning = ENOENT) + { + std::filesystem::path file_path = fsyspath(filename); + return size(file_path, suppress_warning); + } + static S64 size(const std::filesystem::path& file_path, int suppress_warning = ENOENT); + ///< we pass by default ENOENT in the optional 'suppress_warning' parameter to not spam + /// the log with warnings when the file does not exist + /// @returns the file size on success or 0 on failure. + + /// check if filename is an existing file or directory + inline static bool exists(const char* filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::exists(status); + } + inline static bool exists(const std::string& filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::exists(status); + } + inline static bool exists(const std::filesystem::path& file_path) + { + std::filesystem::file_status status = getStatus(file_path); + return std::filesystem::exists(status); + } + ///< @returns true if the path is for an existing file or directory /// check if filename is an existing directory - static bool isdir(const std::string& filename); + inline static bool isdir(const char* filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_directory(status); + } + inline static bool isdir(const std::string& filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_directory(status); + } + inline static bool isdir(const std::filesystem::path& file_path) + { + std::filesystem::file_status status = getStatus(file_path); + return std::filesystem::is_directory(status); + } ///< @returns true if the path is for an existing directory /// check if filename is an existing file - static bool isfile(const std::string& filename); + inline static bool isfile(const char* filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_regular_file(status); + } + inline static bool isfile(const std::string& filename) + { + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_regular_file(status); + } + inline static bool isfile(const std::filesystem::path& file_path) + { + std::filesystem::file_status status = getStatus(file_path); + return std::filesystem::is_regular_file(status); + } ///< @returns true if the path is for an existing file /// check if filename is a symlink - static bool islink(const std::string& filename); + inline static bool islink(const char* filename) + { + std::filesystem::file_status status = getStatus(filename, true); + return std::filesystem::is_symlink(status); + } + inline static bool islink(const std::string& filename) + { + std::filesystem::file_status status = getStatus(filename, true); + return std::filesystem::is_symlink(status); + } + inline static bool islink(const std::filesystem::path& file_path) + { + std::filesystem::file_status status = getStatus(file_path, true); + return std::filesystem::is_symlink(status); + } ///< @returns true if the path is pointing at a symlink /// return a path to the temporary directory on the system - static const char * tmpdir(); + static const std::string& tmpdir(); + +private: +#if LL_WINDOWS + typedef HANDLE llfile_handle_t; + const llfile_handle_t InvalidHandle = INVALID_HANDLE_VALUE; + llfile_handle_t mHandle = INVALID_HANDLE_VALUE; // The file handle/descriptor +#else + typedef int llfile_handle_t; + const llfile_handle_t InvalidHandle = -1; + llfile_handle_t mHandle = -1; // The file handle/descriptor +#endif + + std::ios_base::openmode mOpen{}; // Used to emulate std::ios_base::app under Windows +}; + +#if LL_WINDOWS +/** + * @brief Controlling input for files. + * + * This class supports reading from named files, using the inherited + * functions from std::ifstream. The only added value is that our constructor + * Does The Right Thing when passed a non-ASCII pathname. Sadly, that isn't + * true of Microsoft's std::ifstream. + */ +class llifstream : public std::ifstream +{ + // input stream associated with a C stream + public: + // Constructors: + /** + * @brief Default constructor. + * + * Initializes @c sb using its default constructor, and passes + * @c &sb to the base class initializer. Does not open any files + * (you haven't given it a filename to open). + */ + llifstream() = default; + + /** + * @brief Create an input file stream. + * @param Filename String specifying the filename. + * @param Mode Open file in specified mode (see std::ios_base). + * + * @c ios_base::in is automatically included in @a mode. + */ + explicit llifstream(const char* _Filename, + ios_base::openmode _Mode = ios_base::in); + explicit llifstream(const std::string& _Filename, + ios_base::openmode _Mode = ios_base::in); + explicit llifstream(const std::filesystem::path& _Filepath, + ios_base::openmode _Mode = ios_base::in); + + /** + * @brief Opens an external file. + * @param Filename The name of the file. + * @param Node The open mode flags. + * + * Calls @c llstdio_filebuf::open(s,mode|in). If that function + * fails, @c failbit is set in the stream's error state. + */ + void open(const char* _Filename, + ios_base::openmode _Mode = ios_base::in); + void open(const std::string& _Filename, + ios_base::openmode _Mode = ios_base::in); + void open(const std::filesystem::path& _Filepath, + ios_base::openmode _Mode = ios_base::in); }; /// RAII class @@ -169,22 +748,19 @@ class LLUniqueFile { public: // empty - LLUniqueFile(): mFileHandle(nullptr) {} + LLUniqueFile() = default; // wrap (e.g.) result of LLFile::fopen() - LLUniqueFile(LLFILE* f): mFileHandle(f) {} + LLUniqueFile(LLFILE* f) : mFileHandle(f) {} // no copy LLUniqueFile(const LLUniqueFile&) = delete; // move construction LLUniqueFile(LLUniqueFile&& other) noexcept { - mFileHandle = other.mFileHandle; + mFileHandle = other.mFileHandle; other.mFileHandle = nullptr; } // The point of LLUniqueFile is to close on destruction. - ~LLUniqueFile() - { - close(); - } + ~LLUniqueFile() { close(); } // simple assignment LLUniqueFile& operator=(LLFILE* f) @@ -209,7 +785,7 @@ public: if (mFileHandle) { // in case close() throws, set mFileHandle null FIRST - LLFILE* h{nullptr}; + LLFILE* h{ nullptr }; std::swap(h, mFileHandle); LLFile::close(h); } @@ -217,62 +793,16 @@ public: // detect whether the wrapped LLFILE is open or not explicit operator bool() const { return bool(mFileHandle); } - bool operator!() { return ! mFileHandle; } + bool operator!() { return !mFileHandle; } // LLUniqueFile should be usable for any operation that accepts LLFILE* // (or FILE* for that matter) operator LLFILE*() const { return mFileHandle; } private: - LLFILE* mFileHandle; -}; - -#if LL_WINDOWS -/** - * @brief Controlling input for files. - * - * This class supports reading from named files, using the inherited - * functions from std::ifstream. The only added value is that our constructor - * Does The Right Thing when passed a non-ASCII pathname. Sadly, that isn't - * true of Microsoft's std::ifstream. - */ -class LL_COMMON_API llifstream : public std::ifstream -{ - // input stream associated with a C stream - public: - // Constructors: - /** - * @brief Default constructor. - * - * Initializes @c sb using its default constructor, and passes - * @c &sb to the base class initializer. Does not open any files - * (you haven't given it a filename to open). - */ - llifstream(); - - /** - * @brief Create an input file stream. - * @param Filename String specifying the filename. - * @param Mode Open file in specified mode (see std::ios_base). - * - * @c ios_base::in is automatically included in @a mode. - */ - explicit llifstream(const std::string& _Filename, - ios_base::openmode _Mode = ios_base::in); - - /** - * @brief Opens an external file. - * @param Filename The name of the file. - * @param Node The open mode flags. - * - * Calls @c llstdio_filebuf::open(s,mode|in). If that function - * fails, @c failbit is set in the stream's error state. - */ - void open(const std::string& _Filename, - ios_base::openmode _Mode = ios_base::in); + LLFILE* mFileHandle = nullptr; }; - /** * @brief Controlling output for files. * @@ -281,7 +811,7 @@ class LL_COMMON_API llifstream : public std::ifstream * Right Thing when passed a non-ASCII pathname. Sadly, that isn't true of * Microsoft's std::ofstream. */ -class LL_COMMON_API llofstream : public std::ofstream +class llofstream : public std::ofstream { public: // Constructors: @@ -292,7 +822,7 @@ class LL_COMMON_API llofstream : public std::ofstream * @c &sb to the base class initializer. Does not open any files * (you haven't given it a filename to open). */ - llofstream(); + llofstream() = default; /** * @brief Create an output file stream. @@ -301,8 +831,12 @@ class LL_COMMON_API llofstream : public std::ofstream * * @c ios_base::out is automatically included in @a mode. */ + explicit llofstream(const char* _Filename, + ios_base::openmode _Mode = ios_base::out|ios_base::trunc); explicit llofstream(const std::string& _Filename, ios_base::openmode _Mode = ios_base::out|ios_base::trunc); + explicit llofstream(const std::filesystem::path& _Filepath, + ios_base::openmode _Mode = ios_base::out|ios_base::trunc); /** * @brief Opens an external file. @@ -311,21 +845,14 @@ class LL_COMMON_API llofstream : public std::ofstream * * @c ios_base::out is automatically included in @a mode. */ + void open(const char* _Filename, + ios_base::openmode _Mode = ios_base::out|ios_base::trunc); void open(const std::string& _Filename, ios_base::openmode _Mode = ios_base::out|ios_base::trunc); + void open(const std::filesystem::path& _Filepath, + ios_base::openmode _Mode = ios_base::out|ios_base::trunc); }; - -/** - * @brief filesize helpers. - * - * The file size helpers are not considered particularly efficient, - * and should only be used for config files and the like -- not in a - * loop. - */ -std::streamsize LL_COMMON_API llifstream_size(llifstream& fstr); -std::streamsize LL_COMMON_API llofstream_size(llofstream& fstr); - #else // ! LL_WINDOWS // on non-windows, llifstream and llofstream are just mapped directly to the std:: equivalents diff --git a/indra/llcommon/llfindlocale.cpp b/indra/llcommon/llfindlocale.cpp index b4bcc80ac4..d606a6fdb9 100644 --- a/indra/llcommon/llfindlocale.cpp +++ b/indra/llcommon/llfindlocale.cpp @@ -134,7 +134,7 @@ accumulate_locstring(const char *str, FL_Locale *l) { return 0; } - +#ifndef LL_WINDOWS static int accumulate_env(const char *name, FL_Locale *l) { char *env; @@ -148,7 +148,7 @@ accumulate_env(const char *name, FL_Locale *l) { free(lang); free(country); free(variant); return 0; } - +#endif static void canonise_fl(FL_Locale *l) { diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h index 1234d2014f..d2cd660c18 100644 --- a/indra/llcommon/llfixedbuffer.h +++ b/indra/llcommon/llfixedbuffer.h @@ -27,7 +27,7 @@ #ifndef LL_LLFIXEDBUFFER_H #define LL_LLFIXEDBUFFER_H -#include "timer.h" +#include "lltimer.h" #include <deque> #include <string> #include "llstring.h" diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 92b26354a1..734365767d 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -28,6 +28,8 @@ #ifndef LL_LLINSTANCETRACKER_H #define LL_LLINSTANCETRACKER_H +#include "llpreprocessor.h" + #include <map> #include <set> #include <vector> @@ -35,7 +37,7 @@ #include <memory> #include <type_traits> -#include "mutex.h" +#include <mutex> #include <boost/iterator/transform_iterator.hpp> #include <boost/iterator/indirect_iterator.hpp> @@ -244,6 +246,7 @@ public: } protected: + LL_UBSAN_SUPRESS_VPTR LLInstanceTracker(const KEY& key) { // We do not intend to manage the lifespan of this object with @@ -482,6 +485,7 @@ public: using key_snapshot_of = instance_snapshot_of<SUBCLASS>; protected: + LL_UBSAN_SUPRESS_VPTR LLInstanceTracker() { // Since we do not intend for this shared_ptr to manage lifespan, give diff --git a/indra/llcommon/llkeybind.cpp b/indra/llcommon/llkeybind.cpp index e36c1d0a4c..abb0df0b87 100644 --- a/indra/llcommon/llkeybind.cpp +++ b/indra/llcommon/llkeybind.cpp @@ -114,15 +114,6 @@ void LLKeyData::reset() mIgnoreMasks = false; } -LLKeyData& LLKeyData::operator=(const LLKeyData& rhs) -{ - mMouse = rhs.mMouse; - mKey = rhs.mKey; - mMask = rhs.mMask; - mIgnoreMasks = rhs.mIgnoreMasks; - return *this; -} - bool LLKeyData::operator==(const LLKeyData& rhs) { if (mMouse != rhs.mMouse) return false; diff --git a/indra/llcommon/llkeybind.h b/indra/llcommon/llkeybind.h index 1bbb2fadb5..0a9d4600cc 100644 --- a/indra/llcommon/llkeybind.h +++ b/indra/llcommon/llkeybind.h @@ -43,7 +43,6 @@ public: bool isEmpty() const; bool empty() const { return isEmpty(); }; void reset(); - LLKeyData& operator=(const LLKeyData& rhs); bool operator==(const LLKeyData& rhs); bool operator!=(const LLKeyData& rhs); diff --git a/indra/llcommon/llmd5.cpp b/indra/llcommon/llmd5.cpp index c8ca586e7f..b05fd2e21b 100644 --- a/indra/llcommon/llmd5.cpp +++ b/indra/llcommon/llmd5.cpp @@ -255,13 +255,15 @@ void LLMD5::raw_digest(unsigned char* s) const memcpy(s, digest, 16); /* Flawfinder: ignore */ } -#if LL_DARWIN +#if LL_CLANG #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif void LLMD5::hex_digest(char* s) const { + if(!s) return; + if (!finalized) { std::cerr << "LLMD5::hex_digest: Can't get digest if you haven't " @@ -278,7 +280,7 @@ void LLMD5::hex_digest(char* s) const s[32] = '\0'; } -#if LL_DARWIN +#if LL_CLANG #pragma clang diagnostic pop #endif diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index ba48319a16..9617e30f0b 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -146,9 +146,6 @@ void LLMemory::updateMemoryInfo() LL_WARNS() << "task_info failed" << LL_ENDL; } #elif defined(LL_LINUX) - // Use sysinfo() to get the total physical memory. - struct sysinfo info; - sysinfo(&info); sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(LLMemory::getCurrentRSS())); // represents the RAM allocated by this process only (in line with the windows implementation) #else //not valid for other systems for now. diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index adc556d180..a25387d321 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -48,6 +48,9 @@ class LLMutex ; #define LL_ALIGN_OF __align_of__ #endif +#if defined(LL_X86_64) || defined(LL_ARM64) +#define LL_DEFAULT_HEAP_ALIGN 16 +#else #if LL_WINDOWS #define LL_DEFAULT_HEAP_ALIGN 8 #elif LL_DARWIN @@ -55,7 +58,7 @@ class LLMutex ; #elif LL_LINUX #define LL_DEFAULT_HEAP_ALIGN 8 #endif - +#endif LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment); @@ -89,22 +92,6 @@ template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) (uintptr_t(address) + 0x3F) & ~0x3F); } -#if LL_LINUX || LL_DARWIN - -#define LL_ALIGN_PREFIX(x) -#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x))) - -#elif LL_WINDOWS - -#define LL_ALIGN_PREFIX(x) __declspec(align(x)) -#define LL_ALIGN_POSTFIX(x) - -#else -#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined" -#endif - -#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16) - #define LL_ALIGN_NEW \ public: \ void* operator new(size_t size) \ @@ -127,7 +114,6 @@ public: \ ll_aligned_free_16(ptr); \ } - //------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------ // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library @@ -143,6 +129,10 @@ public: \ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; #if defined(LL_WINDOWS) void* ret = _aligned_malloc(size, align); + #elif defined(LL_LINUX) + void *ret; + if (0 != posix_memalign(&ret, align, size)) + return nullptr; #else char* aligned = NULL; void* mem = malloc( size + (align - 1) + sizeof(void*) ); @@ -165,6 +155,8 @@ public: \ LL_PROFILE_FREE(ptr); #if defined(LL_WINDOWS) _aligned_free(ptr); + #elif defined(LL_LINUX) + free(ptr); #else if (ptr) { @@ -179,10 +171,10 @@ public: \ inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16(). { LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; -#if defined(LL_WINDOWS) +#if LL_DEFAULT_HEAP_ALIGN == 16 + void* ret = malloc(size); // default osx and 64-bit malloc is 16 byte aligned. +#elif defined(LL_WINDOWS) void* ret = _aligned_malloc(size, 16); -#elif defined(LL_DARWIN) - void* ret = malloc(size); // default osx malloc is 16 byte aligned. #else void *ret; if (0 != posix_memalign(&ret, 16, size)) @@ -196,10 +188,10 @@ inline void ll_aligned_free_16(void *p) { LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; LL_PROFILE_FREE(p); -#if defined(LL_WINDOWS) +#if LL_DEFAULT_HEAP_ALIGN == 16 + free(p); +#elif defined(LL_WINDOWS) _aligned_free(p); -#elif defined(LL_DARWIN) - return free(p); #else free(p); // posix_memalign() is compatible with heap deallocator #endif @@ -209,10 +201,10 @@ inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // r { LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; LL_PROFILE_FREE(ptr); -#if defined(LL_WINDOWS) +#if LL_DEFAULT_HEAP_ALIGN == 16 + void* ret = realloc(ptr,size); // default osx and 64bit malloc is 16 byte aligned. +#elif defined(LL_WINDOWS) void* ret = _aligned_realloc(ptr, size, 16); -#elif defined(LL_DARWIN) - void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned. #else //FIXME: memcpy is SLOW void* ret = ll_aligned_malloc_16(size); diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index f3615a1270..bc3d8fed06 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -30,7 +30,7 @@ #include "stdtypes.h" #include "llthread.h" -#include "mutex.h" +#include <mutex> #include <shared_mutex> #include <unordered_map> #include <condition_variable> diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index b2a9734548..104d3cc186 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -61,35 +61,48 @@ # define LL_UNLIKELY(EXPR) (EXPR) #endif - // Figure out differences between compilers -#if defined(__GNUC__) +#if defined(__MSVC_VER__) || defined(_MSC_VER) + #ifndef LL_MSVC + #define LL_MSVC 1 + #endif + + // Clang CL is MSVC-like but also supports its own macros + #if defined(__clang__) + #ifndef LL_CLANG + #define LL_CLANG 1 + #endif + #endif +#elif defined(__clang__) + #define CLANG_VERSION (__clang_major__ * 10000 \ + + __clang_minor__ * 100 \ + + __clang_patchlevel__) + #ifndef LL_CLANG + #define LL_CLANG 1 + #endif +#elif defined(__GNUC__) #define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #ifndef LL_GNUC #define LL_GNUC 1 #endif -#elif defined(__MSVC_VER__) || defined(_MSC_VER) - #ifndef LL_MSVC - #define LL_MSVC 1 - #endif #endif // Set up CPU architecture defines #if LL_MSVC && defined(_M_ARM64) # define LL_ARM64 1 -#elif LL_GNUC && (defined(__arm64__) || defined(__aarch64__)) +#elif (LL_GNUC || LL_CLANG) && (defined(__arm64__) || defined(__aarch64__)) # define LL_ARM64 1 #elif LL_MSVC && _M_X64 # define LL_X86_64 1 # define LL_X86 1 #elif LL_MSVC && _M_IX86 # define LL_X86 1 -#elif LL_GNUC && ( defined(__amd64__) || defined(__x86_64__) ) +#elif (LL_GNUC || LL_CLANG) && ( defined(__amd64__) || defined(__x86_64__) ) # define LL_X86_64 1 # define LL_X86 1 -#elif LL_GNUC && ( defined(__i386__) ) +#elif (LL_GNUC || LL_CLANG) && ( defined(__i386__) ) # define LL_X86 1 #endif @@ -106,16 +119,6 @@ #endif -// Although thread_local is now a standard storage class, we can't just -// #define LL_THREAD_LOCAL as thread_local because the *usage* is different. -// We'll have to take the time to change LL_THREAD_LOCAL declarations by hand. -#if LL_WINDOWS -# define LL_THREAD_LOCAL __declspec(thread) -#else -# define LL_THREAD_LOCAL __thread -#endif - - #if defined(LL_WINDOWS) #define BOOST_REGEX_NO_LIB 1 #define CURL_STATICLIB 1 @@ -140,7 +143,7 @@ #define LL_DLLIMPORT #endif // LL_WINDOWS -#if __clang__ || ! defined(LL_WINDOWS) +#if LL_CLANG || ! defined(LL_WINDOWS) // Only on Windows, and only with the Microsoft compiler (vs. clang) is // wchar_t potentially not a distinct type. #define LL_WCHAR_T_NATIVE 1 @@ -201,6 +204,13 @@ #define LL_PRETTY_FUNCTION __PRETTY_FUNCTION__ #endif +// vptr warning supression funtionality for undefined behavior sanitizer +#if LL_CLANG || LL_GNUC +# define LL_UBSAN_SUPRESS_VPTR __attribute__((no_sanitize("vptr"))) +#else +# define LL_UBSAN_SUPRESS_VPTR +#endif + #if LL_ARM64 #define GLM_FORCE_NEON 1 #else diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index 0778b123ea..9d0ef45bee 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -783,6 +783,20 @@ private: }; #elif LL_LINUX + +// *NOTE:Mani - eww, macros! srry. +#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \ + if (!cpuinfo[cpuinfo_id].empty()) \ + { setInfo(llpi_id, cpuinfo[cpuinfo_id]);} + +#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \ + {\ + S32 result; \ + if (!cpuinfo[cpuinfo_id].empty() \ + && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \ + { setInfo(llpi_id, result);} \ + } + const char CPUINFO_FILE[] = "/proc/cpuinfo"; class LLProcessorInfoLinuxImpl : public LLProcessorInfoImpl @@ -794,12 +808,36 @@ public: } virtual ~LLProcessorInfoLinuxImpl() {} + private: + F64 getCPUMaxMHZ() + { + // Nicky: We just look into cpu0. In theory we could iterate over all cores + // "/sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_max_freq" + // But those should not fluctuate that much? + llifstream fIn{ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" }; + + if( !fIn.is_open() ) + return 0.0; + + std::string strLine; + fIn >> strLine; + if( strLine.empty() ) + return 0.0l; + + F64 mhz {}; + if( !LLStringUtil::convertToF64(strLine, mhz ) ) + return 0.0; + + mhz = mhz / 1000.0; + return mhz; + } + void get_proc_cpuinfo() { std::map< std::string, std::string > cpuinfo; - LLFILE* cpuinfo_fp = LLFile::fopen(CPUINFO_FILE, "rb"); + LLFILE* cpuinfo_fp = LLFile::fopen(CPUINFO_FILE, LLFILE_MODE("rb")); if(cpuinfo_fp) { char line[MAX_STRING]; @@ -830,24 +868,17 @@ private: } # if LL_X86 -// *NOTE:Mani - eww, macros! srry. -#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \ - if (!cpuinfo[cpuinfo_id].empty()) \ - { setInfo(llpi_id, cpuinfo[cpuinfo_id]);} - -#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \ - {\ - S32 result; \ - if (!cpuinfo[cpuinfo_id].empty() \ - && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \ - { setInfo(llpi_id, result);} \ + F64 mhzFromSys = getCPUMaxMHZ(); + F64 mhzFromProc {}; + if( !LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhzFromProc ) ) + mhzFromProc = 0.0; + if (mhzFromSys > 1.0 && mhzFromSys > mhzFromProc ) + { + setInfo( eFrequency, mhzFromSys ); } - - F64 mhz; - if (LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhz) - && 200.0 < mhz && mhz < 10000.0) + else if ( 200.0 < mhzFromProc && mhzFromProc < 10000.0) { - setInfo(eFrequency,(F64)(mhz)); + setInfo(eFrequency,(F64)(mhzFromProc)); } LLPI_SET_INFO_STRING(eBrandName, "model name"); @@ -857,7 +888,7 @@ private: LLPI_SET_INFO_INT(eModel, "model"); - S32 family; + S32 family{}; if (!cpuinfo["cpu family"].empty() && LLStringUtil::convertToS32(cpuinfo["cpu family"], family)) { @@ -923,7 +954,7 @@ private: std::ostringstream s; // *NOTE:Mani - This is for linux only. - LLFILE* cpuinfo = LLFile::fopen(CPUINFO_FILE, "rb"); + LLFILE* cpuinfo = LLFile::fopen(CPUINFO_FILE, LLFILE_MODE("rb")); if(cpuinfo) { char line[MAX_STRING]; diff --git a/indra/llcommon/llprocinfo.cpp b/indra/llcommon/llprocinfo.cpp index 69be00e14a..9fe4af2103 100644 --- a/indra/llcommon/llprocinfo.cpp +++ b/indra/llcommon/llprocinfo.cpp @@ -25,6 +25,7 @@ * $/LicenseInfo$ */ +#include "linden_common.h" #include "llprocinfo.h" diff --git a/indra/llcommon/llprofiler.cpp b/indra/llcommon/llprofiler.cpp index bdddabf977..b2ac7723d4 100644 --- a/indra/llcommon/llprofiler.cpp +++ b/indra/llcommon/llprofiler.cpp @@ -25,6 +25,6 @@ * $/LicenseInfo$ */ -#include "linden_common.h" +#include "llpreprocessor.h" #include "TracyClient.cpp" diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index 5fb32d6280..7c77d94475 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -76,7 +76,16 @@ #if defined(LL_PROFILER_CONFIGURATION) && (LL_PROFILER_CONFIGURATION > LL_PROFILER_CONFIG_NONE) #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER + #include "llpreprocessor.h" + +#if defined(LL_GNUC) && GCC_VERSION >= 130000 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnonnull" +#endif #include "tracy/Tracy.hpp" +#if defined(LL_GNUC) && GCC_VERSION >= 130000 +# pragma GCC diagnostic push +#endif // Enable RenderDoc labeling //#define LL_PROFILER_ENABLE_RENDER_DOC 0 diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h index 1c4f0f5624..261fdf14b7 100644 --- a/indra/llcommon/llprofilercategories.h +++ b/indra/llcommon/llprofilercategories.h @@ -48,6 +48,7 @@ #define LL_PROFILER_CATEGORY_ENABLE_DRAWPOOL 1 #define LL_PROFILER_CATEGORY_ENABLE_ENVIRONMENT 1 #define LL_PROFILER_CATEGORY_ENABLE_FACE 1 +//#define LL_PROFILER_CATEGORY_ENABLE_FILE 1 #define LL_PROFILER_CATEGORY_ENABLE_LLSD 1 #define LL_PROFILER_CATEGORY_ENABLE_LOGGING 1 #define LL_PROFILER_CATEGORY_ENABLE_MATERIAL 1 @@ -126,6 +127,14 @@ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE #endif +#if LL_PROFILER_CATEGORY_ENABLE_FILE +#define LL_PROFILE_ZONE_NAMED_CATEGORY_FILE LL_PROFILE_ZONE_NAMED +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE LL_PROFILE_ZONE_SCOPED +#else +#define LL_PROFILE_ZONE_NAMED_CATEGORY_FILE(name) +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_FILE +#endif + #if LL_PROFILER_CATEGORY_ENABLE_LLSD #define LL_PROFILE_ZONE_NAMED_CATEGORY_LLSD LL_PROFILE_ZONE_NAMED #define LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD LL_PROFILE_ZONE_SCOPED diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index 2c51e6f07f..513613f543 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,9 +58,26 @@ * to restore uniform distribution. */ -// gRandomGenerator is a stateful static object, which is therefore not +// pRandomGenerator is a stateful static object, which is therefore not // inherently thread-safe. -static thread_local LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); +//We use a pointer to not construct a huge object in the TLS space, sadly this is necessary +// due to libcef.so on Linux being compiled with TLS model initial-exec (resulting in +// FLAG STATIC_TLS, see readelf libcef.so). CEFs own TLS objects + LLRandLagFib2281 then will exhaust the +// available TLS space, causing media failure. + +static thread_local std::unique_ptr< LLRandLagFib2281 > pRandomGenerator = nullptr; + +namespace { + F64 ll_internal_get_rand() + { + if( !pRandomGenerator ) + { + pRandomGenerator.reset(new LLRandLagFib2281(LLUUID::getRandomSeed( ) )); + } + + return(*pRandomGenerator)(); + } +} // no default implementation, only specific F64 and F32 specializations template <typename REAL> @@ -73,7 +90,7 @@ inline F64 ll_internal_random<F64>() // CPUs (or at least multi-threaded processes) seem to // occasionally give an obviously incorrect random number -- like // 5^15 or something. Sooooo, clamp it as described above. - F64 rv{ gRandomGenerator() }; + F64 rv{ ll_internal_get_rand() }; if(!((rv >= 0.0) && (rv < 1.0))) return fmod(rv, 1.0); return rv; } @@ -85,7 +102,7 @@ inline F32 ll_internal_random<F32>() // Per Monty, it's important to clamp using the correct fmodf() rather // than expanding to F64 for fmod() and then truncating back to F32. Prior // to this change, we were getting sporadic ll_frand() == 1.0 results. - F32 rv{ narrow<F64>(gRandomGenerator()) }; + F32 rv{ narrow<F64>(ll_internal_get_rand()) }; if(!((rv >= 0.0f) && (rv < 1.0f))) return fmodf(rv, 1.0f); return rv; } diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index ce39bc7af3..ad2ce231fa 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -32,7 +32,6 @@ #include "llbase64.h" #include "llerror.h" -#include "../llmath/llmath.h" #include "llformat.h" #include "llsdserialize.h" #include "stringize.h" @@ -297,10 +296,10 @@ namespace }; LLSD::Boolean ImplReal::asBoolean() const - { return !llisnan(mValue) && mValue != 0.0; } + { return !std::isnan(mValue) && mValue != 0.0; } LLSD::Integer ImplReal::asInteger() const - { return !llisnan(mValue) ? (LLSD::Integer)mValue : 0; } + { return !std::isnan(mValue) ? (LLSD::Integer)mValue : 0; } LLSD::String ImplReal::asString() const { return llformat("%lg", mValue); } diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index bb806f32eb..acd4bafb85 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -26,14 +26,12 @@ // Must turn on conditional declarations in header file so definitions end up // with proper linkage. -#define LLSD_DEBUG_INFO #include "linden_common.h" #include "llsdjson.h" #include "llsdutil.h" #include "llerror.h" -#include "../llmath/llmath.h" #include <boost/json/src.hpp> diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index ee7f0b01cc..7fe1f9cdd4 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1421,11 +1421,7 @@ S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, case LLSD::TypeBoolean: if(mBoolAlpha || -#if( LL_WINDOWS || __GNUC__ > 2) (ostr.flags() & std::ios::boolalpha) -#else - (ostr.flags() & 0x0100) -#endif ) { ostr << (data.asBoolean() @@ -2391,6 +2387,7 @@ U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size { case Z_NEED_DICT: ret = Z_DATA_ERROR; + [[fallthrough]]; case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&strm); diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index dbd89118c9..fd01484128 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -503,7 +503,7 @@ bool filter_llsd_with_template( * Helpers for llsd_matches() *****************************************************************************/ // raw data used for LLSD::Type lookup -struct Data +struct LLSDData { LLSD::Type type; const char* name; @@ -534,7 +534,7 @@ public: { LL_PROFILE_ZONE_SCOPED; - for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) + for (const LLSDData*di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) { mMap[di->type] = di->name; } diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index e6989211ae..ebef2489ce 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -30,7 +30,7 @@ #include <typeinfo> #include <unordered_set> #include <vector> -#include "mutex.h" +#include <mutex> #include "lockstatic.h" #include "llthread.h" // on_main_thread() #include "llmainthreadtask.h" diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 7a1c7caf82..550725cbc8 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -519,18 +519,8 @@ llbind2nd(const _Operation& __oper, const _Tp& __x) inline bool before(const std::type_info* lhs, const std::type_info* rhs) { -#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4)) - // If we're building on Linux with gcc, and it's either gcc 3.x or - // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on - // Mac too, and some people build with gcc on Windows (cygwin or mingw). - // On Linux, different load modules may produce different type_info* - // pointers for the same type. Have to compare name strings to get good - // results. - return strcmp(lhs->name(), rhs->name()) < 0; -#else // not Linux, or gcc 4.4+ // Just use before(), as we normally would return lhs->before(*rhs); -#endif } /** diff --git a/indra/llcommon/llstreamqueue.cpp b/indra/llcommon/llstreamqueue.cpp deleted file mode 100644 index 981d913749..0000000000 --- a/indra/llcommon/llstreamqueue.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file llstreamqueue.cpp - * @author Nat Goodspeed - * @date 2012-01-05 - * @brief Implementation for llstreamqueue. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Copyright (c) 2012, Linden Research, Inc. - * $/LicenseInfo$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "llstreamqueue.h" -// STL headers -// std headers -// external library headers -// other Linden headers - -// As of this writing, llstreamqueue.h is entirely template-based, therefore -// we don't strictly need a corresponding .cpp file. However, our CMake test -// macro assumes one. Here it is. -bool llstreamqueue_cpp_ignored = true; diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h deleted file mode 100644 index 01689457dd..0000000000 --- a/indra/llcommon/llstreamqueue.h +++ /dev/null @@ -1,240 +0,0 @@ -/** - * @file llstreamqueue.h - * @author Nat Goodspeed - * @date 2012-01-04 - * @brief Definition of LLStreamQueue - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Copyright (c) 2012, Linden Research, Inc. - * $/LicenseInfo$ - */ - -#if ! defined(LL_LLSTREAMQUEUE_H) -#define LL_LLSTREAMQUEUE_H - -#include <string> -#include <list> -#include <iosfwd> // std::streamsize -#include <boost/iostreams/categories.hpp> - -/** - * This class is a growable buffer between a producer and consumer. It serves - * as a queue usable with Boost.Iostreams -- hence, a "stream queue." - * - * This is especially useful for buffering nonblocking I/O. For instance, we - * want application logic to be able to serialize LLSD to a std::ostream. We - * may write more data than the destination pipe can handle all at once, but - * it's imperative NOT to block the application-level serialization call. So - * we buffer it instead. Successive frames can try nonblocking writes to the - * destination pipe until all buffered data has been sent. - * - * Similarly, we want application logic be able to deserialize LLSD from a - * std::istream. Again, we must not block that deserialize call waiting for - * more data to arrive from the input pipe! Instead we build up a buffer over - * a number of frames, using successive nonblocking reads, until we have - * "enough" data to be able to present it through a std::istream. - * - * @note The use cases for this class overlap somewhat with those for the - * LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This - * class has two virtues over the older machinery: - * - * # It's vastly simpler -- way fewer concepts. It's not clear to me whether - * there were ever LLIOPipe/etc. use cases that demanded all the fanciness - * rolled in, or whether they were simply overdesigned. In any case, no - * remaining Lindens will admit to familiarity with those classes -- and - * they're sufficiently obtuse that it would take considerable learning - * curve to figure out how to use them properly. The bottom line is that - * current management is not keen on any more engineers climbing that curve. - * # This class is designed around available components such as std::string, - * std::list, Boost.Iostreams. There's less proprietary code. - */ -template <typename Ch> -class LLGenericStreamQueue -{ -public: - LLGenericStreamQueue(): - mSize(0), - mClosed(false) - {} - - /** - * Boost.Iostreams Source Device facade for use with other Boost.Iostreams - * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost - * 1.48 Iostreams concepts; instead it behaves as both a Sink and a - * Source. This is its Source facade. - */ - struct Source - { - typedef Ch char_type; - typedef boost::iostreams::source_tag category; - - /// Bind the underlying LLGenericStreamQueue - Source(LLGenericStreamQueue& sq): - mStreamQueue(sq) - {} - - // Read up to n characters from the underlying data source into the - // buffer s, returning the number of characters read; return -1 to - // indicate EOF - std::streamsize read(Ch* s, std::streamsize n) - { - return mStreamQueue.read(s, n); - } - - LLGenericStreamQueue& mStreamQueue; - }; - - /** - * Boost.Iostreams Sink Device facade for use with other Boost.Iostreams - * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost - * 1.48 Iostreams concepts; instead it behaves as both a Sink and a - * Source. This is its Sink facade. - */ - struct Sink - { - typedef Ch char_type; - typedef boost::iostreams::sink_tag category; - - /// Bind the underlying LLGenericStreamQueue - Sink(LLGenericStreamQueue& sq): - mStreamQueue(sq) - {} - - /// Write up to n characters from the buffer s to the output sequence, - /// returning the number of characters written - std::streamsize write(const Ch* s, std::streamsize n) - { - return mStreamQueue.write(s, n); - } - - /// Send EOF to consumer - void close() - { - mStreamQueue.close(); - } - - LLGenericStreamQueue& mStreamQueue; - }; - - /// Present Boost.Iostreams Source facade - Source asSource() { return Source(*this); } - /// Present Boost.Iostreams Sink facade - Sink asSink() { return Sink(*this); } - - /// append data to buffer - std::streamsize write(const Ch* s, std::streamsize n) - { - // Unclear how often we might be asked to write 0 bytes -- perhaps a - // naive caller responding to an unready nonblocking read. But if we - // do get such a call, don't add a completely empty BufferList entry. - if (n == 0) - return n; - // We could implement this using a single std::string object, a la - // ostringstream. But the trouble with appending to a string is that - // you might have to recopy all previous contents to grow its size. If - // we want this to scale to large data volumes, better to allocate - // individual pieces. - mBuffer.push_back(string(s, n)); - mSize += n; - return n; - } - - /** - * Inform this LLGenericStreamQueue that no further data are forthcoming. - * For our purposes, close() is strictly a producer-side operation; - * there's little point in closing the consumer side. - */ - void close() - { - mClosed = true; - } - - /// consume data from buffer - std::streamsize read(Ch* s, std::streamsize n) - { - // read() is actually a convenience method for peek() followed by - // skip(). - std::streamsize got(peek(s, n)); - // We can only skip() as many characters as we can peek(); ignore - // skip() return here. - skip(n); - return got; - } - - /// Retrieve data from buffer without consuming. Like read(), return -1 on - /// EOF. - std::streamsize peek(Ch* s, std::streamsize n) const; - - /// Consume data from buffer without retrieving. Unlike read() and peek(), - /// at EOF we simply skip 0 characters. - std::streamsize skip(std::streamsize n); - - /// How many characters do we currently have buffered? - std::streamsize size() const - { - return mSize; - } - -private: - typedef std::basic_string<Ch> string; - typedef std::list<string> BufferList; - BufferList mBuffer; - std::streamsize mSize; - bool mClosed; -}; - -template <typename Ch> -std::streamsize LLGenericStreamQueue<Ch>::peek(Ch* s, std::streamsize n) const -{ - // Here we may have to build up 'n' characters from an arbitrary - // number of individual BufferList entries. - typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end()); - // Indicate EOF if producer has closed the pipe AND we've exhausted - // all previously-buffered data. - if (mClosed && bli == blend) - { - return -1; - } - // Here either producer hasn't yet closed, or we haven't yet exhausted - // remaining data. - std::streamsize needed(n), got(0); - // Loop until either we run out of BufferList entries or we've - // completely satisfied the request. - for ( ; bli != blend && needed; ++bli) - { - std::streamsize chunk(std::min(needed, std::streamsize(bli->length()))); - std::copy(bli->begin(), bli->begin() + chunk, s); - needed -= chunk; - s += chunk; - got += chunk; - } - return got; -} - -template <typename Ch> -std::streamsize LLGenericStreamQueue<Ch>::skip(std::streamsize n) -{ - typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end()); - std::streamsize toskip(n), skipped(0); - while (bli != blend && (size_t)toskip >= bli->length()) - { - std::streamsize chunk(bli->length()); - typename BufferList::iterator zap(bli++); - mBuffer.erase(zap); - mSize -= chunk; - toskip -= chunk; - skipped += chunk; - } - if (bli != blend && toskip) - { - bli->erase(bli->begin(), bli->begin() + toskip); - mSize -= toskip; - skipped += toskip; - } - return skipped; -} - -typedef LLGenericStreamQueue<char> LLStreamQueue; -typedef LLGenericStreamQueue<wchar_t> LLWStreamQueue; - -#endif /* ! defined(LL_LLSTREAMQUEUE_H) */ diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 803bab393c..5ef042b5a7 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -506,57 +506,46 @@ const S32 LLOSInfo::getOSBitness() const return mOSBitness; } -//static -U32 LLOSInfo::getProcessVirtualSizeKB() -{ - U32 virtual_size = 0; -#if LL_LINUX -# define STATUS_SIZE 2048 - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ +namespace { - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; + U32 readFromProcStat( std::string entryName ) + { + U32 val{}; +#if LL_LINUX + constexpr U32 STATUS_SIZE = 2048; - // All these guys return numbers in KB - char *memp = strstr(buff, "VmSize:"); - if (memp) + LLFILE* status_filep = LLFile::fopen("/proc/self/status", LLFILE_MODE("rb")); + if (status_filep) { - numRead += sscanf(memp, "%*s %u", &virtual_size); + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, entryName.c_str()); + if (memp) + { + (void) sscanf(memp, "%*s %u", &val); + } + fclose(status_filep); } - fclose(status_filep); - } #endif - return virtual_size; + return val; + } + } //static -U32 LLOSInfo::getProcessResidentSizeKB() +U32 LLOSInfo::getProcessVirtualSizeKB() { - U32 resident_size = 0; -#if LL_LINUX - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep != NULL) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; + return readFromProcStat( "VmSize:" ); +} - // All these guys return numbers in KB - char *memp = strstr(buff, "VmRSS:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &resident_size); - } - fclose(status_filep); - } -#endif - return resident_size; +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + return readFromProcStat( "VmRSS:" ); } //static @@ -721,7 +710,7 @@ public: void add(const LLSD::String& name, const T& value, typename std::enable_if_t<std::is_integral_v<T> >* = 0) { - mStats[name] = LLSD::Integer(value); + mStats[name] = LLSD::Integer(llmin<T>(value, S32_MAX)); } // Store every floating-point type as LLSD::Real. @@ -830,57 +819,8 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb) } #elif LL_LINUX - // mStatsMap is derived from MEMINFO_FILE: - // $ cat /proc/meminfo - // MemTotal: 4108424 kB - // MemFree: 1244064 kB - // Buffers: 85164 kB - // Cached: 1990264 kB - // SwapCached: 0 kB - // Active: 1176648 kB - // Inactive: 1427532 kB - // Active(anon): 529152 kB - // Inactive(anon): 15924 kB - // Active(file): 647496 kB - // Inactive(file): 1411608 kB - // Unevictable: 16 kB - // Mlocked: 16 kB - // HighTotal: 3266316 kB - // HighFree: 721308 kB - // LowTotal: 842108 kB - // LowFree: 522756 kB - // SwapTotal: 6384632 kB - // SwapFree: 6384632 kB - // Dirty: 28 kB - // Writeback: 0 kB - // AnonPages: 528820 kB - // Mapped: 89472 kB - // Shmem: 16324 kB - // Slab: 159624 kB - // SReclaimable: 145168 kB - // SUnreclaim: 14456 kB - // KernelStack: 2560 kB - // PageTables: 5560 kB - // NFS_Unstable: 0 kB - // Bounce: 0 kB - // WritebackTmp: 0 kB - // CommitLimit: 8438844 kB - // Committed_AS: 1271596 kB - // VmallocTotal: 122880 kB - // VmallocUsed: 65252 kB - // VmallocChunk: 52356 kB - // HardwareCorrupted: 0 kB - // HugePages_Total: 0 - // HugePages_Free: 0 - // HugePages_Rsvd: 0 - // HugePages_Surp: 0 - // Hugepagesize: 2048 kB - // DirectMap4k: 434168 kB - // DirectMap2M: 477184 kB - // (could also run 'free', but easier to read a file than run a program) - LLSD statsMap(loadStatsMap()); - - avail_mem_kb = (U32Kilobytes)statsMap["MemFree"].asInteger(); + U64 phys = U64(getpagesize()) * U64(get_avphys_pages()); + avail_mem_kb = U64Bytes(phys); #else //do not know how to collect available memory info for other systems. //leave it blank here for now. @@ -1080,7 +1020,7 @@ LLSD LLMemoryInfo::loadStatsMap() } #elif LL_LINUX - std::ifstream meminfo(MEMINFO_FILE); + llifstream meminfo(MEMINFO_FILE); if (meminfo.is_open()) { // MemTotal: 4108424 kB @@ -1120,9 +1060,10 @@ LLSD LLMemoryInfo::loadStatsMap() LLSD::String key(matched[1].first, matched[1].second); LLSD::String value_str(matched[2].first, matched[2].second); LLSD::Integer value(0); + S64 intval = 0; try { - value = boost::lexical_cast<LLSD::Integer>(value_str); + intval = llclamp(boost::lexical_cast<S64>(value_str), S32_MIN, S32_MAX); } catch (const boost::bad_lexical_cast&) { @@ -1131,6 +1072,7 @@ LLSD LLMemoryInfo::loadStatsMap() << line << LL_ENDL; continue; } + value = LLSD::Integer(intval); // Store this statistic. stats.add(key, value); } @@ -1331,7 +1273,7 @@ bool gunzip_file(const std::string& srcfile, const std::string& dstfile) src = gzopen(srcfile.c_str(), "rb"); #endif if (! src) goto err; - dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ + dst = LLFile::fopen(tmpfile, LLFILE_MODE("wb")); /* Flawfinder: ignore */ if (! dst) goto err; do { @@ -1372,7 +1314,7 @@ bool gzip_file(const std::string& srcfile, const std::string& dstfile) #endif if (! dst) goto err; - src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ + src = LLFile::fopen(srcfile, LLFILE_MODE("rb")); /* Flawfinder: ignore */ if (! src) goto err; while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 4f15626108..277edcb6a2 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -42,6 +42,10 @@ #include <sched.h> #endif +#if LL_DARWIN || LL_LINUX +#include <pthread.h> +#endif + #ifdef LL_WINDOWS @@ -56,25 +60,32 @@ typedef struct tagTHREADNAME_INFO DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; #pragma pack(pop) +#endif -void set_thread_name( DWORD dwThreadID, const char* threadName) +void set_thread_name(const char* threadName) { +#if LL_WINDOWS THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = threadName; - info.dwThreadID = dwThreadID; - info.dwFlags = 0; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; __try { - ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info ); + ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR*)&info); } - __except(EXCEPTION_CONTINUE_EXECUTION) + __except (EXCEPTION_CONTINUE_EXECUTION) { } -} +#elif LL_DARWIN + std::string truncated_name(std::string_view(threadName).substr(0, 15)); + pthread_setname_np(truncated_name.c_str()); +#elif LL_LINUX + std::string truncated_name(std::string_view(threadName).substr(0, 15)); + pthread_setname_np(pthread_self(), truncated_name.c_str()); #endif - +} //---------------------------------------------------------------------------- // Usage: @@ -148,27 +159,12 @@ LL_COMMON_API bool assert_main_thread() return false; } -// this function has become moot -void LLThread::registerThreadID() {} - // // Handed to the APR thread creation function // void LLThread::threadRun() { -#ifdef LL_WINDOWS - set_thread_name(-1, mName.c_str()); - -#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) - HANDLE hThread = GetCurrentThread(); - if (hThread) - { - SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); - } -#endif - -#endif - + set_thread_name(mName.c_str()); LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); // this is the first point at which we're actually running in the new thread diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 8794ac93aa..b97d479abc 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -28,10 +28,11 @@ #define LL_LLTHREAD_H #include "llapr.h" -#include "boost/intrusive_ptr.hpp" #include "llrefcount.h" #include <thread> +extern void set_thread_name(const char* threadName); + namespace LLTrace { class ThreadRecorder; @@ -86,11 +87,6 @@ public: id_t getID() const { return mID; } - // Called by threads *not* created via LLThread to register some - // internal state used by LLMutex. You must call this once early - // in the running thread to prevent collisions with the main thread. - static void registerThreadID(); - private: bool mPaused; std::thread::native_handle_type mNativeHandle; // for termination in case of issues diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index 1a1d06a6fd..2a18b4a438 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -32,7 +32,7 @@ #include <boost/fiber/timed_mutex.hpp> #include LLCOROS_CONDVAR_HEADER #include "llexception.h" -#include "mutex.h" +#include <mutex> #include <chrono> #include <queue> #include <string> diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp index c23adca7e8..f010f0e961 100644 --- a/indra/llcommon/lltracerecording.cpp +++ b/indra/llcommon/lltracerecording.cpp @@ -32,11 +32,6 @@ #include "lltracethreadrecorder.h" #include "llthread.h" -inline F64 lerp(F64 a, F64 b, F64 u) -{ - return a + ((b - a) * u); -} - namespace LLTrace { @@ -302,7 +297,7 @@ F64 Recording::getMean( const StatType<SampleAccumulator>& stat ) { t = (F64)active_accumulator->getSampleCount() / (F64)div; } - return lerp(accumulator.getMean(), active_accumulator->getMean(), t); + return std::lerp(accumulator.getMean(), active_accumulator->getMean(), t); } else { @@ -388,7 +383,7 @@ F64 Recording::getMean( const StatType<EventAccumulator>& stat ) { t = (F64)active_accumulator->getSampleCount() / (F64)div; } - return lerp(accumulator.getMean(), active_accumulator->getMean(), t); + return std::lerp(accumulator.getMean(), active_accumulator->getMean(), t); } else { diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 4aae90626e..867744a29d 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -151,6 +151,48 @@ U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) } #endif +void LLUUID::to_chars(char* out) const +{ + snprintf(out, UUID_STR_LENGTH, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (U8)(mData[0]), + (U8)(mData[1]), + (U8)(mData[2]), + (U8)(mData[3]), + (U8)(mData[4]), + (U8)(mData[5]), + (U8)(mData[6]), + (U8)(mData[7]), + (U8)(mData[8]), + (U8)(mData[9]), + (U8)(mData[10]), + (U8)(mData[11]), + (U8)(mData[12]), + (U8)(mData[13]), + (U8)(mData[14]), + (U8)(mData[15])); +} + +void LLUUID::to_wchars(wchar_t* out) const +{ + swprintf(out, UUID_STR_LENGTH, L"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (U8)(mData[0]), + (U8)(mData[1]), + (U8)(mData[2]), + (U8)(mData[3]), + (U8)(mData[4]), + (U8)(mData[5]), + (U8)(mData[6]), + (U8)(mData[7]), + (U8)(mData[8]), + (U8)(mData[9]), + (U8)(mData[10]), + (U8)(mData[11]), + (U8)(mData[12]), + (U8)(mData[13]), + (U8)(mData[14]), + (U8)(mData[15])); +} + // Common to all UUID implementations void LLUUID::toString(std::string& out) const { @@ -405,8 +447,8 @@ LLUUID LLUUID::combine(const LLUUID& other) const std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) { - std::string uuid_str; - uuid.toString(uuid_str); + char uuid_str[UUID_STR_LENGTH]; + uuid.to_chars(uuid_str); s << uuid_str; return s; } diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index f91aadccc0..122cf414ca 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -104,6 +104,8 @@ public: 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 to_chars(char* out) const; + void to_wchars(wchar_t* out) const; void toString(std::string& out) const; void toCompressedString(std::string& out) const; diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h index 7cc9b7eec0..db4c7af0ec 100644 --- a/indra/llcommon/lockstatic.h +++ b/indra/llcommon/lockstatic.h @@ -13,7 +13,7 @@ #if ! defined(LL_LOCKSTATIC_H) #define LL_LOCKSTATIC_H -#include "mutex.h" // std::unique_lock +#include <mutex> // std::unique_lock namespace llthread { diff --git a/indra/llcommon/mutex.h b/indra/llcommon/mutex.h deleted file mode 100644 index 82e46315e2..0000000000 --- a/indra/llcommon/mutex.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @file mutex.h - * @author Nat Goodspeed - * @date 2019-12-03 - * @brief Wrap <mutex> in odious boilerplate - * - * $LicenseInfo:firstyear=2019&license=viewerlgpl$ - * Copyright (c) 2019, Linden Research, Inc. - * $/LicenseInfo$ - */ - -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable:4265) -#endif -// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual - -#include <mutex> - -#if LL_WINDOWS -#pragma warning (pop) -#endif diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index f1e4c2bc78..6b98a6bc49 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -38,6 +38,11 @@ typedef signed short S16; typedef unsigned short U16; typedef signed int S32; typedef unsigned int U32; +typedef long long int S64; +typedef long long unsigned int U64; + +#define S64L(a) (a##LL) +#define U64L(a) (a##ULL) // to express an index that might go negative // (ssize_t is provided by SOME compilers, don't collide) @@ -62,21 +67,6 @@ typedef wchar_t llwchar; #define LLWCHAR_IS_WCHAR_T 1 #endif -#if LL_WINDOWS -typedef signed __int64 S64; -// probably should be 'hyper' or similiar -#define S64L(a) (a) -typedef unsigned __int64 U64; -#define U64L(a) (a) -#else -typedef long long int S64; -typedef long long unsigned int U64; -#if LL_DARWIN || LL_LINUX -#define S64L(a) (a##LL) -#define U64L(a) (a##ULL) -#endif -#endif - typedef float F32; typedef double F64; @@ -121,10 +111,6 @@ typedef U8 LLPCode; #define LL_ARRAY_SIZE( _kArray ) ( sizeof( (_kArray) ) / sizeof( _kArray[0] ) ) -#if LL_LINUX && __GNUC__ <= 2 -typedef int intptr_t; -#endif - /***************************************************************************** * Narrowing *****************************************************************************/ diff --git a/indra/llcommon/tests/commonmisc_test.cpp b/indra/llcommon/tests/commonmisc_test.cpp index b1a284225e..80be26dfeb 100644 --- a/indra/llcommon/tests/commonmisc_test.cpp +++ b/indra/llcommon/tests/commonmisc_test.cpp @@ -503,7 +503,7 @@ namespace tut result_str = (const char*) result; ensure_equals("U64_to_str converted 1.4", val_str, result_str); - val = U64L(-1); // 0xFFFFFFFFFFFFFFFF == 18446744073709551615 + val = U64(-1); // 0xFFFFFFFFFFFFFFFF == 18446744073709551615 val_str = "18446744073709551615"; U64_to_str(val, result, sizeof(result)); result_str = (const char*) result; @@ -619,7 +619,7 @@ namespace tut val = U64_to_F64(U64L(2)); ensure_equals("U64_to_F64 converted 3.6", val, result); - result = U64L(0x7FFFFFFFFFFFFFFF) * 1.0L; // 0x7FFFFFFFFFFFFFFF + result = F64(0x7FFFFFFFFFFFFFFF) * 1.0; // 0x7FFFFFFFFFFFFFFF val = U64_to_F64(U64L(0x7FFFFFFFFFFFFFFF)); ensure_equals("U64_to_F64 converted 3.7", val, result); } diff --git a/indra/llcommon/tests/llallocator_heap_profile_test.cpp b/indra/llcommon/tests/llallocator_heap_profile_test.cpp deleted file mode 100644 index da8b54ffc0..0000000000 --- a/indra/llcommon/tests/llallocator_heap_profile_test.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @file llallocator_heap_profile_test.cpp - * @author Brad Kittenbrink - * @date 2008-02- - * @brief Test for llallocator_heap_profile.cpp. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "../llallocator_heap_profile.h" -#include "../test/lltut.h" - -namespace tut -{ - struct llallocator_heap_profile_data - { - LLAllocatorHeapProfile prof; - - static char const * const sample_win_profile; - // *TODO - get test output from mac/linux tcmalloc - static char const * const sample_mac_profile; - static char const * const sample_lin_profile; - - static char const * const crash_testcase; - }; - typedef test_group<llallocator_heap_profile_data> factory; - typedef factory::object object; -} -namespace -{ - tut::factory llallocator_heap_profile_test_factory("LLAllocatorHeapProfile"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() - { - prof.parse(sample_win_profile); - - ensure_equals("count lines", prof.mLines.size() , 5); - ensure_equals("alloc counts", prof.mLines[0].mLiveCount, 2131854U); - ensure_equals("alloc counts", prof.mLines[0].mLiveSize, 2245710106ULL); - ensure_equals("alloc counts", prof.mLines[0].mTotalCount, 14069198U); - ensure_equals("alloc counts", prof.mLines[0].mTotalSize, 4295177308ULL); - ensure_equals("count markers", prof.mLines[0].mTrace.size(), 0); - ensure_equals("count markers", prof.mLines[1].mTrace.size(), 0); - ensure_equals("count markers", prof.mLines[2].mTrace.size(), 4); - ensure_equals("count markers", prof.mLines[3].mTrace.size(), 6); - ensure_equals("count markers", prof.mLines[4].mTrace.size(), 7); - - //prof.dump(std::cout); - } - - template<> template<> - void object::test<2>() - { - prof.parse(crash_testcase); - - ensure_equals("count lines", prof.mLines.size(), 2); - ensure_equals("alloc counts", prof.mLines[0].mLiveCount, 3U); - ensure_equals("alloc counts", prof.mLines[0].mLiveSize, 1049652ULL); - ensure_equals("alloc counts", prof.mLines[0].mTotalCount, 8U); - ensure_equals("alloc counts", prof.mLines[0].mTotalSize, 1049748ULL); - ensure_equals("count markers", prof.mLines[0].mTrace.size(), 0); - ensure_equals("count markers", prof.mLines[1].mTrace.size(), 0); - - //prof.dump(std::cout); - } - - template<> template<> - void object::test<3>() - { - // test that we don't crash on edge case data - prof.parse(""); - ensure("emtpy on error", prof.mLines.empty()); - - prof.parse("heap profile:"); - ensure("emtpy on error", prof.mLines.empty()); - } - -char const * const llallocator_heap_profile_data::sample_win_profile = -"heap profile: 2131854: 2245710106 [14069198: 4295177308] @\n" -"308592: 1073398388 [966564: 1280998739] @\n" -"462651: 375969538 [1177377: 753561247] @ 2 3 6 1\n" -"314744: 206611283 [2008722: 570934755] @ 2 3 3 7 21 32\n" -"277152: 82862770 [621961: 168503640] @ 2 3 3 7 21 32 87\n" -"\n" -"MAPPED_LIBRARIES:\n" -"00400000-02681000 r-xp 00000000 00:00 0 c:\\proj\\tcmalloc-eval-9\\indra\\build-vc80\\newview\\RelWithDebInfo\\secondlife-bin.exe\n" -"77280000-773a7000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\ntdll.dll\n" -"76df0000-76ecb000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\kernel32.dll\n" -"76000000-76073000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\comdlg32.dll\n" -"75ee0000-75f8a000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\msvcrt.dll\n" -"76c30000-76c88000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\SHLWAPI.dll\n" -"75f90000-75fdb000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\GDI32.dll\n" -"77420000-774bd000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\USER32.dll\n" -"75e10000-75ed6000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\ADVAPI32.dll\n" -"75b00000-75bc2000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\RPCRT4.dll\n" -"72ca0000-72d25000 r-xp 00000000 00:00 0 C:\\Windows\\WinSxS\\x86_microsoft.windows.common-controls_6595b64144ccf1df_5.82.6001.18000_none_886786f450a74a05\\COMCTL32.dll\n" -"76120000-76c30000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\SHELL32.dll\n" -"71ce0000-71d13000 r-xp 00000000 00:00 0 C:\\Windows\\system32\\DINPUT8.dll\n"; - - -char const * const llallocator_heap_profile_data::crash_testcase = -"heap profile: 3: 1049652 [ 8: 1049748] @\n" -" 3: 1049652 [ 8: 1049748] @\n" -"\n" -"MAPPED_LIBRARIES:\n" -"00400000-004d5000 r-xp 00000000 00:00 0 c:\\code\\linden\\tcmalloc\\indra\\build-vc80\\llcommon\\RelWithDebInfo\\llallocator_test.exe\n" -"7c900000-7c9af000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\ntdll.dll\n" -"7c800000-7c8f6000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\kernel32.dll\n" -"77dd0000-77e6b000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\ADVAPI32.dll\n" -"77e70000-77f02000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\RPCRT4.dll\n" -"77fe0000-77ff1000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\Secur32.dll\n" -"71ab0000-71ac7000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\WS2_32.dll\n" -"77c10000-77c68000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\msvcrt.dll\n" -"71aa0000-71aa8000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\WS2HELP.dll\n" -"76bf0000-76bfb000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\PSAPI.DLL\n" -"5b860000-5b8b5000 r-xp 00000000 00:00 0 C:\\WINDOWS\\system32\\NETAPI32.dll\n" -"10000000-10041000 r-xp 00000000 00:00 0 c:\\code\\linden\\tcmalloc\\indra\\build-vc80\\llcommon\\RelWithDebInfo\\libtcmalloc_minimal.dll\n" -"7c420000-7c4a7000 r-xp 00000000 00:00 0 C:\\WINDOWS\\WinSxS\\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.1433_x-ww_5cf844d2\\MSVCP80.dll\n" -"78130000-781cb000 r-xp 00000000 00:00 0 C:\\WINDOWS\\WinSxS\\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.1433_x-ww_5cf844d2\\MSVCR80.dll\n"; - -} diff --git a/indra/llcommon/tests/llallocator_test.cpp b/indra/llcommon/tests/llallocator_test.cpp deleted file mode 100644 index a7573641c7..0000000000 --- a/indra/llcommon/tests/llallocator_test.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @file llallocator_test.cpp - * @author Brad Kittenbrink - * @date 2008-02- - * @brief Test for llallocator.cpp. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "../llallocator.h" -#include "../test/lltut.h" - -namespace tut -{ - struct llallocator_data - { - LLAllocator llallocator; - }; - typedef test_group<llallocator_data> factory; - typedef factory::object object; -} -namespace -{ - tut::factory llallocator_test_factory("LLAllocator"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() - { - llallocator.setProfilingEnabled(false); - ensure("Profiler disable", !llallocator.isProfiling()); - } - -#if LL_USE_TCMALLOC - template<> template<> - void object::test<2>() - { - llallocator.setProfilingEnabled(true); - ensure("Profiler enable", llallocator.isProfiling()); - } - - template <> template <> - void object::test<3>() - { - llallocator.setProfilingEnabled(true); - - char * test_alloc = new char[1024]; - - llallocator.getProfile(); - - delete [] test_alloc; - - llallocator.getProfile(); - - // *NOTE - this test isn't ensuring anything right now other than no - // exceptions are thrown. - } -#endif // LL_USE_TCMALLOC -}; diff --git a/indra/llcommon/tests/llapp_test.cpp b/indra/llcommon/tests/llapp_test.cpp new file mode 100644 index 0000000000..114af6edbf --- /dev/null +++ b/indra/llcommon/tests/llapp_test.cpp @@ -0,0 +1,162 @@ +/** + * @file llapp_tut.cpp + * @author Phoenix + * @date 2006-09-12 + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2006-2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include <tut/tut.hpp> + +#include "linden_common.h" +#include "llapp.h" +#include "lltut.h" + + +namespace tut +{ + struct application + { + class LLTestApp : public LLApp + { + public: + virtual bool init() { return true; } + virtual bool cleanup() { return true; } + virtual bool frame() { return true; } + }; + LLTestApp* mApp; + application() + { + mApp = new LLTestApp; + } + ~application() + { + delete mApp; + } + }; + + typedef test_group<application> application_t; + typedef application_t::object application_object_t; + tut::application_t tut_application("application"); + + template<> template<> + void application_object_t::test<1>() + { + LLSD defaults; + defaults["template"] = "../../../scripts/messages/message_template.msg"; + defaults["configdir"] = "."; + defaults["datadir"] = "data"; + mApp->setOptionData(LLApp::PRIORITY_DEFAULT, defaults); + + LLSD datadir_sd = mApp->getOption("datadir"); + ensure_equals("data type", datadir_sd.type(), LLSD::TypeString); + ensure_equals( + "data value", datadir_sd.asString(), std::string("data")); + } + + template<> template<> + void application_object_t::test<2>() + { + const int ARGC = 13; + const char* ARGV[ARGC] = + { + "", // argv[0] is usually the application name + "-crashcount", + "2", + "-space", + "spaceserver.grid.lindenlab.com", + "-db_host", + "localhost", + "--allowlslhttprequests", + "-asset-uri", + "http://test.lindenlab.com/assets", + "-data", + "127.0.0.1", + "--smtp" + }; + bool ok = mApp->parseCommandOptions(ARGC, const_cast<char**>(ARGV)); + ensure("command line parsed", ok); + ensure_equals( + "crashcount", mApp->getOption("crashcount").asInteger(), 2); + ensure_equals( + "space", + mApp->getOption("space").asString(), + std::string("spaceserver.grid.lindenlab.com")); + ensure_equals( + "db_host", + mApp->getOption("db_host").asString(), + std::string("localhost")); + ensure("allowlshlttprequests", mApp->getOption("smtp")); + ensure_equals( + "asset-uri", + mApp->getOption("asset-uri").asString(), + std::string("http://test.lindenlab.com/assets")); + ensure_equals( + "data", + mApp->getOption("data").asString(), + std::string("127.0.0.1")); + ensure("smtp", mApp->getOption("smtp")); + } + + template<> template<> + void application_object_t::test<3>() + { + const int ARGC = 4; + const char* ARGV[ARGC] = + { + "", // argv[0] is usually the application name + "crashcount", + "2", + "--space" + }; + bool ok = mApp->parseCommandOptions(ARGC, const_cast<char**>(ARGV)); + ensure("command line parse failure", !ok); + } + + template<> template<> + void application_object_t::test<4>() + { + const int ARGC = 4; + const char* ARGV[ARGC] = + { + "", // argv[0] is usually the application name + "--crashcount", + "2", + "space" + }; + bool ok = mApp->parseCommandOptions(ARGC, const_cast<char**>(ARGV)); + ensure("command line parse failure", !ok); + } + + + template<> template<> + void application_object_t::test<5>() + { + LLSD options; + options["boolean-test"] = true; + mApp->setOptionData(LLApp::PRIORITY_GENERAL_CONFIGURATION, options); + ensure("bool set", mApp->getOption("boolean-test").asBoolean()); + options["boolean-test"] = false; + mApp->setOptionData(LLApp::PRIORITY_RUNTIME_OVERRIDE, options); + ensure("bool unset", !mApp->getOption("boolean-test").asBoolean()); + } +} diff --git a/indra/llcommon/tests/lldependencies_test.cpp b/indra/llcommon/tests/lldependencies_test.cpp index 2fea92e3b7..fd554005a3 100644 --- a/indra/llcommon/tests/lldependencies_test.cpp +++ b/indra/llcommon/tests/lldependencies_test.cpp @@ -228,19 +228,7 @@ namespace tut StringList keys(StringList{ "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 } diff --git a/indra/llcommon/tests/lldoubledispatch_test.cpp b/indra/llcommon/tests/lldoubledispatch_test.cpp new file mode 100644 index 0000000000..79c4edc058 --- /dev/null +++ b/indra/llcommon/tests/lldoubledispatch_test.cpp @@ -0,0 +1,240 @@ +/** + * @file lldoubledispatch_tut.cpp + * @author Nat Goodspeed + * @date 2008-11-13 + * @brief Test for lldoubledispatch.h + * + * This program tests the DoubleDispatch class, using a variation on the example + * from Scott Meyers' "More Effective C++", Item 31. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lldoubledispatch.h" +// STL headers +// std headers +#include <iostream> +#include <memory> +#include <string> +#include <typeinfo> +// external library headers +// other Linden headers +#include "lltut.h" + + +/*---------------------------- Class hierarchy -----------------------------*/ +// All objects are GameObjects. +class GameObject +{ +public: + GameObject(const std::string& name): mName(name) {} + virtual ~GameObject() {} + virtual std::string stringize() { return std::string(typeid(*this).name()) + " " + mName; } + +protected: + std::string mName; +}; + +// SpaceStation, Asteroid and SpaceShip are peer GameObjects. +struct SpaceStation: public GameObject +{ + SpaceStation(const std::string& name): GameObject(name) {} + // Only a dummy SpaceStation is constructed without a name + SpaceStation(): GameObject("dummy") {} +}; + +struct Asteroid: public GameObject +{ + Asteroid(const std::string& name): GameObject(name) {} + Asteroid(): GameObject("dummy") {} +}; + +struct SpaceShip: public GameObject +{ + SpaceShip(const std::string& name): GameObject(name) {} + SpaceShip(): GameObject("dummy") {} +}; + +// SpaceShip is specialized further into CommercialShip and MilitaryShip. +struct CommercialShip: public SpaceShip +{ + CommercialShip(const std::string& name): SpaceShip(name) {} + CommercialShip(): SpaceShip("dummy") {} +}; + +struct MilitaryShip: public SpaceShip +{ + MilitaryShip(const std::string& name): SpaceShip(name) {} + MilitaryShip(): SpaceShip("dummy") {} +}; + +/*-------------------------- Collision functions ---------------------------*/ +// This mechanism permits us to overcome a limitation of Meyers' approach: we +// can declare the parameter types exactly as we want, rather than having to +// make them all GameObject& parameters. +std::string shipAsteroid(SpaceShip& ship, Asteroid& rock) +{ +// std::cout << rock.stringize() << " has pulverized " << ship.stringize() << std::endl; + return "shipAsteroid"; +} + +std::string militaryShipAsteroid(MilitaryShip& ship, Asteroid& rock) +{ +// std::cout << rock.stringize() << " has severely damaged " << ship.stringize() << std::endl; + return "militaryShipAsteroid"; +} + +std::string shipStation(SpaceShip& ship, SpaceStation& dock) +{ +// std::cout << ship.stringize() << " has docked at " << dock.stringize() << std::endl; + return "shipStation"; +} + +std::string asteroidStation(Asteroid& rock, SpaceStation& dock) +{ +// std::cout << rock.stringize() << " has damaged " << dock.stringize() << std::endl; + return "asteroidStation"; +} + +/*------------------------------- Test code --------------------------------*/ +namespace tut +{ + struct dispatch_data + { + dispatch_data(): + home(new SpaceStation("Terra Station")), + obstacle(new Asteroid("Ganymede")), + tug(new CommercialShip("Pilotfish")), + patrol(new MilitaryShip("Enterprise")) + {} + + // Instantiate and populate the DoubleDispatch object. + typedef LLDoubleDispatch<std::string, GameObject> DD; + DD dispatcher; + + // Instantiate a few GameObjects. Make sure we refer to them + // polymorphically, and don't let them leak. + std::unique_ptr<GameObject> home; + std::unique_ptr<GameObject> obstacle; + std::unique_ptr<GameObject> tug; + std::unique_ptr<GameObject> patrol; + + // prototype objects + Asteroid dummyAsteroid; + SpaceShip dummyShip; + MilitaryShip dummyMilitary; + CommercialShip dummyCommercial; + SpaceStation dummyStation; + }; + typedef test_group<dispatch_data> dispatch_group; + typedef dispatch_group::object dispatch_object; + tut::dispatch_group ddgr("double dispatch"); + + template<> template<> + void dispatch_object::test<1>() + { + // Describe param types using explicit DD::Type objects + // (order-sensitive add() variant) + dispatcher.add(DD::Type<SpaceShip>(), DD::Type<Asteroid>(), shipAsteroid, true); + // naive adding, won't work + dispatcher.add(DD::Type<MilitaryShip>(), DD::Type<Asteroid>(), militaryShipAsteroid, true); + dispatcher.add(DD::Type<SpaceShip>(), DD::Type<SpaceStation>(), shipStation, true); + dispatcher.add(DD::Type<Asteroid>(), DD::Type<SpaceStation>(), asteroidStation, true); + + // Try colliding them. + ensure_equals(dispatcher(*home, *tug), // reverse params, SpaceShip subclass + "shipStation"); + ensure_equals(dispatcher(*patrol, *home), // forward params, SpaceShip subclass + "shipStation"); + ensure_equals(dispatcher(*obstacle, *home), // forward params + "asteroidStation"); + ensure_equals(dispatcher(*home, *obstacle), // reverse params + "asteroidStation"); + ensure_equals(dispatcher(*tug, *obstacle), // forward params, SpaceShip subclass + "shipAsteroid"); + ensure_equals(dispatcher(*obstacle, *patrol), // reverse params, SpaceShip subclass + // won't use militaryShipAsteroid() because it was added + // in wrong order + "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<2>() + { + // Describe param types using explicit DD::Type objects + // (order-sensitive add() variant) + // adding in correct order + dispatcher.add(DD::Type<MilitaryShip>(), DD::Type<Asteroid>(), militaryShipAsteroid, true); + dispatcher.add(DD::Type<SpaceShip>(), DD::Type<Asteroid>(), shipAsteroid, true); + dispatcher.add(DD::Type<SpaceShip>(), DD::Type<SpaceStation>(), shipStation, true); + dispatcher.add(DD::Type<Asteroid>(), DD::Type<SpaceStation>(), asteroidStation, true); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<3>() + { + // Describe param types with actual prototype instances + // (order-insensitive add() variant) + dispatcher.add(dummyMilitary, dummyAsteroid, militaryShipAsteroid); + dispatcher.add(dummyShip, dummyAsteroid, shipAsteroid); + dispatcher.add(dummyShip, dummyStation, shipStation); + dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + ensure_equals(dispatcher(*obstacle, *patrol), ""); + } + + template<> template<> + void dispatch_object::test<4>() + { + // Describe param types with actual prototype instances + // (order-insensitive add() variant) + dispatcher.add(dummyShip, dummyAsteroid, shipAsteroid); + // Even if we add the militaryShipAsteroid in the wrong order, it + // should still work. + dispatcher.add(dummyMilitary, dummyAsteroid, militaryShipAsteroid); + dispatcher.add(dummyShip, dummyStation, shipStation); + dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<5>() + { + dispatcher.add<SpaceShip, Asteroid>(shipAsteroid); + dispatcher.add<MilitaryShip, Asteroid>(militaryShipAsteroid); + dispatcher.add<SpaceShip, SpaceStation>(shipStation); + dispatcher.add<Asteroid, SpaceStation>(asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } +} // namespace tut diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index 3ec429530c..653f64f1b6 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -59,8 +59,10 @@ static const char* FieldName[] = namespace { -#ifdef __clang__ +#if defined(LL_CLANG) # pragma clang diagnostic ignored "-Wunused-function" +#elif defined(LL_GNUC) +# pragma GCC diagnostic ignored "-Wunused-function" #endif void test_that_error_h_includes_enough_things_to_compile_a_message() { diff --git a/indra/llcommon/tests/llevents_test.cpp b/indra/llcommon/tests/llevents_test.cpp new file mode 100644 index 0000000000..c5d5dcb9d4 --- /dev/null +++ b/indra/llcommon/tests/llevents_test.cpp @@ -0,0 +1,506 @@ +/** + * @file llevents_tut.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Test of llevents.h + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +// UGLY HACK! We want to verify state internal to the classes without +// providing public accessors. +#define testable public +#include "llevents.h" +#undef testable +// STL headers +// std headers +#include <iostream> +#include <typeinfo> +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "tests/listener.h" // must PRECEDE lltut.h +#include "lltut.h" +#include "catch_and_store_what_in.h" +#include "stringize.h" + +template<typename T> +T make(const T& value) +{ + return value; +} + +/***************************************************************************** + * tut test group + *****************************************************************************/ +namespace tut +{ +struct events_data +{ + events_data() : + pumps(LLEventPumps::instance()), + listener0("first"), + listener1("second") + { + } + LLEventPumps& pumps; + Listener listener0; + Listener listener1; + + void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got) + { + ensure_equals(STRINGIZE(listener << ' ' << desc), + listener.getLastEvent().asInteger(), got); + } +}; +typedef test_group<events_data> events_group; +typedef events_group::object events_object; +tut::events_group evgr("events"); + +template<> template<> +void events_object::test<1>() +{ + set_test_name("basic operations"); + // Having to modify this to track the statically- + // constructed pumps in other TUT modules in this giant monolithic test + // executable isn't such a hot idea. + // ensure_equals("initial pump", pumps.mPumpMap.size(), 1); + size_t initial_pumps(pumps.mPumpMap.size()); + LLEventPump& per_frame(pumps.obtain("per-frame")); + ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps + 1); + // Verify that per_frame was instantiated as an LLEventStream. + ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*> (&per_frame)); + ensure("enabled", per_frame.enabled()); + // Trivial test, but posting an event to an EventPump with no + // listeners should not blow up. The test is relevant because defining + // a boost::signal with a non-void return signature, using the default + // combiner, blows up if there are no listeners. This is because the + // default combiner is defined to return the value returned by the + // last listener, which is meaningless if there were no listeners. + per_frame.post(0); + LLBoundListener connection = listener0.listenTo(per_frame); + ensure("connected", connection.connected()); + ensure("not blocked", !connection.blocked()); + per_frame.post(1); + check_listener("received", listener0, 1); + { // block the connection + LLEventPump::Blocker block(connection); + ensure("blocked", connection.blocked()); + per_frame.post(2); + check_listener("not updated", listener0, 1); + } // unblock + ensure("unblocked", !connection.blocked()); + per_frame.post(3); + check_listener("unblocked", listener0, 3); + LLBoundListener sameConnection = per_frame.getListener(listener0.getName()); + ensure("still connected", sameConnection.connected()); + ensure("still not blocked", !sameConnection.blocked()); + { // block it again + LLEventPump::Blocker block(sameConnection); + ensure("re-blocked", sameConnection.blocked()); + per_frame.post(4); + check_listener("re-blocked", listener0, 3); + } // unblock + std::string threw = catch_what<LLEventPump::DupListenerName>( + [&per_frame, this](){ + // NOTE: boost::bind() saves its arguments by VALUE! If you pass + // an object instance rather than a pointer, you'll end up binding + // to an internal copy of that instance! Use boost::ref() to + // capture a reference instead. + per_frame.listen(listener0.getName(), // note bug, dup name + boost::bind(&Listener::call, boost::ref(listener1), _1)); + }); + ensure_equals(threw, + std::string("DupListenerName: " + "Attempt to register duplicate listener name '") + + listener0.getName() + "' on " + typeid(per_frame).name() + + " '" + per_frame.getName() + "'"); + // do it right this time + listener1.listenTo(per_frame); + per_frame.post(5); + check_listener("got", listener0, 5); + check_listener("got", listener1, 5); + per_frame.enable(false); + per_frame.post(6); + check_listener("didn't get", listener0, 5); + check_listener("didn't get", listener1, 5); + per_frame.enable(); + per_frame.post(7); + check_listener("got", listener0, 7); + check_listener("got", listener1, 7); + per_frame.stopListening(listener0.getName()); + ensure("disconnected 0", ! connection.connected()); + ensure("disconnected 1", ! sameConnection.connected()); + per_frame.post(8); + check_listener("disconnected", listener0, 7); + check_listener("still connected", listener1, 8); + per_frame.stopListening(listener1.getName()); + per_frame.post(9); + check_listener("disconnected", listener1, 8); +} + +template<> template<> +void events_object::test<2>() +{ + set_test_name("callstop() returning true"); + LLEventPump& per_frame(pumps.obtain("per-frame")); + listener0.reset(0); + listener1.reset(0); + LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop); + LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call, + // after listener0 + make<LLEventPump::NameList>(LLEventPump::NameList{ listener0.getName() })); + ensure("enabled", per_frame.enabled()); + ensure("connected 0", bound0.connected()); + ensure("unblocked 0", !bound0.blocked()); + ensure("connected 1", bound1.connected()); + ensure("unblocked 1", !bound1.blocked()); + per_frame.post(1); + check_listener("got", listener0, 1); + // Because listener0.callstop() returns true, control never reaches listener1.call(). + check_listener("got", listener1, 0); +} + +bool chainEvents(Listener& someListener, const LLSD& event) +{ + // Make this call so we can watch for side effects for test purposes. + someListener.call(event); + // This function represents a recursive event chain -- or some other + // scenario in which an event handler raises additional events. + int value = event.asInteger(); + if (value) + { + LLEventPumps::instance().obtain("login").post(value - 1); + } + return false; +} + +template<> template<> +void events_object::test<3>() +{ + set_test_name("explicitly-instantiated LLEventStream"); + // Explicitly instantiate an LLEventStream, and verify that it + // self-registers with LLEventPumps + size_t registered = pumps.mPumpMap.size(); + size_t owned = pumps.mOurPumps.size(); + LLEventPump* localInstance; + { + LLEventStream myEventStream("stream"); + localInstance = &myEventStream; + LLEventPump& stream(pumps.obtain("stream")); + ensure("found named LLEventStream instance", &stream == localInstance); + ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1); + ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned); + } // destroy myEventStream -- should unregister + ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered); + ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned); + LLEventPump& stream(pumps.obtain("stream")); + ensure("new LLEventStream instance", &stream != localInstance); + ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1); + ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1); +} + +template<> template<> +void events_object::test<4>() +{ + set_test_name("stopListening()"); + LLEventPump& login(pumps.obtain("login")); + listener0.listenTo(login); + login.stopListening(listener0.getName()); + // should not throw because stopListening() should have removed name + listener0.listenTo(login, &Listener::callstop); + LLBoundListener wrong = login.getListener("bogus"); + ensure("bogus connection disconnected", !wrong.connected()); + ensure("bogus connection blocked", wrong.blocked()); +} + +template<> template<> +void events_object::test<5>() +{ + set_test_name("chaining LLEventPump instances"); + LLEventPump& upstream(pumps.obtain("upstream")); + // One potentially-useful construct is to chain LLEventPumps together. + // Among other things, this allows you to turn subsets of listeners on + // and off in groups. + LLEventPump& filter0(pumps.obtain("filter0")); + LLEventPump& filter1(pumps.obtain("filter1")); + upstream.listen(filter0.getName(), boost::bind(&LLEventPump::post, boost::ref(filter0), _1)); + upstream.listen(filter1.getName(), boost::bind(&LLEventPump::post, boost::ref(filter1), _1)); + listener0.listenTo(filter0); + listener1.listenTo(filter1); + listener0.reset(0); + listener1.reset(0); + upstream.post(1); + check_listener("got unfiltered", listener0, 1); + check_listener("got unfiltered", listener1, 1); + filter0.enable(false); + upstream.post(2); + check_listener("didn't get filtered", listener0, 1); + check_listener("got filtered", listener1, 2); +} + +template<> template<> +void events_object::test<6>() +{ + set_test_name("listener dependency order"); + typedef LLEventPump::NameList NameList; + LLEventPump& button(pumps.obtain("button")); + Collect collector; + button.listen("Mary", + boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), + // state that "Mary" must come after "checked" + make<NameList>(NameList{ "checked" })); + button.listen("checked", + boost::bind(&Collect::add, boost::ref(collector), "checked", _1), + // "checked" must come after "spot" + make<NameList>(NameList{ "spot" })); + button.listen("spot", + boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); + button.post(1); + ensure_equals(collector.result, make<StringVec>(StringVec{ "spot", "checked", "Mary" })); + collector.clear(); + button.stopListening("Mary"); + button.listen("Mary", + boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), + LLEventPump::empty, // no after dependencies + // now "Mary" must come before "spot" + make<NameList>(NameList{ "spot" })); + button.post(2); + ensure_equals(collector.result, make<StringVec>(StringVec{ "Mary", "spot", "checked" })); + collector.clear(); + button.stopListening("spot"); + std::string threw = catch_what<LLEventPump::Cycle>( + [&button, &collector](){ + button.listen("spot", + boost::bind(&Collect::add, boost::ref(collector), "spot", _1), + // after "Mary" and "checked" -- whoops! + make<NameList>(NameList{ "Mary", "checked" })); + }); + // Obviously the specific wording of the exception text can + // change; go ahead and change the test to match. + // Establish that it contains: + // - the name and runtime type of the LLEventPump + ensure_contains("LLEventPump type", threw, typeid(button).name()); + ensure_contains("LLEventPump name", threw, "'button'"); + // - the name of the new listener that caused the problem + ensure_contains("new listener name", threw, "'spot'"); + // - a synopsis of the problematic dependencies. + ensure_contains("cyclic dependencies", threw, + "\"Mary\" -> before (\"spot\")"); + ensure_contains("cyclic dependencies", threw, + "after (\"spot\") -> \"checked\""); + ensure_contains("cyclic dependencies", threw, + "after (\"Mary\", \"checked\") -> \"spot\""); + button.listen("yellow", + boost::bind(&Collect::add, boost::ref(collector), "yellow", _1), make<NameList>(NameList{ "checked" })); + button.listen("shoelaces", + boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), make<NameList>(NameList{ "checked" })); + button.post(3); + ensure_equals(collector.result, make<StringVec>(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); + collector.clear(); + threw = catch_what<LLEventPump::OrderChange>( + [&button, &collector](){ + button.listen("of", + boost::bind(&Collect::add, boost::ref(collector), "of", _1), + make<NameList>(NameList{ "shoelaces" }), + make<NameList>(NameList{ "yellow" })); + }); + // Same remarks about the specific wording of the exception. Just + // ensure that it contains enough information to clarify the + // problem and what must be done to resolve it. + ensure_contains("LLEventPump type", threw, typeid(button).name()); + ensure_contains("LLEventPump name", threw, "'button'"); + ensure_contains("new listener name", threw, "'of'"); + ensure_contains("prev listener name", threw, "'yellow'"); + // std::cout << "Thrown Exception: " << threw << std::endl; + ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces"); + ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow"); + button.post(4); + ensure_equals(collector.result, make<StringVec>(StringVec{ "Mary", "checked", "yellow", "shoelaces" })); +} + +template<> template<> +void events_object::test<7>() +{ + set_test_name("tweaked and untweaked LLEventPump instance names"); + { // nested scope + // Hand-instantiate an LLEventStream... + LLEventStream bob("bob"); + std::string threw = catch_what<LLEventPump::DupPumpName>( + [](){ + // then another with a duplicate name. + LLEventStream bob2("bob"); + }); + ensure("Caught DupPumpName", !threw.empty()); + } // delete first 'bob' + LLEventStream bob("bob"); // should work, previous one unregistered + LLEventStream bob1("bob", true);// allowed to tweak name + ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1"); + std::vector<std::shared_ptr<LLEventStream> > streams; + for (int i = 2; i <= 10; ++i) + { + streams.push_back(std::shared_ptr<LLEventStream>(new LLEventStream("bob", true))); + } + ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10"); +} + +// Define a function that accepts an LLListenerOrPumpName +void eventSource(const LLListenerOrPumpName& listener) +{ + // Pretend that some time has elapsed. Call listener immediately. + listener(17); +} + +template<> template<> +void events_object::test<8>() +{ + set_test_name("LLListenerOrPumpName"); + // Passing a boost::bind() expression to LLListenerOrPumpName + listener0.reset(0); + eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1)); + check_listener("got by listener", listener0, 17); + // Passing a string LLEventPump name to LLListenerOrPumpName + listener0.reset(0); + LLEventStream random("random"); + listener0.listenTo(random); + eventSource("random"); + check_listener("got by pump name", listener0, 17); + std::string threw = catch_what<LLListenerOrPumpName::Empty>( + [](){ + LLListenerOrPumpName empty; + empty(17); + }); + + ensure("threw Empty", !threw.empty()); +} + +class TempListener: public Listener +{ +public: + TempListener(const std::string& name, bool& liveFlag) : + Listener(name), mLiveFlag(liveFlag) + { + mLiveFlag = true; + } + + virtual ~TempListener() + { + mLiveFlag = false; + } + +private: + bool& mLiveFlag; +}; + +template<> template<> +void events_object::test<9>() +{ + set_test_name("listen(boost::bind(...TempListener...))"); + // listen() can't do anything about a plain TempListener instance: + // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempListener tempListener("temp", live); + ensure("TempListener constructed", live); + connection = heaptest.listen(tempListener.getName(), + boost::bind(&Listener::call, + boost::ref(tempListener), + _1)); + heaptest.post(1); + check_listener("received", tempListener, 1); + } // presumably this will make newListener go away? + // verify that + ensure("TempListener destroyed", !live); + // This is the case against which we can't defend. Don't even try to + // post to heaptest -- that would engage Undefined Behavior. + // Cautiously inspect connection... + ensure("misleadingly connected", connection.connected()); + // then disconnect by hand. + heaptest.stopListening("temp"); +} + +class TempTrackableListener: public TempListener, public LLEventTrackable +{ +public: + TempTrackableListener(const std::string& name, bool& liveFlag): + TempListener(name, liveFlag) + {} +}; + +template<> template<> +void events_object::test<10>() +{ + set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempTrackableListener tempListener("temp", live); + ensure("TempTrackableListener constructed", live); + connection = heaptest.listen(tempListener.getName(), + boost::bind(&TempTrackableListener::call, + boost::ref(tempListener), _1)); + heaptest.post(1); + check_listener("received", tempListener, 1); + } // presumably this will make tempListener go away? + // verify that + ensure("TempTrackableListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); +} + +template<> template<> +void events_object::test<11>() +{ + set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempTrackableListener* newListener(new TempTrackableListener("temp", live)); + ensure("TempTrackableListener constructed", live); + connection = heaptest.listen(newListener->getName(), + boost::bind(&TempTrackableListener::call, + newListener, _1)); + heaptest.post(1); + check_listener("received", *newListener, 1); + // explicitly destroy newListener + delete newListener; + } + // verify that + ensure("TempTrackableListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); +} + +} // namespace tut diff --git a/indra/llcommon/tests/llfile_test.cpp b/indra/llcommon/tests/llfile_test.cpp new file mode 100644 index 0000000000..ab4616cfb2 --- /dev/null +++ b/indra/llcommon/tests/llfile_test.cpp @@ -0,0 +1,299 @@ +/** + * @file llfile_test.cpp + * @author Frederick Martian + * @date 2025-11 + * @brief LLFile test cases. + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include <tut/tut.hpp> +#include "lltut.h" +#include "linden_common.h" +#include "llfile.h" + +namespace tut +{ + static void clear_entire_dir(const std::filesystem::path& dir_path) + { + std::error_code ec; + std::filesystem::remove_all(dir_path, ec); + } + + static std::filesystem::path append_filename(const std::filesystem::path& dir, const std::u8string& element) + { + std::filesystem::path path = dir; + return path.append(element); + } + + static std::filesystem::path get_testdir(const std::filesystem::path& tempdir) + { + return append_filename(tempdir, std::u8string(u8"test_dir")); + } + + static std::filesystem::path get_testdir_unicode(const std::filesystem::path& tempdir) + { + // Example Unicode directory name: "test_ユニコード_dir" + return append_filename(tempdir, std::u8string(u8"test_\xE3\x83\xA6\xE3\x83\x8B\xE3\x82\xB3\xE3\x83\xBC\xE3\x83\x89_dir")); + } + + struct llfile_test + { + std::filesystem::path tempdir = LLFile::tmpdir(); + std::filesystem::path testdir = get_testdir(tempdir); + }; + typedef test_group<llfile_test> llfile_test_t; + typedef llfile_test_t::object llfile_test_object_t; + tut::llfile_test_t tut_llfile_test("llfile_test"); + + template<> template<> + void llfile_test_object_t::test<1>() + { + // Test creating directories and files and deleting them and checking if the + // relevant status functions work as expected + ensure("LLFile::tmpdir() empty", !tempdir.empty()); + ensure("LLFile::tmpdir() doesn't exist", LLFile::exists(tempdir)); + ensure("LLFile::tmpdir() is not a directory", LLFile::isdir(tempdir)); + ensure("LLFile::tmpdir() should not be a file", !LLFile::isfile(tempdir)); + + // Make sure there is nothing left from a previous test run + clear_entire_dir(testdir); + ensure("llfile_test should not exist anymore", !LLFile::exists(testdir)); + + int rc = LLFile::mkdir(testdir); + ensure("LLFile::mkdir() failed", rc == 0); + ensure("llfile_test should be a directory", LLFile::isdir(testdir)); + rc = LLFile::mkdir(testdir); + ensure("LLFile::mkdir() should not fail when the directory already exists", rc == 0); + + std::filesystem::path testfile1 = testdir; + testfile1.append("llfile_test.dat"); + ensure("llfile_test1.dat should not yet exist", !LLFile::exists(testfile1)); + + const char* testdata = "testdata"; + S64 bytes = LLFile::write(testfile1, testdata, 0, sizeof(testdata)); + ensure("LLFile::write() did not write correctly", bytes == sizeof(testdata)); + + rc = LLFile::remove(testfile1); + ensure("LLFile::remove() for file test_file.dat", rc == 0); + ensure("llfile_test.dat should not exist anymore", !LLFile::exists(testfile1)); + ensure("llfile_test.dat should not be a file", !LLFile::isfile(testfile1)); + ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1)); + ensure("llfile_test.dat should not be a symlink", !LLFile::islink(testfile1)); + + rc = LLFile::remove(testdir); + ensure("LLFile::remove() for directory llfile_test failed", rc == 0); + ensure("llfile_test should not exist anymore", !LLFile::exists(testdir)); + } + + template<> template<> + void llfile_test_object_t::test<2>() + { + // High level static file IO functions to read and write data files + LLFile::mkdir(testdir); + ensure("llfile_test should exist", LLFile::isdir(testdir)); + + std::filesystem::path testfile1 = testdir; + testfile1.append("llfile_test.dat"); + + std::string testdata1("testdata"); + std::string testdata2("datateststuff"); + std::time_t current = time(nullptr); + S64 bytes = LLFile::write(testfile1, testdata1.c_str(), 0, testdata1.length()); + ensure("LLFile::write() did not write correctly", bytes == testdata1.length()); + ensure("llfile_test.dat should exist", LLFile::exists(testfile1)); + ensure("llfile_test.dat should be a file", LLFile::isfile(testfile1)); + ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1)); + + bytes = LLFile::size(testfile1); + ensure("LLFile::size() did not return the correct size", bytes == testdata1.length()); + + std::string data = LLFile::getContents(testfile1); + ensure("LLFile::getContents() did not return the correct size data", data.length() == testdata1.length()); + ensure_memory_matches("LLFile::getContents() did not read correct data", testdata1.c_str(), (U32)testdata1.length(), data.c_str(), (U32)data.length()); + + char buffer[1024]; + bytes = LLFile::read(testfile1, buffer, 0, testdata1.length()); + ensure("LLFile:read() did not return the correct size", bytes == testdata1.length()); + ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); + + // What if we try to read more data than there is in the file? + bytes = LLFile::read(testfile1, buffer, 0, bytes + 10); + ensure("LLFile:read() did not correctly stop on eof", bytes == testdata1.length()); + ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); + + // Let's append more data + bytes = LLFile::write(testfile1, testdata2.c_str(), -1, testdata2.length()); + ensure("LLFile::write() did not write correctly", bytes == testdata2.length()); + + bytes = LLFile::size(testfile1); + ensure("LLFile::size() did not return the correct size", bytes == testdata1.length() + testdata2.length()); + bytes = LLFile::read(testfile1, buffer, 0, bytes); + ensure("LLFile:read() did not read correct number of bytes", bytes == testdata1.length() + testdata2.length()); + ensure_memory_matches("LLFile:read() did not read correct testdata1", testdata1.c_str(), (U32)testdata1.length(), buffer, (U32)testdata1.length()); + ensure_memory_matches("LLFile:read() did not read correct testdata2", testdata2.c_str(), (U32)testdata2.length(), buffer + testdata1.length(), (U32)testdata2.length()); + } + + template<> template<> + void llfile_test_object_t::test<3>() + { + const size_t numints = 1024; + + // Testing the LLFile class implementation + std::filesystem::path testfile = testdir; + testfile.append("llfile_test.bin"); + + int data[numints]; + for (int &t : data) + { + t = rand(); + } + + std::error_code ec; + LLFile fileout(testfile, LLFile::out, ec); + ensure("LLFile constructor did not open correctly", (bool)fileout); + ensure("error_code from LLFile constructor should not indicate an error", !ec); + if (fileout) + { + S64 length = fileout.size(ec); + ensure("freshly created file should be empty", length == 0); + ensure("error_code from LLFile::size() should not indicate an error", !ec); + S64 bytes = fileout.write(data, sizeof(data), ec); + ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); + ensure("error_code from LLFile::write() should not indicate an error", !ec); + bytes = fileout.write(data, sizeof(data), ec); + ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); + ensure("error_code from LLFile::write() should not indicate an error", !ec); + bytes = fileout.size(ec); + ensure("LLFile::size() returned wrong size", bytes == 2 * sizeof(data)); + ensure("error_code from LLFile::size() should not indicate an error", !ec); + fileout.close(); + } + + LLFile filein(testfile, LLFile::in, ec); + ensure("LLFile constructor did not open correctly", (bool)filein); + ensure("error_code from LLFile constructor should not indicate an error", !ec); + if (filein) + { + S64 length = filein.size(ec); + ensure("LLFile::size() returned wrong size", length == 2 * sizeof(data)); + ensure("error_code from LLFile::size() should not indicate an error", !ec); + char* buffer = (char*)malloc(length); + S64 bytes = filein.read(buffer, length, ec); + ensure("LLFile::read() did not read correctly", bytes == length); + ensure("error_code from LLFile::read() should not indicate an error", !ec); + ensure_memory_matches("LLFile:read() did not read correct data1", data, (U32)sizeof(data), buffer, (U32)sizeof(data)); + ensure_memory_matches("LLFile:read() did not read correct data2", data, (U32)sizeof(data), buffer + sizeof(data), (U32)sizeof(data)); + S64 offset = filein.tell(ec); + ensure("LLFile::tell() returned a bad offset", offset == length); + ensure("error_code from LLFile::read() should not indicate an error", !ec); + offset = sizeof(data) / 2; + int rc = filein.seek(offset, ec); + ensure("LLFile::seek() indicated an error", rc == 0); + ensure("error_code from LLFile::seek() should not indicate an error", !ec); + bytes = filein.read(buffer, 2 * sizeof(data), ec); + ensure("LLFile::read() did not read correctly", bytes == sizeof(data) + offset); + ensure("error_code from LLFile::read() should not indicate an error", !ec); + ensure_memory_matches("LLFile:read() did not read correct data3", (char*)data + offset, (U32)offset, buffer, (U32)offset); + ensure_memory_matches("LLFile:read() did not read correct data4", (char*)data, (U32)sizeof(data), buffer + offset, (U32)sizeof(data)); + filein.close(); + + free(buffer); + } + } + + template<> template<> + void llfile_test_object_t::test<4>() + { + // Testing the LLFile class implementation with wrong paths and parameters + std::filesystem::path testfile = testdir; + testfile.append("llfile_test.bin"); + + std::error_code ec; + LLFile file(testfile, LLFile::out | LLFile::noreplace, ec); + ensure("LLFile constructor should not have opened the already existing file", !file); + ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + + LLFile::remove(testfile); + file = LLFile(testfile, LLFile::out | LLFile::app | LLFile::trunc, ec); + ensure("LLFile constructor should not have opened the file with conflicting flags", !file); + ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + + file = LLFile(testfile, LLFile::out | LLFile::app | LLFile::noreplace, ec); + ensure("LLFile constructor should not have opened the file with conflicting flags", !file); + ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + + testfile = testdir; + testfile.append("llfile_test"); + testfile.append("llfile_test.bin"); + + file = LLFile(testfile, LLFile::in, ec); + ensure("LLFile constructor should not have been able to open the file in the non-existing directory", !file); + ensure("error_code from LLFile constructor should indicate an error", (bool)ec); + } + + template<> template<> + void llfile_test_object_t::test<5>() + { + // Test file and directory operations with Unicode paths and filenames + std::filesystem::path testdir_unicode = get_testdir_unicode(tempdir); + + // Unicode filename: "ファイル_テスト.bin" (means "file_test.bin" in Japanese) + std::string unicode_filename = "\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB_\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88.bin"; + std::filesystem::path testfile_unicode = testdir_unicode; + testfile_unicode.append(unicode_filename); + + // Clean up any previous test artifacts + clear_entire_dir(testdir_unicode); + ensure("Unicode test directory should not exist", !LLFile::exists(testdir_unicode)); + + // Create the Unicode directory + int rc = LLFile::mkdir(testdir_unicode); + ensure("LLFile::mkdir() failed for Unicode directory", rc == 0); + ensure("Unicode test directory should exist", LLFile::isdir(testdir_unicode)); + + + ensure("Unicode test file should not exist", !LLFile::exists(testfile_unicode)); + + // Write to the Unicode file + const char* testdata = "unicode_testdata"; + S64 bytes = LLFile::write(testfile_unicode, testdata, 0, strlen(testdata)); + ensure("Unicode test file should exist", LLFile::exists(testfile_unicode)); + ensure("Unicode test file should be a file", LLFile::isfile(testfile_unicode)); + ensure("LLFile::write() did not write correctly to Unicode file", bytes == (S64)strlen(testdata)); + + // Read back the data + char buffer[64] = {}; + bytes = LLFile::read(testfile_unicode, buffer, 0, sizeof(buffer)); + ensure("LLFile::read() did not read correctly from Unicode file", bytes == (S64)strlen(testdata)); + ensure_memory_matches("LLFile::read() did not read correct Unicode data", testdata, (U32)bytes, buffer, (U32)bytes); + + // Remove the file and directory + rc = LLFile::remove(testfile_unicode); + ensure("LLFile::remove() failed for Unicode file", rc == 0); + ensure("Unicode test file should not exist after removal", !LLFile::exists(testfile_unicode)); + + rc = LLFile::remove(testdir_unicode); + ensure("LLFile::remove() failed for Unicode directory", rc == 0); + ensure("Unicode test directory should not exist after removal", !LLFile::exists(testdir_unicode)); + } +} // namespace tut diff --git a/indra/llcommon/tests/llhttpdate_test.cpp b/indra/llcommon/tests/llhttpdate_test.cpp new file mode 100644 index 0000000000..b580b09a9f --- /dev/null +++ b/indra/llcommon/tests/llhttpdate_test.cpp @@ -0,0 +1,162 @@ +/** + * @file llhttpdate_tut.cpp + * @author Kartic Krishnamurthy + * @date Wednesday, 18 Jul 2007 17:00:00 GMT :) + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "lltut.h" + +#include "lldate.h" +#include "llcommon.h" +#include "llframetimer.h" + +#include <time.h> +#include <locale.h> + +namespace tut +{ + struct httpdate_data + { + httpdate_data() + { + } + ~httpdate_data() + { + } + LLDate some_date; + }; + typedef test_group<httpdate_data> httpdate_test; + typedef httpdate_test::object httpdate_object; + tut::httpdate_test httpdate("httpdate"); + + template<> template<> + void httpdate_object::test<1>() + { + + static std::string epoch_expected = "Thursday, 01 Jan 1970 00:00:00 GMT" ; + ensure("Check Epoch in RFC 1123", ( epoch_expected == some_date.asRFC1123())); + } + + template<> template<> + void httpdate_object::test<2>() + { + + static std::string expected = "Wednesday, 18 Jul 2007 22:17:24 GMT" ; + some_date = LLDate(1184797044.037586); + ensure("Check some timestamp in RFC 1123", ( expected == some_date.asRFC1123())); + } + + // This test of course most generic.. runs off current time + template<> template<> + void httpdate_object::test<3>() + { + + //F64 sometime = LLFrameTimer::getTotalSeconds(); + time_t sometime; + time(&sometime); + some_date = LLDate((F64) sometime); + struct tm *result; + char expected[255]; + std::string actual; + + result = gmtime(&sometime); + /* + std::cout << " seconds: "<< result->tm_sec + << ", minutes: " << result->tm_min + << ", hours: " << result->tm_hour + << ", day of the month: " << result->tm_mday + << ", month: " << result->tm_mon + << ", year: " << result->tm_year + << ", day of the week: " << result->tm_wday + << ", day in the year: " << result->tm_yday + << ", DST: " << result->tm_isdst << std::endl; + */ + strftime(expected, 255, "%A, %d %b %Y %H:%M:%S GMT", result); + actual = some_date.asRFC1123(); + // probably not a good idea to use strcmp but this is just a unit test + ensure("Current time in RFC 1123", (strcmp(expected, actual.c_str()) == 0)); + } + + void test_date_string(const std::string &locale, struct tm *t, + const std::string &fmt, const std::string &expected) + { + std::string result = LLDate::toHTTPDateString(t, fmt); + LLStringUtil::toLower(result); + std::string label = std::string("toHTTPDateString - ") + locale; + ensure_equals(label.c_str(), result, expected); + } + + template<> template<> + void httpdate_object::test<4>() + { + // test localization of http dates + const char *en_locale = "en_US.UTF-8"; + const char *fr_locale = "fr_FR.UTF-8"; + + std::string prev_locale = LLStringUtil::getLocale(); + std::string prev_clocale = std::string(setlocale(LC_TIME, NULL)); + time_t test_time = 1252374030; // 8 Sep 2009 01:40:01 + struct tm *t = gmtime(&test_time); + + setlocale(LC_TIME, en_locale); + if (strcmp(setlocale(LC_TIME, NULL), en_locale) != 0) + { + setlocale(LC_TIME, prev_clocale.c_str()); + skip("Cannot set English locale"); + } + + LLStringUtil::setLocale(en_locale); + test_date_string(en_locale, t, "%d %B %Y - %H:%M", "08 september 2009 - 01:40"); + test_date_string(en_locale, t, "%H", "01"); + test_date_string(en_locale, t, "%M", "40"); + test_date_string(en_locale, t, "%I", "01"); + test_date_string(en_locale, t, "%d", "08"); + test_date_string(en_locale, t, "%Y", "2009"); + test_date_string(en_locale, t, "%p", "am"); + test_date_string(en_locale, t, "%A", "tuesday"); + test_date_string(en_locale, t, "%B", "september"); + + setlocale(LC_TIME, fr_locale); + if (strcmp(setlocale(LC_TIME, NULL), fr_locale) != 0) + { + LLStringUtil::setLocale(prev_locale); + setlocale(LC_TIME, prev_clocale.c_str()); + skip("Cannot set French locale"); + } + + LLStringUtil::setLocale(fr_locale); + test_date_string(fr_locale, t, "%d %B %Y - %H:%M", "08 septembre 2009 - 01:40"); + test_date_string(fr_locale, t, "%H", "01"); + test_date_string(fr_locale, t, "%M", "40"); + test_date_string(fr_locale, t, "%I", "01"); + test_date_string(fr_locale, t, "%d", "08"); + test_date_string(fr_locale, t, "%Y", "2009"); + test_date_string(fr_locale, t, "%A", "mardi"); + test_date_string(fr_locale, t, "%B", "septembre"); + + LLStringUtil::setLocale(prev_locale); + setlocale(LC_TIME, prev_clocale.c_str()); + } +} diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp deleted file mode 100644 index 923fe952a9..0000000000 --- a/indra/llcommon/tests/lllazy_test.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @file lllazy_test.cpp - * @author Nat Goodspeed - * @date 2009-01-28 - * @brief Tests of lllazy.h. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "lllazy.h" -// STL headers -#include <iostream> -// std headers -// external library headers -#include <boost/lambda/construct.hpp> -#include <boost/lambda/bind.hpp> -// other Linden headers -#include "../test/lltut.h" -#include "../test/catch_and_store_what_in.h" - -namespace bll = boost::lambda; - -/***************************************************************************** -* Test classes -*****************************************************************************/ - -// Let's say that because of its many external dependencies, YuckyFoo is very -// hard to instantiate in a test harness. -class YuckyFoo -{ -public: - virtual ~YuckyFoo() {} - virtual std::string whoami() const { return "YuckyFoo"; } -}; - -// Let's further suppose that YuckyBar is another hard-to-instantiate class. -class YuckyBar -{ -public: - YuckyBar(const std::string& which): - mWhich(which) - {} - virtual ~YuckyBar() {} - - virtual std::string identity() const { return std::string("YuckyBar(") + mWhich + ")"; } - -private: - const std::string mWhich; -}; - -// Pretend that this class would be tough to test because, up until we started -// trying to test it, it contained instances of both YuckyFoo and YuckyBar. -// Now we've refactored so it contains LLLazy<YuckyFoo> and LLLazy<YuckyBar>. -// More than that, it contains them by virtue of deriving from -// LLLazyBase<YuckyFoo> and LLLazyBase<YuckyBar>. -// We postulate two different LLLazyBases because, with only one, you need not -// specify *which* get()/set() method you're talking about. That's a simpler -// case. -class NeedsTesting: public LLLazyBase<YuckyFoo>, public LLLazyBase<YuckyBar> -{ -public: - NeedsTesting(): - // mYuckyBar("RealYuckyBar") - LLLazyBase<YuckyBar>(bll::bind(bll::new_ptr<YuckyBar>(), "RealYuckyBar")) - {} - virtual ~NeedsTesting() {} - - virtual std::string describe() const - { - return std::string("NeedsTesting(") + getLazy<YuckyFoo>(this).whoami() + ", " + - getLazy<YuckyBar>(this).identity() + ")"; - } - -private: - // These instance members were moved to LLLazyBases: - // YuckyFoo mYuckyFoo; - // YuckyBar mYuckyBar; -}; - -// Fake up a test YuckyFoo class -class TestFoo: public YuckyFoo -{ -public: - virtual std::string whoami() const { return "TestFoo"; } -}; - -// and a test YuckyBar -class TestBar: public YuckyBar -{ -public: - TestBar(const std::string& which): YuckyBar(which) {} - virtual std::string identity() const - { - return std::string("TestBar(") + YuckyBar::identity() + ")"; - } -}; - -// So here's a test subclass of NeedsTesting that uses TestFoo and TestBar -// instead of YuckyFoo and YuckyBar. -class TestNeedsTesting: public NeedsTesting -{ -public: - TestNeedsTesting() - { - // Exercise setLazy(T*) - setLazy<YuckyFoo>(this, new TestFoo()); - // Exercise setLazy(Factory) - setLazy<YuckyBar>(this, bll::bind(bll::new_ptr<TestBar>(), "TestYuckyBar")); - } - - virtual std::string describe() const - { - return std::string("TestNeedsTesting(") + NeedsTesting::describe() + ")"; - } - - void toolate() - { - setLazy<YuckyFoo>(this, new TestFoo()); - } -}; - -// This class tests having an explicit LLLazy<T> instance as a named member, -// rather than deriving from LLLazyBase<T>. -class LazyMember -{ -public: - YuckyFoo& getYuckyFoo() { return *mYuckyFoo; } - std::string whoisit() const { return mYuckyFoo->whoami(); } - -protected: - LLLazy<YuckyFoo> mYuckyFoo; -}; - -// This is a test subclass of the above, dynamically replacing the -// LLLazy<YuckyFoo> member. -class TestLazyMember: public LazyMember -{ -public: - // use factory setter - TestLazyMember() - { - mYuckyFoo.set(bll::new_ptr<TestFoo>()); - } - - // use instance setter - TestLazyMember(YuckyFoo* instance) - { - mYuckyFoo.set(instance); - } -}; - -/***************************************************************************** -* TUT -*****************************************************************************/ -namespace tut -{ - struct lllazy_data - { - }; - typedef test_group<lllazy_data> lllazy_group; - typedef lllazy_group::object lllazy_object; - lllazy_group lllazygrp("lllazy"); - - template<> template<> - void lllazy_object::test<1>() - { - // Instantiate an official one, just because we can - NeedsTesting nt; - // and a test one - TestNeedsTesting tnt; -// std::cout << nt.describe() << '\n'; - ensure_equals(nt.describe(), "NeedsTesting(YuckyFoo, YuckyBar(RealYuckyBar))"); -// std::cout << tnt.describe() << '\n'; - ensure_equals(tnt.describe(), - "TestNeedsTesting(NeedsTesting(TestFoo, TestBar(YuckyBar(TestYuckyBar))))"); - } - - template<> template<> - void lllazy_object::test<2>() - { - TestNeedsTesting tnt; - std::string threw = catch_what<LLLazyCommon::InstanceChange>([&tnt](){ - tnt.toolate(); - }); - ensure_contains("InstanceChange exception", threw, "replace LLLazy instance"); - } - - template<> template<> - void lllazy_object::test<3>() - { - { - LazyMember lm; - // operator*() on-demand instantiation - ensure_equals(lm.getYuckyFoo().whoami(), "YuckyFoo"); - } - { - LazyMember lm; - // operator->() on-demand instantiation - ensure_equals(lm.whoisit(), "YuckyFoo"); - } - } - - template<> template<> - void lllazy_object::test<4>() - { - { - // factory setter - TestLazyMember tlm; - ensure_equals(tlm.whoisit(), "TestFoo"); - } - { - // instance setter - TestLazyMember tlm(new TestFoo()); - ensure_equals(tlm.whoisit(), "TestFoo"); - } - } -} // namespace tut diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index fa48bcdefd..ae20e7ed27 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -190,7 +190,7 @@ namespace tut // computation, so I don't mind calling it twice.) Then take the // basename. reader_module(LLProcess::basename( - reader.getName().substr(0, reader.getName().length()-3))), + reader.getPath().string().substr(0, reader.getPath().string().length()-3))), PYTHON(LLStringUtil::getenv("PYTHON")) { ensure("Set PYTHON to interpreter pathname", !PYTHON.empty()); @@ -212,9 +212,9 @@ namespace tut "time.sleep(1)\n"); LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})->getWeak()); + StringVec{PYTHON, script.getPath().string()})->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})->getWeak()); + StringVec{PYTHON, script.getPath().string()})->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other // way. Wait for them to terminate. @@ -229,7 +229,7 @@ namespace tut "import sys\n" "sys.stderr.write('''Hello from Python!\n" "note partial line''')\n"); - StringVec vcommand{ PYTHON, script.getName() }; + StringVec vcommand{ PYTHON, script.getPath().string() }; CaptureLog log(LLError::LEVEL_INFO); waitfor(LLLeap::create(get_test_name(), vcommand)); log.messageWith("Hello from Python!"); @@ -244,7 +244,7 @@ namespace tut "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})); + StringVec{PYTHON, script.getPath().string()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "Hello from Python!"); } @@ -259,7 +259,7 @@ namespace tut "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})); + StringVec{PYTHON, script.getPath().string()})); ensure_contains("error log line", log.messageWith("Discarding"), "Hello from Python!"); } @@ -273,7 +273,7 @@ namespace tut "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})); + StringVec{PYTHON, script.getPath().string()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "5a2:"); } @@ -386,7 +386,7 @@ namespace tut " else 'bad: ' + str(resp)\n" "send(pump='" << result.getName() << "', data=result)\n";}); waitfor(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})); + StringVec{PYTHON, script.getPath().string()})); result.ensure(); } @@ -445,7 +445,7 @@ namespace tut " result = 'expected reqid=%s in %s' % (i, resp)\n" " break\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}), + waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getPath().string()}), 300); // needs more realtime than most tests result.ensure(); } @@ -512,7 +512,7 @@ namespace tut " (start, large[start:end], echoed[start:end]))\n" "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, - StringVec{PYTHON, script.getName(), stringize(size)}), + StringVec{PYTHON, script.getPath().string(), stringize(size)}), 180); // try a longer timeout result.ensure(); } diff --git a/indra/llcommon/tests/llmemtype_test.cpp b/indra/llcommon/tests/llmemtype_test.cpp deleted file mode 100644 index 2d64d342ae..0000000000 --- a/indra/llcommon/tests/llmemtype_test.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @file llmemtype_test.cpp - * @author Palmer Truelson - * @date 2008-03- - * @brief Test for llmemtype.cpp. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "../llmemtype.h" -#include "../test/lltut.h" -#include "../llallocator.h" - - -#include <stack> - -std::stack<S32> memTypeStack; - -void LLAllocator::pushMemType(S32 i) -{ - memTypeStack.push(i); -} - -S32 LLAllocator::popMemType(void) -{ - S32 ret = memTypeStack.top(); - memTypeStack.pop(); - return ret; -} - -namespace tut -{ - struct llmemtype_data - { - }; - - typedef test_group<llmemtype_data> factory; - typedef factory::object object; -} -namespace -{ - tut::factory llmemtype_test_factory("LLMemType"); -} - -namespace tut -{ - template<> template<> - void object::test<1>() - { - ensure("Simplest test ever", true); - } - - // test with no scripts - template<> template<> - void object::test<2>() - { - { - LLMemType m1(LLMemType::MTYPE_INIT); - } - ensure("Test that you can construct and destruct the mem type"); - } - - // test creation and stack testing - template<> template<> - void object::test<3>() - { - { - ensure("Test that creation and destruction properly inc/dec the stack"); - ensure_equals(memTypeStack.size(), 0); - { - LLMemType m1(LLMemType::MTYPE_INIT); - ensure_equals(memTypeStack.size(), 1); - LLMemType m2(LLMemType::MTYPE_STARTUP); - ensure_equals(memTypeStack.size(), 2); - } - ensure_equals(memTypeStack.size(), 0); - } - } - - // test with no scripts - template<> template<> - void object::test<4>() - { - // catch the begining and end - std::string test_name = LLMemType::getNameFromID(LLMemType::MTYPE_INIT.mID); - ensure_equals("Init name", test_name, "Init"); - - std::string test_name2 = LLMemType::getNameFromID(LLMemType::MTYPE_VOLUME.mID); - ensure_equals("Volume name", test_name2, "Volume"); - - std::string test_name3 = LLMemType::getNameFromID(LLMemType::MTYPE_OTHER.mID); - ensure_equals("Other name", test_name3, "Other"); - - std::string test_name4 = LLMemType::getNameFromID(-1); - ensure_equals("Invalid name", test_name4, "INVALID"); - } - -}; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 00dcd52d5d..5df43e497f 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -89,14 +89,14 @@ static void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR * @param desc Optional description of the file for error message; * defaults to "in <pathname>" */ -static std::string readfile(const std::string& pathname, const std::string& desc="") +static std::string readfile(const std::filesystem::path& pathname, const std::string& desc="") { std::string use_desc(desc); if (use_desc.empty()) { - use_desc = "in " + pathname; + use_desc = "in " + pathname.string(); } - std::ifstream inf(pathname.c_str()); + llifstream inf(pathname.c_str()); std::string output; if (!std::getline(inf, output)) { @@ -183,7 +183,7 @@ struct PythonProcessLauncher mParams.desc = desc + " script"; mParams.executable = PYTHON; - mParams.args.add(mScript.getName()); + mParams.args.add(mScript.getPath().string()); } /// Launch Python script; verify that it launched @@ -203,7 +203,7 @@ struct PythonProcessLauncher const char* APR_LOG = getenv("APR_LOG"); if (APR_LOG && *APR_LOG) { - std::ifstream inf(APR_LOG); + llifstream inf(APR_LOG); if (! inf.is_open()) { LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL; @@ -251,11 +251,11 @@ struct PythonProcessLauncher { NamedTempFile out("out", ""); // placeholder // pass name of this temporary file to the script - mParams.args.add(out.getName()); + mParams.args.add(out.getPath().string()); run(); // assuming the script wrote to that file, read it std::string desc = "from " + mDesc + " script"; - return readfile(out.getName(), desc); + return readfile(out.getPath(), desc); } LLProcess::Params mParams; @@ -498,23 +498,23 @@ public: NamedTempDir(): mPath(NamedTempFile::temp_path()), - mCreated(boost::filesystem::create_directories(mPath)) + mCreated(std::filesystem::create_directories(mPath)) { - mPath = boost::filesystem::canonical(mPath); + mPath = std::filesystem::canonical(mPath); } ~NamedTempDir() { if (mCreated) { - boost::filesystem::remove_all(mPath); + std::filesystem::remove_all(mPath); } } std::string getName() const { return mPath.string(); } private: - boost::filesystem::path mPath; + std::filesystem::path mPath; bool mCreated; }; @@ -674,7 +674,7 @@ namespace tut #endif // Have to have a named copy of this std::string so its c_str() value // will persist. - std::string scriptname(script.getName()); + std::string scriptname(script.getPath().string()); argv.push_back(scriptname.c_str()); argv.push_back(NULL); @@ -953,14 +953,14 @@ namespace tut "with open(sys.argv[1], 'w') as f:\n" " f.write('bad')\n"); NamedTempFile out("out", "not started"); - py.mParams.args.add(out.getName()); + py.mParams.args.add(out.getPath().string()); py.launch(); // Wait for the script to wake up and do its first write int i = 0, timeout = 60; for ( ; i < timeout; ++i) { yield(); - if (readfile(out.getName(), "from kill() script") == "ok") + if (readfile(out.getPath(), "from kill() script") == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -979,7 +979,7 @@ namespace tut // If kill() failed, the script would have woken up on its own and // overwritten the file with 'bad'. But if kill() succeeded, it should // not have had that chance. - ensure_equals(get_test_name() + " script output", readfile(out.getName()), "ok"); + ensure_equals(get_test_name() + " script output", readfile(out.getPath()), "ok"); } template<> template<> @@ -999,7 +999,7 @@ namespace tut "# if caller hasn't managed to kill by now, bad\n" "with open(sys.argv[1], 'w') as f:\n" " f.write('bad')\n"); - py.mParams.args.add(out.getName()); + py.mParams.args.add(out.getPath().string()); py.launch(); // Capture handle for later phandle = py.mPy->getProcessHandle(); @@ -1008,7 +1008,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(out.getName(), "from kill() script") == "ok") + if (readfile(out.getPath(), "from kill() script") == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1021,7 +1021,7 @@ namespace tut // If kill() failed, the script would have woken up on its own and // overwritten the file with 'bad'. But if kill() succeeded, it should // not have had that chance. - ensure_equals(get_test_name() + " script output", readfile(out.getName()), "ok"); + ensure_equals(get_test_name() + " script output", readfile(out.getPath()), "ok"); } template<> template<> @@ -1051,8 +1051,8 @@ namespace tut "# okay, saw 'go', write 'ack'\n" "with open(sys.argv[1], 'w') as f:\n" " f.write('ack')\n"); - py.mParams.args.add(from.getName()); - py.mParams.args.add(to.getName()); + py.mParams.args.add(from.getPath().string()); + py.mParams.args.add(to.getPath().string()); py.mParams.autokill = false; py.launch(); // Capture handle for later @@ -1062,7 +1062,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(from.getName(), "from autokill script") == "ok") + if (readfile(from.getPath(), "from autokill script") == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1074,14 +1074,14 @@ namespace tut // How do we know it's not terminated? By making it respond to // a specific stimulus in a specific way. { - std::ofstream outf(to.getName().c_str()); + llofstream outf(to.getPath()); outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. waitfor(phandle, "autokill script"); // If the LLProcess destructor implicitly called kill(), the // script could not have written 'ack' as we expect. - ensure_equals(get_test_name() + " script output", readfile(from.getName()), "ack"); + ensure_equals(get_test_name() + " script output", readfile(from.getPath()), "ack"); } template<> template<> @@ -1113,8 +1113,8 @@ namespace tut "# okay, saw 'go', write 'ack'\n" "with open(sys.argv[1], 'w') as f:\n" " f.write('ack')\n"); - py.mParams.args.add(from.getName()); - py.mParams.args.add(to.getName()); + py.mParams.args.add(from.getPath().string()); + py.mParams.args.add(to.getPath().string()); py.mParams.autokill = true; py.mParams.attached = false; py.launch(); @@ -1125,7 +1125,7 @@ namespace tut for ( ; i < timeout; ++i) { yield(); - if (readfile(from.getName(), "from autokill script") == "ok") + if (readfile(from.getPath(), "from autokill script") == "ok") break; } // If we broke this loop because of the counter, something's wrong @@ -1137,14 +1137,14 @@ namespace tut // How do we know it's not terminated? By making it respond to // a specific stimulus in a specific way. { - std::ofstream outf(to.getName().c_str()); + llofstream outf(to.getPath()); outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. waitfor(phandle, "autokill script"); // If the LLProcess destructor implicitly called kill(), the // script could not have written 'ack' as we expect. - ensure_equals(get_test_name() + " script output", readfile(from.getName()), "ack"); + ensure_equals(get_test_name() + " script output", readfile(from.getPath()), "ack"); } template<> template<> diff --git a/indra/llcommon/tests/llsd_test.cpp b/indra/llcommon/tests/llsd_test.cpp new file mode 100644 index 0000000000..9cc93bcd14 --- /dev/null +++ b/indra/llcommon/tests/llsd_test.cpp @@ -0,0 +1,830 @@ +/** + * @file llsd_new_tut.cpp + * @date February 2006 + * @brief LLSD unit tests + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2006-2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#define LLSD_DEBUG_INFO +#include "linden_common.h" +#include "lltut.h" +#include "llsd.h" + +#include "llsdtraits.h" +#include "llstring.h" + +using std::fpclassify; + +namespace tut +{ + class SDCleanupCheck + { + private: + U32 mOutstandingAtStart; + public: + SDCleanupCheck() : mOutstandingAtStart(llsd::outstandingCount()) { } + ~SDCleanupCheck() + { + ensure_equals("SDCleanupCheck", + llsd::outstandingCount(), mOutstandingAtStart); + } + }; + + class SDAllocationCheck : public SDCleanupCheck + { + private: + std::string mMessage; + U32 mExpectedAllocations; + U32 mAllocationAtStart; + public: + SDAllocationCheck(const std::string& message, int expectedAllocations) + : mMessage(message), + mExpectedAllocations(expectedAllocations), + mAllocationAtStart(llsd::allocationCount()) + { } + ~SDAllocationCheck() + { + ensure_equals(mMessage + " SDAllocationCheck", + llsd::allocationCount() - mAllocationAtStart, + mExpectedAllocations); + } + }; + + struct SDTestData { + template<class T> + static void ensureTypeAndValue(const char* msg, const LLSD& actual, + T expectedValue) + { + LLSDTraits<T> traits; + + std::string s(msg); + + ensure( s + " type", traits.checkType(actual)); + ensure_equals( s + " value", traits.get(actual), expectedValue); + } + + template<class T> + static void ensureTypeAndRefValue(const char* msg, const LLSD& actual, + const T& expectedValue) + { + LLSDTraits<const T&> traits; + + std::string s(msg); + + ensure( s + " type", traits.checkType(actual)); + ensure_equals( s + " value", traits.get(actual), expectedValue); + } + }; + + typedef test_group<SDTestData> SDTestGroup; + typedef SDTestGroup::object SDTestObject; + + SDTestGroup sdTestGroup("LLSD(new)"); + + // template<> template<> + // void SDTestObject::test<1>() + // // construction and test of undefined + // { + // SDCleanupCheck check; + + // LLSD u; + // ensure("is undefined", u.isUndefined()); + // } + + template<> template<> + void SDTestObject::test<2>() + // setting and fetching scalar types + { + SDCleanupCheck check; + + LLSD v; + + v = true; ensureTypeAndValue("set true", v, true); + v = false; ensureTypeAndValue("set false", v, false); + v = true; ensureTypeAndValue("set true again", v, true); + + v = 42; ensureTypeAndValue("set to 42", v, 42); + v = 0; ensureTypeAndValue("set to zero", v, 0); + v = -12345; ensureTypeAndValue("set to neg", v, -12345); + v = 2000000000; ensureTypeAndValue("set to big", v, 2000000000); + + v = 3.14159265359; + ensureTypeAndValue("set to pi", v, 3.14159265359); + ensure_not_equals("isn't float", v.asReal(), + (float)3.14159265359); + v = 6.7e256; ensureTypeAndValue("set to big", v, 6.7e256); + + LLUUID nullUUID; + LLUUID newUUID; + newUUID.generate(); + + v = nullUUID; ensureTypeAndValue("set to null UUID", v, nullUUID); + v = newUUID; ensureTypeAndValue("set to new UUID", v, newUUID); + v = nullUUID; ensureTypeAndValue("set to null again", v, nullUUID); + + // strings must be tested with two types of string objects + std::string s = "now is the time"; + const char* cs = "for all good zorks"; + + v = s; ensureTypeAndValue("set to std::string", v, s); + v = cs; ensureTypeAndValue("set to const char*", v, cs); + + LLDate epoch; + LLDate aDay("2001-10-22T10:11:12.00Z"); + + v = epoch; ensureTypeAndValue("set to epoch", v, epoch); + v = aDay; ensureTypeAndValue("set to a day", v, aDay); + + LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); + + v = path; ensureTypeAndValue("set to a uri", v, path); + + const char source[] = "once in a blue moon"; + std::vector<U8> data; + copy(&source[0], &source[sizeof(source)], back_inserter(data)); + + v = data; ensureTypeAndRefValue("set to data", v, data); + + v.clear(); + ensure("reset to undefined", v.type() == LLSD::TypeUndefined); + } + + template<> template<> + void SDTestObject::test<3>() + // construction via scalar values + // tests both constructor and initialize forms + { + SDCleanupCheck check; + + LLSD b1(true); ensureTypeAndValue("construct boolean", b1, true); + LLSD b2 = true; ensureTypeAndValue("initialize boolean", b2, true); + LLSD i1(42); ensureTypeAndValue("construct int", i1, 42); + LLSD i2 =42; ensureTypeAndValue("initialize int", i2, 42); + LLSD d1(1.2); ensureTypeAndValue("construct double", d1, 1.2); + LLSD d2 = 1.2; ensureTypeAndValue("initialize double", d2, 1.2); + + LLUUID newUUID; + newUUID.generate(); + LLSD u1(newUUID); + ensureTypeAndValue("construct UUID", u1, newUUID); + LLSD u2 = newUUID; + ensureTypeAndValue("initialize UUID", u2, newUUID); + + LLSD ss1(std::string("abc")); + ensureTypeAndValue("construct std::string", ss1, "abc"); + LLSD ss2 = std::string("abc"); + ensureTypeAndValue("initialize std::string",ss2, "abc"); + LLSD sl1(std::string("def")); + ensureTypeAndValue("construct std::string", sl1, "def"); + LLSD sl2 = std::string("def"); + ensureTypeAndValue("initialize std::string", sl2, "def"); + LLSD sc1("ghi"); + ensureTypeAndValue("construct const char*", sc1, "ghi"); + LLSD sc2 = "ghi"; + ensureTypeAndValue("initialize const char*",sc2, "ghi"); + + LLDate aDay("2001-10-22T10:11:12.00Z"); + LLSD t1(aDay); ensureTypeAndValue("construct LLDate", t1, aDay); + LLSD t2 = aDay; ensureTypeAndValue("initialize LLDate", t2, aDay); + + LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); + LLSD p1(path); ensureTypeAndValue("construct LLURI", p1, path); + LLSD p2 = path; ensureTypeAndValue("initialize LLURI", p2, path); + + const char source[] = "once in a blue moon"; + std::vector<U8> data; + copy(&source[0], &source[sizeof(source)], back_inserter(data)); + LLSD x1(data); ensureTypeAndRefValue("construct vector<U8>", x1, data); + LLSD x2 = data; ensureTypeAndRefValue("initialize vector<U8>", x2, data); + } + + void checkConversions(const char* msg, const LLSD& v, + LLSD::Boolean eBoolean, LLSD::Integer eInteger, + LLSD::Real eReal, const LLSD::String& eString) + { + std::string s(msg); + + ensure_equals(s+" to bool", v.asBoolean(), eBoolean); + ensure_equals(s+" to int", v.asInteger(), eInteger); + if (eReal == eReal) + { + ensure_equals(s+" to real", v.asReal(), eReal); + ensure_equals(s+" to string", v.asString(), eString); + } + else + { + int left = fpclassify(v.asReal()); + int right = fpclassify(eReal); + + ensure_equals(s+" to real", left, right); + // ensure_equals(s+" to string", v.asString(), eString); + // I've commented this check out, since there doesn't + // seem to be uniform string representation for NaN on + // all platforms. For example, on my Ubuntu 8.10 laptop + // with libc 2.11.1, sqrt(-1.0) will return '-nan', not + // 'nan'. + } + } + + template<> template<> + void SDTestObject::test<4>() + // conversion between undefined and basic scalar types: + // boolean, integer, real and string + { + SDCleanupCheck check; + + LLSD v; checkConversions("untitled", v, false, 0, 0.0, ""); + + v = false; checkConversions("false", v, false, 0, 0.0, ""); + v = true; checkConversions("true", v, true, 1, 1.0, "true"); + + v = 0; checkConversions("zero", v, false, 0, 0.0, "0"); + v = 1; checkConversions("one", v, true, 1, 1.0, "1"); + v = -33; checkConversions("neg33", v, true, -33, -33.0, "-33"); + + v = 0.0; checkConversions("0.0", v, false, 0, 0.0, "0"); + v = 0.5; checkConversions("point5", v, true, 0, 0.5, "0.5"); + v = 0.9; checkConversions("point9", v, true, 0, 0.9, "0.9"); + v = -3.9; checkConversions("neg3dot9", v, true, -3, -3.9, "-3.9"); + // Get rid of NaN test. First, some libraries don't reliably return + // NaN for sqrt(-1.0) -- meaning that I don't even know how to + // portably, reliably produce a NaN value. Second, we observe failures + // on different platforms in the asString() test. But LLSD's + // ImplReal::asString() does not itself recognize NaN! It merely + // passes the value through to llformat(), which passes it through to + // the library vsnprintf(). That is, even when we do produce NaN, + // we're not testing any LLSD code: we're testing the local library's + // vsnprintf() function, which (empirically) produces idiosyncratic + // results. This is just not a good test case. +// v = sqrt(-1.0); checkConversions("NaN", v, false, 0, sqrt(-1.0), "nan"); + + v = ""; checkConversions("empty", v, false, 0, 0.0, ""); + v = "0"; checkConversions("digit0", v, true, 0, 0.0, "0"); + v = "10"; checkConversions("digit10", v, true, 10, 10.0, "10"); + v = "-2.345"; checkConversions("decdigits", v, + true, -2, -2.345, "-2.345"); + v = "apple"; checkConversions("apple", v, true, 0, 0.0, "apple"); + v = "33bob"; checkConversions("digialpha", v, true, 0, 0.0, "33bob"); + v = " "; checkConversions("space", v, true, 0, 0.0, " "); + v = "\n"; checkConversions("newline", v, true, 0, 0.0, "\n"); + } + + template<class T> + void checkRoundTrip(const std::string& msg, const LLSD& actual, + const char* sExpected, T vExpected) + { + std::string str = actual.asString(); + + if (sExpected) { + ensure_equals(msg + " string", str, sExpected); + } + + LLSD u(str); + LLSDTraits<T> traits; + + ensure_equals(msg + " value", traits.get(u), vExpected); + } + + + template<> template<> + void SDTestObject::test<5>() + // conversion of String to and from UUID, Date and URI. + { + SDCleanupCheck check; + + LLSD v; + + LLUUID nullUUID; + LLUUID someUUID; + someUUID.generate(); + + v = nullUUID; checkRoundTrip("null uuid", v, + "00000000-0000-0000-0000-000000000000", nullUUID); + v = someUUID; checkRoundTrip("random uuid", v, 0, someUUID); + + LLDate epoch; + LLDate beta("2003-04-30T04:00:00Z"); + LLDate oneOh("2003-06-23T04:00:00Z"); + + v = epoch; checkRoundTrip("epoch date", v, 0, epoch); + v = beta; checkRoundTrip("beta date", v, + "2003-04-30T04:00:00Z", beta); + v = oneOh; checkRoundTrip("1.0 date", v, + "2003-06-23T04:00:00Z", oneOh); + + LLURI empty; + LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/"); + LLURI mail("mailto:zero.linden@secondlife.com"); + + v = empty; checkRoundTrip("empty URI", v, 0, empty); + v = path; checkRoundTrip("path URI", v, + "http://slurl.com/secondlife/Ambleside/57/104/26/", + path); + v = mail; checkRoundTrip("mail URI", v, + "mailto:zero.linden@secondlife.com", mail); + } + + template<> template<> + void SDTestObject::test<6>() + // copy construction and assignment + // checking for shared values after constr. or assignment + // checking in both the same type and change of type case + { + SDCleanupCheck check; + + { + LLSD v = 42; + + LLSD w0(v); + ensureTypeAndValue("int constr.", w0, 42); + + LLSD w1(v); + w1 = 13; + ensureTypeAndValue("int constr. change case 1", w1, 13); + ensureTypeAndValue("int constr. change case 2", v, 42); + + LLSD w2(v); + v = 7; + ensureTypeAndValue("int constr. change case 3", w2, 42); + ensureTypeAndValue("int constr. change case 4", v, 7); + } + + { + LLSD v = 42; + + LLSD w1(v); + w1 = "bob"; + ensureTypeAndValue("string constr. change case 1", w1, "bob"); + ensureTypeAndValue("string constr. change case 2", v, 42); + + LLSD w2(v); + v = "amy"; + ensureTypeAndValue("string constr. change case 3", w2, 42); + ensureTypeAndValue("string constr. change case 4", v, "amy"); + } + + { + LLSD v = 42; + + LLSD w0; + w0 = v; + ensureTypeAndValue("int assign", w0, 42); + + LLSD w1; + w1 = v; + w1 = 13; + ensureTypeAndValue("int assign change case 1", w1, 13); + ensureTypeAndValue("int assign change case 2", v, 42); + + LLSD w2; + w2 = v; + v = 7; + ensureTypeAndValue("int assign change case 3", w2, 42); + ensureTypeAndValue("int assign change case 4", v, 7); + } + + { + LLSD v = 42; + + LLSD w1; + w1 = v; + w1 = "bob"; + ensureTypeAndValue("string assign change case 1", w1, "bob"); + ensureTypeAndValue("string assign change case 2", v, 42); + + LLSD w2; + w2 = v; + v = "amy"; + ensureTypeAndValue("string assign change case 3", w2, 42); + ensureTypeAndValue("string assign change case 4", v, "amy"); + } + } + + + template<> template<> + void SDTestObject::test<7>() + // Test assignment and casting to various scalar types. These + // assignments should invoke the right conversion without it being + // mentioned explicitly. The few exceptions are marked SAD. + { + SDCleanupCheck check; + + LLSD v(" 42.375"); + + bool b = false; + b = v; ensure_equals("assign to bool", b, true); + b = (bool)v; ensure_equals("cast to bool", b, true); + + int i = 99; + i = v; ensure_equals("assign to int", i, 42); + i = (int)v; ensure_equals("cast to int", i, 42); + + double d = 3.14159; + d = v; ensure_equals("assign to double", d, 42.375); + d = (double)v; ensure_equals("cast to double", d, 42.375); + + std::string s = "yo"; +// SAD s = v; ensure_equals("assign to string", s, " 42.375"); + s = (std::string)v; ensure_equals("cast to string", s, " 42.375"); + + std::string uuidStr = "b1e50c2b-b627-4d23-8a86-a65d97b6319b"; + v = uuidStr; + LLUUID u; + u = v; + ensure_equals("assign to LLUUID", u, LLUUID(uuidStr)); +// SAD u = (LLUUID)v; +// ensure_equals("cast to LLUUID", u, LLUUID(uuidStr)); + + std::string dateStr = "2005-10-24T15:00:00Z"; + v = dateStr; + LLDate date; + date = v; + ensure_equals("assign to LLDate", date.asString(), dateStr); +// SAD date = (LLDate)v; +// ensure_equals("cast to LLDate", date.asString(), dateStr); + + std::string uriStr = "http://secondlife.com"; + v = uriStr; + LLURI uri; + uri = v; + ensure_equals("assign to LLURI", uri.asString(), uriStr); +// SAD uri = (LLURI)v; +// ensure_equals("cast to LLURI", uri.asString(), uriStr); + } + + template<> template<> + void SDTestObject::test<8>() + // Test construction of various scalar types from LLSD. + // Test both construction and initialization forms. + // These should invoke the right conversion without it being + // mentioned explicitly. The few exceptions are marked SAD. + { + SDCleanupCheck check; + + LLSD v(" 42.375"); + + bool b1(v); ensure_equals("contruct bool", b1, true); + bool b2 = v; ensure_equals("initialize bool", b2, true); + + int i1(v); ensure_equals("contruct int", i1, 42); + int i2 = v; ensure_equals("initialize int", i2, 42); + + double d1(v); ensure_equals("contruct double", d1, 42.375); + double d2 = v; ensure_equals("initialize double", d2, 42.375); + + std::string s1(v); + std::string s2 = v; + ensure_equals("contruct string", s1, " 42.375"); + ensure_equals("initialize string", s2, " 42.375"); + + std::string t1(v); + std::string t2 = v.asString(); // SAD + ensure_equals("contruct std::string", t1, " 42.375"); + ensure_equals("initialize std::string", t2, " 42.375"); + + std::string uuidStr = "b1e50c2b-b627-4d23-8a86-a65d97b6319b"; + v = uuidStr; + LLUUID uuid1(v.asUUID()); // SAD + LLUUID uuid2 = v; + ensure_equals("contruct LLUUID", uuid1, LLUUID(uuidStr)); + ensure_equals("initialize LLUUID", uuid2, LLUUID(uuidStr)); + + std::string dateStr = "2005-10-24T15:00:00Z"; + v = dateStr; + LLDate date1(v.asDate()); // SAD + LLDate date2 = v; + ensure_equals("contruct LLDate", date1.asString(), dateStr); + ensure_equals("initialize LLDate", date2.asString(), dateStr); + + std::string uriStr = "http://secondlife.com"; + v = uriStr; + LLURI uri1(v.asURI()); // SAD + LLURI uri2 = v; + ensure_equals("contruct LLURI", uri1.asString(), uriStr); + ensure_equals("initialize LLURI", uri2.asString(), uriStr); + } + + + template<> template<> + void SDTestObject::test<9>() + // test to make sure v is interpreted as a bool in a various + // scenarios. + { + SDCleanupCheck check; + + LLSD v = "0"; + // magic value that is interpreted as boolean true, but integer false! + + ensure_equals("trinary operator bool", (v ? true : false), true); + ensure_equals("convert to int, then bool", + ((int)v ? true : false), false); + + if(v) + { + ensure("if converted to bool", true); + } + else + { + fail("bool did not convert to a bool in if statement."); + } + + if(!v) + { + fail("bool did not convert to a bool in negated if statement."); + } + } + + template<> template<> + void SDTestObject::test<10>() + // map operations + { + SDCleanupCheck check; + + LLSD v; + ensure("undefined has no members", !v.has("amy")); + ensure("undefined get() is undefined", v.get("bob").isUndefined()); + + v = LLSD::emptyMap(); + ensure("empty map is a map", v.isMap()); + ensure("empty map has no members", !v.has("cam")); + ensure("empty map get() is undefined", v.get("don").isUndefined()); + + v.clear(); + v.insert("eli", 43); + ensure("insert converts to map", v.isMap()); + ensure("inserted key is present", v.has("eli")); + ensureTypeAndValue("inserted value", v.get("eli"), 43); + + v.insert("fra", false); + ensure("first key still present", v.has("eli")); + ensure("second key is present", v.has("fra")); + ensureTypeAndValue("first value", v.get("eli"), 43); + ensureTypeAndValue("second value", v.get("fra"), false); + + v.erase("eli"); + ensure("first key now gone", !v.has("eli")); + ensure("second key still present", v.has("fra")); + ensure("first value gone", v.get("eli").isUndefined()); + ensureTypeAndValue("second value sill there", v.get("fra"), false); + + v.erase("fra"); + ensure("second key now gone", !v.has("fra")); + ensure("second value gone", v.get("fra").isUndefined()); + + v["gil"] = (std::string)"good morning"; + ensure("third key present", v.has("gil")); + ensureTypeAndValue("third key value", v.get("gil"), "good morning"); + + const LLSD& cv = v; // FIX ME IF POSSIBLE + ensure("missing key", cv["ham"].isUndefined()); + ensure("key not present", !v.has("ham")); + + LLSD w = 43; + const LLSD& cw = w; // FIX ME IF POSSIBLE + int i = cw["ian"]; + ensureTypeAndValue("other missing value", i, 0); + ensure("other missing key", !w.has("ian")); + ensure("no conversion", w.isInteger()); + + LLSD x; + x = v; + ensure("copy map type", x.isMap()); + ensureTypeAndValue("copy map value gil", x.get("gil"), "good morning"); + } + + + template<> template<> + void SDTestObject::test<11>() + // array operations + { + SDCleanupCheck check; + + LLSD v; + ensure_equals("undefined has no size", v.size(), 0); + ensure("undefined get() is undefined", v.get(0).isUndefined()); + + v = LLSD::emptyArray(); + ensure("empty array is an array", v.isArray()); + ensure_equals("empty array has no size", v.size(), 0); + ensure("empty map get() is undefined", v.get(0).isUndefined()); + + v.clear(); + v.append(88); + v.append("noodle"); + v.append(true); + ensure_equals("appened array size", v.size(), 3); + ensure("append array is an array", v.isArray()); + ensureTypeAndValue("append 0", v[0], 88); + ensureTypeAndValue("append 1", v[1], "noodle"); + ensureTypeAndValue("append 2", v[2], true); + + v.insert(0, 77); + v.insert(2, "soba"); + v.insert(4, false); + ensure_equals("inserted array size", v.size(), 6); + ensureTypeAndValue("post insert 0", v[0], 77); + ensureTypeAndValue("post insert 1", v[1], 88); + ensureTypeAndValue("post insert 2", v[2], "soba"); + ensureTypeAndValue("post insert 3", v[3], "noodle"); + ensureTypeAndValue("post insert 4", v[4], false); + ensureTypeAndValue("post insert 5", v[5], true); + + ensureTypeAndValue("get 1", v.get(1), 88); + v.set(1, "hot"); + ensureTypeAndValue("set 1", v.get(1), "hot"); + + v.erase(3); + ensure_equals("post erase array size", v.size(), 5); + ensureTypeAndValue("post erase 0", v[0], 77); + ensureTypeAndValue("post erase 1", v[1], "hot"); + ensureTypeAndValue("post erase 2", v[2], "soba"); + ensureTypeAndValue("post erase 3", v[3], false); + ensureTypeAndValue("post erase 4", v[4], true); + + v.append(34); + ensure_equals("size after append", v.size(), 6); + ensureTypeAndValue("post append 5", v[5], 34); + + LLSD w; + w = v; + ensure("copy array type", w.isArray()); + ensure_equals("copy array size", w.size(), 6); + ensureTypeAndValue("copy array 0", w[0], 77); + ensureTypeAndValue("copy array 1", w[1], "hot"); + ensureTypeAndValue("copy array 2", w[2], "soba"); + ensureTypeAndValue("copy array 3", w[3], false); + ensureTypeAndValue("copy array 4", w[4], true); + ensureTypeAndValue("copy array 5", w[5], 34); + } + + + template<> template<> + void SDTestObject::test<12>() + // no sharing + { + SDCleanupCheck check; + + LLSD a = 99; + LLSD b = a; + a = 34; + ensureTypeAndValue("top level original changed", a, 34); + ensureTypeAndValue("top level copy unaltered", b, 99); + b = a; + b = 66; + ensureTypeAndValue("top level original unaltered", a, 34); + ensureTypeAndValue("top level copy changed", b, 66); + + a[0] = "uno"; + a[1] = 99; + a[2] = 1.414; + b = a; + a[1] = 34; + ensureTypeAndValue("array member original changed", a[1], 34); + ensureTypeAndValue("array member copy unaltered", b[1], 99); + b = a; + b[1] = 66; + ensureTypeAndValue("array member original unaltered", a[1], 34); + ensureTypeAndValue("array member copy changed", b[1], 66); + + a["alpha"] = "uno"; + a["beta"] = 99; + a["gamma"] = 1.414; + b = a; + a["beta"] = 34; + ensureTypeAndValue("map member original changed", a["beta"], 34); + ensureTypeAndValue("map member copy unaltered", b["beta"], 99); + b = a; + b["beta"] = 66; + ensureTypeAndValue("map member original unaltered", a["beta"], 34); + ensureTypeAndValue("map member copy changed", b["beta"], 66); + } + + template<> template<> + void SDTestObject::test<13>() + // sharing implementation + { + SDCleanupCheck check; + + { + SDAllocationCheck check("copy construct undefinded", 0); + LLSD v; + LLSD w = v; + } + + { + SDAllocationCheck check("assign undefined", 0); + LLSD v; + LLSD w; + w = v; + } + + { + SDAllocationCheck check("assign integer value", 1); + LLSD v = 45; + v = 33; + v = 0; + } + + { + SDAllocationCheck check("copy construct integer", 1); + LLSD v = 45; + LLSD w = v; + } + + { + SDAllocationCheck check("assign integer", 1); + LLSD v = 45; + LLSD w; + w = v; + } + + { + SDAllocationCheck check("avoids extra clone", 2); + LLSD v = 45; + LLSD w = v; + w = "nice day"; + } + + { + SDAllocationCheck check("shared values test for threaded work", 9); + + //U32 start_llsd_count = LLSD::outstandingCount(); + + LLSD m = LLSD::emptyMap(); + + m["one"] = 1; + m["two"] = 2; + m["one_copy"] = m["one"]; // 3 (m, "one" and "two") + + m["undef_one"] = LLSD(); + m["undef_two"] = LLSD(); + m["undef_one_copy"] = m["undef_one"]; + + { // Ensure first_array gets freed to avoid counting it + LLSD first_array = LLSD::emptyArray(); + first_array.append(1.0f); + first_array.append(2.0f); + first_array.append(3.0f); // 7 + + m["array"] = first_array; + m["array_clone"] = first_array; + m["array_copy"] = m["array"]; // 7 + } + + m["string_one"] = "string one value"; + m["string_two"] = "string two value"; + m["string_one_copy"] = m["string_one"]; // 9 + + //U32 llsd_object_count = LLSD::outstandingCount(); + //std::cout << "Using " << (llsd_object_count - start_llsd_count) << " LLSD objects" << std::endl; + + //m.dumpStats(); + } + } + + template<> template<> + void SDTestObject::test<14>() + // make sure that assignment of char* NULL in a string does not crash. + { + LLSD v; + v = (const char*)NULL; + ensure("type is a string", v.isString()); + } + + /* TO DO: + conversion of undefined to UUID, Date, URI and Binary + conversion of undefined to map and array + test map operations + test array operations + test array extension + + test copying and assign maps and arrays (clone) + test iteration over map + test iteration over array + test iteration over scalar + + test empty map and empty array are indeed shared + test serializations + */ +} + diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f52b3fec71..71096523a9 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -52,7 +52,7 @@ typedef U32 uint32_t; #include "llformat.h" #include "llmemorystream.h" -#include "../test/hexdump.h" +#include "hexdump.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "stringize.h" @@ -1860,7 +1860,7 @@ namespace tut #if LL_WINDOWS std::string q("\""); std::string qPYTHON(q + PYTHON + q); - std::string qscript(q + scriptfile.getName() + q); + std::string qscript(q + scriptfile.getPath().string() + q); int rc = (int)_spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), std::forward<ARGS>(args)..., NULL); if (rc == -1) @@ -1877,7 +1877,7 @@ namespace tut #else // LL_DARWIN, LL_LINUX LLProcess::Params params; params.executable = PYTHON; - params.args.add(scriptfile.getName()); + params.args.add(scriptfile.getPath().string()); for (const std::string& arg : StringVec{ std::forward<ARGS>(args)... }) { params.args.add(arg); @@ -1973,12 +1973,12 @@ namespace tut int bufflen{ static_cast<int>(buffstr.length()) }; out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen)); LL_DEBUGS() << "Wrote length: " - << hexdump(reinterpret_cast<const char*>(&bufflen), + << LL::hexdump(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen)) << LL_ENDL; out.write(buffstr.c_str(), buffstr.length()); LL_DEBUGS() << "Wrote data: " - << hexmix(buffstr.c_str(), buffstr.length()) + << LL::hexmix(buffstr.c_str(), buffstr.length()) << LL_ENDL; } } @@ -2054,8 +2054,8 @@ namespace tut " yield frombytes\n" << pydata << // Don't forget raw-string syntax for Windows pathnames. - "debug = open(r'" << debug.getName() << "', 'w')\n" - "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";}); + "debug = open(r'" << debug.getPath().string() << "', 'w')\n" + "verify(parse_each(open(r'" << file.getPath().string() << "', 'rb')))\n";}); } catch (const failure&) { @@ -2163,13 +2163,13 @@ namespace tut "]\n" // Don't forget raw-string syntax for Windows pathnames. // N.B. Using 'print' implicitly adds newlines. - "with open(r'" << file.getName() << "', 'wb') as f:\n" + "with open(r'" << (const char*)file.getPath().u8string().c_str() << "', 'wb') as f:\n" " for item in DATA:\n" " serialized = llsd." << pyformatter << "(item)\n" " f.write(lenformat.pack(len(serialized)))\n" " f.write(serialized)\n";}); - std::ifstream inf(file.getName().c_str()); + llifstream inf(file.getPath()); LLSD item; try { diff --git a/indra/llcommon/tests/llsdutil_test.cpp b/indra/llcommon/tests/llsdutil_test.cpp new file mode 100644 index 0000000000..4b25a02f30 --- /dev/null +++ b/indra/llcommon/tests/llsdutil_test.cpp @@ -0,0 +1,434 @@ +/** + * @file llsdutil_tut.cpp + * @author Adroit + * @date 2007-02 + * @brief LLSD conversion routines test cases. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "lltut.h" +#include "m4math.h" +#include "v2math.h" +#include "v2math.h" +#include "v3color.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4coloru.h" +#include "v4math.h" +#include "llquaternion.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "stringize.h" +#include <set> +#include <boost/range.hpp> + +namespace tut +{ + struct llsdutil_data + { + void test_matches(const std::string& proto_key, const LLSD& possibles, + const char** begin, const char** end) + { + std::set<std::string> succeed(begin, end); + LLSD prototype(possibles[proto_key]); + for (LLSD::map_const_iterator pi(possibles.beginMap()), pend(possibles.endMap()); + pi != pend; ++pi) + { + std::string match(llsd_matches(prototype, pi->second)); + std::set<std::string>::const_iterator found = succeed.find(pi->first); + if (found != succeed.end()) + { + // This test is supposed to succeed. Comparing to the + // empty string ensures that if the test fails, it will + // display the string received so we can tell what failed. + ensure_equals("match", match, ""); + } + else + { + // This test is supposed to fail. If we get a false match, + // the string 'match' will be empty, which doesn't tell us + // much about which case went awry. So construct a more + // detailed description string. + ensure(proto_key + " shouldn't match " + pi->first, ! match.empty()); + } + } + } + }; + typedef test_group<llsdutil_data> llsdutil_test;; + typedef llsdutil_test::object llsdutil_object; + tut::llsdutil_test tutil("llsdutil"); + + template<> template<> + void llsdutil_object::test<1>() + { + LLSD sd; + U64 valueIn , valueOut; + valueIn = U64L(0xFEDCBA9876543210); + sd = ll_sd_from_U64(valueIn); + valueOut = ll_U64_from_sd(sd); + ensure_equals("U64 valueIn->sd->valueOut", valueIn, valueOut); + } + + template<> template<> + void llsdutil_object::test<2>() + { + LLSD sd; + U32 valueIn, valueOut; + valueIn = 0x87654321; + sd = ll_sd_from_U32(valueIn); + valueOut = ll_U32_from_sd(sd); + ensure_equals("U32 valueIn->sd->valueOut", valueIn, valueOut); + } + + template<> template<> + void llsdutil_object::test<3>() + { + U32 valueIn, valueOut; + valueIn = 0x87654321; + LLSD sd; + sd = ll_sd_from_ipaddr(valueIn); + valueOut = ll_ipaddr_from_sd(sd); + ensure_equals("valueIn->sd->valueOut", valueIn, valueOut); + } + + template<> template<> + void llsdutil_object::test<4>() + { + LLSD sd; + LLVector3 vec1(-1.0, 2.0, -3.0); + sd = ll_sd_from_vector3(vec1); + LLVector3 vec2 = ll_vector3_from_sd(sd); + ensure_equals("vector3 -> sd -> vector3: 1", vec1, vec2); + + LLVector3 vec3(sd); + ensure_equals("vector3 -> sd -> vector3: 2", vec1, vec3); + + sd.clear(); + vec1.setVec(0., 0., 0.); + sd = ll_sd_from_vector3(vec1); + vec2 = ll_vector3_from_sd(sd); + ensure_equals("vector3 -> sd -> vector3: 3", vec1, vec2); + sd.clear(); + } + + template<> template<> + void llsdutil_object::test<5>() + { + LLSD sd; + LLVector3d vec1((F64)(U64L(0xFEDCBA9876543210) << 2), -1., 0); + sd = ll_sd_from_vector3d(vec1); + LLVector3d vec2 = ll_vector3d_from_sd(sd); + ensure_equals("vector3d -> sd -> vector3d: 1", vec1, vec2); + + LLVector3d vec3(sd); + ensure_equals("vector3d -> sd -> vector3d : 2", vec1, vec3); + } + + template<> template<> + void llsdutil_object::test<6>() + { + LLSD sd; + LLVector2 vec((F32) -3., (F32) 4.2); + sd = ll_sd_from_vector2(vec); + LLVector2 vec1 = ll_vector2_from_sd(sd); + ensure_equals("vector2 -> sd -> vector2", vec, vec1); + + LLSD sd2 = ll_sd_from_vector2(vec1); + ensure_equals("sd -> vector2 -> sd: 2", sd, sd2); + } + + template<> template<> + void llsdutil_object::test<7>() + { + LLSD sd; + LLQuaternion quat((F32) 1., (F32) -0.98, (F32) 2.3, (F32) 0xffff); + sd = ll_sd_from_quaternion(quat); + LLQuaternion quat1 = ll_quaternion_from_sd(sd); + ensure_equals("LLQuaternion -> sd -> LLQuaternion", quat, quat1); + + LLSD sd2 = ll_sd_from_quaternion(quat1); + ensure_equals("sd -> LLQuaternion -> sd ", sd, sd2); + } + + template<> template<> + void llsdutil_object::test<8>() + { + LLSD sd; + LLColor4 c(1.0f, 2.2f, 4.0f, 7.f); + sd = ll_sd_from_color4(c); + LLColor4 c1 =ll_color4_from_sd(sd); + ensure_equals("LLColor4 -> sd -> LLColor4", c, c1); + + LLSD sd1 = ll_sd_from_color4(c1); + ensure_equals("sd -> LLColor4 -> sd", sd, sd1); + } + + template<> template<> + void llsdutil_object::test<9>() + { + set_test_name("llsd_matches"); + + // for this test, construct a map of all possible LLSD types + LLSD map; + map.insert("empty", LLSD()); + map.insert("Boolean", LLSD::Boolean()); + map.insert("Integer", LLSD::Integer(0)); + map.insert("Real", LLSD::Real(0.0)); + map.insert("String", LLSD::String("bah")); + map.insert("NumString", LLSD::String("1")); + map.insert("UUID", LLSD::UUID()); + map.insert("Date", LLSD::Date()); + map.insert("URI", LLSD::URI()); + map.insert("Binary", LLSD::Binary()); + map.insert("Map", LLSD().with("foo", LLSD())); + // Only an empty array can be constructed on the fly + LLSD array; + array.append(LLSD()); + map.insert("Array", array); + + // These iterators are declared outside our various for loops to avoid + // fatal MSVC warning: "I used to be broken, but I'm all better now!" + LLSD::map_const_iterator mi, mend(map.endMap()); + + /*-------------------------- llsd_matches --------------------------*/ + + // empty prototype matches anything + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure_equals(std::string("empty matches ") + mi->first, llsd_matches(LLSD(), mi->second), ""); + } + + LLSD proto_array, data_array; + for (int i = 0; i < 3; ++i) + { + proto_array.append(LLSD()); + data_array.append(LLSD()); + } + + // prototype array matches only array + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure(std::string("array doesn't match ") + mi->first, + ! llsd_matches(proto_array, mi->second).empty()); + } + + // data array must be at least as long as prototype array + proto_array.append(LLSD()); + ensure_equals("data array too short", llsd_matches(proto_array, data_array), + "Array size 4 required instead of Array size 3"); + data_array.append(LLSD()); + ensure_equals("data array just right", llsd_matches(proto_array, data_array), ""); + data_array.append(LLSD()); + ensure_equals("data array longer", llsd_matches(proto_array, data_array), ""); + + // array element matching + data_array[0] = LLSD::String(); + ensure_equals("undefined prototype array entry", llsd_matches(proto_array, data_array), ""); + proto_array[0] = LLSD::Binary(); + ensure_equals("scalar prototype array entry", llsd_matches(proto_array, data_array), + "[0]: Binary required instead of String"); + data_array[0] = LLSD::Binary(); + ensure_equals("matching prototype array entry", llsd_matches(proto_array, data_array), ""); + + // build a coupla maps + LLSD proto_map, data_map; + data_map["got"] = LLSD(); + data_map["found"] = LLSD(); + for (LLSD::map_const_iterator dmi(data_map.beginMap()), dmend(data_map.endMap()); + dmi != dmend; ++dmi) + { + proto_map[dmi->first] = dmi->second; + } + proto_map["foo"] = LLSD(); + proto_map["bar"] = LLSD(); + + // prototype map matches only map + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure(std::string("map doesn't match ") + mi->first, + ! llsd_matches(proto_map, mi->second).empty()); + } + + // data map must contain all keys in prototype map + std::string error(llsd_matches(proto_map, data_map)); + ensure_contains("missing keys", error, "missing keys"); + ensure_contains("missing foo", error, "foo"); + ensure_contains("missing bar", error, "bar"); + ensure_does_not_contain("found found", error, "found"); + ensure_does_not_contain("got got", error, "got"); + data_map["bar"] = LLSD(); + error = llsd_matches(proto_map, data_map); + ensure_contains("missing foo", error, "foo"); + ensure_does_not_contain("got bar", error, "bar"); + data_map["foo"] = LLSD(); + ensure_equals("data map just right", llsd_matches(proto_map, data_map), ""); + data_map["extra"] = LLSD(); + ensure_equals("data map with extra", llsd_matches(proto_map, data_map), ""); + + // map element matching + data_map["foo"] = LLSD::String(); + ensure_equals("undefined prototype map entry", llsd_matches(proto_map, data_map), ""); + proto_map["foo"] = LLSD::Binary(); + ensure_equals("scalar prototype map entry", llsd_matches(proto_map, data_map), + "['foo']: Binary required instead of String"); + data_map["foo"] = LLSD::Binary(); + ensure_equals("matching prototype map entry", llsd_matches(proto_map, data_map), ""); + + // String + { + static const char* matches[] = { "String", "NumString", "Boolean", "Integer", + "Real", "UUID", "Date", "URI" }; + test_matches("String", map, boost::begin(matches), boost::end(matches)); + } + + // Boolean, Integer, Real + static const char* numerics[] = { "Boolean", "Integer", "Real" }; + for (const char **ni = boost::begin(numerics), **nend = boost::end(numerics); + ni != nend; ++ni) + { + static const char* matches[] = { "Boolean", "Integer", "Real", "String", "NumString" }; + test_matches(*ni, map, boost::begin(matches), boost::end(matches)); + } + + // UUID + { + static const char* matches[] = { "UUID", "String", "NumString" }; + test_matches("UUID", map, boost::begin(matches), boost::end(matches)); + } + + // Date + { + static const char* matches[] = { "Date", "String", "NumString" }; + test_matches("Date", map, boost::begin(matches), boost::end(matches)); + } + + // URI + { + static const char* matches[] = { "URI", "String", "NumString" }; + test_matches("URI", map, boost::begin(matches), boost::end(matches)); + } + + // Binary + { + static const char* matches[] = { "Binary" }; + test_matches("Binary", map, boost::begin(matches), boost::end(matches)); + } + + /*-------------------------- llsd_equals ---------------------------*/ + + // Cross-product of each LLSD type with every other + for (LLSD::map_const_iterator lmi(map.beginMap()), lmend(map.endMap()); + lmi != lmend; ++lmi) + { + for (LLSD::map_const_iterator rmi(map.beginMap()), rmend(map.endMap()); + rmi != rmend; ++rmi) + { + // Name this test based on the map keys naming the types of + // interest, e.g "String::Integer". + // We expect the values (xmi->second) to be equal if and only + // if the type names (xmi->first) are equal. + ensure(STRINGIZE(lmi->first << "::" << rmi->first), + bool(lmi->first == rmi->first) == + bool(llsd_equals(lmi->second, rmi->second))); + } + } + + // Array cases + LLSD rarray; + rarray.append(1.0); + rarray.append(2); + rarray.append("3"); + LLSD larray(rarray); + ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray)); + rarray[2] = "4"; + ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray)); + rarray = larray; + rarray.append(LLSD::Date()); + ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray)); + rarray = larray; + rarray.erase(2); + ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray)); + + // Map cases + LLSD rmap; + rmap["San Francisco"] = 65; + rmap["Phoenix"] = 92; + rmap["Boston"] = 77; + LLSD lmap(rmap); + ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap)); + rmap["Boston"] = 80; + ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap)); + rmap = lmap; + rmap["Atlanta"] = 95; + ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap)); + rmap = lmap; + lmap["Seattle"] = 72; + ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap)); + } + + template<> template<> + void llsdutil_object::test<10>() + { + set_test_name("llsd_hashing"); + + { + LLSD data_s1 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); + LLSD data_s2 = LLSD::String("The quick brown aardvark jumped over the lazy lemming."); + + ensure("hash: Identical string hashes match.", boost::hash<LLSD>{}(data_s1) == boost::hash<LLSD>{}(data_s2)); + } + { + LLSD data_r1 = LLSD::Real(3.0f); + LLSD data_i1 = LLSD::Integer(3); + ensure("hash: equivalent values but different types do not match.", boost::hash<LLSD>{}(data_r1) != boost::hash<LLSD>{}(data_i1)); + } + { + LLSD data_a1 = llsd::array("A", "B", "C"); + LLSD data_a2 = llsd::array("A", "B", "C"); + + ensure("hash: identical arrays produce identical results", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2)); + + data_a2.append(llsd::array(1, 2)); + + ensure("hash: changing the array changes the hash.", boost::hash<LLSD>{}(data_a1) != boost::hash<LLSD>{}(data_a2)); + + data_a1.append(llsd::array(1, 2)); + ensure("hash: identical arrays produce identical results with nested arrays", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2)); + } + { + LLSD data_m1 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); + LLSD data_m2 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); + + ensure("hash: identical maps produce identical results", boost::hash<LLSD>{}(data_m1) == boost::hash<LLSD>{}(data_m2)); + + LLSD data_m3 = LLSDMap("key1", LLSD::Real(5.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); + ensure("hash: Different values in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m3)); + + LLSD data_m4 = LLSDMap("keyA", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3)); + ensure("hash: Different keys in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m4)); + + } + } + +} diff --git a/indra/llcommon/tests/llstreamqueue_test.cpp b/indra/llcommon/tests/llstreamqueue_test.cpp deleted file mode 100644 index 82b451119e..0000000000 --- a/indra/llcommon/tests/llstreamqueue_test.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/** - * @file llstreamqueue_test.cpp - * @author Nat Goodspeed - * @date 2012-01-05 - * @brief Test for llstreamqueue. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Copyright (c) 2012, Linden Research, Inc. - * $/LicenseInfo$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "llstreamqueue.h" -// STL headers -#include <vector> -// other Linden headers -#include "../test/lltut.h" -#include "stringize.h" - -/***************************************************************************** -* TUT -*****************************************************************************/ -namespace tut -{ - struct llstreamqueue_data - { - llstreamqueue_data(): - // we want a buffer with actual bytes in it, not an empty vector - buffer(10) - {} - // As LLStreamQueue is merely a typedef for - // LLGenericStreamQueue<char>, and no logic in LLGenericStreamQueue is - // specific to the <char> instantiation, we're comfortable for now - // testing only the narrow-char version. - LLStreamQueue strq; - // buffer for use in multiple tests - std::vector<char> buffer; - }; - typedef test_group<llstreamqueue_data> llstreamqueue_group; - typedef llstreamqueue_group::object object; - llstreamqueue_group llstreamqueuegrp("llstreamqueue"); - - template<> template<> - void object::test<1>() - { - set_test_name("empty LLStreamQueue"); - ensure_equals("brand-new LLStreamQueue isn't empty", - strq.size(), 0); - ensure_equals("brand-new LLStreamQueue returns data", - strq.asSource().read(&buffer[0], buffer.size()), 0); - strq.asSink().close(); - ensure_equals("closed empty LLStreamQueue not at EOF", - strq.asSource().read(&buffer[0], buffer.size()), -1); - } - - template<> template<> - void object::test<2>() - { - set_test_name("one internal block, one buffer"); - LLStreamQueue::Sink sink(strq.asSink()); - ensure_equals("write(\"\")", sink.write("", 0), 0); - ensure_equals("0 write should leave LLStreamQueue empty (size())", - strq.size(), 0); - ensure_equals("0 write should leave LLStreamQueue empty (peek())", - strq.peek(&buffer[0], buffer.size()), 0); - // The meaning of "atomic" is that it must be smaller than our buffer. - std::string atomic("atomic"); - ensure("test data exceeds buffer", atomic.length() < buffer.size()); - ensure_equals(STRINGIZE("write(\"" << atomic << "\")"), - sink.write(&atomic[0], atomic.length()), atomic.length()); - ensure_equals("size() after write()", strq.size(), atomic.length()); - size_t peeklen(strq.peek(&buffer[0], buffer.size())); - ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"), - peeklen, atomic.length()); - ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"), - std::string(buffer.begin(), buffer.begin() + peeklen), atomic); - ensure_equals("size() after peek()", strq.size(), atomic.length()); - // peek() should not consume. Use a different buffer to prove it isn't - // just leftover data from the first peek(). - std::vector<char> again(buffer.size()); - peeklen = size_t(strq.peek(&again[0], again.size())); - ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"), - peeklen, atomic.length()); - ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"), - std::string(again.begin(), again.begin() + peeklen), atomic); - // now consume. - std::vector<char> third(buffer.size()); - size_t readlen(strq.read(&third[0], third.size())); - ensure_equals(STRINGIZE("read(\"" << atomic << "\")"), - readlen, atomic.length()); - ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"), - std::string(third.begin(), third.begin() + readlen), atomic); - ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0); - ensure_equals("size() after read()", strq.size(), 0); - } - - template<> template<> - void object::test<3>() - { - set_test_name("basic skip()"); - std::string lovecraft("lovecraft"); - ensure("test data exceeds buffer", lovecraft.length() < buffer.size()); - ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"), - strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length()); - size_t peeklen(strq.peek(&buffer[0], buffer.size())); - ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"), - peeklen, lovecraft.length()); - ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"), - std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft); - std::streamsize skip1(4); - ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1); - ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1); - size_t readlen(strq.read(&buffer[0], buffer.size())); - ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"), - readlen, lovecraft.length() - skip1); - ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"), - std::string(buffer.begin(), buffer.begin() + readlen), - lovecraft.substr(skip1)); - ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0); - } - - template<> template<> - void object::test<4>() - { - set_test_name("skip() multiple blocks"); - std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" }; - std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length()); - std::streamsize leave(5); // len("craft") above - std::streamsize skip(total - leave); - std::streamsize written(0); - for (const std::string& block : blocks) - { - written += strq.write(&block[0], block.length()); - ensure_equals("size() after write()", strq.size(), written); - } - std::streamsize skiplen(strq.skip(skip)); - ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip); - ensure_equals("size() after skip()", strq.size(), leave); - size_t readlen(strq.read(&buffer[0], buffer.size())); - ensure_equals("read(\"craft\")", readlen, leave); - ensure_equals("read(\"craft\") result", - std::string(buffer.begin(), buffer.begin() + readlen), "craft"); - } - - template<> template<> - void object::test<5>() - { - set_test_name("concatenate blocks"); - std::string blocks[] = { "abcd", "efghij", "klmnopqrs" }; - for (const std::string& block : blocks) - { - strq.write(&block[0], block.length()); - } - std::vector<char> longbuffer(30); - std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size())); - ensure_equals("read() multiple blocks", - readlen, blocks[0].length() + blocks[1].length() + blocks[2].length()); - ensure_equals("read() multiple blocks result", - std::string(longbuffer.begin(), longbuffer.begin() + readlen), - blocks[0] + blocks[1] + blocks[2]); - } - - template<> template<> - void object::test<6>() - { - set_test_name("split blocks"); - std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" }; - for (const std::string& block : blocks) - { - strq.write(&block[0], block.length()); - } - strq.close(); - // We've already verified what strq.size() should be at this point; - // see above test named "skip() multiple blocks" - std::streamsize chksize(strq.size()); - std::streamsize readlen(strq.read(&buffer[0], buffer.size())); - ensure_equals("read() 0", readlen, buffer.size()); - ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij"); - chksize -= readlen; - ensure_equals("size() after read() 0", strq.size(), chksize); - readlen = strq.read(&buffer[0], buffer.size()); - ensure_equals("read() 1", readlen, buffer.size()); - ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst"); - chksize -= readlen; - ensure_equals("size() after read() 1", strq.size(), chksize); - readlen = strq.read(&buffer[0], buffer.size()); - ensure_equals("read() 2", readlen, chksize); - ensure_equals("read() 2 result", - std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz"); - ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1); - } -} // namespace tut diff --git a/indra/llcommon/tests/llstreamtools_test.cpp b/indra/llcommon/tests/llstreamtools_test.cpp new file mode 100644 index 0000000000..970afb3b02 --- /dev/null +++ b/indra/llcommon/tests/llstreamtools_test.cpp @@ -0,0 +1,883 @@ +/** + * @file llstreamtools_tut.cpp + * @author Adroit + * @date February 2007 + * @brief llstreamtools test cases. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include <tut/tut.hpp> + +#include "linden_common.h" +#include "llstreamtools.h" +#include "lltut.h" + + +namespace tut +{ + struct streamtools_data + { + }; + typedef test_group<streamtools_data> streamtools_test; + typedef streamtools_test::object streamtools_object; + tut::streamtools_test streamtools_testcase("streamtools"); + + //test cases for skip_whitespace() + template<> template<> + void streamtools_object::test<1>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + is.str(str = ""); + ensure("skip_whitespace: empty string", (false == skip_whitespace(is))); + + is.clear(); + is.str(str = " SecondLife is a 3D World"); + skip_whitespace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World"; + ensure_equals("skip_whitespace: space", arr, expected_result); + + is.clear(); + is.str(str = "\t \tSecondLife is a 3D World"); + skip_whitespace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World"; + ensure_equals("skip_whitespace: space and tabs", arr, expected_result); + + is.clear(); + is.str(str = "\t \tSecondLife is a 3D World "); + skip_whitespace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World "; + ensure_equals("skip_whitespace: space at end", arr, expected_result); + + is.clear(); + is.str(str = "\t \r\nSecondLife is a 3D World"); + skip_whitespace(is); + is.get(arr, 255, '\0'); + expected_result = "\r\nSecondLife is a 3D World"; + ensure_equals("skip_whitespace: space at end", arr, expected_result); + } + + //testcases for skip_emptyspaces() + template<> template<> + void streamtools_object::test<2>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = " \tSecondLife is a 3D World.\n"); + skip_emptyspace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World.\n"; + ensure_equals("skip_emptyspace: space and tabs", arr, expected_result); + + is.clear(); + is.str(str = " \t\r\n \r SecondLife is a 3D World.\n"); + skip_emptyspace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World.\n"; + ensure_equals("skip_emptyspace: space, tabs, carriage return, newline", arr, expected_result); + + is.clear(); + is.str(str = ""); + ret = skip_emptyspace(is); + is.get(arr, 255, '\0'); + ensure("skip_emptyspace: empty string", ret == false); + + is.clear(); + is.str(str = " \r\n \t "); + ret = skip_emptyspace(is); + is.get(arr, 255, '\0'); + ensure("skip_emptyspace: space newline empty", ret == false); + } + + //testcases for skip_comments_and_emptyspace() + template<> template<> + void streamtools_object::test<3>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = " \t\r\n \r SecondLife is a 3D World.\n"); + skip_comments_and_emptyspace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World.\n"; + ensure_equals("skip_comments_and_emptyspace: space, tabs, carriage return, newline", arr, expected_result); + + is.clear(); + is.str(str = "# \r\n SecondLife is a 3D World."); + skip_comments_and_emptyspace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World."; + ensure_equals("skip_comments_and_emptyspace: skip comment - 1", arr, expected_result); + + is.clear(); + is.str(str = "# \r\n # SecondLife is a 3D World. ##"); + ensure("should not be good()", ! skip_comments_and_emptyspace(is)); + ensure("should be at eof()", is.eof()); + // don't get(): given bad state, we can't rely on results + + is.clear(); + is.str(str = " \r\n SecondLife is a 3D World. ##"); + skip_comments_and_emptyspace(is); + is.get(arr, 255, '\0'); + expected_result = "SecondLife is a 3D World. ##"; + ensure_equals("skip_comments_and_emptyspace: skip comment - 3", arr, expected_result); + + is.clear(); + is.str(str = ""); + ret = skip_comments_and_emptyspace(is); + ensure("skip_comments_and_emptyspace: empty string", ! ret); + + is.clear(); + is.str(str = " \r\n \t # SecondLife is a 3D World"); + ret = skip_comments_and_emptyspace(is); + ensure("skip_comments_and_emptyspace: space newline comment empty", ! ret); + } + + //testcases for skip_line() + template<> template<> + void streamtools_object::test<4>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = "SecondLife is a 3D World.\n\n It provides an opportunity to the site \nuser to perform real life activities in virtual world."); + skip_line(is); + is.get(arr, 255, '\0'); + expected_result = "\n It provides an opportunity to the site \nuser to perform real life activities in virtual world."; + ensure_equals("skip_line: 1 newline", arr, expected_result); + + is.clear(); + is.str(expected_result); + skip_line(is); + is.get(arr, 255, '\0'); + expected_result = " It provides an opportunity to the site \nuser to perform real life activities in virtual world."; + ensure_equals("skip_line: 2 newline", arr, expected_result); + + is.clear(); + is.str(expected_result); + skip_line(is); + is.get(arr, 255, '\0'); + expected_result = "user to perform real life activities in virtual world."; + ensure_equals("skip_line: 3 newline", arr, expected_result); + + is.clear(); + is.str(str = ""); + ret = skip_line(is); + ensure("skip_line: empty string", ret == false); + } + + + // testcases for skip_to_next_word() + template<> template<> + void streamtools_object::test<5>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = "SecondLife is a 3D_World.\n\n It-provides an opportunity to the site \nuser to perform real life activities in virtual world."); + skip_to_next_word(is); // get past SecondLife + is.get(arr, 255, '\0'); + expected_result = "is a 3D_World.\n\n It-provides an opportunity to the site \nuser to perform real life activities in virtual world."; + ensure_equals("skip_to_next_word: 1", arr, expected_result); + + is.clear(); + is.str(expected_result); + skip_to_next_word(is); // get past is + skip_to_next_word(is); // get past a + skip_to_next_word(is); // get past 3D_World.\n\n + is.get(arr, 255, '\0'); + expected_result = "It-provides an opportunity to the site \nuser to perform real life activities in virtual world."; + ensure_equals("skip_to_next_word: get past .\n\n 2", arr, expected_result); + + is.clear(); + is.str(expected_result); + skip_to_next_word(is); // get past It- + expected_result = "provides an opportunity to the site \nuser to perform real life activities in virtual world."; + is.get(arr, 255, '\0'); + ensure_equals("skip_to_next_word: get past -", arr, expected_result); + + is.clear(); + is.str(str = ""); + ret = skip_to_next_word(is); + ensure("skip_line: empty string", ret == false); + + is.clear(); + is.str(str = " \r\n\r\n"); + ret = skip_to_next_word(is); + ensure("skip_line: space new lines", ret == false); + } + + + //testcases for skip_to_end_of_next_keyword() + template<> template<> + void streamtools_object::test<6>() + { + char arr[255]; + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = "FIRSTKEY followed by second delimiter\nSECONDKEY\t SecondValue followed by third delimiter \nSECONDKEY\nFOURTHKEY FOURTHVALUEis a 3DWorld."); + ret = skip_to_end_of_next_keyword("FIRSTKEY", is); + is.get(arr, 255, '\0'); + expected_result = " followed by second delimiter\nSECONDKEY\t SecondValue followed by third delimiter \nSECONDKEY\nFOURTHKEY FOURTHVALUEis a 3DWorld."; + ensure_equals("skip_to_end_of_next_keyword: 1", arr, expected_result); + + is.clear(); + is.str(expected_result); + ret = skip_to_end_of_next_keyword("SECONDKEY", is); + is.get(arr, 255, '\0'); + expected_result = "\t SecondValue followed by third delimiter \nSECONDKEY\nFOURTHKEY FOURTHVALUEis a 3DWorld."; + ensure_equals("skip_to_end_of_next_keyword: 2", arr, expected_result); + + is.clear(); + is.str(expected_result); + ret = skip_to_end_of_next_keyword("SECONDKEY", is); + is.get(arr, 255, '\0'); + expected_result = "\nFOURTHKEY FOURTHVALUEis a 3DWorld."; + ensure_equals("skip_to_end_of_next_keyword: 3", arr, expected_result); + + is.clear(); + is.str(expected_result); + ret = skip_to_end_of_next_keyword("FOURTHKEY", is); + is.get(arr, 255, '\0'); + expected_result = " FOURTHVALUEis a 3DWorld."; + ensure_equals("skip_to_end_of_next_keyword: 4", arr, expected_result); + + is.clear(); + is.str(str = "{should be skipped as newline/space/tab does not follow but this one should be picked\n { Does it?\n"); + ret = skip_to_end_of_next_keyword("{", is); + is.get(arr, 255, '\0'); + expected_result = " Does it?\n"; + ensure_equals("skip_to_end_of_next_keyword: multiple delim matches on same line", arr, expected_result); + + is.clear(); + is.str(str = "Delim { could not be found at start"); + ret = skip_to_end_of_next_keyword("{", is); + ensure("skip_to_end_of_next_keyword: delim should not be present", ret == false); + + is.clear(); + is.str(str = "Empty Delim"); + ret = skip_to_end_of_next_keyword("", is); + ensure("skip_to_end_of_next_keyword: empty delim should not be valid", ret == false); + + is.clear(); + is.str(str = ""); + ret = skip_to_end_of_next_keyword("}", is); + ensure("skip_to_end_of_next_keyword: empty string", ret == false); + } + + //testcases for get_word(std::string& output_string, std::istream& input_stream) + template<> template<> + void streamtools_object::test<7>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = " First Second \t \r \n Third Fourth-ShouldThisBePartOfFourth Fifth\n"); + actual_result = ""; + ret = get_word(actual_result, is); + expected_result = "First"; + ensure_equals("get_word: 1", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is); + expected_result = "Second"; + ensure_equals("get_word: 2", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is); + expected_result = "Third"; + ensure_equals("get_word: 3", actual_result, expected_result); + + // the current implementation of get_word seems inconsistent with + // skip_to_next_word. skip_to_next_word treats any character other + // than alhpa-numeric and '_' as a delimter, while get_word() + // treats only isspace() (i.e. space, form-feed('\f'), newline ('\n'), + // carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v') + // as delimiters + actual_result = ""; + ret = get_word(actual_result, is); // will copy Fourth-ShouldThisBePartOfFourth + expected_result = "Fourth-ShouldThisBePartOfFourth"; // as per current impl. + ensure_equals("get_word: 4", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is); // will copy Fifth + expected_result = "Fifth"; // as per current impl. + ensure_equals("get_word: 5", actual_result, expected_result); + + is.clear(); + is.str(str = " \t \r \n "); + actual_result = ""; + ret = get_word(actual_result, is); + ensure("get_word: empty all spaces, newline tabs", ret == false); + + is.clear(); + is.str(str = ""); + actual_result = ""; + ret = get_word(actual_result, is); + ensure("get_word: empty string", ret == false); + } + + // testcase for get_word and skip_to_next_word compatibility + template<> template<> + void streamtools_object::test<8>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + is.clear(); + is.str(str = " First Second \t \r \n Third Fourth-ShouldThisBePartOfFourth Fifth\n"); + actual_result = ""; + get_word(actual_result, is); // First + actual_result = ""; + get_word(actual_result, is); // Second + actual_result = ""; + get_word(actual_result, is); // Third + + // the current implementation of get_word seems inconsistent with + // skip_to_next_word. skip_to_next_word treats any character other + // than alhpa-numeric and '_' as a delimter, while get_word() + // treats only isspace() (i.e. space, form-feed('\f'), newline ('\n'), + // carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v') + // as delimiters + actual_result = ""; + get_word(actual_result, is); // will copy Fourth-ShouldThisBePartOfFourth + + actual_result = ""; + get_word(actual_result, is); // will copy Fifth + + is.clear(); + is.str(str = " First Second \t \r \n Third Fourth_ShouldThisBePartOfFourth Fifth\n"); + skip_to_next_word(is); // should now point to First + skip_to_next_word(is); // should now point to Second + skip_to_next_word(is); // should now point to Third + skip_to_next_word(is); // should now point to Fourth + skip_to_next_word(is); // should now point to ShouldThisBePartOfFourth + expected_result = ""; + // will copy ShouldThisBePartOfFourth, the fifth word, + // while using get_word above five times result in getting "Fifth" + get_word(expected_result, is); + ensure_equals("get_word: skip_to_next_word compatibility", actual_result, expected_result); + } + + //testcases for get_word(std::string& output_string, std::istream& input_stream, int n) + template<> template<> + void streamtools_object::test<9>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = " First Second \t \r \n Third Fourth-ShouldThisBePartOfFourth Fifth\n"); + actual_result = ""; + ret = get_word(actual_result, is, 255); + expected_result = "First"; + ensure_equals("get_word: 1", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is, 4); + expected_result = "Seco"; // should be cut short + ensure_equals("get_word: 2", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is, 255); + expected_result = "nd"; // get remainder of Second + ensure_equals("get_word: 3", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is, 0); // 0 size string + expected_result = ""; // get remainder of Second + ensure_equals("get_word: 0 sized output", actual_result, expected_result); + + actual_result = ""; + ret = get_word(actual_result, is, 255); + expected_result = "Third"; + ensure_equals("get_word: 4", actual_result, expected_result); + + is.clear(); + is.str(str = " \t \r \n "); + actual_result = ""; + ret = get_word(actual_result, is, 255); + ensure("get_word: empty all spaces, newline tabs", ret == false); + + is.clear(); + is.str(str = ""); + actual_result = ""; + ret = get_word(actual_result, is, 255); + ensure("get_word: empty string", ret == false); + } + + //test cases for get_line(std::string& output_string, std::istream& input_stream) + template<> template<> + void streamtools_object::test<10>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + is.clear(); + is.str(str = "First Second \t \r\n Third Fourth-ShouldThisBePartOfFourth IsThisFifth\n"); + actual_result = ""; + get_line(actual_result, is); + expected_result = "First Second \t \r\n"; + ensure_equals("get_line: 1", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is); + expected_result = " Third Fourth-ShouldThisBePartOfFourth IsThisFifth\n"; + ensure_equals("get_line: 2", actual_result, expected_result); + + is.clear(); + is.str(str = "\nFirst Line.\n\nSecond Line.\n"); + actual_result = ""; + get_line(actual_result, is); + expected_result = "\n"; + ensure_equals("get_line: First char as newline", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is); + expected_result = "First Line.\n"; + ensure_equals("get_line: 3", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is); + expected_result = "\n"; + ensure_equals("get_line: 4", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is); + expected_result = "Second Line.\n"; + ensure_equals("get_line: 5", actual_result, expected_result); + } + + //test cases for get_line(std::string& output_string, std::istream& input_stream) + template<> template<> + void streamtools_object::test<11>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = "One Line only with no newline"); + actual_result = ""; + ret = get_line(actual_result, is); + expected_result = "One Line only with no newline"; + ensure_equals("get_line: No newline", actual_result, expected_result); + ensure_equals("return value is good state of stream", ret, is.good()); + } + + //test cases for get_line(std::string& output_string, std::istream& input_stream) + template<> template<> + void streamtools_object::test<12>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + // need to be check if this test case is wrong or the implementation is wrong. + is.clear(); + is.str(str = "Should not skip lone \r.\r\n"); + actual_result = ""; + get_line(actual_result, is); + expected_result = "Should not skip lone \r.\r\n"; + ensure_equals("get_line: carriage return skipped even though not followed by newline", actual_result, expected_result); + } + + //test cases for get_line(std::string& output_string, std::istream& input_stream) + template<> template<> + void streamtools_object::test<13>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + is.clear(); + is.str(str = "\n"); + actual_result = ""; + get_line(actual_result, is); + expected_result = "\n"; + ensure_equals("get_line: Just newline", actual_result, expected_result); + } + + + //testcases for get_line(std::string& output_string, std::istream& input_stream, int n) + template<> template<> + void streamtools_object::test<14>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + + is.clear(); + is.str(str = "First Line.\nSecond Line.\n"); + actual_result = ""; + get_line(actual_result, is, 255); + expected_result = "First Line.\n"; + ensure_equals("get_line: Basic Operation", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is, sizeof("Second")-1); + expected_result = "Second\n"; + ensure_equals("get_line: Insufficient length 1", actual_result, expected_result); + + actual_result = ""; + get_line(actual_result, is, 255); + expected_result = " Line.\n"; + ensure_equals("get_line: Remainder after earlier insufficient length", actual_result, expected_result); + + is.clear(); + is.str(str = "One Line only with no newline with limited length"); + actual_result = ""; + get_line(actual_result, is, sizeof("One Line only with no newline with limited length")-1); + expected_result = "One Line only with no newline with limited length\n"; + ensure_equals("get_line: No newline with limited length", actual_result, expected_result); + + is.clear(); + is.str(str = "One Line only with no newline"); + actual_result = ""; + get_line(actual_result, is, 255); + expected_result = "One Line only with no newline"; + ensure_equals("get_line: No newline", actual_result, expected_result); + } + + //testcases for get_line(std::string& output_string, std::istream& input_stream, int n) + template<> template<> + void streamtools_object::test<15>() + { + std::string str; + std::string expected_result; + std::string actual_result; + std::istringstream is; + bool ret; + + is.clear(); + is.str(str = "One Line only with no newline"); + actual_result = ""; + ret = get_line(actual_result, is, 255); + expected_result = "One Line only with no newline"; + ensure_equals("get_line: No newline", actual_result, expected_result); + ensure_equals("return value is good state of stream", ret, is.good()); + } + + //testcases for remove_last_char() + template<> template<> + void streamtools_object::test<16>() + { + std::string str; + std::string expected_result; + bool ret; + + str = "SecondLife is a 3D World"; + + ret = remove_last_char('d',str); + expected_result = "SecondLife is a 3D Worl"; + ensure_equals("remove_last_char: should remove last char", str, expected_result); + + str = "SecondLife is a 3D World"; + ret = remove_last_char('W',str); + expected_result = "SecondLife is a 3D World"; + ensure_equals("remove_last_char: should not remove as it is not last char", str, expected_result); + ensure("remove_last_char: should return false", ret == false); + + str = "SecondLife is a 3D World\n"; + ret = remove_last_char('\n',str); + expected_result = "SecondLife is a 3D World"; + ensure_equals("remove_last_char: should remove last newline", str, expected_result); + ensure("remove_last_char: should remove newline and return true", ret == true); + } + + + //testcases for unescaped_string() + template<> template<> + void streamtools_object::test<17>() + { + std::string str; + std::string expected_result; + + str = "SecondLife is a 3D world \\n"; + unescape_string(str); + expected_result = "SecondLife is a 3D world \n"; + ensure_equals("unescape_string: newline", str, expected_result); + + str = "SecondLife is a 3D world \\\\t \\n"; + unescape_string(str); + expected_result = "SecondLife is a 3D world \\t \n"; + ensure_equals("unescape_string: backslash and newline", str, expected_result); + + str = "SecondLife is a 3D world \\ "; + unescape_string(str); + expected_result = "SecondLife is a 3D world \\ "; + ensure_equals("unescape_string: insufficient to unescape", str, expected_result); + + str = "SecondLife is a 3D world \\n \\n \\n \\\\\\n"; + unescape_string(str); + expected_result = "SecondLife is a 3D world \n \n \n \\\n"; + ensure_equals("unescape_string: multipel newline and backslash", str, expected_result); + + str = "SecondLife is a 3D world \\t"; + unescape_string(str); + expected_result = "SecondLife is a 3D world \\t"; + ensure_equals("unescape_string: leaves tab as is", str, expected_result); + + str = "\\n"; + unescape_string(str); + expected_result = "\n"; + ensure_equals("unescape_string: only a newline", str, expected_result); + } + + //testcases for escape_string() + template<> template<> + void streamtools_object::test<18>() + { + std::string str; + std::string expected_result; + + str = "SecondLife is a 3D world \n"; + escape_string(str); + expected_result = "SecondLife is a 3D world \\n"; + ensure_equals("escape_string: newline", str, expected_result); + + str = "SecondLife is a 3D world \\t \n"; + escape_string(str); + expected_result = "SecondLife is a 3D world \\\\t \\n"; + ensure_equals("escape_string: backslash and newline", str, expected_result); + + str = "SecondLife is a 3D world \n \n \n \\\n"; + escape_string(str); + expected_result = "SecondLife is a 3D world \\n \\n \\n \\\\\\n"; + ensure_equals("escape_string: multipel newline and backslash", str, expected_result); + + str = "SecondLife is a 3D world \t"; + escape_string(str); + expected_result = "SecondLife is a 3D world \t"; + ensure_equals("unescape_string: leaves tab as is", str, expected_result); + + str = "\n"; + escape_string(str); + expected_result = "\\n"; + ensure_equals("unescape_string: only a newline", str, expected_result); + + // serialization/deserialization escape->unescape + str = "SecondLife is a 3D world \n \n \n \\\n"; + escape_string(str); + unescape_string(str); + expected_result = "SecondLife is a 3D world \n \n \n \\\n"; + ensure_equals("escape_string: should preserve with escape/unescape", str, expected_result); + + // serialization/deserialization unescape->escape + str = "SecondLife is a 3D world \\n \\n \\n \\\\"; + unescape_string(str); + escape_string(str); + expected_result = "SecondLife is a 3D world \\n \\n \\n \\\\"; + ensure_equals("escape_string: should preserve with unescape/escape", str, expected_result); + } + + // testcases for replace_newlines_with_whitespace() + template<> template<> + void streamtools_object::test<19>() + { + std::string str; + std::string expected_result; + + str = "SecondLife is a 3D \n\nworld\n"; + replace_newlines_with_whitespace(str); + expected_result = "SecondLife is a 3D world "; + ensure_equals("replace_newlines_with_whitespace: replace all newline", str, expected_result); + + str = "\nSecondLife is a 3D world\n"; + replace_newlines_with_whitespace(str); + expected_result = " SecondLife is a 3D world "; + ensure_equals("replace_newlines_with_whitespace: begin and newline", str, expected_result); + + str = "SecondLife is a 3D world\r\t"; + replace_newlines_with_whitespace(str); + expected_result = "SecondLife is a 3D world\r\t"; + ensure_equals("replace_newlines_with_whitespace: should only replace newline", str, expected_result); + + str = ""; + replace_newlines_with_whitespace(str); + expected_result = ""; + ensure_equals("replace_newlines_with_whitespace: empty string", str, expected_result); + } + + //testcases for remove_double_quotes() + template<> template<> + void streamtools_object::test<20>() + { + std::string str; + std::string expected_result; + + str = "SecondLife is a \"\"3D world"; + remove_double_quotes(str); + expected_result = "SecondLife is a 3D world"; + ensure_equals("remove_double_quotes: replace empty doube quotes", str, expected_result); + + str = "SecondLife is a \"3D world"; + remove_double_quotes(str); + expected_result = "SecondLife is a 3D world"; + ensure_equals("remove_double_quotes: keep as is it matching quote not found", str, expected_result); + } + + // testcases for get_brace_count() + template<> template<> + void streamtools_object::test<21>() + { + } + + //testcases for get_keyword_and_value() + template<> template<> + void streamtools_object::test<22>() + { + std::string s = "SecondLife is a 3D World"; + std::string keyword; + std::string value; + get_keyword_and_value(keyword, value, s); + ensure("get_keyword_and_value: Unable to get Keyword and Value", ((keyword == "SecondLife") && (value == "is a 3D World"))); + + s = "SecondLife"; + get_keyword_and_value(keyword, value, s); + ensure("get_keyword_and_value: value should be empty", ((keyword == "SecondLife") && (value == ""))); + + s = "SecondLife \t is cool! \n"; + get_keyword_and_value(keyword, value, s); + ensure("get_keyword_and_value: remove space before value but not after", ((keyword == "SecondLife") && (value == "is cool! "))); + } + + //testcases for get_keyword_and_value() + template<> template<> + void streamtools_object::test<23>() + { + std::string s; + std::string keyword = "SOME PRIOR KEYWORD"; + std::string value = "SOME PRIOR VALUE"; + + s = "SecondLife\n"; + get_keyword_and_value(keyword, value, s); + ensure("get_keyword_and_value: terminated with newline. value should be empty", ((keyword == "SecondLife") && (value == ""))); + } + + //testcases for get_keyword_and_value() + template<> template<> + void streamtools_object::test<24>() + { + std::string s; + std::string keyword = "SOME PRIOR KEYWORD"; + std::string value = "SOME PRIOR VALUE"; + + s = ""; + get_keyword_and_value(keyword, value, s); + ensure("get_keyword_and_value: empty string. keyword value should empty", ((keyword == "") && (value == ""))); + } + + //testcase for fullread() + template<> template<> + void streamtools_object::test<25>() + { + std::string str = "First Line.\nSecond Line\n"; + std::istringstream is(str); + char buf[255] = {0}; + + fullread(is, buf, 255); + ensure_memory_matches("fullread: read with newlines", (void*) buf, static_cast<U32>(str.size())-1, (void*) str.c_str(), static_cast<U32>(str.size())-1); + + is.clear(); + is.str(str = "First Line.\nSecond Line\n"); + memset(buf, 0, 255); + + char expected_string[] = "First Line.\nSecond"; + int len = sizeof(expected_string)-1; + fullread(is, buf, len); + ensure_memory_matches("fullread: read with newlines", (void*) buf, len, (void*) &expected_string, len); + } + + +// testcases for operator >> + + template<> template<> + void streamtools_object::test<26>() + { + char arr[255]; + std::string str; + std::string toCheck = "SecondLife" ; + std::string expected_result; + std::istringstream stream("SecondLife is a 3D World"); + stream >> toCheck.c_str(); + stream.get(arr, 255, '\0'); + expected_result = " is a 3D World"; + ensure_equals("istream << operator", arr, expected_result); + + stream.clear(); + stream.str(str = "SecondLife is a 3D World"); + toCheck = "is"; + stream >> toCheck.c_str(); + ensure("istream << operator should have failed", stream.good() == false); + } +} diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 7393e0087c..3c9d8071ac 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -374,7 +374,7 @@ namespace tut { F32 value; std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); + ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647.f); str_val = "0"; ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); @@ -396,7 +396,7 @@ namespace tut { F64 value; std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF - ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); + ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807.0); str_val = "0"; ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 451e60c083..6adbdffba8 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -78,6 +78,7 @@ void LL::ThreadPoolBase::start() std::string tname{ stringize(mName, ':', (i+1), '/', mThreadCount) }; mThreads.emplace_back(tname, [this, tname]() { + set_thread_name(tname.c_str()); LL_PROFILER_SET_THREAD_NAME(tname.c_str()); run(tname); }); diff --git a/indra/llcommon/timer.h b/indra/llcommon/timer.h deleted file mode 100644 index aaa0cf0775..0000000000 --- a/indra/llcommon/timer.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @file timer.h - * @brief Legacy wrapper header. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "lltimer.h" |
