/* 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; } }