chromium/ash/public/cpp/accelerator_actions_unittest.cc

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

#include "ash/public/cpp/accelerator_actions.h"

#include "ash/constants/ash_switches.h"
#include "ash/test/ash_test_base.h"
#include "base/command_line.h"
#include "base/containers/fixed_flat_map.h"
#include "base/hash/md5.h"
#include "base/hash/md5_boringssl.h"
#include "base/test/metrics/histogram_enum_reader.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

// The total number of accelerator actions.
constexpr int kAcceleratorActionsTotalNum = 166;
// The toal number of debug accelerators, these will not be used for hashing.
constexpr int kDebugAcceleratorActionsNum = 28;
// The hash of accelerator actions. Please update this when adding a new
// accelerator action.
constexpr char kAcceleratorActionsHash[] = "af26f45ccfb2450d29171e3b3697a8dd";

// Define the mapping between an AcceleratorAction and its string name.
// Example:
//   AcceleratorAction::kDevToggleUnifiedDesktop -> "DevToggleUnifiedDesktop".
constexpr static auto kAcceleratorActionToName =
    base::MakeFixedFlatMap<AcceleratorAction, const char*>({
#define ACCELERATOR_ACTION_ENTRY(action) \
  {AcceleratorAction::k##action, #action},
#define ACCELERATOR_ACTION_ENTRY_FIXED_VALUE(action, value) \
  {AcceleratorAction::k##action, #action},
        ACCELERATOR_ACTIONS
#undef ACCELERATOR_ACTION_ENTRY
#undef ACCELERATOR_ACTION_ENTRY_FIXED_VALUE
    });

struct TestParams {
  bool use_debug_shortcuts = false;
  bool use_dev_shortcuts = false;
};

class AcceleratorActionsTest
    : public AshTestBase,
      public ::testing::WithParamInterface<TestParams> {
 public:
  AcceleratorActionsTest() = default;

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

  ~AcceleratorActionsTest() override = default;

  // AshTestBase:
  void SetUp() override {
    const TestParams& params = GetParam();
    if (params.use_debug_shortcuts) {
      base::CommandLine::ForCurrentProcess()->AppendSwitch(
          switches::kAshDebugShortcuts);
    }
    if (params.use_dev_shortcuts) {
      base::CommandLine::ForCurrentProcess()->AppendSwitch(
          switches::kAshDeveloperShortcuts);
    }
    AshTestBase::SetUp();
  }
};

}  // namespace

// Tests that the AcceleratorAction enum in enums.xml exactly matches the
// AcceleratorAction enum in C++ file.
TEST_P(AcceleratorActionsTest, CheckHistogramEnum) {
  const auto enums =
      base::ReadEnumFromEnumsXml("AcceleratorAction", "chromeos");
  ASSERT_TRUE(enums);
  // The number of enums in the histogram entry should be equal to the number of
  // enums in the C++ file.
  EXPECT_EQ(enums->size(), kAcceleratorActionToName.size());

  for (const auto& entry : *enums) {
    // Check that the C++ file has a definition equal to the histogram file.
    EXPECT_EQ(entry.second, kAcceleratorActionToName.find(entry.first)->second)
        << "Enum entry name: " << entry.second
        << " in enums.xml is different from enum entry name: "
        << kAcceleratorActionToName.find(entry.first)->second << " in C++ file";
  }
}

TEST_P(AcceleratorActionsTest, AcceleratorActionsHash) {
  const char kCommonMessage[] =
      "If you are adding a non-debug accelerator action, please add "
      "the new action to be bottom of the enums but before "
      "DEBUG accelerator actions. \n"
      "Please update the values `kAcceleratorActionsTotalNum` and "
      "`kDebugAcceleratorActionsNum` (if applicable).";

  // First check that the size of the enum is correct.
  const int current_actions_size = kAcceleratorActionToName.size();
  EXPECT_EQ(current_actions_size, kAcceleratorActionsTotalNum)
      << kCommonMessage;

  // Then check that the hash is correct.
  base::MD5Context context;
  base::MD5Init(&context);
  int iter_count = 0;
  for (const auto iter : kAcceleratorActionToName) {
    base::MD5Update(&context, iter.second);
    // Only hash up non-debug accelerator actions.
    if (++iter_count >= current_actions_size - kDebugAcceleratorActionsNum) {
      break;
    }
  }

  base::MD5Digest digest;
  base::MD5Final(&digest, &context);
  const std::string current_hash = MD5DigestToBase16(digest);

  EXPECT_EQ(current_hash, kAcceleratorActionsHash)
      << kCommonMessage << " Please update kAcceleratorActionsHash to: \n"
      << current_hash << "\n";
}

INSTANTIATE_TEST_SUITE_P(
    All,
    AcceleratorActionsTest,
    ::testing::Values(TestParams{false, false},  // No shortcuts
                      TestParams{true, false},   // Debug shortcuts only
                      TestParams{false, true},   // Dev shortcuts only
                      TestParams{true, true}     // Both shortcuts
                      ));

}  // namespace ash