tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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