From 7429ee1f84161103affefa97a7d4f40803445364 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 28 Oct 2025 09:28:55 -0400 Subject: Fix multiple unicode file io handling issues with llofstream and llifstream Signed-off-by: Rye --- indra/llcommon/tests/llprocess_test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 9eaf5bf3e3..4a88d69ded 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -95,7 +95,7 @@ static std::string readfile(const std::string& pathname, const std::string& desc { use_desc = "in " + pathname; } - std::ifstream inf(pathname.c_str()); + llifstream inf(pathname.c_str()); std::string output; if (!std::getline(inf, output)) { @@ -186,7 +186,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; @@ -838,7 +838,7 @@ 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.getName().c_str()); outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. @@ -901,7 +901,7 @@ 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.getName().c_str()); outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. -- cgit v1.3 From 615b1bcec74f4d6d759d69ecf9965b7d2b423c52 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 29 Dec 2025 07:29:11 -0500 Subject: Replace remaining boost::filesystem usage with std::filesystem Signed-off-by: Rye --- indra/llcommon/tests/llleap_test.cpp | 20 +++--- indra/llcommon/tests/llprocess_test.cpp | 52 +++++++-------- indra/llcommon/tests/llsdserialize_test.cpp | 12 ++-- indra/llfilesystem/lldir_mac.cpp | 16 +++-- indra/llfilesystem/lldiriterator.cpp | 10 +-- indra/llfilesystem/lldiskcache.cpp | 98 ++++++++++++----------------- indra/newview/llappdelegate-objc.mm | 8 +-- indra/newview/lllocalbitmaps.cpp | 18 +----- indra/newview/lllocalbitmaps.h | 3 +- indra/newview/lllocalgltfmaterials.cpp | 18 +----- indra/newview/lllocalgltfmaterials.h | 3 +- indra/newview/llsnapshotlivepreview.cpp | 1 - indra/newview/llviewerwindow.cpp | 19 ++---- indra/test/namedtempfile.h | 45 +++++++------ indra/test/test.cpp | 6 +- 15 files changed, 142 insertions(+), 187 deletions(-) (limited to 'indra/llcommon/tests/llprocess_test.cpp') 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/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index cdf9f70b6e..5e653d0ba0 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -87,12 +87,12 @@ 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 " */ -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(); } llifstream inf(pathname.c_str()); std::string output; @@ -165,7 +165,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 @@ -233,11 +233,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; @@ -271,23 +271,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; }; @@ -440,7 +440,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); @@ -719,14 +719,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 @@ -745,7 +745,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<> @@ -765,7 +765,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(); @@ -774,7 +774,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 @@ -787,7 +787,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<> @@ -817,8 +817,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 @@ -828,7 +828,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 @@ -840,14 +840,14 @@ namespace tut // How do we know it's not terminated? By making it respond to // a specific stimulus in a specific way. { - llofstream 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<> @@ -879,8 +879,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(); @@ -891,7 +891,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 @@ -903,14 +903,14 @@ namespace tut // How do we know it's not terminated? By making it respond to // a specific stimulus in a specific way. { - llofstream 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/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 272eb55521..a7abfda099 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1808,7 +1808,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)..., NULL); if (rc == -1) @@ -1825,7 +1825,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)... }) { params.args.add(arg); @@ -2002,8 +2002,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&) { @@ -2111,13 +2111,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";}); - llifstream inf(file.getName().c_str()); + llifstream inf(file.getPath()); LLSD item; try { diff --git a/indra/llfilesystem/lldir_mac.cpp b/indra/llfilesystem/lldir_mac.cpp index 7bddee0f75..b13e72ff15 100644 --- a/indra/llfilesystem/lldir_mac.cpp +++ b/indra/llfilesystem/lldir_mac.cpp @@ -35,7 +35,7 @@ #include #include #include -#include +#include #include "lldir_utils_objc.h" // -------------------------------------------------------------------------------- @@ -45,15 +45,15 @@ static bool CreateDirectory(const std::string &parent, std::string *fullname) { - boost::filesystem::path p(parent); + std::filesystem::path p(parent); p /= child; if (fullname) *fullname = std::string(p.string()); - if (! boost::filesystem::create_directory(p)) + if (! std::filesystem::create_directory(p)) { - return (boost::filesystem::is_directory(p)); + return (std::filesystem::is_directory(p)); } return true; } @@ -75,10 +75,8 @@ LLDir_Mac::LLDir_Mac() // mExecutablePathAndName mExecutablePathAndName = executablepathstr; - boost::filesystem::path executablepath(executablepathstr); + std::filesystem::path executablepath(executablepathstr); -# ifndef BOOST_SYSTEM_NO_DEPRECATED -#endif mExecutableFilename = executablepath.filename().string(); mExecutableDir = executablepath.parent_path().string(); @@ -140,7 +138,7 @@ LLDir_Mac::LLDir_Mac() mOSUserAppDir = mOSUserDir; // mTempDir - //Aura 120920 boost::filesystem::temp_directory_path() not yet implemented on mac. :( + //Aura 120920 std::filesystem::temp_directory_path() not yet implemented on mac. :( std::string tmpdir = getSystemTempFolder(); if (!tmpdir.empty()) { @@ -174,7 +172,7 @@ void LLDir_Mac::initAppDirs(const std::string &app_name, std::string LLDir_Mac::getCurPath() { - return boost::filesystem::path( boost::filesystem::current_path() ).string(); + return std::filesystem::path( std::filesystem::current_path() ).string(); } /*virtual*/ std::string LLDir_Mac::getLLPluginLauncher() diff --git a/indra/llfilesystem/lldiriterator.cpp b/indra/llfilesystem/lldiriterator.cpp index 57d4912c94..60b55c7bf3 100644 --- a/indra/llfilesystem/lldiriterator.cpp +++ b/indra/llfilesystem/lldiriterator.cpp @@ -29,9 +29,9 @@ #include "lldiriterator.h" #include "llregex.h" -#include +#include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; static std::string glob_to_regex(const std::string& glob); @@ -52,11 +52,7 @@ private: LLDirIterator::Impl::Impl(const std::string &dirname, const std::string &mask) : mIsValid(false) { -#ifdef LL_WINDOWS // or BOOST_WINDOWS_API - fs::path dir_path(ll_convert(dirname)); -#else - fs::path dir_path(dirname); -#endif + fs::path dir_path = fsyspath(dirname); bool is_dir = false; diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 3430bec925..e971e324a0 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -34,8 +34,8 @@ #include "llapp.h" #include "llassettype.h" #include "lldir.h" -#include #include +#include #include "lldiskcache.h" @@ -67,7 +67,7 @@ LLDiskCache::LLDiskCache(const std::string& cache_dir, // Interaction through the filesystem itself should be safe. Let’s say thread // A is accessing the cache file for reading/writing and thread B is trimming // the cache. Let’s also assume using llifstream to open a file and -// boost::filesystem::remove are not atomic (which will be pretty much the +// std::filesystem::remove are not atomic (which will be pretty much the // case). // Now, A is trying to open the file using llifstream ctor. It does some @@ -83,7 +83,7 @@ LLDiskCache::LLDiskCache(const std::string& cache_dir, // garbage.) // Other situation: B is trimming the cache and A wants to read a file that is -// about to get deleted. boost::filesystem::remove does whatever it is doing +// about to get deleted. std::filesystem::remove does whatever it is doing // before actually deleting the file. If A opens the file before the file is // actually gone, the OS call from B to delete the file will fail since the OS // will prevent this. B continues with the next file. If the file is already @@ -96,38 +96,34 @@ void LLDiskCache::purge() LL_INFOS() << "Total dir size before purge is " << dirFileSize(sCacheDir) << LL_ENDL; } - boost::system::error_code ec; + std::error_code ec; auto start_time = std::chrono::high_resolution_clock::now(); - typedef std::pair> file_info_t; + typedef std::pair> file_info_t; std::vector file_info; -#if LL_WINDOWS - std::wstring cache_path(ll_convert(sCacheDir)); -#else - std::string cache_path(sCacheDir); -#endif - if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) + std::filesystem::path cache_path = fsyspath(sCacheDir); + if (std::filesystem::is_directory(cache_path, ec) && !ec) { - boost::filesystem::directory_iterator iter(cache_path, ec); - while (iter != boost::filesystem::directory_iterator() && !ec.failed()) + std::filesystem::directory_iterator iter(cache_path, ec); + while (iter != std::filesystem::directory_iterator() && !ec) { if(!LLApp::isRunning()) { return; } - if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed()) + if (std::filesystem::is_regular_file(*iter, ec) && !ec) { if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos) { - uintmax_t file_size = boost::filesystem::file_size(*iter, ec); - if (ec.failed()) + uintmax_t file_size = std::filesystem::file_size(*iter, ec); + if (ec) { continue; } const std::string file_path = (*iter).path().string(); - const std::time_t file_time = boost::filesystem::last_write_time(*iter, ec); - if (ec.failed()) + const std::filesystem::file_time_type file_time = std::filesystem::last_write_time(*iter, ec); + if (ec) { continue; } @@ -167,8 +163,8 @@ void LLDiskCache::purge() } if (should_remove) { - boost::filesystem::remove(entry.second.second, ec); - if (ec.failed()) + std::filesystem::remove(entry.second.second, ec); + if (ec) { LL_WARNS() << "Failed to delete cache file " << entry.second.second << ": " << ec.message() << LL_ENDL; } @@ -196,7 +192,7 @@ void LLDiskCache::purge() std::ostringstream line; line << action << " "; - line << entry.first << " "; + line << S64(entry.first.time_since_epoch().count()) << " "; line << entry.second.first << " "; line << entry.second.second; line << " (" << file_size_total << "/" << mMaxSizeBytes << ")"; @@ -238,23 +234,19 @@ void LLDiskCache::clearCache() * the component files but it's called infrequently so it's * likely just fine */ - boost::system::error_code ec; -#if LL_WINDOWS - std::wstring cache_path(ll_convert(sCacheDir)); -#else - std::string cache_path(sCacheDir); -#endif - if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) + std::error_code ec; + std::filesystem::path cache_path = fsyspath(sCacheDir); + if (std::filesystem::is_directory(cache_path, ec) && !ec) { - boost::filesystem::directory_iterator iter(cache_path, ec); - while (iter != boost::filesystem::directory_iterator() && !ec.failed()) + std::filesystem::directory_iterator iter(cache_path, ec); + while (iter != std::filesystem::directory_iterator() && !ec) { - if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed()) + if (std::filesystem::is_regular_file(*iter, ec) && !ec) { if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos) { - boost::filesystem::remove(*iter, ec); - if (ec.failed()) + std::filesystem::remove(*iter, ec); + if (ec) { LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL; } @@ -271,24 +263,20 @@ void LLDiskCache::removeOldVFSFiles() static const char CACHE_FORMAT[] = "inv.llsd"; static const char DB_FORMAT[] = "db2.x"; - boost::system::error_code ec; -#if LL_WINDOWS - std::wstring cache_path(ll_convert(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""))); -#else - std::string cache_path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "")); -#endif - if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) + std::error_code ec; + std::filesystem::path cache_path = fsyspath(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "")); + if (std::filesystem::is_directory(cache_path, ec) && !ec) { - boost::filesystem::directory_iterator iter(cache_path, ec); - while (iter != boost::filesystem::directory_iterator() && !ec.failed()) + std::filesystem::directory_iterator iter(cache_path, ec); + while (iter != std::filesystem::directory_iterator() && !ec) { - if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed()) + if (std::filesystem::is_regular_file(*iter, ec) && !ec) { if (((*iter).path().string().find(CACHE_FORMAT) != std::string::npos) || ((*iter).path().string().find(DB_FORMAT) != std::string::npos)) { - boost::filesystem::remove(*iter, ec); - if (ec.failed()) + std::filesystem::remove(*iter, ec); + if (ec) { LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL; } @@ -312,23 +300,19 @@ uintmax_t LLDiskCache::dirFileSize(const std::string& dir) * so if performance is ever an issue, optimizing this or removing it altogether, * is an easy win. */ - boost::system::error_code ec; -#if LL_WINDOWS - std::wstring dir_path(ll_convert(dir)); -#else - std::string dir_path(dir); -#endif - if (boost::filesystem::is_directory(dir_path, ec) && !ec.failed()) + std::error_code ec; + std::filesystem::path dir_path = fsyspath(dir); + if (std::filesystem::is_directory(dir_path, ec) && !ec) { - boost::filesystem::directory_iterator iter(dir_path, ec); - while (iter != boost::filesystem::directory_iterator() && !ec.failed()) + std::filesystem::directory_iterator iter(dir_path, ec); + while (iter != std::filesystem::directory_iterator() && !ec) { - if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed()) + if (std::filesystem::is_regular_file(*iter, ec) && !ec) { if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos) { - uintmax_t file_size = boost::filesystem::file_size(*iter, ec); - if (!ec.failed()) + uintmax_t file_size = std::filesystem::file_size(*iter, ec); + if (!ec) { total_file_size += file_size; } diff --git a/indra/newview/llappdelegate-objc.mm b/indra/newview/llappdelegate-objc.mm index 409671d939..23a4effd87 100644 --- a/indra/newview/llappdelegate-objc.mm +++ b/indra/newview/llappdelegate-objc.mm @@ -26,7 +26,7 @@ #import "llappdelegate-objc.h" #if defined(LL_BUGSPLAT) -#include +#include #include @import CrashReporter; @import HockeySDK; @@ -252,7 +252,7 @@ if(!secondLogPath.empty()) { - boost::filesystem::remove(secondLogPath); + std::filesystem::remove(secondLogPath); } clearDumpLogsDir(); } @@ -326,7 +326,7 @@ struct AttachmentInfo { AttachmentInfo(const std::string& path, const std::string& type): pathname(path), - basename(boost::filesystem::path(path).filename().string()), + basename(std::filesystem::path(path).filename().string()), mimetype(type) {} @@ -361,7 +361,7 @@ struct AttachmentInfo // the log data to a browser, so take this opportunity to rename the file // from .crash to _log.txt info[0].basename = - boost::filesystem::path(info[0].pathname).stem().string() + "_log.txt"; + std::filesystem::path(info[0].pathname).stem().string() + "_log.txt"; infos("attachmentsForBugsplatStartupManager attaching log " + info[0].basename); NSMutableArray *attachments = [[NSMutableArray alloc] init]; diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp index a5ab5538e7..d80d9058e2 100644 --- a/indra/newview/lllocalbitmaps.cpp +++ b/indra/newview/lllocalbitmaps.cpp @@ -31,8 +31,6 @@ /* own header */ #include "lllocalbitmaps.h" -#include - /* image compression headers. */ #include "llimagebmp.h" #include "llimagetga.h" @@ -40,11 +38,8 @@ #include "llimagejpeg.h" #include "llimagepng.h" -/* time headers */ -#include -#include - /* misc headers */ +#include "fsyspath.h" #include "llgltfmaterial.h" #include "llscrolllistctrl.h" #include "lllocaltextureobject.h" @@ -190,15 +185,8 @@ bool LLLocalBitmap::updateSelf(EUpdateType optional_firstupdate) if (gDirUtilp->fileExists(mFilename)) { // verifying that the file has indeed been modified - -#ifndef LL_WINDOWS - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); -#else - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(ll_convert(mFilename))); -#endif - LLSD new_last_modified = asctime(localtime(&temp_time)); - - if (mLastModified.asString() != new_last_modified.asString()) + const std::filesystem::file_time_type new_last_modified = std::filesystem::last_write_time(fsyspath(mFilename)); + if (mLastModified != new_last_modified) { /* loading the image file and decoding it, here is a critical point which, if fails, invalidates the whole update (or unit creation) process. */ diff --git a/indra/newview/lllocalbitmaps.h b/indra/newview/lllocalbitmaps.h index 6c9d65e3b6..c4aaeae719 100644 --- a/indra/newview/lllocalbitmaps.h +++ b/indra/newview/lllocalbitmaps.h @@ -32,6 +32,7 @@ #include "lleventtimer.h" #include "llpointer.h" #include "llwearabletype.h" +#include class LLScrollListCtrl; class LLImageRaw; @@ -99,7 +100,7 @@ class LLLocalBitmap LLUUID mTrackingID; LLUUID mWorldID; bool mValid; - LLSD mLastModified; + std::filesystem::file_time_type mLastModified; EExtension mExtension; ELinkStatus mLinkStatus; S32 mUpdateRetries; diff --git a/indra/newview/lllocalgltfmaterials.cpp b/indra/newview/lllocalgltfmaterials.cpp index aeae7cb56a..e9f2299be7 100644 --- a/indra/newview/lllocalgltfmaterials.cpp +++ b/indra/newview/lllocalgltfmaterials.cpp @@ -30,13 +30,8 @@ /* own header */ #include "lllocalgltfmaterials.h" -#include - -/* time headers */ -#include -#include - /* misc headers */ +#include "fsyspath.h" #include "llgltfmateriallist.h" #include "llimage.h" #include "llinventoryicon.h" @@ -128,15 +123,8 @@ bool LLLocalGLTFMaterial::updateSelf() if (gDirUtilp->fileExists(mFilename)) { // verifying that the file has indeed been modified - -#ifndef LL_WINDOWS - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); -#else - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(ll_convert(mFilename))); -#endif - LLSD new_last_modified = asctime(localtime(&temp_time)); - - if (mLastModified.asString() != new_last_modified.asString()) + const std::filesystem::file_time_type new_last_modified = std::filesystem::last_write_time(fsyspath(mFilename)); + if (mLastModified != new_last_modified) { if (loadMaterial()) { diff --git a/indra/newview/lllocalgltfmaterials.h b/indra/newview/lllocalgltfmaterials.h index b806b54508..bf9a8c68ae 100644 --- a/indra/newview/lllocalgltfmaterials.h +++ b/indra/newview/lllocalgltfmaterials.h @@ -30,6 +30,7 @@ #include "lleventtimer.h" #include "llpointer.h" #include "llgltfmateriallist.h" +#include class LLScrollListCtrl; class LLGLTFMaterial; @@ -73,7 +74,7 @@ private: /* members */ std::string mShortName; LLUUID mTrackingID; LLUUID mWorldID; - LLSD mLastModified; + std::filesystem::file_time_type mLastModified; EExtension mExtension; ELinkStatus mLinkStatus; S32 mUpdateRetries; diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index 3a894996e4..7d2828a872 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -54,7 +54,6 @@ #include "llviewertexturelist.h" #include "llwindow.h" #include "llworld.h" -#include constexpr F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 0980c4a291..c77dd9cc08 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -33,9 +33,6 @@ #include #include #include -#include -#include -#include #include "llagent.h" #include "llagentcamera.h" @@ -4837,14 +4834,10 @@ void LLViewerWindow::saveImageLocal(LLImageFormatted *image, const snapshot_save return; } -// Check if there is enough free space to save snapshot -#ifdef LL_WINDOWS - boost::filesystem::path b_path(ll_convert(lastSnapshotDir)); -#else - boost::filesystem::path b_path(lastSnapshotDir); -#endif - boost::system::error_code ec; - if (!boost::filesystem::is_directory(b_path, ec) || ec.failed()) + // Check if there is enough free space to save snapshot + std::filesystem::path b_path = fsyspath(lastSnapshotDir); + std::error_code ec; + if (!std::filesystem::is_directory(b_path, ec) || ec) { LLSD args; args["PATH"] = lastSnapshotDir; @@ -4853,8 +4846,8 @@ void LLViewerWindow::saveImageLocal(LLImageFormatted *image, const snapshot_save failure_cb(); return; } - boost::filesystem::space_info b_space = boost::filesystem::space(b_path, ec); - if (ec.failed()) + std::filesystem::space_info b_space = std::filesystem::space(b_path, ec); + if (ec) { LLSD args; args["PATH"] = lastSnapshotDir; diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 8027f95728..a38746759f 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -12,26 +12,29 @@ #if ! defined(LL_NAMEDTEMPFILE_H) #define LL_NAMEDTEMPFILE_H +#include "fsyspath.h" #include "llerror.h" #include "llstring.h" #include "stringize.h" #include -#include -#include -#include +#include #include #include #include #include +#include /** * Create a text file with specified content "somewhere in the * filesystem," cleaning up when it goes out of scope. */ -class NamedTempFile: public boost::noncopyable +class NamedTempFile { LOG_CLASS(NamedTempFile); public: + NamedTempFile(const NamedTempFile&) = delete; + NamedTempFile& operator=(const NamedTempFile&) = delete; + NamedTempFile(const std::string_view& pfx, const std::string_view& content, const std::string_view& sfx=std::string_view("")) @@ -62,16 +65,16 @@ public: virtual ~NamedTempFile() { - boost::filesystem::remove(mPath); + std::filesystem::remove(mPath); } - std::string getName() const { return mPath.string(); } + const std::filesystem::path& getPath() const { return mPath; } template void peep_via(CALLABLE&& callable) const { std::forward(callable)(stringize("File '", mPath, "' contains:")); - boost::filesystem::ifstream reader(mPath, std::ios::binary); + std::ifstream reader(mPath, std::ios::binary); std::string line; while (std::getline(reader, line)) std::forward(callable)(line); @@ -94,23 +97,27 @@ public: return out; } - static boost::filesystem::path temp_path(const std::string_view& pfx="", + static std::filesystem::path temp_path(const std::string_view& pfx="", const std::string_view& sfx="") { // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); - boost::filesystem::path tempdir{ + std::filesystem::path tempdir{ // if RUNNER_TEMP is set and not empty (RUNNER_TEMP && *RUNNER_TEMP)? - boost::filesystem::path(RUNNER_TEMP) : // use RUNNER_TEMP if available - boost::filesystem::temp_directory_path()}; // else canonical temp dir - boost::filesystem::path tempname{ - // use filename template recommended by unique_path() doc, but - // with underscores instead of hyphens: some use cases involve - // temporary Python scripts - tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; - return boost::filesystem::unique_path(tempname); + fsyspath::path(RUNNER_TEMP) : // use RUNNER_TEMP if available + std::filesystem::temp_directory_path()}; // else canonical temp dir + + static std::mt19937 random_generator{std::random_device{}()}; + static std::uniform_int_distribution<> distribution{0, std::numeric_limits::max()}; + std::string tempname{}; + static constexpr auto num_bits = 128; + for (auto i = 0; i < (num_bits / std::numeric_limits::digits); ++i) { + tempname += llformat("%02x", distribution(random_generator)); + } + tempname = std::string(pfx) + tempname + std::string(sfx); + return tempdir / tempname; } protected: @@ -120,12 +127,12 @@ protected: { // Create file in a temporary place. mPath = temp_path(pfx, sfx); - boost::filesystem::ofstream out{ mPath, std::ios::binary }; + std::ofstream out{ mPath, std::ios::binary }; // Write desired content. func(out); } - boost::filesystem::path mPath; + std::filesystem::path mPath; }; /** diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 5b36bb618d..cee8b84438 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -79,7 +79,7 @@ public: RecordToTempFile() : LLError::Recorder(), mTempFile("log", ""), - mFile(mTempFile.getName().c_str()) + mFile(mTempFile.getPath()) { } @@ -97,13 +97,13 @@ public: void reset() { mFile.close(); - mFile.open(mTempFile.getName().c_str()); + mFile.open(mTempFile.getPath()); } void replay(std::ostream& out) { mFile.close(); - llifstream inf(mTempFile.getName().c_str()); + llifstream inf(mTempFile.getPath()); std::string line; while (std::getline(inf, line)) { -- cgit v1.3 From cd82e380937069fa4c4cb13a2e9de368c9c5a9d3 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:10:41 +0300 Subject: #5994 Additional llprocess unit test --- indra/llcommon/tests/llprocess_test.cpp | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index d3d8e54d45..075a2099e1 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1431,4 +1431,43 @@ namespace tut waitfor(*py.mPy); ensure("postend never triggered", listener.mTriggered); } + + template<> template<> + void object::test<25>() + { + set_test_name("large stdin write"); + PythonProcessLauncher py(get_test_name(), + "import sys\n" + "expected = int(sys.argv[1])\n" + "data = sys.stdin.buffer.read(expected)\n" + "if len(data) != expected:\n" + " print('short read %s' % len(data))\n" + "elif data != (b'x' * expected):\n" + " print('payload mismatch')\n" + "else:\n" + " print('ok')\n"); + const std::size_t payload_size = 1024 * 1024; + const std::string payload(payload_size, 'x'); + py.mParams.args.add(stringize(payload_size)); + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + std::ostream& childin(py.mPy->getWritePipe(LLProcess::STDIN).get_ostream()); + childin.write(payload.data(), payload.size()); + childin.flush(); + + int i, timeout = 20; + for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) + { + yield(); + } + ensure("large stdin write timed out", i < timeout); + ensure("child never replied", childout.contains("\n")); + ensure_equals("large stdin ack", childout.getline(), "ok"); + waitfor(*py.mPy); + ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); + } } // namespace tut -- cgit v1.3 From d9f67028b515dfdc7d8f3b8bf73b1f23a8ed1273 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 14 Jul 2026 08:17:35 +0300 Subject: #5994 More llprocess tests (#5996) --- indra/llcommon/tests/llprocess_test.cpp | 252 +++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 075a2099e1..2988d30629 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -335,7 +335,7 @@ namespace tut WaitInfo(apr_proc_t* child_): child(child_), rv(-1), // we haven't yet called apr_proc_wait() - rc(0), + rc(0), // child's exit code why(apr_exit_why_e(0)) {} apr_proc_t* child; // which subprocess @@ -1470,4 +1470,254 @@ namespace tut ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); } + + template<> template<> + void object::test<26>() + { + set_test_name("all three pipes active"); + PythonProcessLauncher py(get_test_name(), + "import sys\n" + "sys.stdout.write('stdout message\\n')\n" + "sys.stderr.write('stderr message\\n')\n" + "sys.stdout.flush()\n" + "sys.stderr.flush()\n" + "input_data = sys.stdin.readline()\n" + "sys.stdout.write('received: ' + input_data)\n"); + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.mParams.files.add(LLProcess::FileParam("pipe")); // stderr + py.launch(); + + LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT); + LLProcess::ReadPipe& childerr = py.mPy->getReadPipe(LLProcess::STDERR); + + // Wait for initial output + int i, timeout = 60; + for (i = 0; i < timeout && (!childout.contains("\n") || !childerr.contains("\n")); ++i) + { + yield(); + } + ensure("initial output timeout", i < timeout); + + ensure_equals("stdout message", childout.getline(), "stdout message"); + ensure_equals("stderr message", childerr.getline(), "stderr message"); + + py.mPy->getWritePipe().get_ostream() << "test input" << std::endl; + + waitfor(*py.mPy); + ensure("script never replied", childout.contains("\n")); + ensure_equals("echo response", childout.getline(), "received: test input"); + } + + template<> template<> + void object::test<27>() + { + set_test_name("process state transitions"); + PythonProcessLauncher py(get_test_name(), + "import time\n" + "time.sleep(1)\n"); + + py.launch(); + + // Immediately after launch + LLProcess::Status status = py.mPy->getStatus(); + ensure_equals("post-launch state", status.mState, LLProcess::RUNNING); + ensure("process is running", py.mPy->isRunning()); + + // After completion + waitfor(*py.mPy); + status = py.mPy->getStatus(); + ensure_equals("post-completion state", status.mState, LLProcess::EXITED); + ensure("process is not running", !py.mPy->isRunning()); + } + + template<> template<> + void object::test<28>() + { + set_test_name("ReadPipe limit with rapid data"); + PythonProcessLauncher py(get_test_name(), + "import sys\n" + "for i in range(100):\n" + " sys.stdout.write('line %d\\n' % i)\n" + " sys.stdout.flush()\n"); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + + LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT); + childout.setLimit(50); // Small limit + + EventListener listener(childout.getPump()); + waitfor(*py.mPy); + + // Verify that events respect the limit + listener.checkHistory( + [](const EventListener::Listory& history) + { + bool saw_data = false; + for (const LLSD& event : history) + { + const std::string data = event["data"].asString(); + ensure("event data within limit", data.length() <= 50); + saw_data = saw_data || !data.empty(); + } + ensure("saw at least one data event", saw_data); + }); + } + + template<> template<> + void object::test<29>() + { + set_test_name("nonexistent executable"); + LLProcess::Params params; + params.executable = "/path/to/nonexistent/executable"; + std::string pumpname("postend_invalid"); + params.postend = pumpname; + + EventListener listener(LLEventPumps::instance().obtain(pumpname)); + + LLProcessPtr child = LLProcess::create(params); + ensure("should not create invalid process", !child); + + listener.checkHistory( + [](const EventListener::Listory& history) + { + ensure_equals("got failure event", history.size(), 1); + LLSD event = history.front(); + ensure_equals("state is UNSTARTED", + event["state"].asInteger(), LLProcess::UNSTARTED); + ensure("has error string", !event["string"].asString().empty()); + }); + } + + template<> template<> + void object::test<30>() + { + set_test_name("process ID and handle validity"); + PythonProcessLauncher py(get_test_name(), + "import time\n" + "time.sleep(1)\n"); + py.launch(); + + LLProcess::id pid = py.mPy->getProcessID(); + LLProcess::handle handle = py.mPy->getProcessHandle(); + + ensure("PID is valid", pid != 0); + ensure("handle is valid", handle != 0); + +#if LL_WINDOWS + // On Windows, verify handle is a valid process handle + DWORD exitCode; + ensure("GetExitCodeProcess succeeds", + GetExitCodeProcess(handle, &exitCode) != 0); + ensure_equals("process still running", exitCode, STILL_ACTIVE); +#else + // On POSIX, PID and handle should be the same + ensure_equals("PID equals handle", pid, handle); + ensure("process still running", py.mPy->isRunning()); +#endif + + waitfor(*py.mPy); + } + + template<> template<> + void object::test<31>() + { + set_test_name("ReadPipe search at boundaries"); + PythonProcessLauncher py(get_test_name(), + "import sys\n" + "sys.stdout.write('abcdefghijklmnopqrstuvwxyz')\n"); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + + LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT); + waitfor(*py.mPy); + + // Test find at exact end + ensure("find at end succeeds", + childout.find("xyz", 23) != LLProcess::ReadPipe::npos); + ensure("find past end returns npos", + childout.find("a", 30) == LLProcess::ReadPipe::npos); + + // Test empty string search + ensure("contains empty string", childout.contains("")); + + // Test single char at boundaries + ensure_equals("find 'a' at 0", childout.find('a', 0), 0); + ensure_equals("find 'z' at end", childout.find('z'), 25); + + // Test peek at boundaries + ensure_equals("peek at exact size", childout.peek(26), ""); + ensure_equals("peek past end", childout.peek(30, 10), ""); + } + + template<> template<> + void object::test<32>() + { + set_test_name("rapid process lifecycle"); + const int ITERATIONS = 10; + + for (int i = 0; i < ITERATIONS; ++i) + { + PythonProcessLauncher py(STRINGIZE(get_test_name() << " " << i), + "import sys\n" + "sys.exit(0)\n"); + py.run(); + ensure_equals("quick exit status", + py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("quick exit code", + py.mPy->getStatus().mData, 0); + // Let the process object destroy + } + + // Give time for cleanup + yield(0); + } + + template<> template<> + void object::test<33>() + { + set_test_name("process with no output"); + PythonProcessLauncher py(get_test_name(), + "import time\n" + "time.sleep(1)\n" + "# Produce no output\n"); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.mParams.files.add(LLProcess::FileParam("pipe")); // stderr + py.launch(); + + LLProcess::ReadPipe& childout = py.mPy->getReadPipe(LLProcess::STDOUT); + LLProcess::ReadPipe& childerr = py.mPy->getReadPipe(LLProcess::STDERR); + + EventListener outListener(childout.getPump()); + EventListener errListener(childerr.getPump()); + + waitfor(*py.mPy); + + ensure_equals("stdout size", childout.size(), 0); + ensure_equals("stderr size", childerr.size(), 0); + ensure_equals("process exited", py.mPy->getStatus().mState, LLProcess::EXITED); + + auto check_eof = [](const EventListener::Listory& history, const std::string& which) + { + ensure_equals(STRINGIZE(which << " events"), history.size(), 1); + const LLSD& event = history.front(); + ensure(STRINGIZE(which << " eof event"), event["eof"].asBoolean()); + ensure_equals(STRINGIZE(which << " len"), event["len"].asInteger(), 0); + }; + + outListener.checkHistory( + [&](const EventListener::Listory& history) + { + check_eof(history, "stdout"); + }); + + errListener.checkHistory( + [&](const EventListener::Listory& history) + { + check_eof(history, "stderr"); + }); + } } // namespace tut -- cgit v1.3 From 331c3c758a114d23950ab23bb94ee19e85e662dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:55:10 +0000 Subject: Add timing tests for tick() and destructor after kill() --- indra/llcommon/tests/llprocess_test.cpp | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 2988d30629..65d797947f 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -17,7 +17,9 @@ #include #include // std headers +#include #include +#include // external library headers #include "llapr.h" #include "apr_thread_proc.h" @@ -1720,4 +1722,75 @@ namespace tut check_eof(history, "stderr"); }); } + + template<> template<> + void object::test<34>() + { + set_test_name("tick() completes quickly after kill()"); + // Regression test: tick() must not block after kill() is called. + // The old Windows code called WaitForSingleObject(..., 100) in tick(), + // causing a 100 ms stall on the main thread. This test ensures that a + // mainloop tick completes well under that threshold even when the child + // process has been killed and may still be exiting. + PythonProcessLauncher py(get_test_name(), + "import time\n" + "time.sleep(120)\n"); + py.launch(); + + // Wait for the process to start up + yield(); + ensure("process started", py.mPy->isRunning()); + + // Send the kill signal + py.mPy->kill(); + + // Sleep longer than the tick threshold to ensure the child has had + // time to exit at the OS level, so the next tick is likely to enter + // the "process just exited" code path that used to block for 100 ms. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Time a single mainloop tick: it must not block + auto start = std::chrono::steady_clock::now(); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + auto elapsed_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + + ensure(STRINGIZE("tick() took " << elapsed_ms << " ms, expected < 75 ms"), + elapsed_ms < 75); + + // Let the process fully exit so cleanup is orderly + waitfor(*py.mPy); + } + + template<> template<> + void object::test<35>() + { + set_test_name("LLProcess destructor completes quickly after kill()"); + // Regression test: after an explicit kill() call the destructor must + // not perform a blocking wait. The old Windows code called + // WaitForSingleObject(..., 100) in the destructor, causing a 100 ms + // stall on the main thread. With mKillCalled set to true, the + // destructor skips the termination/wait block entirely. + PythonProcessLauncher py(get_test_name(), + "import time\n" + "time.sleep(120)\n"); + py.launch(); + + // Wait for the process to start up + yield(); + ensure("process started", py.mPy->isRunning()); + + // Kill the process (sets mKillCalled = true) + py.mPy->kill(); + + // Time how long the destructor takes + auto start = std::chrono::steady_clock::now(); + py.mPy.reset(); // explicit destruction + auto elapsed_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + + ensure(STRINGIZE("destructor took " << elapsed_ms << " ms, expected < 75 ms"), + elapsed_ms < 75); + } + } // namespace tut -- cgit v1.3 From 9fce82688f0b9f972ea49f945505ba102bac639e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:10:43 +0000 Subject: Harden llprocess EOF timing tests --- indra/llcommon/tests/llprocess_test.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 65d797947f..84f6c131e7 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -120,6 +120,9 @@ void yield(int seconds=1) LLEventPumps::instance().obtain("mainloop").post(LLSD()); } +constexpr int EOF_EVENT_RETRY_COUNT = 20; +constexpr auto EOF_EVENT_RETRY_DELAY = std::chrono::milliseconds(50); + void waitfor(LLProcess& proc, int timeout=60) { int i = 0; @@ -1222,6 +1225,14 @@ namespace tut LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); EventListener listener(childout.getPump()); waitfor(*py.mPy); + // On Windows the pipe-close EOF notification can trail the process exit + // status by a short interval, so keep pumping for up to 1 second + // (20 * 50 ms) until it arrives. + for (int i = 0; i < EOF_EVENT_RETRY_COUNT && listener.mHistory.empty(); ++i) + { + std::this_thread::sleep_for(EOF_EVENT_RETRY_DELAY); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } // We can't be positive there will only be a single event, if the OS // (or any other intervening layer) does crazy buffering. What we want // to ensure is that there was exactly ONE event with "eof" true, and @@ -1697,6 +1708,17 @@ namespace tut EventListener errListener(childerr.getPump()); waitfor(*py.mPy); + // On Windows the pipe-close EOF notification can trail the process exit + // status by a short interval, so keep pumping for up to 1 second + // (20 * 50 ms) until both pipes report it. + for (int i = 0; + i < EOF_EVENT_RETRY_COUNT && + (outListener.mHistory.empty() || errListener.mHistory.empty()); + ++i) + { + std::this_thread::sleep_for(EOF_EVENT_RETRY_DELAY); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } ensure_equals("stdout size", childout.size(), 0); ensure_equals("stderr size", childerr.size(), 0); -- cgit v1.3 From bc49cc8b3f5488c56f2664d060712aacb52ab5f4 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 25 Jul 2026 10:13:42 +0300 Subject: #5994 Add process autokill test Copilot generated test for testing whether process exits with the app on windows. --- indra/llcommon/tests/llprocess_test.cpp | 250 ++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 84f6c131e7..00dcd52d5d 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -110,6 +110,19 @@ static std::string readfile(const std::string& pathname, const std::string& desc return output; } +#if LL_WINDOWS +static std::string readfile_if_present(const std::string& pathname) +{ + std::ifstream inf(pathname.c_str()); + if (!inf.is_open()) + { + return ""; + } + return std::string((std::istreambuf_iterator(inf)), + std::istreambuf_iterator()); +} +#endif + /// Looping on LLProcess::isRunning() must now be accompanied by pumping /// "mainloop" -- otherwise the status won't update and you get an infinite /// loop. @@ -251,6 +264,215 @@ struct PythonProcessLauncher NamedExtTempFile mScript; }; +#if LL_WINDOWS +namespace +{ + static constexpr const char* AUTOKILL_HELPER_SCRIPT_ENV = "LLPROCESS_AUTOKILL_HELPER_SCRIPT"; + static constexpr const char* AUTOKILL_HELPER_PIDFILE_ENV = "LLPROCESS_AUTOKILL_HELPER_PIDFILE"; + static constexpr const char* AUTOKILL_HELPER_RELEASE_ENV = "LLPROCESS_AUTOKILL_HELPER_RELEASE"; + static constexpr const char* AUTOKILL_HELPER_COUNT_ENV = "LLPROCESS_AUTOKILL_HELPER_COUNT"; + static constexpr int AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS = 60; + static constexpr int AUTOKILL_HELPER_PID_TIMEOUT_SECONDS = 15; + static constexpr DWORD AUTOKILL_HELPER_POLL_INTERVAL_MS = 100; + static constexpr DWORD AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS = 5000; + static constexpr int AUTOKILL_HELPER_INVALID_ENV_EXIT = 2; + static constexpr int AUTOKILL_HELPER_INVALID_COUNT_EXIT = 3; + static constexpr int AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT = 4; + + struct ScopedEnvironmentVariable + { + ScopedEnvironmentVariable(const char* name, const std::string& value): + mName(name), + mHadValue(false) + { + DWORD size = GetEnvironmentVariableA(name, nullptr, 0); + if (size > 0) + { + std::vector buffer(size); + DWORD copied = GetEnvironmentVariableA(name, buffer.data(), size); + if (copied > 0) + { + mHadValue = true; + mOldValue.assign(buffer.data(), copied); + } + } + SetEnvironmentVariableA(name, value.c_str()); + } + + ~ScopedEnvironmentVariable() + { + SetEnvironmentVariableA(mName.c_str(), mHadValue ? mOldValue.c_str() : nullptr); + } + + std::string mName; + std::string mOldValue; + bool mHadValue; + }; + + std::string get_current_executable_path() + { + std::vector buffer(MAX_PATH); + for (;;) + { + DWORD length = GetModuleFileNameA(nullptr, buffer.data(), static_cast(buffer.size())); + tut::ensure("GetModuleFileNameA() failed", length > 0); + if (length < buffer.size() && + (length < buffer.size() - 1 || buffer[length] == '\0')) + { + return std::string(buffer.data(), length); + } + buffer.resize(buffer.size() * 2); + } + } + + void run_autokill_helper_from_environment() + { + const std::string script = LLStringUtil::getenv(AUTOKILL_HELPER_SCRIPT_ENV); + if (script.empty()) + { + return; + } + + const std::string pidfile = LLStringUtil::getenv(AUTOKILL_HELPER_PIDFILE_ENV); + const std::string releasefile = LLStringUtil::getenv(AUTOKILL_HELPER_RELEASE_ENV); + const std::string countstr = LLStringUtil::getenv(AUTOKILL_HELPER_COUNT_ENV); + const std::string python = LLStringUtil::getenv("PYTHON"); + int child_count = 1; + if (!countstr.empty()) + { + try + { + child_count = std::stoi(countstr); + } + catch (const std::exception& err) + { + LL_WARNS("LLProcess") << "Invalid autokill helper child count '" + << countstr << "': " << err.what() << LL_ENDL; + std::exit(AUTOKILL_HELPER_INVALID_COUNT_EXIT); + } + } + + if (pidfile.empty() || releasefile.empty() || python.empty() || child_count < 1) + { + std::exit(AUTOKILL_HELPER_INVALID_ENV_EXIT); + } + + std::vector children; + children.reserve(child_count); + std::ofstream out(pidfile.c_str(), std::ios::trunc); + for (int i = 0; i < child_count; ++i) + { + LLProcess::Params params; + params.executable = python; + params.args.add(script); + params.autokill = true; + params.attached = false; + LLProcessPtr child = LLProcess::create(params); + if (!child) + { + std::exit(AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT); + } + children.push_back(child); + out << child->getProcessID() << '\n'; + } + out.flush(); + out.close(); + + for (DWORD elapsed_ms = 0; + elapsed_ms < AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS * 1000; + elapsed_ms += AUTOKILL_HELPER_POLL_INTERVAL_MS) + { + if (readfile_if_present(releasefile) == "exit") + { + break; + } + Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS); + } + + // Exit the helper process itself so the job handle closes and Windows + // terminates the autokilled children. + std::exit(0); + } + + std::vector wait_for_helper_pids( + const std::string& pidfile, + int expected_count, + int timeout = AUTOKILL_HELPER_PID_TIMEOUT_SECONDS) + { + for (int i = 0; + i < (timeout * 1000) / static_cast(AUTOKILL_HELPER_POLL_INTERVAL_MS); + ++i) + { + std::ifstream inf(pidfile.c_str()); + std::vector pids; + DWORD pid = 0; + while (inf >> pid) + { + pids.push_back(pid); + } + if (static_cast(pids.size()) == expected_count) + { + return pids; + } + Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } + tut::ensure(STRINGIZE("expected " << expected_count + << " child pids within " << timeout + << " seconds"), false); + return {}; + } + + void verify_autokill_on_helper_exit(const std::string& desc, int child_count) + { + NamedExtTempFile child_script("py", + "import time\n" + "time.sleep(30)\n"); + NamedTempFile pidfile("pid", ""); + NamedTempFile releasefile("release", ""); + + ScopedEnvironmentVariable helper_script(AUTOKILL_HELPER_SCRIPT_ENV, child_script.getName()); + ScopedEnvironmentVariable helper_pidfile(AUTOKILL_HELPER_PIDFILE_ENV, pidfile.getName()); + ScopedEnvironmentVariable helper_release(AUTOKILL_HELPER_RELEASE_ENV, releasefile.getName()); + ScopedEnvironmentVariable helper_count(AUTOKILL_HELPER_COUNT_ENV, std::to_string(child_count)); + + LLProcess::Params params; + params.executable = get_current_executable_path(); + params.desc = desc + " helper"; + LLProcessPtr helper = LLProcess::create(params); + tut::ensure("helper launched", bool(helper)); + + std::vector pids = wait_for_helper_pids(pidfile.getName(), child_count); + std::vector handles; + handles.reserve(pids.size()); + for (DWORD pid : pids) + { + // SYNCHRONIZE lets the test wait for the child to terminate, while + // PROCESS_QUERY_LIMITED_INFORMATION keeps the requested access minimal. + HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, pid); + tut::ensure(STRINGIZE("opened child process handle for pid " << pid), handle != nullptr); + handles.push_back(handle); + } + + { + std::ofstream out(releasefile.getName().c_str(), std::ios::trunc); + out << "exit"; + } + + waitfor(*helper); + tut::ensure_equals("helper exited", helper->getStatus().mState, LLProcess::EXITED); + + for (HANDLE handle : handles) + { + tut::ensure_equals("autokilled child exited", + WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS), + WAIT_OBJECT_0); + CloseHandle(handle); + } + } +} +#endif + /// convenience function for PythonProcessLauncher::run() template static void python(const std::string& desc, const CONTENT& script) @@ -303,6 +525,13 @@ namespace tut { struct llprocess_data { + llprocess_data() + { +#if LL_WINDOWS + run_autokill_helper_from_environment(); +#endif + } + LLAPRPool pool; }; typedef test_group llprocess_group; @@ -1815,4 +2044,25 @@ namespace tut elapsed_ms < 75); } + template<> template<> + void object::test<36>() + { + set_test_name("autokill ensures child termination on parent exit"); +#if !LL_WINDOWS + skip("Windows-specific test"); +#else + verify_autokill_on_helper_exit(get_test_name(), 1); +#endif + } + + template<> template<> + void object::test<37>() + { + set_test_name("multiple processes with autokill"); +#if !LL_WINDOWS + skip("Windows-specific test"); +#else + verify_autokill_on_helper_exit(get_test_name(), 2); +#endif + } } // namespace tut -- cgit v1.3 From 159a08c98dcba2bdf25caf13b236e27c54eda050 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 12 Aug 2026 18:37:00 -0400 Subject: Fix up LLProcess tests. --- indra/llcommon/tests/llprocess_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/tests/llprocess_test.cpp') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 5df43e497f..dfc34373da 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -431,9 +431,9 @@ namespace NamedTempFile pidfile("pid", ""); NamedTempFile releasefile("release", ""); - ScopedEnvironmentVariable helper_script(AUTOKILL_HELPER_SCRIPT_ENV, child_script.getName()); - ScopedEnvironmentVariable helper_pidfile(AUTOKILL_HELPER_PIDFILE_ENV, pidfile.getName()); - ScopedEnvironmentVariable helper_release(AUTOKILL_HELPER_RELEASE_ENV, releasefile.getName()); + ScopedEnvironmentVariable helper_script(AUTOKILL_HELPER_SCRIPT_ENV, child_script.getPath().string()); + ScopedEnvironmentVariable helper_pidfile(AUTOKILL_HELPER_PIDFILE_ENV, pidfile.getPath().string()); + ScopedEnvironmentVariable helper_release(AUTOKILL_HELPER_RELEASE_ENV, releasefile.getPath().string()); ScopedEnvironmentVariable helper_count(AUTOKILL_HELPER_COUNT_ENV, std::to_string(child_count)); LLProcess::Params params; @@ -442,7 +442,7 @@ namespace LLProcessPtr helper = LLProcess::create(params); tut::ensure("helper launched", bool(helper)); - std::vector pids = wait_for_helper_pids(pidfile.getName(), child_count); + std::vector pids = wait_for_helper_pids(pidfile.getPath().string(), child_count); std::vector handles; handles.reserve(pids.size()); for (DWORD pid : pids) @@ -455,7 +455,7 @@ namespace } { - std::ofstream out(releasefile.getName().c_str(), std::ios::trunc); + std::ofstream out(releasefile.getPath(), std::ios::trunc); out << "exit"; } -- cgit v1.3