diff options
Diffstat (limited to 'indra/llcommon/tests')
-rw-r--r-- | indra/llcommon/tests/classic_callback_test.cpp | 144 | ||||
-rw-r--r-- | indra/llcommon/tests/llinstancetracker_test.cpp | 14 | ||||
-rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 41 | ||||
-rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 40 | ||||
-rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 15 | ||||
-rw-r--r-- | indra/llcommon/tests/threadsafeschedule_test.cpp | 69 | ||||
-rw-r--r-- | indra/llcommon/tests/tuple_test.cpp | 47 | ||||
-rw-r--r-- | indra/llcommon/tests/workqueue_test.cpp | 235 |
8 files changed, 556 insertions, 49 deletions
diff --git a/indra/llcommon/tests/classic_callback_test.cpp b/indra/llcommon/tests/classic_callback_test.cpp new file mode 100644 index 0000000000..c060775c24 --- /dev/null +++ b/indra/llcommon/tests/classic_callback_test.cpp @@ -0,0 +1,144 @@ +/** + * @file classic_callback_test.cpp + * @author Nat Goodspeed + * @date 2021-09-22 + * @brief Test ClassicCallback and HeapClassicCallback. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "classic_callback.h" +// STL headers +#include <iostream> +#include <string> +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" + +/***************************************************************************** +* example callback +*****************************************************************************/ +// callback_t is part of the specification of someAPI() +typedef void (*callback_t)(const char*, void*); +void someAPI(callback_t callback, void* userdata) +{ + callback("called", userdata); +} + +// C++ callable I want as the actual callback +struct MyCallback +{ + void operator()(const char* msg, void*) + { + mMsg = msg; + } + + void callback_with_extra(const std::string& extra, const char* msg) + { + mMsg = extra + ' ' + msg; + } + + std::string mMsg; +}; + +/***************************************************************************** +* example callback accepting several params, and void* userdata isn't first +*****************************************************************************/ +typedef std::string (*complex_callback)(int, const char*, void*, double); +std::string otherAPI(complex_callback callback, void* userdata) +{ + return callback(17, "hello world", userdata, 3.0); +} + +// struct into which we can capture complex_callback params +static struct Data +{ + void set(int i, const char* s, double f) + { + mi = i; + ms = s; + mf = f; + } + + void clear() { set(0, "", 0.0); } + + int mi; + std::string ms; + double mf; +} sData; + +// C++ callable I want to pass +struct OtherCallback +{ + std::string operator()(int num, const char* str, void*, double approx) + { + sData.set(num, str, approx); + return "hello back!"; + } +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct classic_callback_data + { + }; + typedef test_group<classic_callback_data> classic_callback_group; + typedef classic_callback_group::object object; + classic_callback_group classic_callbackgrp("classic_callback"); + + template<> template<> + void object::test<1>() + { + set_test_name("ClassicCallback"); + // engage someAPI(MyCallback()) + auto ccb{ makeClassicCallback<callback_t>(MyCallback()) }; + someAPI(ccb.get_callback(), ccb.get_userdata()); + // Unfortunately, with the side effect confined to the bound + // MyCallback instance, that call was invisible. Bind a reference to a + // named instance by specifying a ref type. + MyCallback mcb; + ClassicCallback<callback_t, void*, MyCallback&> ccb2(mcb); + someAPI(ccb2.get_callback(), ccb2.get_userdata()); + ensure_equals("failed to call through ClassicCallback", mcb.mMsg, "called"); + + // try with HeapClassicCallback + mcb.mMsg.clear(); + auto hcbp{ makeHeapClassicCallback<callback_t>(mcb) }; + someAPI(hcbp->get_callback(), hcbp->get_userdata()); + ensure_equals("failed to call through HeapClassicCallback", mcb.mMsg, "called"); + + // lambda + // The tricky thing here is that a lambda is an unspecified type, so + // you can't declare a ClassicCallback<signature, void*, that type>. + mcb.mMsg.clear(); + auto xcb( + makeClassicCallback<callback_t>( + [&mcb](const char* msg, void*) + { mcb.callback_with_extra("extra", msg); })); + someAPI(xcb.get_callback(), xcb.get_userdata()); + ensure_equals("failed to call lambda", mcb.mMsg, "extra called"); + + // engage otherAPI(OtherCallback()) + OtherCallback ocb; + // Instead of specifying a reference type for the bound CALLBACK, as + // with ccb2 above, you can alternatively move the callable object + // into the ClassicCallback (of course AFTER any other reference). + // That's why OtherCallback uses external data for its observable side + // effect. + auto occb{ makeClassicCallback<complex_callback>(std::move(ocb)) }; + std::string result{ otherAPI(occb.get_callback(), occb.get_userdata()) }; + ensure_equals("failed to return callback result", result, "hello back!"); + ensure_equals("failed to set int", sData.mi, 17); + ensure_equals("failed to set string", sData.ms, "hello world"); + ensure_equals("failed to set double", sData.mf, 3.0); + } +} // namespace tut diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index 9b89159625..5daa29adf4 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -90,19 +90,19 @@ namespace tut { Keyed one("one"); ensure_equals(Keyed::instanceCount(), 1); - Keyed* found = Keyed::getInstance("one"); - ensure("couldn't find stack Keyed", found); - ensure_equals("found wrong Keyed instance", found, &one); + auto found = Keyed::getInstance("one"); + ensure("couldn't find stack Keyed", bool(found)); + ensure_equals("found wrong Keyed instance", found.get(), &one); { boost::scoped_ptr<Keyed> two(new Keyed("two")); ensure_equals(Keyed::instanceCount(), 2); - Keyed* found = Keyed::getInstance("two"); - ensure("couldn't find heap Keyed", found); - ensure_equals("found wrong Keyed instance", found, two.get()); + auto found = Keyed::getInstance("two"); + ensure("couldn't find heap Keyed", bool(found)); + ensure_equals("found wrong Keyed instance", found.get(), two.get()); } ensure_equals(Keyed::instanceCount(), 1); } - Keyed* found = Keyed::getInstance("one"); + auto found = Keyed::getInstance("one"); ensure("Keyed key lives too long", ! found); ensure_equals(Keyed::instanceCount(), 0); } diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 9d71e327d8..9754353ab0 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -145,13 +145,13 @@ namespace tut " data = ''.join(parts)\n" " assert len(data) == length\n" " try:\n" - " return llsd.parse(data)\n" + " return llsd.parse(data.encode())\n" // Seems the old indra.base.llsd module didn't properly // convert IndexError (from running off end of string) to // LLSDParseError. - " except (IndexError, llsd.LLSDParseError), e:\n" + " except (IndexError, llsd.LLSDParseError) as e:\n" " msg = 'Bad received packet (%s)' % e\n" - " print >>sys.stderr, '%s, %s bytes:' % (msg, len(data))\n" + " print('%s, %s bytes:' % (msg, len(data)), file=sys.stderr)\n" " showmax = 40\n" // We've observed failures with very large packets; // dumping the entire packet wastes time and space. @@ -167,12 +167,12 @@ namespace tut " data = data[:trunc]\n" " ellipsis = '... (%s more)' % (length - trunc)\n" " offset = -showmax\n" - " for offset in xrange(0, len(data)-showmax, showmax):\n" - " print >>sys.stderr, '%04d: %r +' % \\\n" - " (offset, data[offset:offset+showmax])\n" + " for offset in range(0, len(data)-showmax, showmax):\n" + " print('%04d: %r +' % \\\n" + " (offset, data[offset:offset+showmax]), file=sys.stderr)\n" " offset += showmax\n" - " print >>sys.stderr, '%04d: %r%s' % \\\n" - " (offset, data[offset:], ellipsis)\n" + " print('%04d: %r%s' % \\\n" + " (offset, data[offset:], ellipsis), file=sys.stderr)\n" " raise ParseError(msg, data)\n" "\n" "# deal with initial stdin message\n" @@ -189,7 +189,7 @@ namespace tut " sys.stdout.flush()\n" "\n" "def send(pump, data):\n" - " put(llsd.format_notation(dict(pump=pump, data=data)))\n" + " put(llsd.format_notation(dict(pump=pump, data=data)).decode())\n" "\n" "def request(pump, data):\n" " # we expect 'data' is a dict\n" @@ -253,7 +253,7 @@ namespace tut { set_test_name("bad stdout protocol"); NamedTempFile script("py", - "print 'Hello from Python!'\n"); + "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); @@ -438,8 +438,8 @@ namespace tut // guess how many messages it will take to // accumulate BUFFERED_LENGTH "count = int(" << BUFFERED_LENGTH << "/samplen)\n" - "print >>sys.stderr, 'Sending %s requests' % count\n" - "for i in xrange(count):\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 @@ -450,7 +450,7 @@ namespace tut // arbitrary order, and we'd have to tick them // off from a set. "result = ''\n" - "for i in xrange(count):\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" @@ -476,13 +476,13 @@ namespace tut "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 xrange(count))\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, e:\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 @@ -493,17 +493,18 @@ namespace tut // strange. " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" " chunk = 40\n" - " for offset in xrange(0, max(len(e.data), len(expect)), chunk):\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 >>sys.stderr, 'Offset %06d: expect %r,\\n'\\\n" + " print('Offset %06d: expect %r,\\n'\\\n" " ' get %r' %\\\n" " (offset,\n" " expect[offset:offset+chunk],\n" - " e.data[offset:offset+chunk])\n" + " e.data[offset:offset+chunk]),\n" + " file=sys.stderr)\n" " break\n" " else:\n" - " print >>sys.stderr, 'incoming data matches expect?!'\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" @@ -512,7 +513,7 @@ namespace tut " send('" << result.getName() << "', '')\n" " sys.exit(0)\n" // Here we know echoed did NOT match; try to find where - "for i in xrange(count):\n" + "for i in range(count):\n" " start = 7*i\n" " end = 7*(i+1)\n" " if end > len(echoed)\\\n" diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index f0eafa8201..999d432079 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -356,14 +356,15 @@ namespace tut // Create a script file in a temporary place. NamedTempFile script("py", + "from __future__ import print_function" EOL "import sys" EOL "import time" EOL EOL "time.sleep(2)" EOL - "print >>sys.stdout, 'stdout after wait'" EOL + "print('stdout after wait',file=sys.stdout)" EOL "sys.stdout.flush()" EOL "time.sleep(2)" EOL - "print >>sys.stderr, 'stderr after wait'" EOL + "print('stderr after wait',file=sys.stderr)" EOL "sys.stderr.flush()" EOL ); @@ -381,7 +382,11 @@ namespace tut std::vector<const char*> argv; apr_proc_t child; +#if defined(LL_WINDOWS) argv.push_back("python"); +#else + argv.push_back("python3"); +#endif // Have to have a named copy of this std::string so its c_str() value // will persist. std::string scriptname(script.getName()); @@ -568,12 +573,12 @@ namespace tut { set_test_name("arguments"); PythonProcessLauncher py(get_test_name(), - "from __future__ import with_statement\n" + "from __future__ import with_statement, print_function\n" "import sys\n" // note nonstandard output-file arg! "with open(sys.argv[3], 'w') as f:\n" " for arg in sys.argv[1:]:\n" - " print >>f, arg\n"); + " print(arg,file=f)\n"); // We expect that PythonProcessLauncher has already appended // its own NamedTempFile to mParams.args (sys.argv[0]). py.mParams.args.add("first arg"); // sys.argv[1] @@ -742,7 +747,7 @@ namespace tut "with open(sys.argv[1], 'w') as f:\n" " f.write('ok')\n" "# wait for 'go' from test program\n" - "for i in xrange(60):\n" + "for i in range(60):\n" " time.sleep(1)\n" " with open(sys.argv[2]) as f:\n" " go = f.read()\n" @@ -804,7 +809,7 @@ namespace tut "with open(sys.argv[1], 'w') as f:\n" " f.write('ok')\n" "# wait for 'go' from test program\n" - "for i in xrange(60):\n" + "for i in range(60):\n" " time.sleep(1)\n" " with open(sys.argv[2]) as f:\n" " go = f.read()\n" @@ -857,7 +862,8 @@ namespace tut set_test_name("'bogus' test"); CaptureLog recorder; PythonProcessLauncher py(get_test_name(), - "print 'Hello world'\n"); + "from __future__ import print_function\n" + "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam("bogus")); py.mPy = LLProcess::create(py.mParams); ensure("should have rejected 'bogus'", ! py.mPy); @@ -872,7 +878,8 @@ namespace tut // Replace this test with one or more real 'file' tests when we // implement 'file' support PythonProcessLauncher py(get_test_name(), - "print 'Hello world'\n"); + "from __future__ import print_function\n" + "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("file")); py.mPy = LLProcess::create(py.mParams); @@ -887,7 +894,8 @@ namespace tut // implement 'tpipe' support CaptureLog recorder; PythonProcessLauncher py(get_test_name(), - "print 'Hello world'\n"); + "from __future__ import print_function\n" + "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("tpipe")); py.mPy = LLProcess::create(py.mParams); @@ -904,7 +912,8 @@ namespace tut // implement 'npipe' support CaptureLog recorder; PythonProcessLauncher py(get_test_name(), - "print 'Hello world'\n"); + "from __future__ import print_function\n" + "print('Hello world')\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("npipe")); @@ -980,7 +989,8 @@ namespace tut { set_test_name("get*Pipe() validation"); PythonProcessLauncher py(get_test_name(), - "print 'this output is expected'\n"); + "from __future__ import print_function\n" + "print('this output is expected')\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr @@ -1000,14 +1010,15 @@ namespace tut { set_test_name("talk to stdin/stdout"); PythonProcessLauncher py(get_test_name(), + "from __future__ import print_function\n" "import sys, time\n" - "print 'ok'\n" + "print('ok')\n" "sys.stdout.flush()\n" "# wait for 'go' from test program\n" "go = sys.stdin.readline()\n" "if go != 'go\\n':\n" " sys.exit('expected \"go\", saw %r' % go)\n" - "print 'ack'\n"); + "print('ack')\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout py.launch(); @@ -1118,7 +1129,8 @@ namespace tut { set_test_name("ReadPipe \"eof\" event"); PythonProcessLauncher py(get_test_name(), - "print 'Hello from Python!'\n"); + "from __future__ import print_function\n" + "print('Hello from Python!')\n"); py.mParams.files.add(LLProcess::FileParam()); // stdin py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout py.launch(); diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 642c1c3879..c246f5ee56 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1795,7 +1795,7 @@ namespace tut set_test_name("verify NamedTempFile"); python("platform", "import sys\n" - "print 'Running on', sys.platform\n"); + "print('Running on', sys.platform)\n"); } // helper for test<3> @@ -1825,14 +1825,14 @@ namespace tut const char pydata[] = "def verify(iterable):\n" " it = iter(iterable)\n" - " assert it.next() == 17\n" - " assert abs(it.next() - 3.14) < 0.01\n" - " assert it.next() == '''\\\n" + " assert next(it) == 17\n" + " assert abs(next(it) - 3.14) < 0.01\n" + " assert next(it) == '''\\\n" "This string\n" "has several\n" "lines.'''\n" " try:\n" - " it.next()\n" + " next(it)\n" " except StopIteration:\n" " pass\n" " else:\n" @@ -1855,7 +1855,7 @@ namespace tut " yield llsd.parse(item)\n" << pydata << // Don't forget raw-string syntax for Windows pathnames. - "verify(parse_each(open(r'" << file.getName() << "')))\n"); + "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n"); } template<> template<> @@ -1870,7 +1870,6 @@ namespace tut python("write Python notation", placeholders::arg1 << - "from __future__ import with_statement\n" << import_llsd << "DATA = [\n" " 17,\n" @@ -1884,7 +1883,7 @@ namespace tut // N.B. Using 'print' implicitly adds newlines. "with open(r'" << file.getName() << "', 'w') as f:\n" " for item in DATA:\n" - " print >>f, llsd.format_notation(item)\n"); + " print(llsd.format_notation(item).decode(), file=f)\n"); std::ifstream inf(file.getName().c_str()); LLSD item; diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp new file mode 100644 index 0000000000..c421cc7b1c --- /dev/null +++ b/indra/llcommon/tests/threadsafeschedule_test.cpp @@ -0,0 +1,69 @@ +/** + * @file threadsafeschedule_test.cpp + * @author Nat Goodspeed + * @date 2021-10-04 + * @brief Test for threadsafeschedule. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "threadsafeschedule.h" +// STL headers +// std headers +#include <chrono> +// external library headers +// other Linden headers +#include "../test/lltut.h" + +using namespace std::literals::chrono_literals; // ms suffix +using namespace std::literals::string_literals; // s suffix +using Queue = LL::ThreadSafeSchedule<std::string>; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct threadsafeschedule_data + { + Queue queue; + }; + typedef test_group<threadsafeschedule_data> threadsafeschedule_group; + typedef threadsafeschedule_group::object object; + threadsafeschedule_group threadsafeschedulegrp("threadsafeschedule"); + + template<> template<> + void object::test<1>() + { + set_test_name("push"); + // Simply calling push() a few times might result in indeterminate + // delivery order if the resolution of steady_clock is coarser than + // the real time required for each push() call. Explicitly increment + // the timestamp for each one -- but since we're passing explicit + // timestamps, make the queue reorder them. + queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi")); + // Given the various push() overloads, you have to match the type + // exactly: conversions are ambiguous. + queue.push("abc"s); + queue.push(Queue::Clock::now() + 100ms, "def"); + queue.close(); + auto entry = queue.pop(); + ensure_equals("failed to pop first", std::get<0>(entry), "abc"s); + entry = queue.pop(); + ensure_equals("failed to pop second", std::get<0>(entry), "def"s); + ensure("queue not closed", queue.isClosed()); + ensure("queue prematurely done", ! queue.done()); + std::string s; + bool popped = queue.tryPopFor(1s, s); + ensure("failed to pop third", popped); + ensure_equals("third is wrong", s, "ghi"s); + popped = queue.tryPop(s); + ensure("queue not empty", ! popped); + ensure("queue not done", queue.done()); + } +} // namespace tut diff --git a/indra/llcommon/tests/tuple_test.cpp b/indra/llcommon/tests/tuple_test.cpp new file mode 100644 index 0000000000..af94e2086c --- /dev/null +++ b/indra/llcommon/tests/tuple_test.cpp @@ -0,0 +1,47 @@ +/** + * @file tuple_test.cpp + * @author Nat Goodspeed + * @date 2021-10-04 + * @brief Test for tuple. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "tuple.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct tuple_data + { + }; + typedef test_group<tuple_data> tuple_group; + typedef tuple_group::object object; + tuple_group tuplegrp("tuple"); + + template<> template<> + void object::test<1>() + { + set_test_name("tuple"); + std::tuple<std::string, int> tup{ "abc", 17 }; + std::tuple<int, std::string, int> ptup{ tuple_cons(34, tup) }; + std::tuple<std::string, int> tup2; + int i; + std::tie(i, tup2) = tuple_split(ptup); + ensure_equals("tuple_car() fail", i, 34); + ensure_equals("tuple_cdr() (0) fail", std::get<0>(tup2), "abc"); + ensure_equals("tuple_cdr() (1) fail", std::get<1>(tup2), 17); + } +} // namespace tut diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp new file mode 100644 index 0000000000..1d73f7aa0d --- /dev/null +++ b/indra/llcommon/tests/workqueue_test.cpp @@ -0,0 +1,235 @@ +/** + * @file workqueue_test.cpp + * @author Nat Goodspeed + * @date 2021-10-07 + * @brief Test for workqueue. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "workqueue.h" +// STL headers +// std headers +#include <chrono> +#include <deque> +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "../test/catch_and_store_what_in.h" +#include "llcond.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "llstring.h" +#include "stringize.h" + +using namespace LL; +using namespace std::literals::chrono_literals; // ms suffix +using namespace std::literals::string_literals; // s suffix + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct workqueue_data + { + WorkQueue queue{"queue"}; + }; + typedef test_group<workqueue_data> workqueue_group; + typedef workqueue_group::object object; + workqueue_group workqueuegrp("workqueue"); + + template<> template<> + void object::test<1>() + { + set_test_name("name"); + ensure_equals("didn't capture name", queue.getKey(), "queue"); + ensure("not findable", WorkQueue::getInstance("queue") == queue.getWeak().lock()); + WorkQueue q2; + ensure("has no name", LLStringUtil::startsWith(q2.getKey(), "WorkQueue")); + } + + template<> template<> + void object::test<2>() + { + set_test_name("post"); + bool wasRun{ false }; + // We only get away with binding a simple bool because we're running + // the work on the same thread. + queue.post([&wasRun](){ wasRun = true; }); + queue.close(); + ensure("ran too soon", ! wasRun); + queue.runUntilClose(); + ensure("didn't run", wasRun); + } + + template<> template<> + void object::test<3>() + { + set_test_name("postEvery"); + // record of runs + using Shared = std::deque<WorkQueue::TimePoint>; + // This is an example of how to share data between the originator of + // postEvery(work) and the work item itself, since usually a WorkQueue + // is used to dispatch work to a different thread. Neither of them + // should call any of LLCond's wait methods: you don't want to stall + // either the worker thread or the originating thread (conventionally + // main). Use LLCond or a subclass even if all you want to do is + // signal the work item that it can quit; consider LLOneShotCond. + LLCond<Shared> data; + auto start = WorkQueue::TimePoint::clock::now(); + auto interval = 100ms; + queue.postEvery( + interval, + [&data, count = 0] + () mutable + { + // record the timestamp at which this instance is running + data.update_one( + [](Shared& data) + { + data.push_back(WorkQueue::TimePoint::clock::now()); + }); + // by the 3rd call, return false to stop + return (++count < 3); + }); + // no convenient way to close() our queue while we've got a + // postEvery() running, so run until we have exhausted the iterations + // or we time out waiting + for (auto finish = start + 10*interval; + WorkQueue::TimePoint::clock::now() < finish && + data.get([](const Shared& data){ return data.size(); }) < 3; ) + { + queue.runPending(); + std::this_thread::sleep_for(interval/10); + } + // Take a copy of the captured deque. + Shared result = data.get(); + ensure_equals("called wrong number of times", result.size(), 3); + // postEvery() assumes you want the first call to happen right away. + // Pretend our start time was (interval) earlier than that, to make + // our too early/too late tests uniform for all entries. + start -= interval; + for (size_t i = 0; i < result.size(); ++i) + { + auto diff = result[i] - start; + start += interval; + try + { + ensure(STRINGIZE("call " << i << " too soon"), diff >= interval); + ensure(STRINGIZE("call " << i << " too late"), diff < interval*1.5); + } + catch (const tut::failure&) + { + auto interval_ms = interval / 1ms; + auto diff_ms = diff / 1ms; + std::cerr << "interval " << interval_ms + << "ms; diff " << diff_ms << "ms" << std::endl; + throw; + } + } + } + + template<> template<> + void object::test<4>() + { + set_test_name("postTo"); + WorkQueue main("main"); + auto qptr = WorkQueue::getInstance("queue"); + int result = 0; + main.postTo( + qptr, + [](){ return 17; }, + // Note that a postTo() *callback* can safely bind a reference to + // a variable on the invoking thread, because the callback is run + // on the invoking thread. (Of course the bound variable must + // survive until the callback is called.) + [&result](int i){ result = i; }); + // this should post the callback to main + qptr->runOne(); + // this should run the callback + main.runOne(); + ensure_equals("failed to run int callback", result, 17); + + std::string alpha; + // postTo() handles arbitrary return types + main.postTo( + qptr, + [](){ return "abc"s; }, + [&alpha](const std::string& s){ alpha = s; }); + qptr->runPending(); + main.runPending(); + ensure_equals("failed to run string callback", alpha, "abc"); + } + + template<> template<> + void object::test<5>() + { + set_test_name("postTo with void return"); + WorkQueue main("main"); + auto qptr = WorkQueue::getInstance("queue"); + std::string observe; + main.postTo( + qptr, + // The ONLY reason we can get away with binding a reference to + // 'observe' in our work callable is because we're directly + // calling qptr->runOne() on this same thread. It would be a + // mistake to do that if some other thread were servicing 'queue'. + [&observe](){ observe = "queue"; }, + [&observe](){ observe.append(";main"); }); + qptr->runOne(); + main.runOne(); + ensure_equals("failed to run both lambdas", observe, "queue;main"); + } + + template<> template<> + void object::test<6>() + { + set_test_name("waitForResult"); + std::string stored; + // Try to call waitForResult() on this thread's main coroutine. It + // should throw because the main coroutine must service the queue. + auto what{ catch_what<WorkQueue::Error>( + [this, &stored](){ stored = queue.waitForResult( + [](){ return "should throw"; }); }) }; + ensure("lambda should not have run", stored.empty()); + ensure_not("waitForResult() should have thrown", what.empty()); + ensure(STRINGIZE("should mention waitForResult: " << what), + what.find("waitForResult") != std::string::npos); + + // Call waitForResult() on a coroutine, with a string result. + LLCoros::instance().launch( + "waitForResult string", + [this, &stored]() + { stored = queue.waitForResult( + [](){ return "string result"; }); }); + llcoro::suspend(); + // Nothing will have happened yet because, even if the coroutine did + // run immediately, all it did was to queue the inner lambda on + // 'queue'. Service it. + queue.runOne(); + llcoro::suspend(); + ensure_equals("bad waitForResult return", stored, "string result"); + + // Call waitForResult() on a coroutine, with a void callable. + stored.clear(); + bool done = false; + LLCoros::instance().launch( + "waitForResult void", + [this, &stored, &done]() + { + queue.waitForResult([&stored](){ stored = "ran"; }); + done = true; + }); + llcoro::suspend(); + queue.runOne(); + llcoro::suspend(); + ensure_equals("didn't run coroutine", stored, "ran"); + ensure("void waitForResult() didn't return", done); + } +} // namespace tut |