/* 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 #include "nsComponentManagerUtils.h" #include "nsIThread.h" #include "nsThreadUtils.h" #include "mozilla/IdleTaskRunner.h" #include "mozilla/RefCounted.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/UniquePtr.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() = default; }; NS_IMPL_ISUPPORTS0(nsFoo) class TestSuicide : public mozilla::Runnable { public: TestSuicide() : mozilla::Runnable("TestSuicide") {} 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 mThread; }; class nsBar : public nsISupports { virtual ~nsBar() = default; 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 += 1; }; // 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 += 1; }; // 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 += 1; } ~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 += 1; }; TestCopyMove(TestCopyMove&& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; *mMoveCounter += 1; } ~TestCopyMove() { mCopyCounter = nullptr; mMoveCounter = nullptr; } void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); MOZ_RELEASE_ASSERT(mMoveCounter); } int* mCopyCounter; int* mMoveCounter; }; struct TestRefCounted : RefCounted { MOZ_DECLARE_REFCOUNTED_TYPENAME(TestRefCounted); }; static void Expect(const char* aContext, int aCounter, int aMaxExpected) { EXPECT_LE(aCounter, aMaxExpected) << aContext; } static void ExpectRunnableName(Runnable* aRunnable, const char* aExpectedName) { #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY nsAutoCString name; EXPECT_TRUE(NS_SUCCEEDED(aRunnable->GetName(name))) << "Runnable::GetName()"; EXPECT_TRUE(name.EqualsASCII(aExpectedName)) << "Verify Runnable name"; #endif } struct BasicRunnableFactory { static constexpr bool SupportsCopyWithDeletedMove = true; template static auto Create(const char* aName, Function&& aFunc) { return NS_NewRunnableFunction(aName, std::forward(aFunc)); } }; struct CancelableRunnableFactory { static constexpr bool SupportsCopyWithDeletedMove = false; template static auto Create(const char* aName, Function&& aFunc) { return NS_NewCancelableRunnableFunction(aName, std::forward(aFunc)); } }; template static void TestRunnableFactory(bool aNamed) { // Test RunnableFactory with copyable-only function object. { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithNoMove tracker(©Counter); trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) : RunnableFactory::Create( "TestNewRunnableFunction", tracker); // Original 'tracker' is destroyed here. } // Verify that the runnable contains a non-destroyed function object. trackedRunnable->Run(); } Expect( "RunnableFactory with copyable-only (and no move) function, " "copies", copyCounter, 1); } { int copyCounter = 0; { nsCOMPtr trackedRunnable; { // Passing as rvalue, but using copy. // (TestCopyWithDeletedMove wouldn't allow this.) trackedRunnable = aNamed ? RunnableFactory::Create("unused", TestCopyWithNoMove(©Counter)) : RunnableFactory::Create("TestNewRunnableFunction", TestCopyWithNoMove(©Counter)); } trackedRunnable->Run(); } Expect( "RunnableFactory with copyable-only (and no move) function " "rvalue, copies", copyCounter, 1); } if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithDeletedMove tracker(©Counter); trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) : RunnableFactory::Create( "TestNewRunnableFunction", tracker); } trackedRunnable->Run(); } Expect( "RunnableFactory with copyable-only (and deleted move) " "function, copies", copyCounter, 1); } // Test RunnableFactory with movable-only function object. { int moveCounter = 0; { nsCOMPtr trackedRunnable; { TestMove tracker(&moveCounter); trackedRunnable = aNamed ? RunnableFactory::Create("unused", std::move(tracker)) : RunnableFactory::Create("TestNewRunnableFunction", std::move(tracker)); } trackedRunnable->Run(); } Expect("RunnableFactory with movable-only function, moves", moveCounter, 1); } { int moveCounter = 0; { nsCOMPtr trackedRunnable; { trackedRunnable = aNamed ? RunnableFactory::Create("unused", TestMove(&moveCounter)) : RunnableFactory::Create("TestNewRunnableFunction", TestMove(&moveCounter)); } trackedRunnable->Run(); } Expect("RunnableFactory with movable-only function rvalue, moves", moveCounter, 1); } // Test RunnableFactory with copyable&movable function object. { int copyCounter = 0; int moveCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyMove tracker(©Counter, &moveCounter); trackedRunnable = aNamed ? RunnableFactory::Create("unused", std::move(tracker)) : RunnableFactory::Create("TestNewRunnableFunction", std::move(tracker)); } trackedRunnable->Run(); } Expect("RunnableFactory with copyable&movable function, copies", copyCounter, 0); Expect("RunnableFactory with copyable&movable function, moves", moveCounter, 1); } { int copyCounter = 0; int moveCounter = 0; { nsCOMPtr trackedRunnable; { trackedRunnable = aNamed ? RunnableFactory::Create( "unused", TestCopyMove(©Counter, &moveCounter)) : RunnableFactory::Create( "TestNewRunnableFunction", TestCopyMove(©Counter, &moveCounter)); } trackedRunnable->Run(); } Expect("RunnableFactory with copyable&movable function rvalue, copies", copyCounter, 0); Expect("RunnableFactory with copyable&movable function rvalue, moves", moveCounter, 1); } // Test RunnableFactory with copyable-only lambda capture. { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithNoMove tracker(©Counter); // Expect 2 copies (here -> local lambda -> runnable lambda). trackedRunnable = aNamed ? RunnableFactory::Create("unused", [tracker]() mutable { tracker(); }) : RunnableFactory::Create("TestNewRunnableFunction", [tracker]() mutable { tracker(); }); } trackedRunnable->Run(); } Expect( "RunnableFactory with copyable-only (and no move) capture, " "copies", copyCounter, 2); } { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithDeletedMove tracker(©Counter); // Expect 2 copies (here -> local lambda -> runnable lambda). trackedRunnable = aNamed ? RunnableFactory::Create("unused", [tracker]() mutable { tracker(); }) : RunnableFactory::Create("TestNewRunnableFunction", [tracker]() mutable { tracker(); }); } trackedRunnable->Run(); } Expect( "RunnableFactory 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 RunnableFactory with copyable&movable lambda capture. { int copyCounter = 0; int moveCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyMove tracker(©Counter, &moveCounter); trackedRunnable = aNamed ? RunnableFactory::Create("unused", [tracker]() mutable { tracker(); }) : RunnableFactory::Create("TestNewRunnableFunction", [tracker]() mutable { tracker(); }); // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable // lambda). } trackedRunnable->Run(); } Expect("RunnableFactory with copyable&movable capture, copies", copyCounter, 1); Expect("RunnableFactory with copyable&movable capture, moves", moveCounter, 1); } } TEST(ThreadUtils, NewRunnableFunction) { TestRunnableFactory(/*aNamed*/ false); } TEST(ThreadUtils, NewNamedRunnableFunction) { // The named overload shall behave identical to the non-named counterpart. TestRunnableFactory(/*aNamed*/ true); // Test naming. { const char* expectedName = "NamedRunnable"; RefPtr NamedRunnable = NS_NewRunnableFunction(expectedName, [] {}); ExpectRunnableName(NamedRunnable, expectedName); } } TEST(ThreadUtils, NewCancelableRunnableFunction) { TestRunnableFactory(/*aNamed*/ false); } TEST(ThreadUtils, NewNamedCancelableRunnableFunction) { // The named overload shall behave identical to the non-named counterpart. TestRunnableFactory(/*aNamed*/ true); // Test naming. { const char* expectedName = "NamedRunnable"; RefPtr NamedRunnable = NS_NewCancelableRunnableFunction(expectedName, [] {}); ExpectRunnableName(NamedRunnable, expectedName); } // Test release on cancelation. { auto foo = MakeRefPtr(); bool ran = false; RefPtr func = NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); EXPECT_EQ(foo->refCount(), 2u); func->Cancel(); EXPECT_EQ(foo->refCount(), 1u); EXPECT_FALSE(ran); } // Test no-op after cancelation. { auto foo = MakeRefPtr(); bool ran = false; RefPtr func = NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); EXPECT_EQ(foo->refCount(), 2u); func->Cancel(); func->Run(); EXPECT_FALSE(ran); } } static void TestNewRunnableMethod(bool aNamed) { memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool)); // Scope the smart ptrs so that the runnables need to hold on to whatever they // need { RefPtr foo = new nsFoo(); RefPtr bar = new nsBar(); RefPtr constBar = bar; // This pointer will be freed at the end of the block // Do not dereference this pointer in the runnable method! RefPtr rawFoo = new nsFoo(); // Read only string. Dereferencing in runnable method to check this works. char* message = (char*)"Test message"; { auto bar = MakeRefPtr(); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", std::move(bar), &nsBar::DoBar1) : NewRunnableMethod("nsBar::DoBar1", std::move(bar), &nsBar::DoBar1)); } NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1) : NewRunnableMethod("nsBar::DoBar1", bar, &nsBar::DoBar1)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", constBar, &nsBar::DoBar1Const) : NewRunnableMethod("nsBar::DoBar1Const", constBar, &nsBar::DoBar1Const)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2) : NewRunnableMethod("nsBar::DoBar2", bar, &nsBar::DoBar2)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar3, foo) : NewRunnableMethod>("nsBar::DoBar3", bar, &nsBar::DoBar3, foo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar4, foo) : NewRunnableMethod>("nsBar::DoBar4", bar, &nsBar::DoBar4, foo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar5, rawFoo) : NewRunnableMethod("nsBar::DoBar5", bar, &nsBar::DoBar5, rawFoo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar6, message) : NewRunnableMethod("nsBar::DoBar6", bar, &nsBar::DoBar6, message)); #ifdef HAVE_STDCALL NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1std) : NewRunnableMethod(bar, &nsBar::DoBar1std)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2std) : NewRunnableMethod(bar, &nsBar::DoBar2std)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar3std, foo) : NewRunnableMethod>(bar, &nsBar::DoBar3std, foo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar4std, foo) : NewRunnableMethod>(bar, &nsBar::DoBar4std, foo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar5std, rawFoo) : NewRunnableMethod(bar, &nsBar::DoBar5std, rawFoo)); NS_DispatchToMainThread( aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar6std, message) : NewRunnableMethod(bar, &nsBar::DoBar6std, message)); #endif } // Spin the event loop NS_ProcessPendingEvents(nullptr); // Now test a suicidal event in NS_New(Named)Thread nsCOMPtr 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; } } TEST(ThreadUtils, RunnableMethod) { TestNewRunnableMethod(/* aNamed */ false); } TEST(ThreadUtils, NamedRunnableMethod) { // The named overloads shall behave identical to the non-named counterparts. TestNewRunnableMethod(/* aNamed */ true); // Test naming. { RefPtr foo = new nsFoo(); const char* expectedName = "NamedRunnable"; bool unused; RefPtr NamedRunnable = NewRunnableMethod(expectedName, foo, &nsFoo::DoFoo, &unused); ExpectRunnableName(NamedRunnable, expectedName); } } class IdleObjectWithoutSetDeadline final { public: NS_INLINE_DECL_REFCOUNTING(IdleObjectWithoutSetDeadline) IdleObjectWithoutSetDeadline() : mRunnableExecuted(false) {} void Method() { mRunnableExecuted = true; } bool mRunnableExecuted; private: ~IdleObjectWithoutSetDeadline() = default; }; class IdleObjectParentWithSetDeadline { public: IdleObjectParentWithSetDeadline() : mSetDeadlineCalled(false) {} void SetDeadline(TimeStamp aDeadline) { mSetDeadlineCalled = true; } bool mSetDeadlineCalled; }; class IdleObjectInheritedSetDeadline final : public IdleObjectParentWithSetDeadline { public: NS_INLINE_DECL_REFCOUNTING(IdleObjectInheritedSetDeadline) IdleObjectInheritedSetDeadline() : mRunnableExecuted(false) {} void Method() { mRunnableExecuted = true; } bool mRunnableExecuted; private: ~IdleObjectInheritedSetDeadline() = default; }; class IdleObject final { public: NS_INLINE_DECL_REFCOUNTING(IdleObject) IdleObject() { for (uint32_t index = 0; index < ArrayLength(mRunnableExecuted); ++index) { mRunnableExecuted[index] = false; mSetIdleDeadlineCalled = false; } } void SetDeadline(TimeStamp aTimeStamp) { mSetIdleDeadlineCalled = true; } void CheckExecutedMethods(const char* aKey, uint32_t aNumExecuted) { uint32_t index; for (index = 0; index < aNumExecuted; ++index) { ASSERT_TRUE(mRunnableExecuted[index]) << aKey << ": Method" << index << " should've executed"; } for (; index < ArrayLength(mRunnableExecuted); ++index) { ASSERT_FALSE(mRunnableExecuted[index]) << aKey << ": Method" << index << " shouldn't have executed"; } } void Method0() { CheckExecutedMethods("Method0", 0); mRunnableExecuted[0] = true; mSetIdleDeadlineCalled = false; } void Method1() { CheckExecutedMethods("Method1", 1); ASSERT_TRUE(mSetIdleDeadlineCalled); mRunnableExecuted[1] = true; mSetIdleDeadlineCalled = false; } void Method2() { CheckExecutedMethods("Method2", 2); ASSERT_TRUE(mSetIdleDeadlineCalled); mRunnableExecuted[2] = true; mSetIdleDeadlineCalled = false; NS_DispatchToCurrentThread( NewRunnableMethod("IdleObject::Method3", this, &IdleObject::Method3)); } void Method3() { CheckExecutedMethods("Method3", 3); NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), Method4, this, 10, nsITimer::TYPE_ONE_SHOT, "IdleObject::Method3"); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethodWithTimer("IdleObject::Method5", this, &IdleObject::Method5), 50, EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewRunnableMethod("IdleObject::Method6", this, &IdleObject::Method6), 100, EventQueuePriority::Idle); PR_Sleep(PR_MillisecondsToInterval(200)); mRunnableExecuted[3] = true; mSetIdleDeadlineCalled = false; } static void Method4(nsITimer* aTimer, void* aClosure) { RefPtr self = static_cast(aClosure); self->CheckExecutedMethods("Method4", 4); self->mRunnableExecuted[4] = true; self->mSetIdleDeadlineCalled = false; } void Method5() { CheckExecutedMethods("Method5", 5); ASSERT_TRUE(mSetIdleDeadlineCalled); mRunnableExecuted[5] = true; mSetIdleDeadlineCalled = false; } void Method6() { CheckExecutedMethods("Method6", 6); mRunnableExecuted[6] = true; mSetIdleDeadlineCalled = false; } void Method7() { CheckExecutedMethods("Method7", 7); ASSERT_TRUE(mSetIdleDeadlineCalled); mRunnableExecuted[7] = true; mSetIdleDeadlineCalled = false; } private: nsCOMPtr mTimer; bool mRunnableExecuted[8]; bool mSetIdleDeadlineCalled; ~IdleObject() = default; }; // Disable test due to frequent failures #if 0 // because test fails on multiple platforms TEST(ThreadUtils, IdleRunnableMethod) { { RefPtr idle = new IdleObject(); RefPtr idleNoSetDeadline = new IdleObjectWithoutSetDeadline(); RefPtr idleInheritedSetDeadline = new IdleObjectInheritedSetDeadline(); NS_DispatchToCurrentThread( NewRunnableMethod("IdleObject::Method0", idle, &IdleObject::Method0)); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethod("IdleObject::Method1", idle, &IdleObject::Method1), EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethodWithTimer("IdleObject::Method2", idle, &IdleObject::Method2), 60000, EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethod("IdleObject::Method7", idle, &IdleObject::Method7), EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethod( "IdleObject::CheckExecutedMethods", idle, &IdleObject::CheckExecutedMethods, "final", 8), EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethod("IdleObjectWithoutSetDeadline::Method", idleNoSetDeadline, &IdleObjectWithoutSetDeadline::Method), EventQueuePriority::Idle); NS_DispatchToCurrentThreadQueue( NewIdleRunnableMethod("IdleObjectInheritedSetDeadline::Method", idleInheritedSetDeadline, &IdleObjectInheritedSetDeadline::Method), EventQueuePriority::Idle); NS_ProcessPendingEvents(nullptr); ASSERT_TRUE(idleNoSetDeadline->mRunnableExecuted); ASSERT_TRUE(idleInheritedSetDeadline->mRunnableExecuted); ASSERT_TRUE(idleInheritedSetDeadline->mSetDeadlineCalled); } } #endif TEST(ThreadUtils, IdleTaskRunner) { using namespace mozilla; // Repeating. int cnt1 = 0; RefPtr runner1 = IdleTaskRunner::Create( [&cnt1](TimeStamp) { cnt1++; return true; }, "runner1", 0, TimeDuration::FromMilliseconds(10), TimeDuration::FromMilliseconds(3), true, nullptr); // Non-repeating but callback always return false so it's still repeating. int cnt2 = 0; RefPtr runner2 = IdleTaskRunner::Create( [&cnt2](TimeStamp) { cnt2++; return false; }, "runner2", 0, TimeDuration::FromMilliseconds(10), TimeDuration::FromMilliseconds(3), false, nullptr); // Repeating until cnt3 >= 2 by returning 'true' in MayStopProcessing // callback. The strategy is to stop repeating as early as possible so that we // are more probable to catch the bug if it didn't stop as expected. int cnt3 = 0; RefPtr runner3 = IdleTaskRunner::Create( [&cnt3](TimeStamp) { cnt3++; return true; }, "runner3", 0, TimeDuration::FromMilliseconds(10), TimeDuration::FromMilliseconds(3), true, [&cnt3] { return cnt3 >= 2; }); // Non-repeating can callback return true so the callback will // be only run once. int cnt4 = 0; RefPtr runner4 = IdleTaskRunner::Create( [&cnt4](TimeStamp) { cnt4++; return true; }, "runner4", 0, TimeDuration::FromMilliseconds(10), TimeDuration::FromMilliseconds(3), false, nullptr); // Firstly we wait until the two repeating tasks reach their limits. MOZ_ALWAYS_TRUE( SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt1"_ns, [&]() { return cnt1 >= 100; })); MOZ_ALWAYS_TRUE( SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt2"_ns, [&]() { return cnt2 >= 100; })); // At any point ==> 0 <= cnt3 <= 2 since MayStopProcessing() would return // true when cnt3 >= 2. MOZ_ALWAYS_TRUE(SpinEventLoopUntil( "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt3"_ns, [&]() { if (cnt3 > 2) { EXPECT_TRUE(false) << "MaybeContinueProcess() doesn't work."; return true; // Stop on failure. } return cnt3 == 2; // Stop finish if we have reached its max value. })); // At any point ==> 0 <= cnt4 <= 1 since this is a non-repeating // idle runner. MOZ_ALWAYS_TRUE(SpinEventLoopUntil( "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt4"_ns, [&]() { // At any point: 0 <= cnt4 <= 1 if (cnt4 > 1) { EXPECT_TRUE(false) << "The 'mRepeating' flag doesn't work."; return true; // Stop on failure. } return cnt4 == 1; })); // The repeating timers require an explicit Cancel() call. runner1->Cancel(); runner2->Cancel(); } // {9e70a320-be02-11d1-8031-006008159b5a} #define NS_IFOO_IID \ { \ 0x9e70a320, 0xbe02, 0x11d1, { \ 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ } \ } TEST(ThreadUtils, TypeTraits) { static_assert(std::is_same_v>, "RemoveSmartPointer should be int"); static_assert(std::is_same_v>, "RemoveSmartPointer should be int*"); static_assert(std::is_same_v, mozilla::RemoveSmartPointer>>, "RemoveSmartPointer> should be UniquePtr"); static_assert(std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert(std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveSmartPointer> should be int"); static_assert(std::is_same_v>, "RemoveRawOrSmartPointer should be int"); static_assert( std::is_same_v, mozilla::RemoveRawOrSmartPointer>>, "RemoveRawOrSmartPointer> should be UniquePtr"); static_assert(std::is_same_v>, "RemoveRawOrSmartPointer should be int"); static_assert( std::is_same_v>, "RemoveRawOrSmartPointer should be const int"); static_assert( std::is_same_v>, "RemoveRawOrSmartPointer should be volatile int"); static_assert( std::is_same_v>, "RemoveRawOrSmartPointer should be const " "volatile int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v< int, mozilla::RemoveRawOrSmartPointer>>, "RemoveRawOrSmartPointer> should be " "int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v>>, "RemoveRawOrSmartPointer> should be int"); static_assert( std::is_same_v< int, mozilla::RemoveRawOrSmartPointer>>, "RemoveRawOrSmartPointer> should be " "int"); } namespace TestThreadUtils { static bool gDebug = false; static int gAlive, gZombies; static int gAllConstructions, gConstructions, gCopyConstructions, gMoveConstructions, gDestructions, gAssignments, gMoves; struct Spy { static void ClearActions() { gAllConstructions = gConstructions = gCopyConstructions = gMoveConstructions = gDestructions = gAssignments = gMoves = 0; } static void ClearAll() { ClearActions(); gAlive = 0; } explicit Spy(int aID) : mID(aID) { ++gAlive; ++gAllConstructions; ++gConstructions; if (gDebug) { printf("Spy[%d@%p]()\n", mID, this); } } Spy(const Spy& o) : mID(o.mID) { ++gAlive; ++gAllConstructions; ++gCopyConstructions; if (gDebug) { printf("Spy[%d@%p](&[%d@%p])\n", mID, this, o.mID, &o); } } Spy(Spy&& o) : mID(o.mID) { o.mID = -o.mID; ++gZombies; ++gAllConstructions; ++gMoveConstructions; if (gDebug) { printf("Spy[%d@%p](&&[%d->%d@%p])\n", mID, this, -o.mID, o.mID, &o); } } ~Spy() { if (mID >= 0) { --gAlive; } else { --gZombies; } ++gDestructions; if (gDebug) { printf("~Spy[%d@%p]()\n", mID, this); } mID = 0; } Spy& operator=(const Spy& o) { ++gAssignments; if (gDebug) { printf("Spy[%d->%d@%p] = &[%d@%p]\n", mID, o.mID, this, o.mID, &o); } mID = o.mID; return *this; }; Spy& operator=(Spy&& o) { --gAlive; ++gZombies; ++gMoves; if (gDebug) { printf("Spy[%d->%d@%p] = &&[%d->%d@%p]\n", mID, o.mID, this, o.mID, -o.mID, &o); } mID = o.mID; o.mID = -o.mID; return *this; }; int mID; // ID given at construction, or negation if was moved from; 0 when // destroyed. }; struct ISpyWithISupports : public nsISupports { NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) NS_IMETHOD_(nsrefcnt) RefCnt() = 0; NS_IMETHOD_(int32_t) ID() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(ISpyWithISupports, NS_IFOO_IID) struct SpyWithISupports : public ISpyWithISupports, public Spy { private: virtual ~SpyWithISupports() = default; public: explicit SpyWithISupports(int aID) : Spy(aID){}; NS_DECL_ISUPPORTS NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } NS_IMETHOD_(int32_t) ID() override { return mID; } }; NS_IMPL_ISUPPORTS(SpyWithISupports, ISpyWithISupports) class IThreadUtilsObject : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) NS_IMETHOD_(nsrefcnt) RefCnt() = 0; NS_IMETHOD_(int32_t) ID() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(IThreadUtilsObject, NS_IFOO_IID) struct ThreadUtilsObjectNonRefCountedBase { virtual void MethodFromNonRefCountedBase() {} }; struct ThreadUtilsObject : public IThreadUtilsObject, public ThreadUtilsObjectNonRefCountedBase { // nsISupports implementation NS_DECL_ISUPPORTS // IThreadUtilsObject implementation NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } NS_IMETHOD_(int32_t) ID() override { return 0; } int mCount; // Number of calls + arguments processed. int mA0, mA1, mA2, mA3; Spy mSpy; const Spy* mSpyPtr; ThreadUtilsObject() : mCount(0), mA0(0), mA1(0), mA2(0), mA3(0), mSpy(1), mSpyPtr(nullptr) {} private: virtual ~ThreadUtilsObject() = default; public: void Test0() { mCount += 1; } void Test1i(int a0) { mCount += 2; mA0 = a0; } void Test2i(int a0, int a1) { mCount += 3; mA0 = a0; mA1 = a1; } void Test3i(int a0, int a1, int a2) { mCount += 4; mA0 = a0; mA1 = a1; mA2 = a2; } void Test4i(int a0, int a1, int a2, int a3) { mCount += 5; mA0 = a0; mA1 = a1; mA2 = a2; mA3 = a3; } void Test1pi(int* ap) { mCount += 2; mA0 = ap ? *ap : -1; } void Test1pci(const int* ap) { mCount += 2; mA0 = ap ? *ap : -1; } void Test1ri(int& ar) { mCount += 2; mA0 = ar; } void Test1rri(int&& arr) { mCount += 2; mA0 = arr; } void Test1upi(mozilla::UniquePtr aup) { mCount += 2; mA0 = aup ? *aup : -1; } void Test1rupi(mozilla::UniquePtr& aup) { mCount += 2; mA0 = aup ? *aup : -1; } void Test1rrupi(mozilla::UniquePtr&& aup) { mCount += 2; mA0 = aup ? *aup : -1; } void Test1s(Spy) { mCount += 2; } void Test1ps(Spy*) { mCount += 2; } void Test1rs(Spy&) { mCount += 2; } void Test1rrs(Spy&&) { mCount += 2; } void Test1ups(mozilla::UniquePtr) { mCount += 2; } void Test1rups(mozilla::UniquePtr&) { mCount += 2; } void Test1rrups(mozilla::UniquePtr&&) { mCount += 2; } // Possible parameter passing styles: void TestByValue(Spy s) { if (gDebug) { printf("TestByValue(Spy[%d@%p])\n", s.mID, &s); } mSpy = s; }; void TestByConstLRef(const Spy& s) { if (gDebug) { printf("TestByConstLRef(Spy[%d@%p]&)\n", s.mID, &s); } mSpy = s; }; void TestByRRef(Spy&& s) { if (gDebug) { printf("TestByRRef(Spy[%d@%p]&&)\n", s.mID, &s); } mSpy = std::move(s); }; void TestByLRef(Spy& s) { if (gDebug) { printf("TestByLRef(Spy[%d@%p]&)\n", s.mID, &s); } mSpy = s; mSpyPtr = &s; }; void TestByPointer(Spy* p) { if (p) { if (gDebug) { printf("TestByPointer(&Spy[%d@%p])\n", p->mID, p); } mSpy = *p; } else { if (gDebug) { printf("TestByPointer(nullptr)\n"); } } mSpyPtr = p; }; void TestByPointerToConst(const Spy* p) { if (p) { if (gDebug) { printf("TestByPointerToConst(&Spy[%d@%p])\n", p->mID, p); } mSpy = *p; } else { if (gDebug) { printf("TestByPointerToConst(nullptr)\n"); } } mSpyPtr = p; }; }; NS_IMPL_ISUPPORTS(ThreadUtilsObject, IThreadUtilsObject) class ThreadUtilsRefCountedFinal final { public: ThreadUtilsRefCountedFinal() : m_refCount(0) {} ~ThreadUtilsRefCountedFinal() = default; // 'AddRef' and 'Release' methods with different return types, to verify // that the return type doesn't influence storage selection. long AddRef(void) { return ++m_refCount; } void Release(void) { --m_refCount; } private: long m_refCount; }; class ThreadUtilsRefCountedBase { public: ThreadUtilsRefCountedBase() : m_refCount(0) {} virtual ~ThreadUtilsRefCountedBase() = default; // 'AddRef' and 'Release' methods with different return types, to verify // that the return type doesn't influence storage selection. virtual void AddRef(void) { ++m_refCount; } virtual MozExternalRefCountType Release(void) { return --m_refCount; } private: MozExternalRefCountType m_refCount; }; class ThreadUtilsRefCountedDerived : public ThreadUtilsRefCountedBase {}; class ThreadUtilsNonRefCounted {}; } // namespace TestThreadUtils TEST(ThreadUtils, main) { using namespace TestThreadUtils; static_assert(!IsParameterStorageClass::value, "'int' should not be recognized as Storage Class"); static_assert( IsParameterStorageClass>::value, "StoreCopyPassByConstLRef should be recognized as Storage Class"); static_assert( IsParameterStorageClass>::value, "StoreCopyPassByRRef should be recognized as Storage Class"); static_assert( IsParameterStorageClass>::value, "StoreRefPassByLRef should be recognized as Storage Class"); static_assert( IsParameterStorageClass>::value, "StoreConstRefPassByConstLRef should be recognized as Storage " "Class"); static_assert( IsParameterStorageClass>::value, "StoreRefPtrPassByPtr should be recognized as Storage Class"); static_assert(IsParameterStorageClass>::value, "StorePtrPassByPtr should be recognized as Storage Class"); static_assert( IsParameterStorageClass>::value, "StoreConstPtrPassByConstPtr should be recognized as Storage Class"); RefPtr rpt(new ThreadUtilsObject); int count = 0; // Test legacy functions. nsCOMPtr r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, &ThreadUtilsObject::Test0); r1->Run(); EXPECT_EQ(count += 1, rpt->mCount); r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, &ThreadUtilsObject::Test1i, 11); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(11, rpt->mA0); // Test calling a method from a non-ref-counted base. r1 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObjectNonRefCountedBase::" "MethodFromNonRefCountedBase", rpt, &ThreadUtilsObject::MethodFromNonRefCountedBase); r1->Run(); EXPECT_EQ(count, rpt->mCount); // Test variadic function with simple POD arguments. r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, &ThreadUtilsObject::Test0); r1->Run(); EXPECT_EQ(count += 1, rpt->mCount); static_assert(std::is_same_v<::detail::ParameterStorage::Type, StoreCopyPassByConstLRef>, "detail::ParameterStorage::Type should be " "StoreCopyPassByConstLRef"); r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, &ThreadUtilsObject::Test1i, 12); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(12, rpt->mA0); r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test2i", rpt, &ThreadUtilsObject::Test2i, 21, 22); r1->Run(); EXPECT_EQ(count += 3, rpt->mCount); EXPECT_EQ(21, rpt->mA0); EXPECT_EQ(22, rpt->mA1); r1 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::Test3i", rpt, &ThreadUtilsObject::Test3i, 31, 32, 33); r1->Run(); EXPECT_EQ(count += 4, rpt->mCount); EXPECT_EQ(31, rpt->mA0); EXPECT_EQ(32, rpt->mA1); EXPECT_EQ(33, rpt->mA2); r1 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::Test4i", rpt, &ThreadUtilsObject::Test4i, 41, 42, 43, 44); r1->Run(); EXPECT_EQ(count += 5, rpt->mCount); EXPECT_EQ(41, rpt->mA0); EXPECT_EQ(42, rpt->mA1); EXPECT_EQ(43, rpt->mA2); EXPECT_EQ(44, rpt->mA3); // More interesting types of arguments. // Passing a short to make sure forwarding works with an inexact type match. short int si = 11; r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, &ThreadUtilsObject::Test1i, si); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(si, rpt->mA0); // Raw pointer, possible cv-qualified. static_assert( std::is_same_v<::detail::ParameterStorage::Type, StorePtrPassByPtr>, "detail::ParameterStorage::Type should be StorePtrPassByPtr"); static_assert(std::is_same_v<::detail::ParameterStorage::Type, StorePtrPassByPtr>, "detail::ParameterStorage::Type should be " "StorePtrPassByPtr"); static_assert(std::is_same_v<::detail::ParameterStorage::Type, StorePtrPassByPtr>, "detail::ParameterStorage::Type should be " "StorePtrPassByPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type, StorePtrPassByPtr>, "detail::ParameterStorage::Type should be " "StorePtrPassByPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, int*>, "detail::ParameterStorage::Type::stored_type should be int*"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::passed_type, int*>, "detail::ParameterStorage::Type::passed_type should be int*"); { int i = 12; r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1pi", rpt, &ThreadUtilsObject::Test1pi, &i); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(i, rpt->mA0); } // Raw pointer to const. static_assert(std::is_same_v<::detail::ParameterStorage::Type, StoreConstPtrPassByConstPtr>, "detail::ParameterStorage::Type should be " "StoreConstPtrPassByConstPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type, StoreConstPtrPassByConstPtr>, "detail::ParameterStorage::Type should be " "StoreConstPtrPassByConstPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type, StoreConstPtrPassByConstPtr>, "detail::ParameterStorage::Type should be " "StoreConstPtrPassByConstPtr"); static_assert(std::is_same_v< ::detail::ParameterStorage::Type, StoreConstPtrPassByConstPtr>, "detail::ParameterStorage::Type " "should be StoreConstPtrPassByConstPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, const int*>, "detail::ParameterStorage::Type::stored_type should be const " "int*"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::passed_type, const int*>, "detail::ParameterStorage::Type::passed_type should be const " "int*"); { int i = 1201; r1 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt, &ThreadUtilsObject::Test1pci, &i); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(i, rpt->mA0); } // nsRefPtr to pointer. static_assert( std::is_same_v<::detail::ParameterStorage< StoreRefPtrPassByPtr>::Type, StoreRefPtrPassByPtr>, "ParameterStorage>::Type should " "be StoreRefPtrPassByPtr"); static_assert( std::is_same_v<::detail::ParameterStorage::Type, StoreRefPtrPassByPtr>, "ParameterStorage::Type should be " "StoreRefPtrPassByPtr"); static_assert( std::is_same_v::stored_type, RefPtr>, "StoreRefPtrPassByPtr::stored_type should be " "RefPtr"); static_assert( std::is_same_v::passed_type, SpyWithISupports*>, "StoreRefPtrPassByPtr::passed_type should be " "SpyWithISupports*"); // (more nsRefPtr tests below) // nsRefPtr for ref-countable classes that do not derive from ISupports. static_assert(::detail::HasRefCountMethods, "ThreadUtilsRefCountedFinal has AddRef() and Release()"); static_assert( std::is_same_v< ::detail::ParameterStorage::Type, StoreRefPtrPassByPtr>, "ParameterStorage::Type should be " "StoreRefPtrPassByPtr"); static_assert(::detail::HasRefCountMethods, "ThreadUtilsRefCountedBase has AddRef() and Release()"); static_assert( std::is_same_v< ::detail::ParameterStorage::Type, StoreRefPtrPassByPtr>, "ParameterStorage::Type should be " "StoreRefPtrPassByPtr"); static_assert(::detail::HasRefCountMethods, "ThreadUtilsRefCountedDerived has AddRef() and Release()"); static_assert( std::is_same_v< ::detail::ParameterStorage::Type, StoreRefPtrPassByPtr>, "ParameterStorage::Type should be " "StoreRefPtrPassByPtr"); static_assert(!::detail::HasRefCountMethods, "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()"); static_assert(!std::is_same_v< ::detail::ParameterStorage::Type, StoreRefPtrPassByPtr>, "ParameterStorage::Type should NOT " "be StoreRefPtrPassByPtr"); // Lvalue reference. static_assert( std::is_same_v<::detail::ParameterStorage::Type, StoreRefPassByLRef>, "ParameterStorage::Type should be StoreRefPassByLRef"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, StoreRefPassByLRef::stored_type>, "ParameterStorage::Type::stored_type should be " "StoreRefPassByLRef::stored_type"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, int&>, "ParameterStorage::Type::stored_type should be int&"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::passed_type, int&>, "ParameterStorage::Type::passed_type should be int&"); { int i = 13; r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1ri", rpt, &ThreadUtilsObject::Test1ri, i); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(i, rpt->mA0); } // Rvalue reference -- Actually storing a copy and then moving it. static_assert( std::is_same_v<::detail::ParameterStorage::Type, StoreCopyPassByRRef>, "ParameterStorage::Type should be StoreCopyPassByRRef"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, StoreCopyPassByRRef::stored_type>, "ParameterStorage::Type::stored_type should be " "StoreCopyPassByRRef::stored_type"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::stored_type, int>, "ParameterStorage::Type::stored_type should be int"); static_assert( std::is_same_v<::detail::ParameterStorage::Type::passed_type, int&&>, "ParameterStorage::Type::passed_type should be int&&"); { int i = 14; r1 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::Test1rri", rpt, &ThreadUtilsObject::Test1rri, std::move(i)); } r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(14, rpt->mA0); // Null unique pointer, by semi-implicit store&move with "T&&" syntax. static_assert(std::is_same_v< ::detail::ParameterStorage&&>::Type, StoreCopyPassByRRef>>, "ParameterStorage&&>::Type should be " "StoreCopyPassByRRef>"); static_assert( std::is_same_v<::detail::ParameterStorage< mozilla::UniquePtr&&>::Type::stored_type, StoreCopyPassByRRef>::stored_type>, "ParameterStorage&&>::Type::stored_type should be " "StoreCopyPassByRRef>::stored_type"); static_assert( std::is_same_v<::detail::ParameterStorage< mozilla::UniquePtr&&>::Type::stored_type, mozilla::UniquePtr>, "ParameterStorage&&>::Type::stored_type should be " "UniquePtr"); static_assert( std::is_same_v<::detail::ParameterStorage< mozilla::UniquePtr&&>::Type::passed_type, mozilla::UniquePtr&&>, "ParameterStorage&&>::Type::passed_type should be " "UniquePtr&&"); { mozilla::UniquePtr upi; r1 = NewRunnableMethod&&>( "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, &ThreadUtilsObject::Test1upi, std::move(upi)); } r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(-1, rpt->mA0); rpt->mA0 = 0; // Null unique pointer, by explicit store&move with "StoreCopyPassByRRef" // syntax. static_assert( std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, StoreCopyPassByRRef>::stored_type>, "ParameterStorage>>::Type::stored_" "type should be StoreCopyPassByRRef>::stored_type"); static_assert( std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, StoreCopyPassByRRef>::stored_type>, "ParameterStorage>>::Type::stored_" "type should be StoreCopyPassByRRef>::stored_type"); static_assert( std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, mozilla::UniquePtr>, "ParameterStorage>>::Type::stored_" "type should be UniquePtr"); static_assert( std::is_same_v<::detail::ParameterStorage>>::Type::passed_type, mozilla::UniquePtr&&>, "ParameterStorage>>::Type::passed_" "type should be UniquePtr&&"); { mozilla::UniquePtr upi; r1 = NewRunnableMethod>>( "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, &ThreadUtilsObject::Test1upi, std::move(upi)); } r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(-1, rpt->mA0); // Unique pointer as xvalue. { mozilla::UniquePtr upi = mozilla::MakeUnique(1); r1 = NewRunnableMethod&&>( "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, &ThreadUtilsObject::Test1upi, std::move(upi)); } r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(1, rpt->mA0); { mozilla::UniquePtr upi = mozilla::MakeUnique(1); r1 = NewRunnableMethod>>( "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, &ThreadUtilsObject::Test1upi, std::move(upi)); } r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(1, rpt->mA0); // Unique pointer as prvalue. r1 = NewRunnableMethod&&>( "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, &ThreadUtilsObject::Test1upi, mozilla::MakeUnique(2)); r1->Run(); EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(2, rpt->mA0); // Unique pointer as lvalue to lref. { mozilla::UniquePtr upi; r1 = NewRunnableMethod&>( "TestThreadUtils::ThreadUtilsObject::Test1rupi", rpt, &ThreadUtilsObject::Test1rupi, upi); // Passed as lref, so Run() must be called while local upi is still alive! r1->Run(); } EXPECT_EQ(count += 2, rpt->mCount); EXPECT_EQ(-1, rpt->mA0); // Verify copy/move assumptions. Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n", __LINE__); } { // Block around nsCOMPtr lifetime. nsCOMPtr r5; { // Block around Spy lifetime. if (gDebug) { printf("%d - Spy s(20)\n", __LINE__); } Spy s(20); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); if (gDebug) { printf( "%d - r5 = " "NewRunnableMethod>(&TestByConstLRef," " s)\n", __LINE__); } r5 = NewRunnableMethod>( "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, &ThreadUtilsObject::TestByConstLRef, s); EXPECT_EQ(2, gAlive); EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. Spy::ClearActions(); if (gDebug) { printf("%d - End block with Spy s(20)\n", __LINE__); } } EXPECT_EQ(1, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r5->Run(); EXPECT_EQ(0, gCopyConstructions); // No copies in call. EXPECT_EQ(20, rpt->mSpy.mID); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store copy from prvalue, pass by const lvalue ref\n", __LINE__); } { if (gDebug) { printf( "%d - r6 = " "NewRunnableMethod>(&TestByConstLRef, " "Spy(21))\n", __LINE__); } nsCOMPtr r6 = NewRunnableMethod>( "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, &ThreadUtilsObject::TestByConstLRef, Spy(21)); EXPECT_EQ(1, gAlive); EXPECT_EQ(1, gConstructions); EXPECT_LE(1, gMoveConstructions); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r6->Run(); EXPECT_EQ(0, gCopyConstructions); // No copies in call. EXPECT_EQ(21, rpt->mSpy.mID); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store copy from lvalue, pass by rvalue ref\n", __LINE__); } { // Block around nsCOMPtr lifetime. nsCOMPtr r7; { // Block around Spy lifetime. if (gDebug) { printf("%d - Spy s(30)\n", __LINE__); } Spy s(30); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); if (gDebug) { printf( "%d - r7 = " "NewRunnableMethod>(&TestByRRef, s)\n", __LINE__); } r7 = NewRunnableMethod>( "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, &ThreadUtilsObject::TestByRRef, s); EXPECT_EQ(2, gAlive); EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. Spy::ClearActions(); if (gDebug) { printf("%d - End block with Spy s(30)\n", __LINE__); } } EXPECT_EQ(1, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r7->Run(); EXPECT_LE(1, gMoves); // Move in call. EXPECT_EQ(30, rpt->mSpy.mID); EXPECT_EQ(0, gDestructions); EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store copy from prvalue, pass by rvalue ref\n", __LINE__); } { if (gDebug) { printf( "%d - r8 = NewRunnableMethod>(&TestByRRef, " "Spy(31))\n", __LINE__); } nsCOMPtr r8 = NewRunnableMethod>( "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, &ThreadUtilsObject::TestByRRef, Spy(31)); EXPECT_EQ(1, gAlive); EXPECT_EQ(1, gConstructions); EXPECT_LE(1, gMoveConstructions); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r8->Run(); EXPECT_LE(1, gMoves); // Move in call. EXPECT_EQ(31, rpt->mSpy.mID); EXPECT_EQ(0, gDestructions); EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store lvalue ref, pass lvalue ref\n", __LINE__); } { if (gDebug) { printf("%d - Spy s(40)\n", __LINE__); } Spy s(40); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - r9 = NewRunnableMethod(&TestByLRef, s)\n", __LINE__); } nsCOMPtr r9 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::TestByLRef", rpt, &ThreadUtilsObject::TestByLRef, s); EXPECT_EQ(0, gAllConstructions); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r9->Run(); EXPECT_LE(1, gAssignments); // Assignment from reference in call. EXPECT_EQ(40, rpt->mSpy.mID); EXPECT_EQ(&s, rpt->mSpyPtr); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store nsRefPtr, pass by pointer\n", __LINE__); } { // Block around nsCOMPtr lifetime. nsCOMPtr r10; SpyWithISupports* ptr = 0; { // Block around RefPtr lifetime. if (gDebug) { printf("%d - RefPtr s(new SpyWithISupports(45))\n", __LINE__); } RefPtr s(new SpyWithISupports(45)); ptr = s.get(); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); if (gDebug) { printf( "%d - r10 = " "NewRunnableMethod>(&TestByRRef, " "s.get())\n", __LINE__); } r10 = NewRunnableMethod>( "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, &ThreadUtilsObject::TestByPointer, s.get()); EXPECT_LE(0, gAllConstructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - End block with RefPtr s\n", __LINE__); } } EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r10->Run(); EXPECT_LE(1, gAssignments); // Assignment from pointee in call. EXPECT_EQ(45, rpt->mSpy.mID); EXPECT_EQ(ptr, rpt->mSpyPtr); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store pointer to lvalue, pass by pointer\n", __LINE__); } { if (gDebug) { printf("%d - Spy s(55)\n", __LINE__); } Spy s(55); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - r11 = NewRunnableMethod(&TestByPointer, s)\n", __LINE__); } nsCOMPtr r11 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, &ThreadUtilsObject::TestByPointer, &s); EXPECT_EQ(0, gAllConstructions); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r11->Run(); EXPECT_LE(1, gAssignments); // Assignment from pointee in call. EXPECT_EQ(55, rpt->mSpy.mID); EXPECT_EQ(&s, rpt->mSpyPtr); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); Spy::ClearAll(); if (gDebug) { printf("%d - Test: Store pointer to const lvalue, pass by pointer\n", __LINE__); } { if (gDebug) { printf("%d - Spy s(60)\n", __LINE__); } Spy s(60); EXPECT_EQ(1, gConstructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - r12 = NewRunnableMethod(&TestByPointer, s)\n", __LINE__); } nsCOMPtr r12 = NewRunnableMethod( "TestThreadUtils::ThreadUtilsObject::TestByPointerToConst", rpt, &ThreadUtilsObject::TestByPointerToConst, &s); EXPECT_EQ(0, gAllConstructions); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); Spy::ClearActions(); if (gDebug) { printf("%d - Run()\n", __LINE__); } r12->Run(); EXPECT_LE(1, gAssignments); // Assignment from pointee in call. EXPECT_EQ(60, rpt->mSpy.mID); EXPECT_EQ(&s, rpt->mSpyPtr); EXPECT_EQ(0, gDestructions); EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. Spy::ClearActions(); if (gDebug) { printf("%d - End block with r\n", __LINE__); } } if (gDebug) { printf("%d - After end block with r\n", __LINE__); } EXPECT_EQ(1, gDestructions); EXPECT_EQ(0, gAlive); }