summaryrefslogtreecommitdiffstats
path: root/ipc/ipdl/test/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /ipc/ipdl/test/gtest
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/ipdl/test/gtest')
-rw-r--r--ipc/ipdl/test/gtest/IPDLUnitTest.cpp308
-rw-r--r--ipc/ipdl/test/gtest/IPDLUnitTest.h79
-rw-r--r--ipc/ipdl/test/gtest/IPDLUnitTestChild.h32
-rw-r--r--ipc/ipdl/test/gtest/IPDLUnitTestParent.h50
-rw-r--r--ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl33
-rw-r--r--ipc/ipdl/test/gtest/PTestBasic.ipdl14
-rw-r--r--ipc/ipdl/test/gtest/PTestManyHandles.ipdl15
-rw-r--r--ipc/ipdl/test/gtest/TestBasic.cpp29
-rw-r--r--ipc/ipdl/test/gtest/TestBasicChild.h26
-rw-r--r--ipc/ipdl/test/gtest/TestBasicParent.h23
-rw-r--r--ipc/ipdl/test/gtest/TestManyHandles.cpp78
-rw-r--r--ipc/ipdl/test/gtest/moz.build31
12 files changed, 718 insertions, 0 deletions
diff --git a/ipc/ipdl/test/gtest/IPDLUnitTest.cpp b/ipc/ipdl/test/gtest/IPDLUnitTest.cpp
new file mode 100644
index 0000000000..66e0149dcb
--- /dev/null
+++ b/ipc/ipdl/test/gtest/IPDLUnitTest.cpp
@@ -0,0 +1,308 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/_ipdltest/IPDLUnitTest.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/ipc/NodeController.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsDebugImpl.h"
+#include "nsThreadManager.h"
+
+#include <string>
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "nsIAppShell.h"
+# include "nsServiceManagerUtils.h"
+# include "nsWidgetsCID.h"
+#endif
+
+namespace mozilla::_ipdltest {
+
+static std::unordered_map<std::string_view, ipc::IToplevelProtocol* (*)()>
+ sAllocChildActorRegistry;
+
+const char* RegisterAllocChildActor(const char* aName,
+ ipc::IToplevelProtocol* (*aFunc)()) {
+ sAllocChildActorRegistry[aName] = aFunc;
+ return aName;
+}
+
+already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossProcess() {
+#ifdef MOZ_WIDGET_ANDROID
+ // Force-initialize the appshell on android, as android child process
+ // launching depends on widget component initialization.
+ nsCOMPtr<nsIAppShell> _appShell = do_GetService(NS_APPSHELL_CID);
+#endif
+
+ RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent();
+ parent->mSubprocess =
+ new ipc::GeckoChildProcessHost(GeckoProcessType_IPDLUnitTest);
+
+ std::vector<std::string> extraArgs;
+
+ auto prefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>();
+ if (!prefSerializer->SerializeToSharedMemory(GeckoProcessType_IPDLUnitTest,
+ /* remoteType */ ""_ns)) {
+ ADD_FAILURE()
+ << "SharedPreferenceSerializer::SerializeToSharedMemory failed";
+ return nullptr;
+ }
+ prefSerializer->AddSharedPrefCmdLineArgs(*parent->mSubprocess, extraArgs);
+
+ if (!parent->mSubprocess->SyncLaunch(extraArgs)) {
+ ADD_FAILURE() << "Subprocess launch failed";
+ return nullptr;
+ }
+
+ if (!parent->mSubprocess->TakeInitialEndpoint().Bind(parent.get())) {
+ ADD_FAILURE() << "Opening the parent actor failed";
+ return nullptr;
+ }
+
+ EXPECT_TRUE(parent->CanSend());
+ return parent.forget();
+}
+
+already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossThread() {
+ RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent();
+ RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild();
+
+ nsresult rv =
+ NS_NewNamedThread("IPDL UnitTest", getter_AddRefs(parent->mOtherThread));
+ if (NS_FAILED(rv)) {
+ ADD_FAILURE() << "Failed to create IPDLUnitTest thread";
+ return nullptr;
+ }
+ if (!parent->Open(child, parent->mOtherThread)) {
+ ADD_FAILURE() << "Opening the actor failed";
+ return nullptr;
+ }
+
+ EXPECT_TRUE(parent->CanSend());
+ return parent.forget();
+}
+
+IPDLUnitTestParent::~IPDLUnitTestParent() {
+ if (mSubprocess) {
+ mSubprocess->Destroy();
+ mSubprocess = nullptr;
+ }
+ if (mOtherThread) {
+ mOtherThread->Shutdown();
+ }
+}
+
+bool IPDLUnitTestParent::Start(const char* aName,
+ ipc::IToplevelProtocol* aActor) {
+ nsID channelId = nsID::GenerateUUID();
+ auto [parentPort, childPort] =
+ ipc::NodeController::GetSingleton()->CreatePortPair();
+ if (!SendStart(nsDependentCString(aName), std::move(childPort), channelId)) {
+ ADD_FAILURE() << "IPDLUnitTestParent::SendStart failed";
+ return false;
+ }
+ if (!aActor->Open(std::move(parentPort), channelId, OtherPid())) {
+ ADD_FAILURE() << "Unable to open parent actor";
+ return false;
+ }
+ return true;
+}
+
+ipc::IPCResult IPDLUnitTestParent::RecvReport(const TestPartResult& aReport) {
+ if (!aReport.failed()) {
+ return IPC_OK();
+ }
+
+ // Report the failure
+ ADD_FAILURE_AT(aReport.filename().get(), aReport.lineNumber())
+ << "[child " << OtherPid() << "] " << aReport.summary();
+
+ // If the failure was fatal, kill the child process to avoid hangs.
+ if (aReport.fatal()) {
+ KillHard();
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult IPDLUnitTestParent::RecvComplete() {
+ mComplete = true;
+ Close();
+ return IPC_OK();
+}
+
+void IPDLUnitTestParent::KillHard() {
+ if (mCalledKillHard) {
+ return;
+ }
+ mCalledKillHard = true;
+
+ // We can't effectively kill a same-process situation, but we can trigger
+ // shutdown early to avoid hanging.
+ if (mOtherThread) {
+ Close();
+ nsCOMPtr<nsIThread> otherThread = mOtherThread.forget();
+ otherThread->Shutdown();
+ }
+
+ if (mSubprocess) {
+ ProcessHandle handle = mSubprocess->GetChildProcessHandle();
+ if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) {
+ NS_WARNING("failed to kill subprocess!");
+ }
+ mSubprocess->SetAlreadyDead();
+ }
+}
+
+ipc::IPCResult IPDLUnitTestChild::RecvStart(const nsCString& aName,
+ ipc::ScopedPort aPort,
+ const nsID& aMessageChannelId) {
+ auto* allocChildActor =
+ sAllocChildActorRegistry[std::string_view{aName.get()}];
+ if (!allocChildActor) {
+ ADD_FAILURE() << "No AllocChildActor for name " << aName.get()
+ << " registered!";
+ return IPC_FAIL(this, "No AllocChildActor registered!");
+ }
+
+ // Store references to the node & port to watch for test completion.
+ RefPtr<ipc::NodeController> controller = aPort.Controller();
+ mojo::core::ports::PortRef port = aPort.Port();
+
+ auto* child = allocChildActor();
+ if (!child->Open(std::move(aPort), aMessageChannelId, OtherPid())) {
+ ADD_FAILURE() << "Unable to open child actor";
+ return IPC_FAIL(this, "Unable to open child actor");
+ }
+
+ // Wait for the port which was created for this actor to be fully torn down.
+ SpinEventLoopUntil("IPDLUnitTestChild::RecvStart"_ns,
+ [&] { return controller->GetStatus(port).isNothing(); });
+
+ // Tear down the test actor to end the test.
+ SendComplete();
+ return IPC_OK();
+}
+
+void IPDLUnitTestChild::ActorDestroy(ActorDestroyReason aWhy) {
+ if (!XRE_IsParentProcess()) {
+ XRE_ShutdownChildProcess();
+ }
+}
+
+void IPDLTestHelper::TestWrapper(bool aCrossProcess) {
+ // Create the host and start the test actor with it.
+ RefPtr<IPDLUnitTestParent> host =
+ aCrossProcess ? IPDLUnitTestParent::CreateCrossProcess()
+ : IPDLUnitTestParent::CreateCrossThread();
+ ASSERT_TRUE(host);
+ if (!host->Start(GetName(), GetActor())) {
+ FAIL();
+ }
+
+ // XXX: Consider adding a test timeout?
+
+ // Run the test body. This will send the initial messages to our actor, which
+ // will eventually clean itself up.
+ TestBody();
+
+ // Spin the event loop until the test wrapper host has fully shut down.
+ SpinEventLoopUntil("IPDLTestHelper::TestWrapper"_ns,
+ [&] { return !host->CanSend(); });
+
+ EXPECT_TRUE(host->ReportedComplete())
+ << "child process exited without signalling completion";
+}
+
+// Listener registered within the IPDLUnitTest process used to relay GTest
+// failures to the parent process, so that the are marked as failing the overall
+// gtest.
+class IPDLChildProcessTestListener : public testing::EmptyTestEventListener {
+ public:
+ explicit IPDLChildProcessTestListener(IPDLUnitTestChild* aActor)
+ : mActor(aActor) {}
+
+ virtual void OnTestPartResult(
+ const testing::TestPartResult& aTestPartResult) override {
+ mActor->SendReport(TestPartResult(
+ aTestPartResult.failed(), aTestPartResult.fatally_failed(),
+ nsDependentCString(aTestPartResult.file_name()),
+ aTestPartResult.line_number(),
+ nsDependentCString(aTestPartResult.summary()),
+ nsDependentCString(aTestPartResult.message())));
+ }
+
+ RefPtr<IPDLUnitTestChild> mActor;
+};
+
+// ProcessChild instance used to run the IPDLUnitTest process.
+class IPDLUnitTestProcessChild : public ipc::ProcessChild {
+ public:
+ using ipc::ProcessChild::ProcessChild;
+ bool Init(int aArgc, char* aArgv[]) override {
+ nsDebugImpl::SetMultiprocessMode("IPDLUnitTest");
+
+ if (!ProcessChild::InitPrefs(aArgc, aArgv)) {
+ MOZ_CRASH("InitPrefs failed");
+ return false;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+ MOZ_CRASH("nsThreadManager initialization failed");
+ return false;
+ }
+
+ RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild();
+ if (!TakeInitialEndpoint().Bind(child.get())) {
+ MOZ_CRASH("Bind of IPDLUnitTestChild failed");
+ return false;
+ }
+
+ // Register a listener to forward test results from the child process to the
+ // parent process to be handled there.
+ mListener = new IPDLChildProcessTestListener(child);
+ testing::UnitTest::GetInstance()->listeners().Append(mListener);
+
+ if (NS_FAILED(NS_InitMinimalXPCOM())) {
+ MOZ_CRASH("NS_InitMinimalXPCOM failed");
+ return false;
+ }
+
+ ipc::SetThisProcessName("IPDLUnitTest");
+ return true;
+ }
+
+ void CleanUp() override {
+ // Clean up the test listener we registered to get a clean shutdown.
+ if (mListener) {
+ testing::UnitTest::GetInstance()->listeners().Release(mListener);
+ delete mListener;
+ }
+
+ NS_ShutdownXPCOM(nullptr);
+ }
+
+ IPDLChildProcessTestListener* mListener = nullptr;
+};
+
+// Defined in nsEmbedFunctions.cpp
+extern UniquePtr<ipc::ProcessChild> (*gMakeIPDLUnitTestProcessChild)(
+ base::ProcessId, const nsID&);
+
+// Initialize gMakeIPDLUnitTestProcessChild in a static constructor.
+int _childProcessEntryPointStaticConstructor = ([] {
+ gMakeIPDLUnitTestProcessChild =
+ [](base::ProcessId aParentPid,
+ const nsID& aMessageChannelId) -> UniquePtr<ipc::ProcessChild> {
+ return MakeUnique<IPDLUnitTestProcessChild>(aParentPid, aMessageChannelId);
+ };
+ return 0;
+})();
+
+} // namespace mozilla::_ipdltest
diff --git a/ipc/ipdl/test/gtest/IPDLUnitTest.h b/ipc/ipdl/test/gtest/IPDLUnitTest.h
new file mode 100644
index 0000000000..a5fb72e2fe
--- /dev/null
+++ b/ipc/ipdl/test/gtest/IPDLUnitTest.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla__ipdltest_IPDLUnitTest_h
+#define mozilla__ipdltest_IPDLUnitTest_h
+
+#include "mozilla/_ipdltest/IPDLUnitTestChild.h"
+#include "mozilla/_ipdltest/IPDLUnitTestParent.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla::_ipdltest {
+
+// Should be called from static constructors to register a child actor
+// constructor so that it can be called from the child process.
+const char* RegisterAllocChildActor(
+ const char* aName, mozilla::ipc::IToplevelProtocol* (*aFunc)());
+
+// Internal helper type used to declare IPDL tests.
+class IPDLTestHelper {
+ public:
+ void TestWrapper(bool aCrossProcess);
+ virtual const char* GetName() = 0;
+ virtual ipc::IToplevelProtocol* GetActor() = 0;
+ virtual void TestBody() = 0;
+};
+
+#define IPDL_TEST_CLASS_NAME_(actorname) IPDL_TEST_##actorname
+
+#define IPDL_TEST_HEAD_(actorname) \
+ class IPDL_TEST_CLASS_NAME_(actorname) \
+ : public ::mozilla::_ipdltest::IPDLTestHelper { \
+ public: \
+ IPDL_TEST_CLASS_NAME_(actorname)() : mActor(new actorname##Parent) {} \
+ \
+ private: \
+ void TestBody() override; \
+ const char* GetName() override { return sName; }; \
+ actorname##Parent* GetActor() override { return mActor; }; \
+ \
+ actorname##Parent* mActor; \
+ static const char* sName; \
+ }; \
+ const char* IPDL_TEST_CLASS_NAME_(actorname)::sName = \
+ ::mozilla::_ipdltest::RegisterAllocChildActor( \
+ #actorname, []() -> ::mozilla::ipc::IToplevelProtocol* { \
+ return new actorname##Child; \
+ });
+
+#define IPDL_TEST_DECL_(testgroup, actorname, crossprocess) \
+ TEST(testgroup, actorname) \
+ { \
+ IPDL_TEST_CLASS_NAME_(actorname) test; \
+ test.TestWrapper(crossprocess); \
+ }
+
+#define IPDL_TEST_BODY_SEGUE_(actorname) \
+ void IPDL_TEST_CLASS_NAME_(actorname)::TestBody()
+
+// Declare a basic IPDL unit test which will be run in both both cross-thread
+// and cross-process configurations. The actor `actorname` will be instantiated
+// by default-constructing the parent & child actors in the relevant processes,
+// and the test block will be executed, with `mActor` being a pointer to the
+// parent actor. The test is asynchronous, and will end when the IPDL connection
+// between the created actors is destroyed.
+//
+// GTest assertions fired in the child process will be relayed to the parent
+// process, and should generally function correctly.
+#define IPDL_TEST(actorname) \
+ IPDL_TEST_HEAD_(actorname) \
+ IPDL_TEST_DECL_(IPDLTest_CrossProcess, actorname, true) \
+ IPDL_TEST_DECL_(IPDLTest_CrossThread, actorname, false) \
+ IPDL_TEST_BODY_SEGUE_(actorname)
+
+} // namespace mozilla::_ipdltest
+
+#endif // mozilla__ipdltest_IPDLUnitTest_h
diff --git a/ipc/ipdl/test/gtest/IPDLUnitTestChild.h b/ipc/ipdl/test/gtest/IPDLUnitTestChild.h
new file mode 100644
index 0000000000..3f9243b274
--- /dev/null
+++ b/ipc/ipdl/test/gtest/IPDLUnitTestChild.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla__ipdltest_IPDLUnitTestChild_h
+#define mozilla__ipdltest_IPDLUnitTestChild_h
+
+#include "mozilla/_ipdltest/PIPDLUnitTestChild.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::_ipdltest {
+
+class IPDLUnitTestChild : public PIPDLUnitTestChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPDLUnitTestChild, override)
+
+ private:
+ friend class PIPDLUnitTestChild;
+
+ ipc::IPCResult RecvStart(const nsCString& aName, ipc::ScopedPort aPort,
+ const nsID& aChannelId);
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ ~IPDLUnitTestChild() = default;
+};
+
+} // namespace mozilla::_ipdltest
+
+#endif // mozilla__ipdltest_IPDLUnitTestChild_h
diff --git a/ipc/ipdl/test/gtest/IPDLUnitTestParent.h b/ipc/ipdl/test/gtest/IPDLUnitTestParent.h
new file mode 100644
index 0000000000..c292506932
--- /dev/null
+++ b/ipc/ipdl/test/gtest/IPDLUnitTestParent.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla__ipdltest_IPDLUnitTestParent_h
+#define mozilla__ipdltest_IPDLUnitTestParent_h
+
+#include "mozilla/_ipdltest/PIPDLUnitTestParent.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::_ipdltest {
+
+class IPDLUnitTestParent : public PIPDLUnitTestParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPDLUnitTestParent, override)
+
+ static already_AddRefed<IPDLUnitTestParent> CreateCrossProcess();
+ static already_AddRefed<IPDLUnitTestParent> CreateCrossThread();
+
+ // Try to start a connection with the given name and open `aParentActor` with
+ // it. Fails the current test and returns false on failure.
+ bool Start(const char* aName, IToplevelProtocol* aParentActor);
+
+ bool ReportedComplete() const { return mComplete; }
+
+ private:
+ friend class PIPDLUnitTestParent;
+
+ ipc::IPCResult RecvReport(const TestPartResult& aResult);
+ ipc::IPCResult RecvComplete();
+
+ void KillHard();
+
+ ~IPDLUnitTestParent();
+
+ // Only one of these two will be set depending.
+ nsCOMPtr<nsIThread> mOtherThread;
+ mozilla::ipc::GeckoChildProcessHost* mSubprocess = nullptr;
+
+ // Set to true when the test is complete.
+ bool mComplete = false;
+ bool mCalledKillHard = false;
+};
+
+} // namespace mozilla::_ipdltest
+
+#endif // mozilla__ipdltest_IPDLUnitTestParent_h
diff --git a/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl b/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl
new file mode 100644
index 0000000000..2f06bccac8
--- /dev/null
+++ b/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl
@@ -0,0 +1,33 @@
+/* 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/. */
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h";
+[MoveOnly] using class mozilla::ipc::ScopedPort from "mozilla/ipc/ScopedPort.h";
+using struct nsID from "nsID.h";
+
+namespace mozilla {
+namespace _ipdltest {
+
+// IPDL representation of GTest's TestPartResult type.
+struct TestPartResult {
+ bool failed;
+ bool fatal;
+ nsCString filename;
+ int32_t lineNumber;
+ nsCString summary;
+ nsCString message;
+};
+
+// Primary actor for the IPDLUnitTest process and thread.
+[NeedsOtherPid] async protocol PIPDLUnitTest {
+ child:
+ async Start(nsCString name, ScopedPort port, nsID channelId);
+
+ parent:
+ async Report(TestPartResult result);
+ async Complete();
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
diff --git a/ipc/ipdl/test/gtest/PTestBasic.ipdl b/ipc/ipdl/test/gtest/PTestBasic.ipdl
new file mode 100644
index 0000000000..25f32cacbd
--- /dev/null
+++ b/ipc/ipdl/test/gtest/PTestBasic.ipdl
@@ -0,0 +1,14 @@
+/* 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/. */
+
+namespace mozilla {
+namespace _ipdltest {
+
+async protocol PTestBasic {
+child:
+ async Hello();
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
diff --git a/ipc/ipdl/test/gtest/PTestManyHandles.ipdl b/ipc/ipdl/test/gtest/PTestManyHandles.ipdl
new file mode 100644
index 0000000000..b1db633fdb
--- /dev/null
+++ b/ipc/ipdl/test/gtest/PTestManyHandles.ipdl
@@ -0,0 +1,15 @@
+/* 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/. */
+
+namespace mozilla {
+namespace _ipdltest {
+
+[ParentImpl=virtual, ChildImpl=virtual]
+async protocol PTestManyHandles {
+child:
+ async ManyHandles(FileDescriptor[] descrs);
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
diff --git a/ipc/ipdl/test/gtest/TestBasic.cpp b/ipc/ipdl/test/gtest/TestBasic.cpp
new file mode 100644
index 0000000000..36ad55ba99
--- /dev/null
+++ b/ipc/ipdl/test/gtest/TestBasic.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/_ipdltest/IPDLUnitTest.h"
+#include "mozilla/_ipdltest/TestBasicChild.h"
+#include "mozilla/_ipdltest/TestBasicParent.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::_ipdltest {
+
+IPCResult TestBasicChild::RecvHello() {
+ EXPECT_TRUE(CanSend());
+ Close();
+ EXPECT_FALSE(CanSend());
+ return IPC_OK();
+}
+
+IPDL_TEST(TestBasic) {
+ bool ok = mActor->SendHello();
+ ASSERT_TRUE(ok);
+}
+
+} // namespace mozilla::_ipdltest
diff --git a/ipc/ipdl/test/gtest/TestBasicChild.h b/ipc/ipdl/test/gtest/TestBasicChild.h
new file mode 100644
index 0000000000..9971073f4c
--- /dev/null
+++ b/ipc/ipdl/test/gtest/TestBasicChild.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla__ipdltest_TestBasicChild_h
+#define mozilla__ipdltest_TestBasicChild_h
+
+#include "mozilla/_ipdltest/PTestBasicChild.h"
+
+namespace mozilla::_ipdltest {
+
+class TestBasicChild : public PTestBasicChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBasicChild, override)
+
+ public:
+ mozilla::ipc::IPCResult RecvHello();
+
+ private:
+ ~TestBasicChild() = default;
+};
+
+} // namespace mozilla::_ipdltest
+
+#endif // mozilla__ipdltest_TestBasicChild_h
diff --git a/ipc/ipdl/test/gtest/TestBasicParent.h b/ipc/ipdl/test/gtest/TestBasicParent.h
new file mode 100644
index 0000000000..22b07375ba
--- /dev/null
+++ b/ipc/ipdl/test/gtest/TestBasicParent.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla__ipdltest_TestBasicParent_h
+#define mozilla__ipdltest_TestBasicParent_h
+
+#include "mozilla/_ipdltest/PTestBasicParent.h"
+
+namespace mozilla::_ipdltest {
+
+class TestBasicParent : public PTestBasicParent {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBasicParent, override)
+
+ private:
+ ~TestBasicParent() = default;
+};
+
+} // namespace mozilla::_ipdltest
+
+#endif // mozilla__ipdltest_TestBasicParent_h
diff --git a/ipc/ipdl/test/gtest/TestManyHandles.cpp b/ipc/ipdl/test/gtest/TestManyHandles.cpp
new file mode 100644
index 0000000000..ead8af22b1
--- /dev/null
+++ b/ipc/ipdl/test/gtest/TestManyHandles.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/_ipdltest/IPDLUnitTest.h"
+#include "mozilla/_ipdltest/PTestManyHandlesChild.h"
+#include "mozilla/_ipdltest/PTestManyHandlesParent.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::_ipdltest {
+
+class TestManyHandlesChild : public PTestManyHandlesChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestManyHandlesChild, override)
+
+ public:
+ IPCResult RecvManyHandles(nsTArray<FileDescriptor>&& aDescrs) override {
+ EXPECT_EQ(aDescrs.Length(), 500u);
+ for (int i = 0; i < static_cast<int>(aDescrs.Length()); ++i) {
+ UniqueFileHandle handle = aDescrs[i].TakePlatformHandle();
+ int value;
+ const int size = sizeof(value);
+#ifdef XP_WIN
+ DWORD numberOfBytesRead;
+ EXPECT_TRUE(
+ ::ReadFile(handle.get(), &value, size, &numberOfBytesRead, nullptr));
+ EXPECT_EQ(numberOfBytesRead, (DWORD)size);
+#else
+ EXPECT_EQ(read(handle.get(), &value, size), size);
+#endif
+ EXPECT_EQ(value, i);
+ }
+ Close();
+ return IPC_OK();
+ }
+
+ private:
+ ~TestManyHandlesChild() = default;
+};
+
+class TestManyHandlesParent : public PTestManyHandlesParent {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestManyHandlesParent, override)
+
+ private:
+ ~TestManyHandlesParent() = default;
+};
+
+IPDL_TEST(TestManyHandles) {
+ nsTArray<FileDescriptor> descrs;
+ for (int i = 0; i < 500; ++i) {
+ const int size = sizeof(i);
+ UniqueFileHandle readPipe;
+ UniqueFileHandle writePipe;
+#ifdef XP_WIN
+ ASSERT_TRUE(::CreatePipe(getter_Transfers(readPipe),
+ getter_Transfers(writePipe), nullptr, size));
+ DWORD numberOfBytesWritten;
+ ASSERT_TRUE(
+ ::WriteFile(writePipe.get(), &i, size, &numberOfBytesWritten, nullptr));
+ ASSERT_EQ(numberOfBytesWritten, (DWORD)size);
+#else
+ int fds[2];
+ ASSERT_EQ(pipe(fds), 0);
+ readPipe.reset(fds[0]);
+ writePipe.reset(fds[1]);
+ ASSERT_EQ(write(writePipe.get(), &i, size), size);
+#endif
+ descrs.AppendElement(FileDescriptor(std::move(readPipe)));
+ }
+ bool ok = mActor->SendManyHandles(descrs);
+ ASSERT_TRUE(ok);
+}
+
+} // namespace mozilla::_ipdltest
diff --git a/ipc/ipdl/test/gtest/moz.build b/ipc/ipdl/test/gtest/moz.build
new file mode 100644
index 0000000000..1abd039e36
--- /dev/null
+++ b/ipc/ipdl/test/gtest/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("ipdltest")
+
+EXPORTS.mozilla._ipdltest += [
+ "IPDLUnitTest.h",
+ "IPDLUnitTestChild.h",
+ "IPDLUnitTestParent.h",
+ "TestBasicChild.h",
+ "TestBasicParent.h",
+]
+
+SOURCES += [
+ "IPDLUnitTest.cpp",
+ "TestBasic.cpp",
+ "TestManyHandles.cpp",
+]
+
+IPDL_SOURCES += [
+ "PIPDLUnitTest.ipdl",
+ "PTestBasic.ipdl",
+ "PTestManyHandles.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"