chromium/base/process/process_metrics_unittest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/process/process_metrics.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/writable_shared_memory_region.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/gtest_util.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
#include "base/types/expected.h"
#include "build/blink_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

#if BUILDFLAG(IS_APPLE)
#include <sys/mman.h>
#endif

#if BUILDFLAG(IS_MAC)
#include <mach/mach.h>

#include "base/apple/mach_logging.h"
#include "base/apple/scoped_mach_port.h"
#include "base/mac/mach_port_rendezvous.h"
#include "base/process/port_provider_mac.h"
#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||      \
    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN) || \
    BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_APPLE)
#define ENABLE_CPU_TESTS
#else
#define ENABLE_CPU_TESTS
#endif

namespace base::debug {

namespace {

ErrorIs;
ValueIs;
_;
AssertionFailure;
AssertionResult;
AssertionSuccess;
Ge;

#if ENABLE_CPU_TESTS

void BusyWork(std::vector<std::string>* vec) {}

// Tests that GetCumulativeCPUUsage() returns a valid result that's equal to or
// greater than `prev_cpu_usage`, and returns the result. If
// GetCumulativeCPUUsage() returns an error, records a failed expectation and
// returns an empty TimeDelta so that each test doesn't need to check for
// nullopt repeatedly.
TimeDelta TestCumulativeCPU(ProcessMetrics* metrics, TimeDelta prev_cpu_usage) {}

#endif  // ENABLE_CPU_TESTS

// Helper to deal with Mac process launching complexity. On other platforms this
// is just a thin wrapper around SpawnMultiProcessTestChild.
class TestChildLauncher {};

#if BUILDFLAG(IS_MAC)

// Adapted from base/mac/mach_port_rendezvous_unittest.cc and
// https://mw.foldr.org/posts/computers/macosx/task-info-fun-with-mach/

constexpr MachPortsForRendezvous::key_type kTestChildRendezvousKey = 'test';

// A PortProvider that tracks child processes spawned by TestChildLauncher.
class TestChildLauncher::TestChildPortProvider final : public PortProvider {
 public:
  TestChildPortProvider(ProcessHandle handle, apple::ScopedMachSendRight port)
      : handle_(handle), port_(std::move(port)) {}

  ~TestChildPortProvider() final = default;

  TestChildPortProvider(const TestChildPortProvider&) = delete;
  TestChildPortProvider& operator=(const TestChildPortProvider&) = delete;

  mach_port_t TaskForHandle(ProcessHandle process_handle) const final {
    return process_handle == handle_ ? port_.get() : MACH_PORT_NULL;
  }

 private:
  ProcessHandle handle_;
  apple::ScopedMachSendRight port_;
};

AssertionResult TestChildLauncher::SpawnChildProcess(
    const std::string& procname) {
  // Allocate a port for the parent to receive details from the child process.
  apple::ScopedMachReceiveRight receive_port;
  if (!apple::CreateMachPort(&receive_port, nullptr)) {
    return AssertionFailure() << "Failed to allocate receive port";
  }

  // Pass the sending end of the port to the child.
  LaunchOptions options = LaunchOptionsForTest();
  options.mach_ports_for_rendezvous.emplace(
      kTestChildRendezvousKey,
      MachRendezvousPort(receive_port.get(), MACH_MSG_TYPE_MAKE_SEND));
  child_process_ =
      SpawnMultiProcessTestChild(procname, command_line_, std::move(options));
  if (!child_process_.IsValid()) {
    return AssertionFailure() << "Failed to launch child process.";
  }

  // Wait for the child to send back its mach_task_self().
  struct : mach_msg_base_t {
    mach_msg_port_descriptor_t task_port;
    mach_msg_trailer_t trailer;
  } msg{};
  kern_return_t kr =
      mach_msg(&msg.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(msg),
               receive_port.get(),
               TestTimeouts::action_timeout().InMilliseconds(), MACH_PORT_NULL);
  if (kr != KERN_SUCCESS) {
    return AssertionFailure()
           << "Failed to read mach_task_self from child process: "
           << mach_error_string(kr);
  }
  port_provider_ = std::make_unique<TestChildPortProvider>(
      child_process_.Handle(), apple::ScopedMachSendRight(msg.task_port.name));
  return AssertionSuccess();
}

std::unique_ptr<ProcessMetrics> TestChildLauncher::CreateChildProcessMetrics() {
#if BUILDFLAG(IS_MAC)
  return ProcessMetrics::CreateProcessMetrics(child_process_.Handle(),
                                              port_provider_.get());
#else
  return ProcessMetrics::CreateProcessMetrics(child_process_.Handle());
#endif
}

bool TestChildLauncher::TerminateChildProcess() {
  return TerminateMultiProcessTestChild(child_process_, /*exit_code=*/0,
                                        /*wait=*/true);
}

// static
void TestChildLauncher::NotifyParent() {
  auto* client = MachPortRendezvousClient::GetInstance();
  ASSERT_TRUE(client);
  apple::ScopedMachSendRight send_port =
      client->TakeSendRight(kTestChildRendezvousKey);
  ASSERT_TRUE(send_port.is_valid());

  // Send mach_task_self to the parent process so that it can use the port to
  // create ProcessMetrics.
  struct : mach_msg_base_t {
    mach_msg_port_descriptor_t task_port;
  } msg{};
  msg.header.msgh_bits =
      MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) | MACH_MSGH_BITS_COMPLEX;
  msg.header.msgh_remote_port = send_port.get();
  msg.header.msgh_size = sizeof(msg);
  msg.body.msgh_descriptor_count = 1;
  msg.task_port.name = mach_task_self();
  msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND;
  msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR;
  kern_return_t kr =
      mach_msg(&msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0,
               MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
  MACH_CHECK(kr == KERN_SUCCESS, kr);
}

#else

AssertionResult TestChildLauncher::SpawnChildProcess(
    const std::string& procname) {}

std::unique_ptr<ProcessMetrics> TestChildLauncher::CreateChildProcessMetrics() {}

bool TestChildLauncher::TerminateChildProcess() {}

// static
void TestChildLauncher::NotifyParent() {}

#endif  // BUILDFLAG(IS_MAC)

}  // namespace

// Tests for SystemMetrics.
// Exists as a class so it can be a friend of SystemMetrics.
class SystemMetricsTest : public testing::Test {};

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
TEST_F(SystemMetricsTest, IsValidDiskName) {}

TEST_F(SystemMetricsTest, ParseMeminfo) {}

TEST_F(SystemMetricsTest, ParseVmstat) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
        // BUILDFLAG(IS_ANDROID)

#if ENABLE_CPU_TESTS
// Test that ProcessMetrics::GetPlatformIndependentCPUUsage() doesn't return
// negative values when the number of threads running on the process decreases
// between two successive calls to it.
TEST_F(SystemMetricsTest, TestNoNegativeCpuUsage) {}

#if !BUILDFLAG(IS_APPLE)

// Subprocess to test the child CPU usage.
MULTIPROCESS_TEST_MAIN(CPUUsageChildMain) {}

TEST_F(SystemMetricsTest, MeasureChildCpuUsage) {}

#endif  // !BUILDFLAG(IS_APPLE)

TEST_F(SystemMetricsTest, InvalidProcessCpuUsage) {}

#endif  // ENABLE_CPU_TESTS

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(SystemMetricsTest, ParseZramMmStat) {
  SwapInfo swapinfo;

  const char invalid_input1[] = "aaa";
  const char invalid_input2[] = "1 2 3 4 5 6";
  const char invalid_input3[] = "a 2 3 4 5 6 7";
  EXPECT_FALSE(ParseZramMmStat(invalid_input1, &swapinfo));
  EXPECT_FALSE(ParseZramMmStat(invalid_input2, &swapinfo));
  EXPECT_FALSE(ParseZramMmStat(invalid_input3, &swapinfo));

  const char valid_input1[] =
      "17715200 5008166 566062  0 1225715712  127 183842";
  EXPECT_TRUE(ParseZramMmStat(valid_input1, &swapinfo));
  EXPECT_EQ(17715200ULL, swapinfo.orig_data_size);
  EXPECT_EQ(5008166ULL, swapinfo.compr_data_size);
  EXPECT_EQ(566062ULL, swapinfo.mem_used_total);
}

TEST_F(SystemMetricsTest, ParseZramStat) {
  SwapInfo swapinfo;

  const char invalid_input1[] = "aaa";
  const char invalid_input2[] = "1 2 3 4 5 6 7 8 9 10";
  const char invalid_input3[] = "a 2 3 4 5 6 7 8 9 10 11";
  EXPECT_FALSE(ParseZramStat(invalid_input1, &swapinfo));
  EXPECT_FALSE(ParseZramStat(invalid_input2, &swapinfo));
  EXPECT_FALSE(ParseZramStat(invalid_input3, &swapinfo));

  const char valid_input1[] =
      "299    0    2392    0    1    0    8    0    0    0    0";
  EXPECT_TRUE(ParseZramStat(valid_input1, &swapinfo));
  EXPECT_EQ(299ULL, swapinfo.num_reads);
  EXPECT_EQ(1ULL, swapinfo.num_writes);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
TEST(SystemMetrics2Test, GetSystemMemoryInfo) {}
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
TEST(ProcessMetricsTest, ParseProcStatCPU) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
        // BUILDFLAG(IS_ANDROID)

// Disable on Android because base_unittests runs inside a Dalvik VM that
// starts and stop threads (crbug.com/175563).
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// http://crbug.com/396455
TEST(ProcessMetricsTest, DISABLED_GetNumberOfThreads) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
namespace {

// Keep these in sync so the GetChildOpenFdCount test can refer to correct test
// main.
#define ChildMain
#define ChildMainString

// Command line flag name and file name used for synchronization.
const char kTempDirFlag[] =;

const char kSignalReady[] =;
const char kSignalReadyAck[] =;
const char kSignalOpened[] =;
const char kSignalOpenedAck[] =;
const char kSignalClosed[] =;

const int kChildNumFilesToOpen =;

bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {}

// Check whether an event was signaled.
bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {}

// Busy-wait for an event to be signaled.
void WaitForEvent(const FilePath& signal_dir, const char* signal_file) {}

// Subprocess to test the number of open file descriptors.
MULTIPROCESS_TEST_MAIN(ChildMain) {}

}  // namespace

TEST(ProcessMetricsTest, GetChildOpenFdCount) {}

TEST(ProcessMetricsTest, GetOpenFdCount) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)

TEST(ProcessMetricsTestLinux, GetPageFaultCounts) {}

TEST(ProcessMetricsTestLinux, GetCumulativeCPUUsagePerThread) {}
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS)

}  // namespace base::debug