diff options
Diffstat (limited to 'xpcom/tests/gtest/TestThreadUtils.cpp')
-rw-r--r-- | xpcom/tests/gtest/TestThreadUtils.cpp | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp new file mode 100644 index 000000000..0d5d2f234 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadUtils.cpp @@ -0,0 +1,378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http:mozilla.org/MPL/2.0/. */ + +#include "nsThreadUtils.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +enum { + TEST_CALL_VOID_ARG_VOID_RETURN, + TEST_CALL_VOID_ARG_VOID_RETURN_CONST, + TEST_CALL_VOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#ifdef HAVE_STDCALL + TEST_STDCALL_VOID_ARG_VOID_RETURN, + TEST_STDCALL_VOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_VOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#endif + TEST_CALL_NEWTHREAD_SUICIDAL, + MAX_TESTS +}; + +bool gRunnableExecuted[MAX_TESTS]; + +class nsFoo : public nsISupports { + NS_DECL_ISUPPORTS + nsresult DoFoo(bool* aBool) { + *aBool = true; + return NS_OK; + } + +private: + virtual ~nsFoo() {} +}; + +NS_IMPL_ISUPPORTS0(nsFoo) + +class TestSuicide : public mozilla::Runnable { + NS_IMETHOD Run() override { + // Runs first time on thread "Suicide", then dies on MainThread + if (!NS_IsMainThread()) { + mThread = do_GetCurrentThread(); + NS_DispatchToMainThread(this); + return NS_OK; + } + MOZ_RELEASE_ASSERT(mThread); + mThread->Shutdown(); + gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL] = true; + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mThread; +}; + +class nsBar : public nsISupports { + virtual ~nsBar() {} +public: + NS_DECL_ISUPPORTS + void DoBar1(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN] = true; + } + void DoBar1Const(void) const { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN_CONST] = true; + } + nsresult DoBar2(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void DoBar3(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult DoBar4(nsFoo* aFoo) { + return aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN]); + } + void DoBar5(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult DoBar6(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#ifdef HAVE_STDCALL + void __stdcall DoBar1std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_VOID_RETURN] = true; + } + nsresult __stdcall DoBar2std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void __stdcall DoBar3std(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult __stdcall DoBar4std(nsFoo* aFoo) { + return aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN]); + } + void __stdcall DoBar5std(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult __stdcall DoBar6std(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#endif +}; + +NS_IMPL_ISUPPORTS0(nsBar) + +struct TestCopyWithNoMove +{ + explicit TestCopyWithNoMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {} + TestCopyWithNoMove(const TestCopyWithNoMove& a) : mCopyCounter(a.mCopyCounter) { ++mCopyCounter; }; + // No 'move' declaration, allows passing object by rvalue copy. + // Destructor nulls member variable... + ~TestCopyWithNoMove() { mCopyCounter = nullptr; } + // ... so we can check that the object is called when still alive. + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestCopyWithDeletedMove +{ + explicit TestCopyWithDeletedMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {} + TestCopyWithDeletedMove(const TestCopyWithDeletedMove& a) : mCopyCounter(a.mCopyCounter) { ++mCopyCounter; }; + // Deleted move prevents passing by rvalue (even if copy would work) + TestCopyWithDeletedMove(TestCopyWithDeletedMove&&) = delete; + ~TestCopyWithDeletedMove() { mCopyCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestMove +{ + explicit TestMove(int* aMoveCounter) : mMoveCounter(aMoveCounter) {} + TestMove(const TestMove&) = delete; + TestMove(TestMove&& a) : mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; ++mMoveCounter; } + ~TestMove() { mMoveCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mMoveCounter); } + int* mMoveCounter; +}; +struct TestCopyMove +{ + TestCopyMove(int* aCopyCounter, int* aMoveCounter) : mCopyCounter(aCopyCounter), mMoveCounter(aMoveCounter) {} + TestCopyMove(const TestCopyMove& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { ++mCopyCounter; }; + TestCopyMove(TestCopyMove&& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; ++mMoveCounter; } + ~TestCopyMove() { mCopyCounter = nullptr; mMoveCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); MOZ_RELEASE_ASSERT(mMoveCounter); } + int* mCopyCounter; + int* mMoveCounter; +}; + +static void Expect(const char* aContext, int aCounter, int aMaxExpected) +{ + EXPECT_LE(aCounter, aMaxExpected) << aContext; +} + +TEST(ThreadUtils, NewRunnableFunction) +{ + // Test NS_NewRunnableFunction with copyable-only function object. + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + trackedRunnable = NS_NewRunnableFunction(tracker); + // Original 'tracker' is destroyed here. + } + // Verify that the runnable contains a non-destroyed function object. + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and no move) function, copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + // Passing as rvalue, but using copy. + // (TestCopyWithDeletedMove wouldn't allow this.) + trackedRunnable = NS_NewRunnableFunction(TestCopyWithNoMove(©Counter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and no move) function rvalue, copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + trackedRunnable = NS_NewRunnableFunction(tracker); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and deleted move) function, copies", + copyCounter, 1); + } + + // Test NS_NewRunnableFunction with movable-only function object. + { + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestMove tracker(&moveCounter); + trackedRunnable = NS_NewRunnableFunction(Move(tracker)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with movable-only function, moves", + moveCounter, 1); + } + { + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + trackedRunnable = NS_NewRunnableFunction(TestMove(&moveCounter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with movable-only function rvalue, moves", + moveCounter, 1); + } + + // Test NS_NewRunnableFunction with copyable&movable function object. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = NS_NewRunnableFunction(Move(tracker)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable function, copies", + copyCounter, 0); + Expect("NS_NewRunnableFunction with copyable&movable function, moves", + moveCounter, 1); + } + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + trackedRunnable = + NS_NewRunnableFunction(TestCopyMove(©Counter, &moveCounter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable function rvalue, copies", + copyCounter, 0); + Expect("NS_NewRunnableFunction with copyable&movable function rvalue, moves", + moveCounter, 1); + } + + // Test NS_NewRunnableFunction with copyable-only lambda capture. + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and no move) capture, copies", + copyCounter, 2); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and deleted move) capture, copies", + copyCounter, 2); + } + + // Note: Not possible to use move-only captures. + // (Until we can use C++14 generalized lambda captures) + + // Test NS_NewRunnableFunction with copyable&movable lambda capture. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable lambda). + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable capture, copies", + copyCounter, 1); + Expect("NS_NewRunnableFunction with copyable&movable capture, moves", + moveCounter, 1); + } +} + +TEST(ThreadUtils, RunnableMethod) +{ + memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool)); + // Scope the smart ptrs so that the runnables need to hold on to whatever they need + { + RefPtr<nsFoo> foo = new nsFoo(); + RefPtr<nsBar> bar = new nsBar(); + RefPtr<const nsBar> constBar = bar; + + // This pointer will be freed at the end of the block + // Do not dereference this pointer in the runnable method! + RefPtr<nsFoo> rawFoo = new nsFoo(); + + // Read only string. Dereferencing in runnable method to check this works. + char* message = (char*)"Test message"; + + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1)); + NS_DispatchToMainThread(NewRunnableMethod(constBar, &nsBar::DoBar1Const)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2)); + NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>> + (bar, &nsBar::DoBar3, foo)); + NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>> + (bar, &nsBar::DoBar4, foo)); + NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5, rawFoo)); + NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6, message)); +#ifdef HAVE_STDCALL + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1std)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2std)); + NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>> + (bar, &nsBar::DoBar3std, foo)); + NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>> + (bar, &nsBar::DoBar4std, foo)); + NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo)); + NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6std, message)); +#endif + } + + // Spin the event loop + NS_ProcessPendingEvents(nullptr); + + // Now test a suicidal event in NS_New(Named)Thread + nsCOMPtr<nsIThread> thread; + NS_NewNamedThread("SuicideThread", getter_AddRefs(thread), new TestSuicide()); + ASSERT_TRUE(thread); + + while (!gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL]) { + NS_ProcessPendingEvents(nullptr); + } + + for (uint32_t i = 0; i < MAX_TESTS; i++) { + EXPECT_TRUE(gRunnableExecuted[i]) << "Error in test " << i; + } +} |