summaryrefslogtreecommitdiffstats
path: root/ipc/ipdl/test/gtest/IPDLUnitTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/ipdl/test/gtest/IPDLUnitTest.cpp')
-rw-r--r--ipc/ipdl/test/gtest/IPDLUnitTest.cpp308
1 files changed, 308 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..0450019ba9
--- /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();
+
+ RefPtr<IToplevelProtocol> 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