diff options
Diffstat (limited to 'ipc/ipdl/test/gtest/IPDLUnitTest.cpp')
-rw-r--r-- | ipc/ipdl/test/gtest/IPDLUnitTest.cpp | 308 |
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 |