summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2019-12-10 11:11:20 -0500
committerNat Goodspeed <nat@lindenlab.com>2020-03-25 19:24:25 -0400
commit38da7d5d5f6066a53b44b9eaa0efc01aedc99a6a (patch)
tree9d61233ae75286c0a51c92e6ff3a35a320080ed1
parent1efb4aefed4724c74a2579ee99bf21037ab6aaf1 (diff)
DRTVWR-476: Add unit tests for LLMainThreadTask.
Now that we have the Sync class to help construct unit tests that move forward in a deterministic stepwise order, we can build suitable unit tests for LLMainThreadTask.
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/tests/llmainthreadtask_test.cpp139
2 files changed, 140 insertions, 0 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index d17ee4c70a..eeb315ead6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -340,6 +340,7 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp
new file mode 100644
index 0000000000..e54c7eda18
--- /dev/null
+++ b/indra/llcommon/tests/llmainthreadtask_test.cpp
@@ -0,0 +1,139 @@
+/**
+ * @file llmainthreadtask_test.cpp
+ * @author Nat Goodspeed
+ * @date 2019-12-05
+ * @brief Test for llmainthreadtask.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llmainthreadtask.h"
+// STL headers
+// std headers
+#include <atomic>
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "../test/sync.h"
+#include "llthread.h" // on_main_thread()
+#include "lleventtimer.h"
+#include "lockstatic.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llmainthreadtask_data
+ {
+ // 2-second timeout
+ Sync mSync{F32Milliseconds(2000.0f)};
+
+ llmainthreadtask_data()
+ {
+ // we're not testing the result; this is just to cache the
+ // initial thread as the main thread.
+ on_main_thread();
+ }
+ };
+ typedef test_group<llmainthreadtask_data> llmainthreadtask_group;
+ typedef llmainthreadtask_group::object object;
+ llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("inline");
+ bool ran = false;
+ bool result = LLMainThreadTask::dispatch(
+ [&ran]()->bool{
+ ran = true;
+ return true;
+ });
+ ensure("didn't run lambda", ran);
+ ensure("didn't return result", result);
+ }
+
+ struct StaticData
+ {
+ std::mutex mMutex; // LockStatic looks for mMutex
+ bool ran{false};
+ };
+ typedef llthread::LockStatic<StaticData> LockStatic;
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("cross-thread");
+ std::atomic_bool result(false);
+ // wrapping our thread lambda in a packaged_task will catch any
+ // exceptions it might throw and deliver them via future
+ std::packaged_task<void()> thread_work(
+ [this, &result](){
+ // lock static data first
+ LockStatic lk;
+ // unblock test<2>()'s yield_until(1)
+ mSync.set(1);
+ // dispatch work to main thread -- should block here
+ bool on_main(
+ LLMainThreadTask::dispatch(
+ lk, // unlock this before blocking!
+ []()->bool{
+ // have to lock static mutex to set static data
+ LockStatic()->ran = true;
+ // indicate whether task was run on the main thread
+ return on_main_thread();
+ }));
+ // wait for test<2>() to unblock us again
+ mSync.yield_until(3);
+ result = on_main;
+ });
+ auto thread_result = thread_work.get_future();
+ std::thread thread;
+ try
+ {
+ // run thread_work
+ thread = std::thread(std::move(thread_work));
+ // wait for thread to set(1)
+ mSync.yield_until(1);
+ // try to acquire the lock, should block because thread has it
+ LockStatic lk;
+ // wake up when dispatch() unlocks the static mutex
+ ensure("shouldn't have run yet", !lk->ran);
+ ensure("shouldn't have returned yet", !result);
+ // unlock so the task can acquire the lock
+ lk.unlock();
+ // run the task -- should unblock thread, which will immediately block
+ // on mSync
+ LLEventTimer::updateClass();
+ // 'lk', having unlocked, can no longer be used to access; relock with
+ // a new LockStatic instance
+ ensure("should now have run", LockStatic()->ran);
+ ensure("returned too early", !result);
+ // okay, let thread perform the assignment
+ mSync.set(3);
+ }
+ catch (...)
+ {
+ // A test failure exception anywhere in the try block can cause
+ // the test program to terminate without explanation when
+ // ~thread() finds that 'thread' is still joinable. We could
+ // either join() or detach() it -- but since it might be blocked
+ // waiting for something from the main thread that now can never
+ // happen, it's safer to detach it.
+ thread.detach();
+ throw;
+ }
+ // 'thread' should be all done now
+ thread.join();
+ // deliver any exception thrown by thread_work
+ thread_result.get();
+ ensure("ran changed", LockStatic()->ran);
+ ensure("didn't run on main thread", result);
+ }
+} // namespace tut