IPDLUnitTest.cpp (10168B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 9 #include "mozilla/_ipdltest/IPDLUnitTest.h" 10 #include "mozilla/ipc/NodeController.h" 11 #include "mozilla/ipc/ProtocolUtils.h" 12 #include "mozilla/ipc/ProcessChild.h" 13 #include "mozilla/ipc/ProcessUtils.h" 14 #include "mozilla/SpinEventLoopUntil.h" 15 #include "nsDebugImpl.h" 16 #include "nsThreadManager.h" 17 18 #ifdef MOZ_WIDGET_ANDROID 19 # include "nsIAppShell.h" 20 # include "nsServiceManagerUtils.h" 21 # include "nsWidgetsCID.h" 22 #endif 23 24 namespace mozilla::_ipdltest { 25 26 MOZ_RUNINIT static std::unordered_map<std::string_view, 27 ipc::IToplevelProtocol* (*)()> 28 sAllocChildActorRegistry; 29 30 const char* RegisterAllocChildActor(const char* aName, 31 ipc::IToplevelProtocol* (*aFunc)()) { 32 sAllocChildActorRegistry[aName] = aFunc; 33 return aName; 34 } 35 36 already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossProcess() { 37 #ifdef MOZ_WIDGET_ANDROID 38 // Force-initialize the appshell on android, as android child process 39 // launching depends on widget component initialization. 40 nsCOMPtr<nsIAppShell> _appShell = do_GetService(NS_APPSHELL_CID); 41 #endif 42 43 RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent(); 44 parent->mSubprocess = 45 new ipc::GeckoChildProcessHost(GeckoProcessType_IPDLUnitTest); 46 47 geckoargs::ChildProcessArgs extraArgs; 48 49 auto prefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); 50 if (!prefSerializer->SerializeToSharedMemory(GeckoProcessType_IPDLUnitTest, 51 /* remoteType */ ""_ns)) { 52 ADD_FAILURE() 53 << "SharedPreferenceSerializer::SerializeToSharedMemory failed"; 54 return nullptr; 55 } 56 prefSerializer->AddSharedPrefCmdLineArgs(*parent->mSubprocess, extraArgs); 57 58 if (!parent->mSubprocess->SyncLaunch(std::move(extraArgs))) { 59 ADD_FAILURE() << "Subprocess launch failed"; 60 return nullptr; 61 } 62 63 if (!parent->mSubprocess->TakeInitialEndpoint().Bind(parent.get())) { 64 ADD_FAILURE() << "Opening the parent actor failed"; 65 return nullptr; 66 } 67 68 EXPECT_TRUE(parent->CanSend()); 69 return parent.forget(); 70 } 71 72 already_AddRefed<IPDLUnitTestParent> IPDLUnitTestParent::CreateCrossThread() { 73 RefPtr<IPDLUnitTestParent> parent = new IPDLUnitTestParent(); 74 RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild(); 75 76 nsresult rv = 77 NS_NewNamedThread("IPDL UnitTest", getter_AddRefs(parent->mOtherThread)); 78 if (NS_FAILED(rv)) { 79 ADD_FAILURE() << "Failed to create IPDLUnitTest thread"; 80 return nullptr; 81 } 82 if (!parent->Open(child, parent->mOtherThread)) { 83 ADD_FAILURE() << "Opening the actor failed"; 84 return nullptr; 85 } 86 87 EXPECT_TRUE(parent->CanSend()); 88 return parent.forget(); 89 } 90 91 IPDLUnitTestParent::~IPDLUnitTestParent() { 92 if (mSubprocess) { 93 mSubprocess->Destroy(); 94 mSubprocess = nullptr; 95 } 96 if (mOtherThread) { 97 mOtherThread->Shutdown(); 98 } 99 } 100 101 bool IPDLUnitTestParent::Start(const char* aName, 102 ipc::IToplevelProtocol* aActor) { 103 nsID channelId = nsID::GenerateUUID(); 104 auto [parentPort, childPort] = 105 ipc::NodeController::GetSingleton()->CreatePortPair(); 106 if (!SendStart(nsDependentCString(aName), std::move(childPort), channelId)) { 107 ADD_FAILURE() << "IPDLUnitTestParent::SendStart failed"; 108 return false; 109 } 110 if (!aActor->Open(std::move(parentPort), channelId, 111 OtherEndpointProcInfo())) { 112 ADD_FAILURE() << "Unable to open parent actor"; 113 return false; 114 } 115 return true; 116 } 117 118 ipc::IPCResult IPDLUnitTestParent::RecvReport(const TestPartResult& aReport) { 119 if (!aReport.failed()) { 120 return IPC_OK(); 121 } 122 123 // Report the failure 124 ADD_FAILURE_AT(aReport.filename().get(), aReport.lineNumber()) 125 << "[child " << OtherPid() << "] " << aReport.summary(); 126 127 // If the failure was fatal, kill the child process to avoid hangs. 128 if (aReport.fatal()) { 129 KillHard(); 130 } 131 return IPC_OK(); 132 } 133 134 ipc::IPCResult IPDLUnitTestParent::RecvComplete() { 135 mComplete = true; 136 Close(); 137 return IPC_OK(); 138 } 139 140 void IPDLUnitTestParent::KillHard() { 141 if (mCalledKillHard) { 142 return; 143 } 144 mCalledKillHard = true; 145 146 // We can't effectively kill a same-process situation, but we can trigger 147 // shutdown early to avoid hanging. 148 if (mOtherThread) { 149 Close(); 150 nsCOMPtr<nsIThread> otherThread = mOtherThread.forget(); 151 otherThread->Shutdown(); 152 } 153 154 if (mSubprocess) { 155 ProcessHandle handle = mSubprocess->GetChildProcessHandle(); 156 if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) { 157 NS_WARNING("failed to kill subprocess!"); 158 } 159 mSubprocess->SetAlreadyDead(); 160 } 161 } 162 163 ipc::IPCResult IPDLUnitTestChild::RecvStart(const nsCString& aName, 164 ipc::ScopedPort aPort, 165 const nsID& aMessageChannelId) { 166 auto* allocChildActor = 167 sAllocChildActorRegistry[std::string_view{aName.get()}]; 168 if (!allocChildActor) { 169 ADD_FAILURE() << "No AllocChildActor for name " << aName.get() 170 << " registered!"; 171 return IPC_FAIL(this, "No AllocChildActor registered!"); 172 } 173 174 // Store references to the node & port to watch for test completion. 175 RefPtr<ipc::NodeController> controller = aPort.Controller(); 176 mojo::core::ports::PortRef port = aPort.Port(); 177 178 RefPtr<IToplevelProtocol> child = allocChildActor(); 179 if (!child->Open(std::move(aPort), aMessageChannelId, 180 OtherEndpointProcInfo())) { 181 ADD_FAILURE() << "Unable to open child actor"; 182 return IPC_FAIL(this, "Unable to open child actor"); 183 } 184 185 // Wait for the port which was created for this actor to be fully torn down. 186 SpinEventLoopUntil("IPDLUnitTestChild::RecvStart"_ns, 187 [&] { return controller->GetStatus(port).isNothing(); }); 188 189 // Tear down the test actor to end the test. 190 SendComplete(); 191 return IPC_OK(); 192 } 193 194 void IPDLUnitTestChild::ActorDestroy(ActorDestroyReason aWhy) { 195 if (!XRE_IsParentProcess()) { 196 XRE_ShutdownChildProcess(); 197 } 198 } 199 200 void IPDLTestHelper::TestWrapper(bool aCrossProcess) { 201 // Create the host and start the test actor with it. 202 RefPtr<IPDLUnitTestParent> host = 203 aCrossProcess ? IPDLUnitTestParent::CreateCrossProcess() 204 : IPDLUnitTestParent::CreateCrossThread(); 205 ASSERT_TRUE(host); 206 if (!host->Start(GetName(), GetActor())) { 207 FAIL(); 208 } 209 210 // XXX: Consider adding a test timeout? 211 212 // Run the test body. This will send the initial messages to our actor, which 213 // will eventually clean itself up. 214 TestBody(); 215 216 // Spin the event loop until the test wrapper host has fully shut down. 217 SpinEventLoopUntil("IPDLTestHelper::TestWrapper"_ns, 218 [&] { return !host->CanSend(); }); 219 220 EXPECT_TRUE(host->ReportedComplete()) 221 << "child process exited without signalling completion"; 222 } 223 224 // Listener registered within the IPDLUnitTest process used to relay GTest 225 // failures to the parent process, so that the are marked as failing the overall 226 // gtest. 227 class IPDLChildProcessTestListener : public testing::EmptyTestEventListener { 228 public: 229 explicit IPDLChildProcessTestListener(IPDLUnitTestChild* aActor) 230 : mActor(aActor) {} 231 232 virtual void OnTestPartResult( 233 const testing::TestPartResult& aTestPartResult) override { 234 mActor->SendReport(TestPartResult( 235 aTestPartResult.failed(), aTestPartResult.fatally_failed(), 236 nsDependentCString(aTestPartResult.file_name()), 237 aTestPartResult.line_number(), 238 nsDependentCString(aTestPartResult.summary()), 239 nsDependentCString(aTestPartResult.message()))); 240 } 241 242 RefPtr<IPDLUnitTestChild> mActor; 243 }; 244 245 // ProcessChild instance used to run the IPDLUnitTest process. 246 class IPDLUnitTestProcessChild : public ipc::ProcessChild { 247 public: 248 using ipc::ProcessChild::ProcessChild; 249 bool Init(int aArgc, char* aArgv[]) override { 250 nsDebugImpl::SetMultiprocessMode("IPDLUnitTest"); 251 252 if (!ProcessChild::InitPrefs(aArgc, aArgv)) { 253 MOZ_CRASH("InitPrefs failed"); 254 return false; 255 } 256 257 if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { 258 MOZ_CRASH("nsThreadManager initialization failed"); 259 return false; 260 } 261 262 RefPtr<IPDLUnitTestChild> child = new IPDLUnitTestChild(); 263 if (!TakeInitialEndpoint().Bind(child.get())) { 264 MOZ_CRASH("Bind of IPDLUnitTestChild failed"); 265 return false; 266 } 267 268 // Register a listener to forward test results from the child process to the 269 // parent process to be handled there. 270 mListener = new IPDLChildProcessTestListener(child); 271 testing::UnitTest::GetInstance()->listeners().Append(mListener); 272 273 if (NS_FAILED(NS_InitMinimalXPCOM())) { 274 MOZ_CRASH("NS_InitMinimalXPCOM failed"); 275 return false; 276 } 277 278 ipc::SetThisProcessName("IPDLUnitTest"); 279 return true; 280 } 281 282 void CleanUp() override { 283 // Clean up the test listener we registered to get a clean shutdown. 284 if (mListener) { 285 testing::UnitTest::GetInstance()->listeners().Release(mListener); 286 delete mListener; 287 } 288 289 NS_ShutdownXPCOM(nullptr); 290 } 291 292 IPDLChildProcessTestListener* mListener = nullptr; 293 }; 294 295 // Defined in nsEmbedFunctions.cpp 296 extern UniquePtr<ipc::ProcessChild> (*gMakeIPDLUnitTestProcessChild)( 297 IPC::Channel::ChannelHandle, base::ProcessId, const nsID&); 298 299 // Initialize gMakeIPDLUnitTestProcessChild in a static constructor. 300 MOZ_RUNINIT int _childProcessEntryPointStaticConstructor = ([] { 301 gMakeIPDLUnitTestProcessChild = 302 [](IPC::Channel::ChannelHandle aClientChannel, base::ProcessId aParentPid, 303 const nsID& aMessageChannelId) -> UniquePtr<ipc::ProcessChild> { 304 return MakeUnique<IPDLUnitTestProcessChild>(std::move(aClientChannel), 305 aParentPid, aMessageChannelId); 306 }; 307 return 0; 308 })(); 309 310 } // namespace mozilla::_ipdltest