summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest/TestThreadUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/tests/gtest/TestThreadUtils.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xpcom/tests/gtest/TestThreadUtils.cpp2226
1 files changed, 2226 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp
new file mode 100644
index 0000000000..2b9ff97192
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadUtils.cpp
@@ -0,0 +1,2226 @@
+/* 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 <type_traits>
+
+#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<nsIThread> 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<TestRefCounted> {
+ 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 <typename Function>
+ static auto Create(const char* aName, Function&& aFunc) {
+ return NS_NewRunnableFunction(aName, std::forward<Function>(aFunc));
+ }
+};
+
+struct CancelableRunnableFactory {
+ static constexpr bool SupportsCopyWithDeletedMove = false;
+
+ template <typename Function>
+ static auto Create(const char* aName, Function&& aFunc) {
+ return NS_NewCancelableRunnableFunction(aName,
+ std::forward<Function>(aFunc));
+ }
+};
+
+template <typename RunnableFactory>
+static void TestRunnableFactory(bool aNamed) {
+ // Test RunnableFactory with copyable-only function object.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ 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<nsIRunnable> trackedRunnable;
+ {
+ // Passing as rvalue, but using copy.
+ // (TestCopyWithDeletedMove wouldn't allow this.)
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create("unused",
+ TestCopyWithNoMove(&copyCounter))
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ TestCopyWithNoMove(&copyCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory with copyable-only (and no move) function "
+ "rvalue, copies",
+ copyCounter, 1);
+ }
+ if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ 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<nsIRunnable> 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<nsIRunnable> 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<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &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<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create(
+ "unused", TestCopyMove(&copyCounter, &moveCounter))
+ : RunnableFactory::Create(
+ "TestNewRunnableFunction",
+ TestCopyMove(&copyCounter, &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<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ // 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<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ // 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<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &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<BasicRunnableFactory>(/*aNamed*/ false); }
+
+TEST(ThreadUtils, NewNamedRunnableFunction)
+{
+ // The named overload shall behave identical to the non-named counterpart.
+ TestRunnableFactory<BasicRunnableFactory>(/*aNamed*/ true);
+
+ // Test naming.
+ {
+ const char* expectedName = "NamedRunnable";
+ RefPtr<Runnable> NamedRunnable =
+ NS_NewRunnableFunction(expectedName, [] {});
+ ExpectRunnableName(NamedRunnable, expectedName);
+ }
+}
+
+TEST(ThreadUtils, NewCancelableRunnableFunction)
+{ TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ false); }
+
+TEST(ThreadUtils, NewNamedCancelableRunnableFunction)
+{
+ // The named overload shall behave identical to the non-named counterpart.
+ TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ true);
+
+ // Test naming.
+ {
+ const char* expectedName = "NamedRunnable";
+ RefPtr<Runnable> NamedRunnable =
+ NS_NewCancelableRunnableFunction(expectedName, [] {});
+ ExpectRunnableName(NamedRunnable, expectedName);
+ }
+
+ // Test release on cancelation.
+ {
+ auto foo = MakeRefPtr<TestRefCounted>();
+ bool ran = false;
+
+ RefPtr<CancelableRunnable> 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<TestRefCounted>();
+ bool ran = false;
+
+ RefPtr<CancelableRunnable> 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<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";
+
+ {
+ auto bar = MakeRefPtr<nsBar>();
+
+ 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<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar3,
+ foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar3", bar,
+ &nsBar::DoBar3, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar4,
+ foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar4", bar,
+ &nsBar::DoBar4, foo));
+ NS_DispatchToMainThread(
+ aNamed
+ ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5, rawFoo)
+ : NewRunnableMethod<nsFoo*>("nsBar::DoBar5", bar, &nsBar::DoBar5,
+ rawFoo));
+ NS_DispatchToMainThread(
+ aNamed
+ ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6, message)
+ : NewRunnableMethod<char*>("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<RefPtr<nsFoo>>("unused", bar,
+ &nsBar::DoBar3std, foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar3std, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar,
+ &nsBar::DoBar4std, foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar4std, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5std,
+ rawFoo)
+ : NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6std,
+ message)
+ : 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;
+ }
+}
+
+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<nsFoo> foo = new nsFoo();
+ const char* expectedName = "NamedRunnable";
+ bool unused;
+ RefPtr<Runnable> NamedRunnable =
+ NewRunnableMethod<bool*>(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<IdleObject> self = static_cast<IdleObject*>(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<nsITimer> 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<IdleObject> idle = new IdleObject();
+ RefPtr<IdleObjectWithoutSetDeadline> idleNoSetDeadline =
+ new IdleObjectWithoutSetDeadline();
+ RefPtr<IdleObjectInheritedSetDeadline> 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<const char*, uint32_t>(
+ "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<IdleTaskRunner> 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<IdleTaskRunner> 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<IdleTaskRunner> 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<IdleTaskRunner> 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(!mozilla::IsRefcountedSmartPointer<int>::value,
+ "IsRefcountedSmartPointer<int> should be false");
+ static_assert(mozilla::IsRefcountedSmartPointer<RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<RefPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<const RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<const RefPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<volatile RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<volatile RefPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<const volatile RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<const volatile RefPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<nsCOMPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<const nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<const nsCOMPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<volatile nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<volatile nsCOMPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<const volatile nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<const volatile nsCOMPtr<...>> should be true");
+
+ static_assert(std::is_same_v<int, mozilla::RemoveSmartPointer<int>::Type>,
+ "RemoveSmartPointer<int>::Type should be int");
+ static_assert(std::is_same_v<int*, mozilla::RemoveSmartPointer<int*>::Type>,
+ "RemoveSmartPointer<int*>::Type should be int*");
+ static_assert(
+ std::is_same_v<UniquePtr<int>,
+ mozilla::RemoveSmartPointer<UniquePtr<int>>::Type>,
+ "RemoveSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<RefPtr<int>>::Type>,
+ "RemoveSmartPointer<RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<const RefPtr<int>>::Type>,
+ "RemoveSmartPointer<const RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<volatile RefPtr<int>>::Type>,
+ "RemoveSmartPointer<volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveSmartPointer<const volatile RefPtr<int>>::Type>,
+ "RemoveSmartPointer<const volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<const nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<const nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<volatile nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<volatile nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type should be int");
+
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int>::Type>,
+ "RemoveRawOrSmartPointer<int>::Type should be int");
+ static_assert(
+ std::is_same_v<UniquePtr<int>,
+ mozilla::RemoveRawOrSmartPointer<UniquePtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int*>::Type>,
+ "RemoveRawOrSmartPointer<int*>::Type should be int");
+ static_assert(
+ std::is_same_v<const int,
+ mozilla::RemoveRawOrSmartPointer<const int*>::Type>,
+ "RemoveRawOrSmartPointer<const int*>::Type should be const int");
+ static_assert(
+ std::is_same_v<volatile int,
+ mozilla::RemoveRawOrSmartPointer<volatile int*>::Type>,
+ "RemoveRawOrSmartPointer<volatile int*>::Type should be volatile int");
+ static_assert(
+ std::is_same_v<const volatile int, mozilla::RemoveRawOrSmartPointer<
+ const volatile int*>::Type>,
+ "RemoveRawOrSmartPointer<const volatile int*>::Type should be const "
+ "volatile int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveRawOrSmartPointer<const RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<
+ const volatile RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const volatile RefPtr<int>>::Type should be "
+ "int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<
+ const volatile nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const volatile nsCOMPtr<int>>::Type 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<int> aup) {
+ mCount += 2;
+ mA0 = aup ? *aup : -1;
+ }
+ void Test1rupi(mozilla::UniquePtr<int>& aup) {
+ mCount += 2;
+ mA0 = aup ? *aup : -1;
+ }
+ void Test1rrupi(mozilla::UniquePtr<int>&& 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<Spy>) { mCount += 2; }
+ void Test1rups(mozilla::UniquePtr<Spy>&) { mCount += 2; }
+ void Test1rrups(mozilla::UniquePtr<Spy>&&) { 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<int>::value,
+ "'int' should not be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByValue<int>>::value,
+ "StoreCopyPassByValue<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByConstLRef<int>>::value,
+ "StoreCopyPassByConstLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByLRef<int>>::value,
+ "StoreCopyPassByLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByRRef<int>>::value,
+ "StoreCopyPassByRRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreRefPassByLRef<int>>::value,
+ "StoreRefPassByLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreConstRefPassByConstLRef<int>>::value,
+ "StoreConstRefPassByConstLRef<int> should be recognized as Storage "
+ "Class");
+ static_assert(
+ IsParameterStorageClass<StoreRefPtrPassByPtr<int>>::value,
+ "StoreRefPtrPassByPtr<int> should be recognized as Storage Class");
+ static_assert(IsParameterStorageClass<StorePtrPassByPtr<int>>::value,
+ "StorePtrPassByPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreConstPtrPassByConstPtr<int>>::value,
+ "StoreConstPtrPassByConstPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByConstPtr<int>>::value,
+ "StoreCopyPassByConstPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByPtr<int>>::value,
+ "StoreCopyPassByPtr<int> should be recognized as Storage Class");
+
+ RefPtr<ThreadUtilsObject> rpt(new ThreadUtilsObject);
+ int count = 0;
+
+ // Test legacy functions.
+
+ nsCOMPtr<nsIRunnable> r1 =
+ NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt,
+ &ThreadUtilsObject::Test0);
+ r1->Run();
+ EXPECT_EQ(count += 1, rpt->mCount);
+
+ r1 = NewRunnableMethod<int>("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<int>::Type,
+ StoreCopyPassByConstLRef<int>>,
+ "detail::ParameterStorage<int>::Type should be "
+ "StoreCopyPassByConstLRef<int>");
+ static_assert(std::is_same_v<
+ ::detail::ParameterStorage<StoreCopyPassByValue<int>>::Type,
+ StoreCopyPassByValue<int>>,
+ "detail::ParameterStorage<StoreCopyPassByValue<int>>::Type "
+ "should be StoreCopyPassByValue<int>");
+
+ r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt,
+ &ThreadUtilsObject::Test1i, 12);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(12, rpt->mA0);
+
+ r1 = NewRunnableMethod<int, int>("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<int, int, int>(
+ "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<int, int, int, int>(
+ "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<int>("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<int*>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int*>::Type should be StorePtrPassByPtr<int>");
+ static_assert(std::is_same_v<::detail::ParameterStorage<int* const>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* const>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(std::is_same_v<::detail::ParameterStorage<int* volatile>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* volatile>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int* const volatile>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* const volatile>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int*>::Type::stored_type, int*>,
+ "detail::ParameterStorage<int*>::Type::stored_type should be int*");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int*>::Type::passed_type, int*>,
+ "detail::ParameterStorage<int*>::Type::passed_type should be int*");
+ {
+ int i = 12;
+ r1 = NewRunnableMethod<int*>("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<const int*>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int*>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int* const>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* const>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int* volatile>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* volatile>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(std::is_same_v<
+ ::detail::ParameterStorage<const int* const volatile>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* const volatile>::Type "
+ "should be StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int*>::Type::stored_type,
+ const int*>,
+ "detail::ParameterStorage<const int*>::Type::stored_type should be const "
+ "int*");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int*>::Type::passed_type,
+ const int*>,
+ "detail::ParameterStorage<const int*>::Type::passed_type should be const "
+ "int*");
+ {
+ int i = 1201;
+ r1 = NewRunnableMethod<const int*>(
+ "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt,
+ &ThreadUtilsObject::Test1pci, &i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Raw pointer to copy.
+ static_assert(std::is_same_v<StoreCopyPassByPtr<int>::stored_type, int>,
+ "StoreCopyPassByPtr<int>::stored_type should be int");
+ static_assert(std::is_same_v<StoreCopyPassByPtr<int>::passed_type, int*>,
+ "StoreCopyPassByPtr<int>::passed_type should be int*");
+ {
+ int i = 1202;
+ r1 = NewRunnableMethod<StoreCopyPassByPtr<int>>(
+ "TestThreadUtils::ThreadUtilsObject::Test1pi", rpt,
+ &ThreadUtilsObject::Test1pi, i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Raw pointer to const copy.
+ static_assert(std::is_same_v<StoreCopyPassByConstPtr<int>::stored_type, int>,
+ "StoreCopyPassByConstPtr<int>::stored_type should be int");
+ static_assert(
+ std::is_same_v<StoreCopyPassByConstPtr<int>::passed_type, const int*>,
+ "StoreCopyPassByConstPtr<int>::passed_type should be const int*");
+ {
+ int i = 1203;
+ r1 = NewRunnableMethod<StoreCopyPassByConstPtr<int>>(
+ "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<SpyWithISupports>>::Type,
+ StoreRefPtrPassByPtr<SpyWithISupports>>,
+ "ParameterStorage<StoreRefPtrPassByPtr<SpyWithISupports>>::Type should "
+ "be StoreRefPtrPassByPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<SpyWithISupports*>::Type,
+ StoreRefPtrPassByPtr<SpyWithISupports>>,
+ "ParameterStorage<SpyWithISupports*>::Type should be "
+ "StoreRefPtrPassByPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::stored_type,
+ RefPtr<SpyWithISupports>>,
+ "StoreRefPtrPassByPtr<SpyWithISupports>::stored_type should be "
+ "RefPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::passed_type,
+ SpyWithISupports*>,
+ "StoreRefPtrPassByPtr<SpyWithISupports>::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>::value,
+ "ThreadUtilsRefCountedFinal has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedFinal*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>>,
+ "ParameterStorage<ThreadUtilsRefCountedFinal*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>");
+ static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedBase>::value,
+ "ThreadUtilsRefCountedBase has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedBase*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>>,
+ "ParameterStorage<ThreadUtilsRefCountedBase*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>");
+ static_assert(
+ ::detail::HasRefCountMethods<ThreadUtilsRefCountedDerived>::value,
+ "ThreadUtilsRefCountedDerived has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedDerived*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>>,
+ "ParameterStorage<ThreadUtilsRefCountedDerived*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>");
+
+ static_assert(!::detail::HasRefCountMethods<ThreadUtilsNonRefCounted>::value,
+ "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()");
+ static_assert(!std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsNonRefCounted*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>>,
+ "ParameterStorage<ThreadUtilsNonRefCounted*>::Type should NOT "
+ "be StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>");
+
+ // Lvalue reference.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type,
+ StoreRefPassByLRef<int>>,
+ "ParameterStorage<int&>::Type should be StoreRefPassByLRef<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type,
+ StoreRefPassByLRef<int>::stored_type>,
+ "ParameterStorage<int&>::Type::stored_type should be "
+ "StoreRefPassByLRef<int>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type, int&>,
+ "ParameterStorage<int&>::Type::stored_type should be int&");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::passed_type, int&>,
+ "ParameterStorage<int&>::Type::passed_type should be int&");
+ {
+ int i = 13;
+ r1 = NewRunnableMethod<int&>("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<int&&>::Type,
+ StoreCopyPassByRRef<int>>,
+ "ParameterStorage<int&&>::Type should be StoreCopyPassByRRef<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type,
+ StoreCopyPassByRRef<int>::stored_type>,
+ "ParameterStorage<int&&>::Type::stored_type should be "
+ "StoreCopyPassByRRef<int>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type, int>,
+ "ParameterStorage<int&&>::Type::stored_type should be int");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::passed_type,
+ int&&>,
+ "ParameterStorage<int&&>::Type::passed_type should be int&&");
+ {
+ int i = 14;
+ r1 = NewRunnableMethod<int&&>(
+ "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<mozilla::UniquePtr<int>&&>::Type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>>,
+ "ParameterStorage<UniquePtr<int>&&>::Type should be "
+ "StoreCopyPassByRRef<UniquePtr<int>>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be "
+ "StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::stored_type,
+ mozilla::UniquePtr<int>>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be "
+ "UniquePtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::passed_type,
+ mozilla::UniquePtr<int>&&>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::passed_type should be "
+ "UniquePtr<int>&&");
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>(
+ "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<T>"
+ // syntax.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ mozilla::UniquePtr<int>>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::passed_type,
+ mozilla::UniquePtr<int>&&>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::passed_"
+ "type should be UniquePtr<int>&&");
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>(
+ "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<int> upi = mozilla::MakeUnique<int>(1);
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, std::move(upi));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(1, rpt->mA0);
+
+ {
+ mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1);
+ r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>(
+ "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<mozilla::UniquePtr<int>&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, mozilla::MakeUnique<int>(2));
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(2, rpt->mA0);
+
+ // Unique pointer as lvalue to lref.
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&>(
+ "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 value\n", __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> r2;
+ { // Block around Spy lifetime.
+ if (gDebug) {
+ printf("%d - Spy s(10)\n", __LINE__);
+ }
+ Spy s(10);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r2 = "
+ "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, s)\n",
+ __LINE__);
+ }
+ r2 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, 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(10)\n", __LINE__);
+ }
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r2->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(10, rpt->mSpy.mID);
+ EXPECT_LE(1, 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 value\n", __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf(
+ "%d - r3 = "
+ "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, "
+ "Spy(11))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r3 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, Spy(11));
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_LE(1, gMoveConstructions);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r3->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(11, rpt->mSpy.mID);
+ EXPECT_LE(1, 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();
+ { // Store copy from xvalue, pass by value.
+ nsCOMPtr<nsIRunnable> r4;
+ {
+ Spy s(12);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ r4 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, std::move(s));
+ EXPECT_LE(1, gMoveConstructions);
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gZombies);
+ Spy::ClearActions();
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(0, gZombies);
+ Spy::ClearActions();
+ r4->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(12, rpt->mSpy.mID);
+ EXPECT_LE(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+ // Won't test xvalues anymore, prvalues are enough to verify all rvalues.
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n",
+ __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> 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<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef,"
+ " s)\n",
+ __LINE__);
+ }
+ r5 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(
+ "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<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef, "
+ "Spy(21))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r6 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(
+ "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<nsIRunnable> 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<StoreCopyPassByRRef<Spy>>(&TestByRRef, s)\n",
+ __LINE__);
+ }
+ r7 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(
+ "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<StoreCopyPassByRRef<Spy>>(&TestByRRef, "
+ "Spy(31))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(
+ "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<Spy&>(&TestByLRef, s)\n", __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r9 = NewRunnableMethod<Spy&>(
+ "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<nsIRunnable> r10;
+ SpyWithISupports* ptr = 0;
+ { // Block around RefPtr<Spy> lifetime.
+ if (gDebug) {
+ printf("%d - RefPtr<SpyWithISupports> s(new SpyWithISupports(45))\n",
+ __LINE__);
+ }
+ RefPtr<SpyWithISupports> s(new SpyWithISupports(45));
+ ptr = s.get();
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r10 = "
+ "NewRunnableMethod<StoreRefPtrPassByPtr<Spy>>(&TestByRRef, "
+ "s.get())\n",
+ __LINE__);
+ }
+ r10 = NewRunnableMethod<StoreRefPtrPassByPtr<SpyWithISupports>>(
+ "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<Spy> 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<Spy*>(&TestByPointer, s)\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r11 = NewRunnableMethod<Spy*>(
+ "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<Spy*>(&TestByPointer, s)\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r12 = NewRunnableMethod<const Spy*>(
+ "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);
+}