chromium/chromeos/ash/components/sparky/system_info_delegate_impl_unittest.cc

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

#include "chromeos/ash/components/sparky/system_info_delegate_impl.h"

#include <memory>

#include "base/system/sys_info.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/mojo_service_manager/fake_mojo_service_manager.h"
#include "chromeos/ash/components/system_info/cpu_usage_data.h"
#include "chromeos/ash/components/system_info/system_info_util.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "components/manta/sparky/system_info_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sparky {
namespace {

namespace healthd_mojom = ash::cros_healthd::mojom;

void SetProbeTelemetryInfoResponse(healthd_mojom::BatteryInfoPtr battery_info,
                                   healthd_mojom::CpuInfoPtr cpu_info,
                                   healthd_mojom::MemoryInfoPtr memory_info) {
  auto info = healthd_mojom::TelemetryInfo::New();
  if (battery_info) {
    info->battery_result =
        healthd_mojom::BatteryResult::NewBatteryInfo(std::move(battery_info));
  }
  if (memory_info) {
    info->memory_result =
        healthd_mojom::MemoryResult::NewMemoryInfo(std::move(memory_info));
  }
  if (cpu_info) {
    info->cpu_result =
        healthd_mojom::CpuResult::NewCpuInfo(std::move(cpu_info));
  }

  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(info);
}

ash::cros_healthd::mojom::CpuInfoPtr GetCpuResponse(
    const std::vector<system_info::CpuUsageData>& usage_data,
    const std::vector<int32_t>& cpu_temps,
    const std::vector<uint32_t>& scaled_cpu_clock_speed) {
  auto cpu_info_ptr = healthd_mojom::CpuInfo::New();
  auto physical_cpu_info_ptr = healthd_mojom::PhysicalCpuInfo::New();

  DCHECK_EQ(usage_data.size(), scaled_cpu_clock_speed.size());
  for (size_t i = 0; i < usage_data.size(); ++i) {
    const auto& data = usage_data[i];
    auto logical_cpu_info_ptr = healthd_mojom::LogicalCpuInfo::New();

    logical_cpu_info_ptr->user_time_user_hz = data.GetUserTime();
    logical_cpu_info_ptr->system_time_user_hz = data.GetSystemTime();
    logical_cpu_info_ptr->idle_time_user_hz = data.GetIdleTime();

    logical_cpu_info_ptr->scaling_current_frequency_khz =
        scaled_cpu_clock_speed[i];

    physical_cpu_info_ptr->logical_cpus.emplace_back(
        std::move(logical_cpu_info_ptr));
  }

  cpu_info_ptr->physical_cpus.push_back(std::move(physical_cpu_info_ptr));
  for (const auto& cpu_temp : cpu_temps) {
    auto cpu_temp_channel_ptr = healthd_mojom::CpuTemperatureChannel::New();
    cpu_temp_channel_ptr->temperature_celsius = cpu_temp;
    cpu_info_ptr->temperature_channels.emplace_back(
        std::move(cpu_temp_channel_ptr));
  }
  return cpu_info_ptr;
}

void SetCrosHealthdCpuResponse(
    const std::vector<system_info::CpuUsageData>& usage_data,
    const std::vector<int32_t>& cpu_temps,
    const std::vector<uint32_t>& scaled_cpu_clock_speed) {
  auto cpu_info_ptr =
      GetCpuResponse(usage_data, cpu_temps, scaled_cpu_clock_speed);

  SetProbeTelemetryInfoResponse(/*battery_info=*/nullptr,
                                std::move(cpu_info_ptr),
                                /*memory_info=*/nullptr);
}

void SetCrosHealthdMemoryUsageResponse(uint32_t total_memory_kib,
                                       uint32_t free_memory_kib,
                                       uint32_t available_memory_kib) {
  healthd_mojom::MemoryInfoPtr memory_info = healthd_mojom::MemoryInfo::New(
      total_memory_kib, free_memory_kib, available_memory_kib,
      /*page_faults_since_last_boot=*/0);
  SetProbeTelemetryInfoResponse(/*battery_info=*/nullptr, /*cpu_info=*/nullptr,
                                /*memory_info=*/std::move(memory_info));
}

// Constructs a BatteryInfoPtr.
healthd_mojom::BatteryInfoPtr CreateCrosHealthdBatteryHealthResponse(
    double charge_full_now,
    double charge_full_design,
    int32_t cycle_count) {
  healthd_mojom::NullableUint64Ptr temp_value_ptr(
      healthd_mojom::NullableUint64::New());
  auto battery_info = healthd_mojom::BatteryInfo::New(
      /*cycle_count=*/cycle_count, /*voltage_now=*/0,
      /*vendor=*/"",
      /*serial_number=*/"", /*charge_full_design=*/charge_full_design,
      /*charge_full=*/charge_full_now,
      /*voltage_min_design=*/0,
      /*model_name=*/"",
      /*charge_now=*/0,
      /*current_now=*/0,
      /*technology=*/"",
      /*status=*/"",
      /*manufacture_date=*/std::nullopt, std::move(temp_value_ptr));
  return battery_info;
}

void SetCrosHealthdBatteryHealthResponse(double charge_full_now,
                                         double charge_full_design,
                                         int32_t cycle_count) {
  healthd_mojom::BatteryInfoPtr battery_info =
      CreateCrosHealthdBatteryHealthResponse(charge_full_now,
                                             charge_full_design, cycle_count);
  SetProbeTelemetryInfoResponse(std::move(battery_info),
                                /*cpu_info=*/nullptr,
                                /*memory_info=*/nullptr);
}

bool AreValidPowerTimes(int64_t time_to_full, int64_t time_to_empty) {
  // Exactly one of `time_to_full` or `time_to_empty` must be zero. The other
  // can be a positive integer to represent the time to charge/discharge or -1
  // to represent that the time is being calculated.
  return (time_to_empty == 0 && (time_to_full > 0 || time_to_full == -1)) ||
         (time_to_full == 0 && (time_to_empty > 0 || time_to_empty == -1));
}

power_manager::PowerSupplyProperties ConstructPowerSupplyProperties(
    power_manager::PowerSupplyProperties::ExternalPower power_source,
    power_manager::PowerSupplyProperties::BatteryState battery_state,
    bool is_calculating_battery_time,
    int64_t time_to_full,
    int64_t time_to_empty,
    double battery_percent) {
  power_manager::PowerSupplyProperties props;
  props.set_external_power(power_source);
  props.set_battery_state(battery_state);

  if (battery_state ==
      power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT) {
    // Leave `time_to_full` and `time_to_empty` unset.
    return props;
  }

  DCHECK(AreValidPowerTimes(time_to_full, time_to_empty));

  props.set_is_calculating_battery_time(is_calculating_battery_time);
  props.set_battery_time_to_full_sec(time_to_full);
  props.set_battery_time_to_empty_sec(time_to_empty);
  props.set_battery_percent(battery_percent);

  return props;
}

// Sets the PowerSupplyProperties on FakePowerManagerClient. Calling this
// method immediately notifies PowerManagerClient observers. One of
// `time_to_full` or `time_to_empty` must be either -1 or a positive number.
// The other must be 0. If `battery_state` is NOT_PRESENT, both `time_to_full`
// and `time_to_empty` will be left unset.
void SetPowerManagerProperties(
    power_manager::PowerSupplyProperties::ExternalPower power_source,
    power_manager::PowerSupplyProperties::BatteryState battery_state,
    bool is_calculating_battery_time,
    int64_t time_to_full,
    int64_t time_to_empty,
    double battery_percent) {
  power_manager::PowerSupplyProperties props = ConstructPowerSupplyProperties(
      power_source, battery_state, is_calculating_battery_time, time_to_full,
      time_to_empty, battery_percent);
  chromeos::FakePowerManagerClient::Get()->UpdatePowerProperties(props);
}

}  // namespace

class SystemInfoDelegateImplTest : public testing::Test {
 public:
  SystemInfoDelegateImplTest() = default;

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

  ~SystemInfoDelegateImplTest() override = default;

  void SetUp() override {
    chromeos::PowerManagerClient::InitializeFake();
    ash::cros_healthd::FakeCrosHealthd::Initialize();
    system_info_delegate_impl_ = std::make_unique<SystemInfoDelegateImpl>();
    Wait();
  }

  void TearDown() override {
    ash::cros_healthd::FakeCrosHealthd::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
  }

  void Wait() { task_environment_.RunUntilIdle(); }

  void OnDiagnosticsReceived(
      std::unique_ptr<manta::DiagnosticsData> diagnostics_data) {}

 protected:
  base::test::TaskEnvironment task_environment_;
  ::ash::mojo_service_manager::FakeMojoServiceManager fake_service_manager_;
  std::unique_ptr<SystemInfoDelegateImpl> system_info_delegate_impl_;
};

TEST_F(SystemInfoDelegateImplTest, Memory) {
  const uint32_t total_memory_kib = 8000000;
  const uint32_t free_memory_kib = 2000000;
  const uint32_t available_memory_kib = 4000000;

  SetCrosHealthdMemoryUsageResponse(total_memory_kib, free_memory_kib,
                                    available_memory_kib);
  Wait();
  auto quit_closure = task_environment_.QuitClosure();

  system_info_delegate_impl_->ObtainDiagnostics(
      {manta::Diagnostics::kMemory},
      base::BindLambdaForTesting(
          [&quit_closure,
           this](std::unique_ptr<manta::DiagnosticsData> diagnostics_data) {
            Wait();
            ASSERT_TRUE(diagnostics_data->memory_data);
            ASSERT_DOUBLE_EQ(diagnostics_data->memory_data->available_memory_gb,
                             3.814697265625);
            ASSERT_DOUBLE_EQ(diagnostics_data->memory_data->total_memory_gb,
                             7.62939453125);
            quit_closure.Run();
          }));
  task_environment_.RunUntilQuit();
}

TEST_F(SystemInfoDelegateImplTest, CPU) {
  int temp_1 = 40;
  int temp_2 = 50;
  int temp_3 = 15;
  uint32_t core_1_speed = 4000000;
  uint32_t core_2_speed = 2000000;
  system_info::CpuUsageData core_1(1000, 1000, 1000);
  system_info::CpuUsageData core_2(2000, 2000, 2000);

  SetCrosHealthdCpuResponse({core_1, core_2}, {temp_1, temp_2, temp_3},
                            {core_1_speed, core_2_speed});
  Wait();
  auto quit_closure = task_environment_.QuitClosure();

  system_info_delegate_impl_->ObtainDiagnostics(
      {manta::Diagnostics::kCpu},
      base::BindLambdaForTesting(
          [&quit_closure,
           this](std::unique_ptr<manta::DiagnosticsData> diagnostics_data) {
            Wait();
            ASSERT_TRUE(diagnostics_data->cpu_data);
            ASSERT_EQ(diagnostics_data->cpu_data->average_cpu_temp_celsius, 35);
            ASSERT_EQ(diagnostics_data->cpu_data->scaling_current_frequency_ghz,
                      3.0);
            quit_closure.Run();
          }));
  task_environment_.RunUntilQuit();
}

TEST_F(SystemInfoDelegateImplTest, Battery) {
  const double charge_full_now = 20;
  const double charge_full_design = 26;
  const int32_t cycle_count = 500;

  SetCrosHealthdBatteryHealthResponse(charge_full_now, charge_full_design,
                                      cycle_count);

  const auto power_source =
      power_manager::PowerSupplyProperties_ExternalPower_AC;
  const auto battery_state =
      power_manager::PowerSupplyProperties_BatteryState_CHARGING;
  const bool is_calculating_battery_time = false;
  const int64_t time_to_full_secs = 1000;
  const int64_t time_to_empty_secs = 0;
  const double battery_percent = 94.0;

  SetPowerManagerProperties(power_source, battery_state,
                            is_calculating_battery_time, time_to_full_secs,
                            time_to_empty_secs, battery_percent);
  Wait();
  auto quit_closure = task_environment_.QuitClosure();

  system_info_delegate_impl_->ObtainDiagnostics(
      {manta::Diagnostics::kBattery},
      base::BindLambdaForTesting(
          [&quit_closure,
           this](std::unique_ptr<manta::DiagnosticsData> diagnostics_data) {
            Wait();
            ASSERT_TRUE(diagnostics_data->battery_data);
            ASSERT_EQ(diagnostics_data->battery_data->battery_percentage, 94);
            ASSERT_EQ(diagnostics_data->battery_data->battery_wear_percentage,
                      76);
            ASSERT_EQ(diagnostics_data->battery_data->cycle_count, 500);
            ASSERT_EQ(diagnostics_data->battery_data->power_time,
                      "17 minutes until full");
            quit_closure.Run();
          }));
  task_environment_.RunUntilQuit();
}

TEST_F(SystemInfoDelegateImplTest, MultipleFields) {
  const uint32_t total_memory_kib = 8000000;
  const uint32_t free_memory_kib = 2000000;
  const uint32_t available_memory_kib = 4000000;

  int temp_1 = 40;
  int temp_2 = 50;
  int temp_3 = 15;
  uint32_t core_1_speed = 4000000;
  uint32_t core_2_speed = 2000000;
  system_info::CpuUsageData core_1(1000, 1000, 1000);
  system_info::CpuUsageData core_2(2000, 2000, 2000);

  healthd_mojom::MemoryInfoPtr memory_info = healthd_mojom::MemoryInfo::New(
      total_memory_kib, free_memory_kib, available_memory_kib,
      /*page_faults_since_last_boot=*/0);
  auto cpu_info = GetCpuResponse({core_1, core_2}, {temp_1, temp_2, temp_3},
                                 {core_1_speed, core_2_speed});
  const double charge_full_now = 20;

  const double charge_full_design = 26;
  const int32_t cycle_count = 500;

  healthd_mojom::BatteryInfoPtr battery_info =
      CreateCrosHealthdBatteryHealthResponse(charge_full_now,
                                             charge_full_design, cycle_count);

  SetProbeTelemetryInfoResponse(std::move(battery_info), std::move(cpu_info),
                                std::move(memory_info));

  Wait();

  const auto power_source =
      power_manager::PowerSupplyProperties_ExternalPower_AC;
  const auto battery_state =
      power_manager::PowerSupplyProperties_BatteryState_CHARGING;
  const bool is_calculating_battery_time = false;
  const int64_t time_to_full_secs = 1000;
  const int64_t time_to_empty_secs = 0;
  const double battery_percent = 94.0;

  SetPowerManagerProperties(power_source, battery_state,
                            is_calculating_battery_time, time_to_full_secs,
                            time_to_empty_secs, battery_percent);
  Wait();
  auto quit_closure = task_environment_.QuitClosure();

  system_info_delegate_impl_->ObtainDiagnostics(
      {manta::Diagnostics::kMemory, manta::Diagnostics::kCpu,
       manta::Diagnostics::kBattery},
      base::BindLambdaForTesting(
          [&quit_closure,
           this](std::unique_ptr<manta::DiagnosticsData> diagnostics_data) {
            Wait();
            ASSERT_TRUE(diagnostics_data->memory_data);
            ASSERT_DOUBLE_EQ(diagnostics_data->memory_data->available_memory_gb,
                             3.814697265625);
            ASSERT_DOUBLE_EQ(diagnostics_data->memory_data->total_memory_gb,
                             7.62939453125);
            ASSERT_TRUE(diagnostics_data->cpu_data);
            ASSERT_EQ(diagnostics_data->cpu_data->average_cpu_temp_celsius, 35);
            ASSERT_EQ(diagnostics_data->cpu_data->scaling_current_frequency_ghz,
                      3.0);
            ASSERT_TRUE(diagnostics_data->battery_data);
            ASSERT_EQ(diagnostics_data->battery_data->battery_percentage, 94);
            ASSERT_EQ(diagnostics_data->battery_data->battery_wear_percentage,
                      76);
            ASSERT_EQ(diagnostics_data->battery_data->cycle_count, 500);
            ASSERT_EQ(diagnostics_data->battery_data->power_time,
                      "17 minutes until full");
            quit_closure.Run();
          }));
  task_environment_.RunUntilQuit();
}

}  // namespace sparky