chromium/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_info_metric_sampler_browsertest.cc

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

#include <array>
#include <optional>

#include "base/functional/bind.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_info_metric_sampler_test_utils.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h"
#include "chromeos/dbus/missive/missive_client_test_observer.h"
#include "components/reporting/proto/synced/metric_data.pb.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/mock_clock.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace reporting {
namespace {

namespace cros_healthd = ::ash::cros_healthd::mojom;

using ::chromeos::MissiveClientTestObserver;
using ::reporting::Destination;
using ::reporting::MetricData;
using ::reporting::Priority;
using ::reporting::Record;
using ::testing::Eq;
using ::testing::StrEq;

// Is the given record about info metric? If yes, return the underlying
// MetricData object.
std::optional<MetricData> IsRecordInfo(const Record& record) {
  if (record.destination() != Destination::INFO_METRIC) {
    return std::nullopt;
  }

  MetricData record_data;
  EXPECT_TRUE(record_data.ParseFromString(record.data()));
  EXPECT_TRUE(record_data.has_info_data());
  return record_data;
}

void AssertRecordData(Priority priority, const Record& record) {
  EXPECT_THAT(priority, Eq(Priority::SLOW_BATCH));
  EXPECT_THAT(record.destination(), Eq(Destination::INFO_METRIC));
  ASSERT_TRUE(record.has_source_info());
  EXPECT_THAT(record.source_info().source(), Eq(SourceInfo::ASH));
}

}  // namespace

// ---- Bus ----

class BusInfoSamplerBrowserTest : public policy::DevicePolicyCrosBrowserTest {
 public:
  BusInfoSamplerBrowserTest(const BusInfoSamplerBrowserTest&) = delete;
  BusInfoSamplerBrowserTest& operator=(const BusInfoSamplerBrowserTest&) =
      delete;

 protected:
  BusInfoSamplerBrowserTest() { test::MockClock::Get(); }
  ~BusInfoSamplerBrowserTest() override = default;

  void SetUpOnMainThread() override {
    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ::ash::kReportDeviceSecurityStatus, true);
  }

  // Is the given record about Bus info metric?
  static bool IsRecordBusInfo(const Record& record) {
    auto record_data = IsRecordInfo(record);
    return record_data.has_value() &&
           record_data.value().info_data().has_bus_device_info();
  }

 private:
  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};

IN_PROC_BROWSER_TEST_F(BusInfoSamplerBrowserTest, Thunderbolt) {
  static constexpr std::array<::reporting::ThunderboltSecurityLevel, 2>
      kErpSecurityLevels = {::reporting::ThunderboltSecurityLevel::
                                THUNDERBOLT_SECURITY_NONE_LEVEL,
                            ::reporting::ThunderboltSecurityLevel::
                                THUNDERBOLT_SECURITY_SECURE_LEVEL};
  const std::vector<cros_healthd::ThunderboltSecurityLevel>
      kHealthdSecurityLevels = {
          cros_healthd::ThunderboltSecurityLevel::kNone,
          cros_healthd::ThunderboltSecurityLevel::kSecureLevel};
  auto thunderbolt_bus_result =
      ::reporting::test::CreateThunderboltBusResult(kHealthdSecurityLevels);
  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(thunderbolt_bus_result);
  MissiveClientTestObserver observer(base::BindRepeating(&IsRecordBusInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);
  auto [priority, record] = observer.GetNextEnqueuedRecord();
  AssertRecordData(priority, record);
  MetricData metric_data;
  ASSERT_TRUE(metric_data.ParseFromString(record.data()));
  EXPECT_TRUE(metric_data.has_timestamp_ms());
  ASSERT_TRUE(metric_data.has_info_data());
  const auto& info_data = metric_data.info_data();
  ASSERT_THAT(
      static_cast<size_t>(info_data.bus_device_info().thunderbolt_info_size()),
      Eq(kErpSecurityLevels.size()));
  for (size_t i = 0; i < kErpSecurityLevels.size(); ++i) {
    EXPECT_THAT(
        info_data.bus_device_info().thunderbolt_info(i).security_level(),
        Eq(kErpSecurityLevels[i]));
  }
}

// ---- CPU ----

class CpuInfoSamplerBrowserTest : public policy::DevicePolicyCrosBrowserTest {
 public:
  CpuInfoSamplerBrowserTest(const CpuInfoSamplerBrowserTest&) = delete;
  CpuInfoSamplerBrowserTest& operator=(const CpuInfoSamplerBrowserTest&) =
      delete;

 protected:
  CpuInfoSamplerBrowserTest() { test::MockClock::Get(); }
  ~CpuInfoSamplerBrowserTest() override = default;

  void SetUpOnMainThread() override {
    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ::ash::kReportDeviceCpuInfo, true);
  }

  // Is the given record about CPU info metric?
  static bool IsRecordCpuInfo(const Record& record) {
    auto record_data = IsRecordInfo(record);
    return record_data.has_value() &&
           record_data.value().info_data().has_cpu_info();
  }

 private:
  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};

IN_PROC_BROWSER_TEST_F(CpuInfoSamplerBrowserTest, KeylockerUnsupported) {
  auto cpu_result = ::reporting::test::CreateCpuResult(nullptr);
  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(cpu_result);
  MissiveClientTestObserver observer(base::BindRepeating(&IsRecordCpuInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);
  auto [priority, record] = observer.GetNextEnqueuedRecord();
  AssertRecordData(priority, record);
  MetricData metric_data;
  ASSERT_TRUE(metric_data.ParseFromString(record.data()));
  EXPECT_TRUE(metric_data.has_timestamp_ms());
  ASSERT_TRUE(metric_data.has_info_data());
  const auto& info_data = metric_data.info_data();
  ASSERT_TRUE(info_data.cpu_info().has_keylocker_info());
  EXPECT_FALSE(info_data.cpu_info().keylocker_info().configured());
  EXPECT_FALSE(info_data.cpu_info().keylocker_info().supported());
}

IN_PROC_BROWSER_TEST_F(CpuInfoSamplerBrowserTest, KeylockerConfigured) {
  auto cpu_result = ::reporting::test::CreateCpuResult(
      ::reporting::test::CreateKeylockerInfo(true));
  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(cpu_result);
  MissiveClientTestObserver observer(base::BindRepeating(&IsRecordCpuInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);
  auto [priority, record] = observer.GetNextEnqueuedRecord();
  AssertRecordData(priority, record);
  MetricData metric_data;
  ASSERT_TRUE(metric_data.ParseFromString(record.data()));
  EXPECT_TRUE(metric_data.has_timestamp_ms());
  ASSERT_TRUE(metric_data.has_info_data());
  const auto& info_data = metric_data.info_data();
  ASSERT_TRUE(info_data.cpu_info().has_keylocker_info());
  EXPECT_TRUE(info_data.cpu_info().keylocker_info().configured());
  EXPECT_TRUE(info_data.cpu_info().keylocker_info().supported());
}

// ---- Memory ----

// Memory constants.
static constexpr int64_t kTmeMaxKeys = 2;
static constexpr int64_t kTmeKeysLength = 4;

class MemoryInfoSamplerBrowserTest
    : public policy::DevicePolicyCrosBrowserTest,
      public testing::WithParamInterface<
          ::reporting::test::MemoryInfoTestCase> {
 public:
  MemoryInfoSamplerBrowserTest(const MemoryInfoSamplerBrowserTest&) = delete;
  MemoryInfoSamplerBrowserTest& operator=(const MemoryInfoSamplerBrowserTest&) =
      delete;

 protected:
  MemoryInfoSamplerBrowserTest() { test::MockClock::Get(); }
  ~MemoryInfoSamplerBrowserTest() override = default;

  void SetUpOnMainThread() override {
    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ::ash::kReportDeviceMemoryInfo, true);
  }

  // Is the given record about memory info metric?
  static bool IsRecordMemoryInfo(const Record& record) {
    auto record_data = IsRecordInfo(record);
    return record_data.has_value() &&
           record_data.value().info_data().has_memory_info();
  }

  static void AssertMemoryInfo(MissiveClientTestObserver* observer) {
    auto [priority, record] = observer->GetNextEnqueuedRecord();
    AssertRecordData(priority, record);
    MetricData metric_data;
    ASSERT_TRUE(metric_data.ParseFromString(record.data()));
    EXPECT_TRUE(metric_data.has_timestamp_ms());
    ASSERT_TRUE(metric_data.has_info_data());
    ::reporting::test::AssertMemoryInfo(metric_data, GetParam());
  }

 private:
  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};

IN_PROC_BROWSER_TEST_P(MemoryInfoSamplerBrowserTest, ReportMemoryInfo) {
  const auto& test_case = GetParam();
  auto memory_result = ::reporting::test::CreateMemoryResult(
      ::reporting::test::CreateMemoryEncryptionInfo(
          test_case.healthd_encryption_state, test_case.max_keys,
          test_case.key_length, test_case.healthd_encryption_algorithm));

  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(memory_result);
  MissiveClientTestObserver observer(base::BindRepeating(&IsRecordMemoryInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);
  AssertMemoryInfo(&observer);
}

INSTANTIATE_TEST_SUITE_P(
    MemoryInfoSamplerBrowserTests,
    MemoryInfoSamplerBrowserTest,
    testing::ValuesIn<::reporting::test::MemoryInfoTestCase>({
        {"UnknownEncryptionState", cros_healthd::EncryptionState::kUnknown,
         ::reporting::MEMORY_ENCRYPTION_STATE_UNKNOWN,
         cros_healthd::CryptoAlgorithm::kUnknown,
         ::reporting::MEMORY_ENCRYPTION_ALGORITHM_UNKNOWN, 0, 0},
        {"KeyValuesSet", cros_healthd::EncryptionState::kUnknown,
         ::reporting::MEMORY_ENCRYPTION_STATE_UNKNOWN,
         cros_healthd::CryptoAlgorithm::kUnknown,
         ::reporting::MEMORY_ENCRYPTION_ALGORITHM_UNKNOWN, kTmeMaxKeys,
         kTmeKeysLength},
    }),
    [](const testing::TestParamInfo<MemoryInfoSamplerBrowserTest::ParamType>&
           info) { return info.param.test_name; });

// ---- Input ----

class InputInfoSamplerBrowserTest : public policy::DevicePolicyCrosBrowserTest {
 public:
  InputInfoSamplerBrowserTest(const InputInfoSamplerBrowserTest&) = delete;
  InputInfoSamplerBrowserTest& operator=(const InputInfoSamplerBrowserTest&) =
      delete;

 protected:
  InputInfoSamplerBrowserTest() { test::MockClock::Get(); }
  ~InputInfoSamplerBrowserTest() override = default;

  void SetUpOnMainThread() override {
    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ::ash::kReportDeviceGraphicsStatus, true);
  }

  // Is the given record about touchscreen info metric?
  static bool IsRecordTouchScreenInfo(const Record& record) {
    auto record_data = IsRecordInfo(record);
    return record_data.has_value() &&
           record_data.value().info_data().has_touch_screen_info();
  }

 private:
  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};

IN_PROC_BROWSER_TEST_F(InputInfoSamplerBrowserTest,
                       TouchScreenMultipleInternal) {
  static constexpr char kSampleLibrary[] = "SampleLibrary";
  static constexpr char kSampleDevice[] = "SampleDevice";
  static constexpr char kSampleDevice2[] = "SampleDevice2";
  static constexpr int kTouchPoints = 10;
  static constexpr int kTouchPoints2 = 5;

  // Create the test result.
  auto input_device_first = cros_healthd::TouchscreenDevice::New(
      cros_healthd::InputDevice::New(
          kSampleDevice, cros_healthd::InputDevice_ConnectionType::kInternal,
          /*physical_location*/ "", /*is_enabled*/ true),
      kTouchPoints, /*has_stylus*/ true,
      /*has_stylus_garage_switch*/ false);
  auto input_device_second = cros_healthd::TouchscreenDevice::New(
      cros_healthd::InputDevice::New(
          kSampleDevice2, cros_healthd::InputDevice_ConnectionType::kInternal,
          /*physical_location*/ "", /*is_enabled*/ true),
      kTouchPoints2, /*has_stylus*/ false,
      /*has_stylus_garage_switch*/ false);
  std::vector<cros_healthd::TouchscreenDevicePtr> touchscreen_devices;
  touchscreen_devices.push_back(std::move(input_device_first));
  touchscreen_devices.push_back(std::move(input_device_second));
  auto input_result = ::reporting::test::CreateInputResult(
      kSampleLibrary, std::move(touchscreen_devices));
  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(input_result);
  MissiveClientTestObserver observer(
      base::BindRepeating(&IsRecordTouchScreenInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);

  // Assertions
  auto [priority, record] = observer.GetNextEnqueuedRecord();
  AssertRecordData(priority, record);
  MetricData metric_data;
  ASSERT_TRUE(metric_data.ParseFromString(record.data()));
  EXPECT_TRUE(metric_data.has_timestamp_ms());
  ASSERT_TRUE(metric_data.has_info_data());
  const auto& info_data = metric_data.info_data();
  ASSERT_TRUE(info_data.has_touch_screen_info());
  ASSERT_TRUE(info_data.touch_screen_info().has_library_name());
  EXPECT_THAT(info_data.touch_screen_info().library_name(),
              StrEq(kSampleLibrary));
  ASSERT_EQ(info_data.touch_screen_info().touch_screen_devices().size(), 2);
  EXPECT_THAT(
      info_data.touch_screen_info().touch_screen_devices(0).display_name(),
      StrEq(kSampleDevice));
  EXPECT_THAT(
      info_data.touch_screen_info().touch_screen_devices(0).touch_points(),
      Eq(kTouchPoints));
  EXPECT_TRUE(
      info_data.touch_screen_info().touch_screen_devices(0).has_stylus());
  EXPECT_THAT(
      info_data.touch_screen_info().touch_screen_devices(1).display_name(),
      StrEq(kSampleDevice2));
  EXPECT_THAT(
      info_data.touch_screen_info().touch_screen_devices(1).touch_points(),
      Eq(kTouchPoints2));
  EXPECT_FALSE(
      info_data.touch_screen_info().touch_screen_devices(1).has_stylus());
}

// ---- Display ----

class DisplayInfoSamplerBrowserTest
    : public policy::DevicePolicyCrosBrowserTest {
 public:
  DisplayInfoSamplerBrowserTest(const DisplayInfoSamplerBrowserTest&) = delete;
  DisplayInfoSamplerBrowserTest& operator=(
      const DisplayInfoSamplerBrowserTest&) = delete;

 protected:
  DisplayInfoSamplerBrowserTest() { test::MockClock::Get(); }
  ~DisplayInfoSamplerBrowserTest() override = default;

  void SetUpOnMainThread() override {
    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ::ash::kReportDeviceGraphicsStatus, true);
  }

  // Is the given record about display info metric?
  static bool IsRecordDisplayInfo(const Record& record) {
    auto record_data = IsRecordInfo(record);
    return record_data.has_value() &&
           record_data.value().info_data().has_display_info();
  }

 private:
  ::ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};

IN_PROC_BROWSER_TEST_F(DisplayInfoSamplerBrowserTest, MultipleDisplays) {
  static constexpr bool kPrivacyScreenSupported = false;
  static constexpr int kDisplayWidth = 1080;
  static constexpr int kDisplayHeight = 27282;
  static constexpr char kDisplayManufacture[] = "Samsung";
  static constexpr int kDisplayManufactureYear = 2020;
  static constexpr int kDisplayModelId = 54321;
  static constexpr char kExternalDisplayName[] = "External display";
  static constexpr char kInternalDisplayName[] = "Internal display";

  // Create display results
  std::vector<cros_healthd::ExternalDisplayInfoPtr> external_displays;
  external_displays.push_back(::reporting::test::CreateExternalDisplay(
      kDisplayWidth, kDisplayHeight, /*resolution_horizontal*/ 1000,
      /*resolution_vertical*/ 500, /*refresh_rate*/ 100, kDisplayManufacture,
      kDisplayModelId, kDisplayManufactureYear, kExternalDisplayName));
  external_displays.push_back(::reporting::test::CreateExternalDisplay(
      kDisplayWidth, kDisplayHeight, /*resolution_horizontal*/ 1000,
      /*resolution_vertical*/ 500, /*refresh_rate*/ 100, kDisplayManufacture,
      kDisplayModelId, kDisplayManufactureYear, kExternalDisplayName));
  auto display_result = ::reporting::test::CreateDisplayResult(
      ::reporting::test::CreateEmbeddedDisplay(
          kPrivacyScreenSupported, kDisplayWidth, kDisplayHeight,
          /*resolution_horizontal*/ 1000,
          /*resolution_vertical*/ 500, /*refresh_rate*/ 100,
          kDisplayManufacture, kDisplayModelId, kDisplayManufactureYear,
          kInternalDisplayName),
      std::move(external_displays));
  ::ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(display_result);
  MissiveClientTestObserver observer(base::BindRepeating(&IsRecordDisplayInfo));
  test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);

  // assertions
  auto [priority, record] = observer.GetNextEnqueuedRecord();
  AssertRecordData(priority, record);
  MetricData metric_data;
  ASSERT_TRUE(metric_data.ParseFromString(record.data()));
  EXPECT_TRUE(metric_data.has_timestamp_ms());
  ASSERT_TRUE(metric_data.has_info_data());
  const auto& info_data = metric_data.info_data();
  ASSERT_TRUE(info_data.has_display_info());
  ASSERT_EQ(info_data.display_info().display_device_size(), 3);

  ASSERT_TRUE(info_data.has_privacy_screen_info());
  ASSERT_FALSE(info_data.privacy_screen_info().supported());

  auto internal_display = info_data.display_info().display_device(0);
  EXPECT_EQ(internal_display.display_name(), kInternalDisplayName);
  EXPECT_EQ(internal_display.manufacturer(), kDisplayManufacture);
  EXPECT_EQ(internal_display.display_width(), kDisplayWidth);
  EXPECT_EQ(internal_display.display_height(), kDisplayHeight);
  EXPECT_EQ(internal_display.model_id(), kDisplayModelId);
  EXPECT_EQ(internal_display.manufacture_year(), kDisplayManufactureYear);

  auto external_display_1 = info_data.display_info().display_device(1);
  EXPECT_EQ(external_display_1.display_name(), kExternalDisplayName);
  EXPECT_EQ(external_display_1.manufacturer(), kDisplayManufacture);
  EXPECT_EQ(external_display_1.display_width(), kDisplayWidth);
  EXPECT_EQ(external_display_1.display_height(), kDisplayHeight);
  EXPECT_EQ(external_display_1.model_id(), kDisplayModelId);
  EXPECT_EQ(external_display_1.manufacture_year(), kDisplayManufactureYear);

  auto external_display_2 = info_data.display_info().display_device(2);
  EXPECT_EQ(external_display_2.display_name(), kExternalDisplayName);
  EXPECT_EQ(external_display_2.manufacturer(), kDisplayManufacture);
  EXPECT_EQ(external_display_2.display_width(), kDisplayWidth);
  EXPECT_EQ(external_display_2.display_height(), kDisplayHeight);
  EXPECT_EQ(external_display_2.model_id(), kDisplayModelId);
  EXPECT_EQ(external_display_2.manufacture_year(), kDisplayManufactureYear);
}
}  // namespace reporting