diff options
author | Brad Linden <brad@lindenlab.com> | 2023-10-25 13:22:13 -0700 |
---|---|---|
committer | Brad Linden <brad@lindenlab.com> | 2023-10-25 16:12:13 -0700 |
commit | 673b3309dde153fdadf7559bd16a5bb6db4723a1 (patch) | |
tree | 726d5e38b016d1d5504c3024b0959cec00d6c9c1 /indra/llcommon | |
parent | 887226c7d2462306191126cc5bfc0de96e18ad42 (diff) | |
parent | e4d6a08941f9c4e81bda8ae14d481be5029353fd (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-559
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/llcoros.cpp | 6 | ||||
-rw-r--r-- | indra/llcommon/llerror.cpp | 17 | ||||
-rw-r--r-- | indra/llcommon/llerror.h | 11 | ||||
-rw-r--r-- | indra/llcommon/llleap.cpp | 11 | ||||
-rw-r--r-- | indra/llcommon/llprocessor.cpp | 2 | ||||
-rw-r--r-- | indra/llcommon/llrand.cpp | 104 | ||||
-rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 259 | ||||
-rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 74 | ||||
-rw-r--r-- | indra/llcommon/tests/llrand_test.cpp | 53 | ||||
-rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 133 | ||||
-rw-r--r-- | indra/llcommon/tests/workqueue_test.cpp | 6 |
11 files changed, 360 insertions, 316 deletions
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 70d8dfc8b9..cfaf3415e7 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -123,11 +123,7 @@ LLCoros::LLCoros(): // Previously we used // boost::context::guarded_stack_allocator::default_stacksize(); // empirically this is insufficient. -#if ADDRESS_SIZE == 64 - mStackSize(512*1024), -#else - mStackSize(256*1024), -#endif + mStackSize(768*1024), // mCurrent does NOT own the current CoroData instance -- it simply // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index eaf1be60f2..414515854a 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1603,5 +1603,18 @@ namespace LLError } } - - +void crashdriver(void (*callback)(int*)) +{ + // The LLERROR_CRASH macro used to have inline code of the form: + //int* make_me_crash = NULL; + //*make_me_crash = 0; + + // But compilers are getting smart enough to recognize that, so we must + // assign to an address supplied by a separate source file. We could do + // the assignment here in crashdriver() -- but then BugSplat would group + // all LL_ERRS() crashes as the fault of this one function, instead of + // identifying the specific LL_ERRS() source line. So instead, do the + // assignment in a lambda in the caller's source. We just provide the + // nullptr target. + callback(nullptr); +} diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index f229b4964a..05dd88ee51 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -385,11 +385,9 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; #define LL_NEWLINE '\n' // Use this only in LL_ERRS or in a place that LL_ERRS may not be used -#define LLERROR_CRASH \ -{ \ - int* make_me_crash = NULL;\ - *make_me_crash = 0; \ - exit(*make_me_crash); \ +#define LLERROR_CRASH \ +{ \ + crashdriver([](int* ptr){ *ptr = 0; exit(*ptr); }); \ } #define LL_ENDL \ @@ -491,4 +489,7 @@ LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches the result in a static variable. */ +// used by LLERROR_CRASH +void crashdriver(void (*)(int*)); + #endif // LL_LLERROR_H diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 259f5bc505..5b5bf97cef 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -389,6 +389,17 @@ public: // Read all remaining bytes and log. LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL; } + /*--------------------------- diagnostic ---------------------------*/ + else if (data["eof"].asBoolean()) + { + LL_DEBUGS("LLLeap") << mDesc << " ended, no partial line" << LL_ENDL; + } + else + { + LL_DEBUGS("LLLeap") << mDesc << " (still running, " << childerr.size() + << " bytes pending)" << LL_ENDL; + } + /*------------------------- end diagnostic -------------------------*/ return false; } diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index 4a1a81f083..28f8bc2b93 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -746,7 +746,7 @@ private: __cpuid(0x1, eax, ebx, ecx, edx); if(feature_infos[0] != (S32)edx) { - LL_ERRS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL; + LL_WARNS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL; } #endif // LL_RELEASE_FOR_DOWNLOAD diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index cb28a8f5c3..33afc50cf7 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,46 +58,14 @@ * to restore uniform distribution. */ -// *NOTE: The system rand implementation is probably not correct. -#define LL_USE_SYSTEM_RAND 0 +static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); -#if LL_USE_SYSTEM_RAND -#include <cstdlib> -#endif +// no default implementation, only specific F64 and F32 specializations +template <typename REAL> +inline REAL ll_internal_random(); -#if LL_USE_SYSTEM_RAND -class LLSeedRand -{ -public: - LLSeedRand() - { -#if LL_WINDOWS - srand(LLUUID::getRandomSeed()); -#else - srand48(LLUUID::getRandomSeed()); -#endif - } -}; -static LLSeedRand sRandomSeeder; -inline F64 ll_internal_random_double() -{ -#if LL_WINDOWS - return (F64)rand() / (F64)RAND_MAX; -#else - return drand48(); -#endif -} -inline F32 ll_internal_random_float() -{ -#if LL_WINDOWS - return (F32)rand() / (F32)RAND_MAX; -#else - return (F32)drand48(); -#endif -} -#else -static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); -inline F64 ll_internal_random_double() +template <> +inline F64 ll_internal_random<F64>() { // *HACK: Through experimentation, we have found that dual core // CPUs (or at least multi-threaded processes) seem to @@ -108,15 +76,35 @@ inline F64 ll_internal_random_double() return rv; } +template <> +inline F32 ll_internal_random<F32>() +{ + return F32(ll_internal_random<F64>()); +} + +/*------------------------------ F64 aliases -------------------------------*/ +inline F64 ll_internal_random_double() +{ + return ll_internal_random<F64>(); +} + +F64 ll_drand() +{ + return ll_internal_random_double(); +} + +/*------------------------------ F32 aliases -------------------------------*/ inline F32 ll_internal_random_float() { - // The clamping rules are described above. - F32 rv = (F32)gRandomGenerator(); - if(!((rv >= 0.0f) && (rv < 1.0f))) return fmod(rv, 1.f); - return rv; + return ll_internal_random<F32>(); +} + +F32 ll_frand() +{ + return ll_internal_random_float(); } -#endif +/*-------------------------- clamped random range --------------------------*/ S32 ll_rand() { return ll_rand(RAND_MAX); @@ -130,42 +118,28 @@ S32 ll_rand(S32 val) return rv; } -F32 ll_frand() -{ - return ll_internal_random_float(); -} - -F32 ll_frand(F32 val) +template <typename REAL> +REAL ll_grand(REAL val) { // The clamping rules are described above. - F32 rv = ll_internal_random_float() * val; + REAL rv = ll_internal_random<REAL>() * val; if(val > 0) { - if(rv >= val) return 0.0f; + if(rv >= val) return REAL(); } else { - if(rv <= val) return 0.0f; + if(rv <= val) return REAL(); } return rv; } -F64 ll_drand() +F32 ll_frand(F32 val) { - return ll_internal_random_double(); + return ll_grand<F32>(val); } F64 ll_drand(F64 val) { - // The clamping rules are described above. - F64 rv = ll_internal_random_double() * val; - if(val > 0) - { - if(rv >= val) return 0.0; - } - else - { - if(rv <= val) return 0.0; - } - return rv; + return ll_grand<F64>(val); } diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 3ae48a2532..7197dedfbf 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,8 +17,6 @@ // std headers #include <functional> // external library headers -#include <boost/assign/list_of.hpp> -#include <boost/phoenix/core/argument.hpp> // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -30,10 +28,6 @@ #include "stringize.h" #include "StringVec.h" -using boost::assign::list_of; - -StringVec sv(const StringVec& listof) { return listof; } - #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -104,17 +98,12 @@ namespace tut llleap_data(): reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() - boost::phoenix::placeholders::arg1 << + [](std::ostream& out){ out << "import re\n" "import os\n" "import sys\n" "\n" - "try:\n" - // new freestanding llsd package - " import llsd\n" - "except ImportError:\n" - // older llbase.llsd module - " from llbase import llsd\n" + "import llsd\n" "\n" "class ProtocolError(Exception):\n" " def __init__(self, msg, data):\n" @@ -193,7 +182,7 @@ namespace tut "def request(pump, data):\n" " # we expect 'data' is a dict\n" " data['reply'] = _reply\n" - " send(pump, data)\n"), + " send(pump, data)\n";}), // Get the actual pathname of the NamedExtTempFile and trim off // the ".py" extension. (We could cache reader.getName() in a // separate member variable, but I happen to know getName() just @@ -218,14 +207,14 @@ namespace tut void object::test<1>() { set_test_name("multiple LLLeap instances"); - NamedTempFile script("py", - "import time\n" - "time.sleep(1)\n"); + NamedExtTempFile script("py", + "import time\n" + "time.sleep(1)\n"); LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->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. @@ -236,10 +225,10 @@ namespace tut void object::test<2>() { set_test_name("stderr to log"); - NamedTempFile script("py", - "import sys\n" - "sys.stderr.write('''Hello from Python!\n" - "note partial line''')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stderr.write('''Hello from Python!\n" + "note partial line''')\n"); StringVec vcommand{ PYTHON, script.getName() }; CaptureLog log(LLError::LEVEL_INFO); waitfor(LLLeap::create(get_test_name(), vcommand)); @@ -251,11 +240,11 @@ namespace tut void object::test<3>() { set_test_name("bad stdout protocol"); - NamedTempFile script("py", - "print('Hello from Python!')\n"); + NamedExtTempFile script("py", + "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "Hello from Python!"); } @@ -264,13 +253,13 @@ namespace tut void object::test<4>() { set_test_name("leftover stdout"); - NamedTempFile script("py", - "import sys\n" - // note lack of newline - "sys.stdout.write('Hello from Python!')\n"); + NamedExtTempFile script("py", + "import sys\n" + // note lack of newline + "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("Discarding"), "Hello from Python!"); } @@ -279,12 +268,12 @@ namespace tut void object::test<5>() { set_test_name("bad stdout len prefix"); - NamedTempFile script("py", - "import sys\n" - "sys.stdout.write('5a2:something')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "5a2:"); } @@ -386,17 +375,18 @@ namespace tut set_test_name("round trip"); AckAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "from " << reader_module << " import *\n" - // make a request on our little API - "request(pump='" << api.getName() << "', data={})\n" - // wait for its response - "resp = get()\n" - "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" - " else 'bad: ' + str(resp)\n" - "send(pump='" << result.getName() << "', data=result)\n"); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "from " << reader_module << " import *\n" + // make a request on our little API + "request(pump='" << api.getName() << "', data={})\n" + // wait for its response + "resp = get()\n" + "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" + " else 'bad: ' + str(resp)\n" + "send(pump='" << result.getName() << "', data=result)\n";}); + waitfor(LLLeap::create(get_test_name(), + StringVec{PYTHON, script.getName()})); result.ensure(); } @@ -424,38 +414,38 @@ namespace tut // iterations etc. in OS pipes and the LLLeap/LLProcess implementation. ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Note that since reader imports llsd, this - // 'import *' gets us llsd too. - "sample = llsd.format_notation(dict(pump='" << - api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" - // The whole packet has length prefix too: "len:data" - "samplen = len(str(len(sample))) + 1 + len(sample)\n" - // guess how many messages it will take to - // accumulate BUFFERED_LENGTH - "count = int(" << BUFFERED_LENGTH << "/samplen)\n" - "print('Sending %s requests' % count, file=sys.stderr)\n" - "for i in range(count):\n" - " request('" << api.getName() << "', dict(reqid=i))\n" - // The assumption in this specific test that - // replies will arrive in the same order as - // requests is ONLY valid because the API we're - // invoking sends replies instantly. If the API - // had to wait for some external event before - // sending its reply, replies could arrive in - // arbitrary order, and we'd have to tick them - // off from a set. - "result = ''\n" - "for i in range(count):\n" - " resp = get()\n" - " if resp['data']['reqid'] != i:\n" - " result = 'expected reqid=%s in %s' % (i, resp)\n" - " break\n" - "send(pump='" << result.getName() << "', data=result)\n"); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "import sys\n" + "from " << reader_module << " import *\n" + // Note that since reader imports llsd, this + // 'import *' gets us llsd too. + "sample = llsd.format_notation(dict(pump='" << + api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" + // The whole packet has length prefix too: "len:data" + "samplen = len(str(len(sample))) + 1 + len(sample)\n" + // guess how many messages it will take to + // accumulate BUFFERED_LENGTH + "count = int(" << BUFFERED_LENGTH << "/samplen)\n" + "print('Sending %s requests' % count, file=sys.stderr)\n" + "for i in range(count):\n" + " request('" << api.getName() << "', dict(reqid=i))\n" + // The assumption in this specific test that + // replies will arrive in the same order as + // requests is ONLY valid because the API we're + // invoking sends replies instantly. If the API + // had to wait for some external event before + // sending its reply, replies could arrive in + // arbitrary order, and we'd have to tick them + // off from a set. + "result = ''\n" + "for i in range(count):\n" + " resp = get()\n" + " if resp['data']['reqid'] != i:\n" + " 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()}), 300); // needs more realtime than most tests result.ensure(); } @@ -467,65 +457,62 @@ namespace tut { ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Generate a very large string value. - "desired = int(sys.argv[1])\n" - // 7 chars per item: 6 digits, 1 comma - "count = int((desired - 50)/7)\n" - "large = ''.join('%06d,' % i for i in range(count))\n" - // Pass 'large' as reqid because we know the API - // will echo reqid, and we want to receive it back. - "request('" << api.getName() << "', dict(reqid=large))\n" - "try:\n" - " resp = get()\n" - "except ParseError as e:\n" - " # try to find where e.data diverges from expectation\n" - // Normally we'd expect a 'pump' key in there, - // too, with value replypump(). But Python - // serializes keys in a different order than C++, - // so incoming data start with 'data'. - // Truthfully, though, if we get as far as 'pump' - // before we find a difference, something's very - // strange. - " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" - " chunk = 40\n" - " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" - " if e.data[offset:offset+chunk] != \\\n" - " expect[offset:offset+chunk]:\n" - " print('Offset %06d: expect %r,\\n'\\\n" - " ' get %r' %\\\n" - " (offset,\n" - " expect[offset:offset+chunk],\n" - " e.data[offset:offset+chunk]),\n" - " file=sys.stderr)\n" - " break\n" - " else:\n" - " print('incoming data matches expect?!', file=sys.stderr)\n" - " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" - " sys.exit(1)\n" - "\n" - "echoed = resp['data']['reqid']\n" - "if echoed == large:\n" - " send('" << result.getName() << "', '')\n" - " sys.exit(0)\n" - // Here we know echoed did NOT match; try to find where - "for i in range(count):\n" - " start = 7*i\n" - " end = 7*(i+1)\n" - " if end > len(echoed)\\\n" - " or echoed[start:end] != large[start:end]:\n" - " send('" << result.getName() << "',\n" - " 'at offset %s, expected %r but got %r' %\n" - " (start, large[start:end], echoed[start:end]))\n" - "sys.exit(1)\n"); + NamedExtTempFile script("py", + [&](std::ostream& out){ out << + "import sys\n" + "from " << reader_module << " import *\n" + // Generate a very large string value. + "desired = int(sys.argv[1])\n" + // 7 chars per item: 6 digits, 1 comma + "count = int((desired - 50)/7)\n" + "large = ''.join('%06d,' % i for i in range(count))\n" + // Pass 'large' as reqid because we know the API + // will echo reqid, and we want to receive it back. + "request('" << api.getName() << "', dict(reqid=large))\n" + "try:\n" + " resp = get()\n" + "except ParseError as e:\n" + " # try to find where e.data diverges from expectation\n" + // Normally we'd expect a 'pump' key in there, + // too, with value replypump(). But Python + // serializes keys in a different order than C++, + // so incoming data start with 'data'. + // Truthfully, though, if we get as far as 'pump' + // before we find a difference, something's very + // strange. + " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" + " chunk = 40\n" + " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" + " if e.data[offset:offset+chunk] != \\\n" + " expect[offset:offset+chunk]:\n" + " print('Offset %06d: expect %r,\\n'\\\n" + " ' get %r' %\\\n" + " (offset,\n" + " expect[offset:offset+chunk],\n" + " e.data[offset:offset+chunk]),\n" + " file=sys.stderr)\n" + " break\n" + " else:\n" + " print('incoming data matches expect?!', file=sys.stderr)\n" + " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" + " sys.exit(1)\n" + "\n" + "echoed = resp['data']['reqid']\n" + "if echoed == large:\n" + " send('" << result.getName() << "', '')\n" + " sys.exit(0)\n" + // Here we know echoed did NOT match; try to find where + "for i in range(count):\n" + " start = 7*i\n" + " end = 7*(i+1)\n" + " if end > len(echoed)\\\n" + " or echoed[start:end] != large[start:end]:\n" + " send('" << result.getName() << "',\n" + " 'at offset %s, expected %r but got %r' %\n" + " (start, large[start:end], echoed[start:end]))\n" + "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, - sv(list_of - (PYTHON) - (script.getName()) - (stringize(size)))), + StringVec{PYTHON, script.getName(), 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 81449b4a42..b6b297b8d7 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -151,8 +151,38 @@ struct PythonProcessLauncher /// Launch Python script; verify that it launched void launch() { - mPy = LLProcess::create(mParams); - tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + try + { + mPy = LLProcess::create(mParams); + tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + } + catch (const tut::failure&) + { + // On Windows, if APR_LOG is set, our version of APR's + // apr_create_proc() logs to the specified file. If this test + // failed, try to report that log. + const char* APR_LOG = getenv("APR_LOG"); + if (APR_LOG && *APR_LOG) + { + std::ifstream inf(APR_LOG); + if (! inf.is_open()) + { + LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL; + } + else + { + LL_WARNS() << "==============================" << LL_ENDL; + LL_WARNS() << "From '" << APR_LOG << "':" << LL_ENDL; + std::string line; + while (std::getline(inf, line)) + { + LL_WARNS() << line << LL_ENDL; + } + LL_WARNS() << "==============================" << LL_ENDL; + } + } + throw; + } } /// Run Python script and wait for it to complete. @@ -191,7 +221,7 @@ struct PythonProcessLauncher LLProcess::Params mParams; LLProcessPtr mPy; std::string mDesc; - NamedTempFile mScript; + NamedExtTempFile mScript; }; /// convenience function for PythonProcessLauncher::run() @@ -214,30 +244,26 @@ static std::string python_out(const std::string& desc, const CONTENT& script) class NamedTempDir: public boost::noncopyable { public: - // Use python() function to create a temp directory: I've found - // nothing in either Boost.Filesystem or APR quite like Python's - // tempfile.mkdtemp(). - // Special extra bonus: on Mac, mkdtemp() reports a pathname - // starting with /var/folders/something, whereas that's really a - // symlink to /private/var/folders/something. Have to use - // realpath() to compare properly. NamedTempDir(): - mPath(python_out("mkdtemp()", - "from __future__ import with_statement\n" - "import os.path, sys, tempfile\n" - "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n")) - {} + mPath(NamedTempFile::temp_path()), + mCreated(boost::filesystem::create_directories(mPath)) + { + mPath = boost::filesystem::canonical(mPath); + } ~NamedTempDir() { - aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); + if (mCreated) + { + boost::filesystem::remove_all(mPath); + } } - std::string getName() const { return mPath; } + std::string getName() const { return mPath.string(); } private: - std::string mPath; + boost::filesystem::path mPath; + bool mCreated; }; /***************************************************************************** @@ -355,7 +381,7 @@ namespace tut set_test_name("raw APR nonblocking I/O"); // Create a script file in a temporary place. - NamedTempFile script("py", + NamedExtTempFile script("py", "from __future__ import print_function" EOL "import sys" EOL "import time" EOL @@ -565,7 +591,13 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); + std::string expected{ tempdir.getName() }; +#if LL_WINDOWS + // SIGH, don't get tripped up by "C:" != "c:" -- + // but on the Mac, using tolower() fails because "/users" != "/Users"! + expected = utf8str_tolower(expected); +#endif + ensure_equals("os.getcwd()", py.run_read(), expected); } template<> template<> diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index 383e6f9e0a..ac5a33d0ba 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -29,7 +29,23 @@ #include "../test/lltut.h" #include "../llrand.h" +#include "stringize.h" +// In llrand.h, every function is documented to return less than the high end +// -- specifically, because you can pass a negative extent, they're documented +// never to return a value equal to the extent. +// So that we don't need two different versions of ensure_in_range(), when +// testing extent < 0, negate the return value and the extent before passing +// into ensure_in_range(). +template <typename NUMBER> +void ensure_in_range(const std::string_view& name, + NUMBER value, NUMBER low, NUMBER high) +{ + auto failmsg{ stringize(name, " >= ", low, " (", value, ')') }; + tut::ensure(failmsg, (value >= low)); + failmsg = stringize(name, " < ", high, " (", value, ')'); + tut::ensure(failmsg, (value < high)); +} namespace tut { @@ -44,84 +60,65 @@ namespace tut template<> template<> void random_object_t::test<1>() { - F32 number = 0.0f; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_frand(); - ensure("frand >= 0", (number >= 0.0f)); - ensure("frand < 1", (number < 1.0f)); + ensure_in_range("frand", ll_frand(), 0.0f, 1.0f); } } template<> template<> void random_object_t::test<2>() { - F64 number = 0.0f; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_drand(); - ensure("drand >= 0", (number >= 0.0)); - ensure("drand < 1", (number < 1.0)); + ensure_in_range("drand", ll_drand(), 0.0, 1.0); } } template<> template<> void random_object_t::test<3>() { - F32 number = 0.0f; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_frand(2.0f) - 1.0f; - ensure("frand >= 0", (number >= -1.0f)); - ensure("frand < 1", (number <= 1.0f)); + ensure_in_range("frand(2.0f)", ll_frand(2.0f) - 1.0f, -1.0f, 1.0f); } } template<> template<> void random_object_t::test<4>() { - F32 number = 0.0f; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_frand(-7.0); - ensure("drand <= 0", (number <= 0.0)); - ensure("drand > -7", (number > -7.0)); + // Negate the result so we don't have to allow a templated low-end + // comparison as well. + ensure_in_range("-frand(-7.0)", -ll_frand(-7.0), 0.0f, 7.0f); } } template<> template<> void random_object_t::test<5>() { - F64 number = 0.0f; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_drand(-2.0); - ensure("drand <= 0", (number <= 0.0)); - ensure("drand > -2", (number > -2.0)); + ensure_in_range("-drand(-2.0)", -ll_drand(-2.0), 0.0, 2.0); } } template<> template<> void random_object_t::test<6>() { - S32 number = 0; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_rand(100); - ensure("rand >= 0", (number >= 0)); - ensure("rand < 100", (number < 100)); + ensure_in_range("rand(100)", ll_rand(100), 0, 100); } } template<> template<> void random_object_t::test<7>() { - S32 number = 0; for(S32 ii = 0; ii < 100000; ++ii) { - number = ll_rand(-127); - ensure("rand <= 0", (number <= 0)); - ensure("rand > -127", (number > -127)); + ensure_in_range("-rand(-127)", -ll_rand(-127), 0, 127); } } } diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index acb2953b5b..ac40125f75 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -45,11 +45,6 @@ typedef U32 uint32_t; #endif #include "boost/range.hpp" -#include "boost/foreach.hpp" -#include "boost/bind.hpp" -#include "boost/phoenix/bind/bind_function.hpp" -#include "boost/phoenix/core/argument.hpp" -using namespace boost::phoenix; #include "llsd.h" #include "llsdserialize.h" @@ -57,9 +52,11 @@ using namespace boost::phoenix; #include "llformat.h" #include "llmemorystream.h" +#include "../test/hexdump.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "stringize.h" +#include "StringVec.h" #include <functional> typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction; @@ -1796,16 +1793,12 @@ namespace tut // helper for TestPythonCompatible static std::string import_llsd("import os.path\n" "import sys\n" - "try:\n" - // new freestanding llsd package - " import llsd\n" - "except ImportError:\n" - // older llbase.llsd module - " from llbase import llsd\n"); + "import llsd\n"); // helper for TestPythonCompatible - template <typename CONTENT> - void python(const std::string& desc, const CONTENT& script, int expect=0) + template <typename CONTENT, typename... ARGS> + void python_expect(const std::string& desc, const CONTENT& script, int expect=0, + ARGS&&... args) { auto PYTHON(LLStringUtil::getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); @@ -1816,7 +1809,8 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL); + int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), + std::forward<ARGS>(args)..., NULL); if (rc == -1) { char buffer[256]; @@ -1832,6 +1826,10 @@ namespace tut LLProcess::Params params; params.executable = PYTHON; params.args.add(scriptfile.getName()); + for (const std::string& arg : StringVec{ std::forward<ARGS>(args)... }) + { + params.args.add(arg); + } LLProcessPtr py(LLProcess::create(params)); ensure(STRINGIZE("Couldn't launch " << desc << " script"), bool(py)); // Implementing timeout would mean messing with alarm() and @@ -1866,6 +1864,14 @@ namespace tut #endif } + // helper for TestPythonCompatible + template <typename CONTENT, typename... ARGS> + void python(const std::string& desc, const CONTENT& script, ARGS&&... args) + { + // plain python() expects rc 0 + python_expect(desc, script, 0, std::forward<ARGS>(args)...); + } + struct TestPythonCompatible { TestPythonCompatible() {} @@ -1880,10 +1886,10 @@ namespace tut void TestPythonCompatibleObject::test<1>() { set_test_name("verify python()"); - python("hello", - "import sys\n" - "sys.exit(17)\n", - 17); // expect nonzero rc + python_expect("hello", + "import sys\n" + "sys.exit(17)\n", + 17); // expect nonzero rc } template<> template<> @@ -1899,7 +1905,7 @@ namespace tut static void writeLLSDArray(const FormatterFunction& serialize, std::ostream& out, const LLSD& array) { - for (const LLSD& item : llsd::inArray(array)) + for (const LLSD& item: llsd::inArray(array)) { // It's important to delimit the entries in this file somehow // because, although Python's llsd.parse() can accept a file @@ -1914,7 +1920,14 @@ namespace tut auto buffstr{ buffer.str() }; 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), + sizeof(bufflen)) + << LL_ENDL; out.write(buffstr.c_str(), buffstr.length()); + LL_DEBUGS() << "Wrote data: " + << hexmix(buffstr.c_str(), buffstr.length()) + << LL_ENDL; } } @@ -1943,10 +1956,10 @@ namespace tut " else:\n" " raise AssertionError('Too many data items')\n"; - // Create an llsdXXXXXX file containing 'data' serialized to - // notation. + // Create an llsdXXXXXX file containing 'data' serialized per + // FormatterFunction. NamedTempFile file("llsd", - // NamedTempFile's boost::function constructor + // NamedTempFile's function constructor // takes a callable. To this callable it passes the // std::ostream with which it's writing the // NamedTempFile. @@ -1954,34 +1967,50 @@ namespace tut (std::ostream& out) { writeLLSDArray(serialize, out, cdata); }); - python("read C++ " + desc, - placeholders::arg1 << - import_llsd << - "from functools import partial\n" - "import io\n" - "import struct\n" - "lenformat = struct.Struct('i')\n" - "def parse_each(inf):\n" - " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" - " len = lenformat.unpack(rawlen)[0]\n" - // Since llsd.parse() has no max_bytes argument, instead of - // passing the input stream directly to parse(), read the item - // into a distinct bytes object and parse that. - " data = inf.read(len)\n" - " try:\n" - " frombytes = llsd.parse(data)\n" - " except llsd.LLSDParseError as err:\n" - " print(f'*** {err}')\n" - " print(f'Bad content:\\n{data!r}')\n" - " raise\n" - // Also try parsing from a distinct stream. - " stream = io.BytesIO(data)\n" - " fromstream = llsd.parse(stream)\n" - " assert frombytes == fromstream\n" - " yield frombytes\n" - << pydata << - // Don't forget raw-string syntax for Windows pathnames. - "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n"); + // 'debug' starts empty because it's intended as an output file + NamedTempFile debug("debug", ""); + + try + { + python("read C++ " + desc, + [&](std::ostream& out){ out << + import_llsd << + "from functools import partial\n" + "import io\n" + "import struct\n" + "lenformat = struct.Struct('i')\n" + "def parse_each(inf):\n" + " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" + " print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n" + " file=debug)\n" + " len = lenformat.unpack(rawlen)[0]\n" + // Since llsd.parse() has no max_bytes argument, instead of + // passing the input stream directly to parse(), read the item + // into a distinct bytes object and parse that. + " data = inf.read(len)\n" + " print('Read data: ', repr(data), file=debug)\n" + " try:\n" + " frombytes = llsd.parse(data)\n" + " except llsd.LLSDParseError as err:\n" + " print(f'*** {err}')\n" + " print(f'Bad content:\\n{data!r}')\n" + " raise\n" + // Also try parsing from a distinct stream. + " stream = io.BytesIO(data)\n" + " fromstream = llsd.parse(stream)\n" + " assert frombytes == fromstream\n" + " 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";}); + } + catch (const failure&) + { + LL_DEBUGS() << "Script debug output:" << LL_ENDL; + debug.peep_log(); + throw; + } } template<> template<> @@ -2068,7 +2097,7 @@ namespace tut NamedTempFile file("llsd", ""); python("Python " + pyformatter, - placeholders::arg1 << + [&](std::ostream& out){ out << import_llsd << "import struct\n" "lenformat = struct.Struct('i')\n" @@ -2086,7 +2115,7 @@ namespace tut " for item in DATA:\n" " serialized = llsd." << pyformatter << "(item)\n" " f.write(lenformat.pack(len(serialized)))\n" - " f.write(serialized)\n"); + " f.write(serialized)\n";}); std::ifstream inf(file.getName().c_str()); LLSD item; diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp index 41aa858084..df16f4a46e 100644 --- a/indra/llcommon/tests/workqueue_test.cpp +++ b/indra/llcommon/tests/workqueue_test.cpp @@ -83,7 +83,11 @@ namespace tut // signal the work item that it can quit; consider LLOneShotCond. LLCond<Shared> data; auto start = WorkSchedule::TimePoint::clock::now(); - auto interval = 100ms; + // 2s seems like a long time to wait, since it directly impacts the + // duration of this test program. Unfortunately GitHub's Mac runners + // are pretty wimpy, and we're getting spurious "too late" errors just + // because the thread doesn't wake up as soon as we want. + auto interval = 2s; queue.postEvery( interval, [&data, count = 0] |