diff options
Diffstat (limited to 'ipc/ipdl/test/gtest')
-rw-r--r-- | ipc/ipdl/test/gtest/IPDLUnitTest.cpp | 308 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/IPDLUnitTest.h | 79 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/IPDLUnitTestChild.h | 32 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/IPDLUnitTestParent.h | 50 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl | 34 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/PTestBasic.ipdl | 15 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/PTestCrossProcessSemaphore.ipdl | 17 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/PTestInduceConnectionError.ipdl | 19 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/PTestManyHandles.ipdl | 15 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestBasic.cpp | 29 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestBasicChild.h | 26 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestBasicParent.h | 23 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestCrossProcessSemaphore.cpp | 63 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestInduceConnectionError.cpp | 95 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/TestManyHandles.cpp | 78 | ||||
-rw-r--r-- | ipc/ipdl/test/gtest/moz.build | 35 |
16 files changed, 918 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 diff --git a/ipc/ipdl/test/gtest/IPDLUnitTest.h b/ipc/ipdl/test/gtest/IPDLUnitTest.h new file mode 100644 index 0000000000..f4548b508c --- /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; }; \ + \ + RefPtr<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..6bc5693a72 --- /dev/null +++ b/ipc/ipdl/test/gtest/PIPDLUnitTest.ipdl @@ -0,0 +1,34 @@ +/* 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, ChildProc=any] +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..9bc81f4830 --- /dev/null +++ b/ipc/ipdl/test/gtest/PTestBasic.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 { + +[ChildProc=any] +async protocol PTestBasic { +child: + async Hello(); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/gtest/PTestCrossProcessSemaphore.ipdl b/ipc/ipdl/test/gtest/PTestCrossProcessSemaphore.ipdl new file mode 100644 index 0000000000..fd25afbd13 --- /dev/null +++ b/ipc/ipdl/test/gtest/PTestCrossProcessSemaphore.ipdl @@ -0,0 +1,17 @@ +/* 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/. */ + +[MoveOnly] using mozilla::CrossProcessSemaphoreHandle from "mozilla/ipc/CrossProcessSemaphore.h"; + +namespace mozilla { +namespace _ipdltest { + +[ParentImpl=virtual, ChildImpl=virtual, ChildProc=any] +async protocol PTestCrossProcessSemaphore { +child: + async CrossProcessSemaphore(CrossProcessSemaphoreHandle aSem); +}; + +} // namespace _ipdltest +} // namespace mozilla diff --git a/ipc/ipdl/test/gtest/PTestInduceConnectionError.ipdl b/ipc/ipdl/test/gtest/PTestInduceConnectionError.ipdl new file mode 100644 index 0000000000..d5de893884 --- /dev/null +++ b/ipc/ipdl/test/gtest/PTestInduceConnectionError.ipdl @@ -0,0 +1,19 @@ +/* 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, ChildProc=any] +async protocol PTestInduceConnectionError { +child: + async Begin(); +parent: + [LazySend] async FirstMessage(); + [LazySend] async FollowupMessage(); +}; + +} // 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..31e03cee6f --- /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, ChildProc=any] +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/TestCrossProcessSemaphore.cpp b/ipc/ipdl/test/gtest/TestCrossProcessSemaphore.cpp new file mode 100644 index 0000000000..dc05614ded --- /dev/null +++ b/ipc/ipdl/test/gtest/TestCrossProcessSemaphore.cpp @@ -0,0 +1,63 @@ +/* -*- 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/PTestCrossProcessSemaphoreChild.h" +#include "mozilla/_ipdltest/PTestCrossProcessSemaphoreParent.h" + +#include "mozilla/ipc/CrossProcessSemaphore.h" + +using namespace mozilla::ipc; + +namespace mozilla::_ipdltest { + +class TestCrossProcessSemaphoreChild : public PTestCrossProcessSemaphoreChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestCrossProcessSemaphoreChild, + override) + + public: + IPCResult RecvCrossProcessSemaphore( + CrossProcessSemaphoreHandle&& aSem) override { + UniquePtr<CrossProcessSemaphore> cps( + CrossProcessSemaphore::Create(std::move(aSem))); + EXPECT_TRUE(bool(cps)); + cps->Signal(); + Close(); + return IPC_OK(); + } + + private: + ~TestCrossProcessSemaphoreChild() = default; +}; + +class TestCrossProcessSemaphoreParent + : public PTestCrossProcessSemaphoreParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestCrossProcessSemaphoreParent, + override) + + private: + ~TestCrossProcessSemaphoreParent() = default; +}; + +IPDL_TEST(TestCrossProcessSemaphore) { + // Create a semaphore with an initial count of 1. The test will then try to + // send the semaphore over IPDL to the other end which will signal, increasing + // the count to 2. The test will then try to wait on (decrement) the semaphore + // twice, which should succeed only if the semaphore was properly signaled. + UniquePtr<CrossProcessSemaphore> cps( + CrossProcessSemaphore::Create("TestCrossProcessSemaphore", 1)); + ASSERT_TRUE(bool(cps)); + CrossProcessSemaphoreHandle handle = cps->CloneHandle(); + ASSERT_TRUE(bool(handle)); + bool ok = mActor->SendCrossProcessSemaphore(std::move(handle)); + ASSERT_TRUE(ok); + EXPECT_TRUE(cps->Wait()); + EXPECT_TRUE(cps->Wait(Some(TimeDuration::FromSeconds(10)))); +} + +} // namespace mozilla::_ipdltest diff --git a/ipc/ipdl/test/gtest/TestInduceConnectionError.cpp b/ipc/ipdl/test/gtest/TestInduceConnectionError.cpp new file mode 100644 index 0000000000..988e22fe5a --- /dev/null +++ b/ipc/ipdl/test/gtest/TestInduceConnectionError.cpp @@ -0,0 +1,95 @@ +/* -*- 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/SpinEventLoopUntil.h" +#include "mozilla/_ipdltest/IPDLUnitTest.h" +#include "mozilla/_ipdltest/PTestInduceConnectionErrorChild.h" +#include "mozilla/_ipdltest/PTestInduceConnectionErrorParent.h" + +using namespace mozilla::ipc; + +namespace mozilla::_ipdltest { + +class TestInduceConnectionErrorChild : public PTestInduceConnectionErrorChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestInduceConnectionErrorChild, + override) + + public: + IPCResult RecvBegin() override { + SendFirstMessage(); + SendFollowupMessage(); + Close(); + return IPC_OK(); + } + + private: + ~TestInduceConnectionErrorChild() = default; +}; + +class TestInduceConnectionErrorParent + : public PTestInduceConnectionErrorParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestInduceConnectionErrorParent, + override) + + IPCResult RecvFirstMessage() override { + EXPECT_TRUE(CanSend()) << "Actor still alive before inducing"; + GetIPCChannel()->InduceConnectionError(); + EXPECT_TRUE(CanSend()) + << "Actor still alive after inducing - notification will be async"; + mRecvdFirstMessage = true; + + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "AfterRecvFirstMessage", [self = RefPtr{this}]() { + EXPECT_FALSE(self->CanSend()) + << "Actor shut down after spinning the event loop"; + self->mTestComplete = true; + })); + return IPC_OK(); + } + + IPCResult RecvFollowupMessage() override { + MOZ_CRASH( + "Should never receive followup message, despite them being sent " + "together"); + } + + void ActorDestroy(ActorDestroyReason aReason) override { + EXPECT_TRUE(mRecvdFirstMessage) + << "Should have mRecvdFirstMessage set before ActorDestroy"; + EXPECT_FALSE(mTestComplete) << "The test has not completed"; + EXPECT_EQ(aReason, ActorDestroyReason::AbnormalShutdown) + << "Should be an abnormal shutdown"; + mDestroyReason = Some(aReason); + } + + bool mRecvdFirstMessage = false; + bool mTestComplete = false; + Maybe<ActorDestroyReason> mDestroyReason; + + private: + ~TestInduceConnectionErrorParent() = default; +}; + +IPDL_TEST(TestInduceConnectionError) { + bool ok = mActor->SendBegin(); + GTEST_ASSERT_TRUE(ok); + + // Wait for the actor to be shut down. + SpinEventLoopUntil("TestInduceConnectionError"_ns, + [&] { return mActor->mTestComplete; }); + EXPECT_TRUE(mActor->mRecvdFirstMessage) + << "Actor should have received first message, but not second one"; + EXPECT_FALSE(mActor->CanSend()) << "Actor can no longer send"; + GTEST_ASSERT_TRUE(mActor->mDestroyReason.isSome()) + << "Actor should have been destroyed"; + EXPECT_EQ(*mActor->mDestroyReason, + IProtocol::ActorDestroyReason::AbnormalShutdown) + << "Actor should have an 'abnormal shutdown'"; +} + +} // namespace mozilla::_ipdltest 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..129e366a4b --- /dev/null +++ b/ipc/ipdl/test/gtest/moz.build @@ -0,0 +1,35 @@ +# -*- 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", + "TestCrossProcessSemaphore.cpp", + "TestInduceConnectionError.cpp", + "TestManyHandles.cpp", +] + +IPDL_SOURCES += [ + "PIPDLUnitTest.ipdl", + "PTestBasic.ipdl", + "PTestCrossProcessSemaphore.ipdl", + "PTestInduceConnectionError.ipdl", + "PTestManyHandles.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" |