chromium/ash/components/arc/session/arc_dlc_installer_unittest.cc

// Copyright 2021 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/components/arc/session/arc_dlc_installer.h"

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

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace arc {

namespace {
enum class Action { EnableDlc, DisableDlc };

struct EnableDisableTestData {
  const char* dlc_error;
  ArcDlcInstaller::InstallerState state_to_be_set;
  size_t callback_count;
  Action action;
  size_t dlc_count;
  ArcDlcInstaller::InstallerState expected_state;
  bool is_dlc_enabled;
};

constexpr EnableDisableTestData enable_disable_test_data[] = {
    // Tests that DLC is installed when install error is kErrorNone.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kUninstalled, 1,
     Action::EnableDlc, 1, ArcDlcInstaller::InstallerState::kInstalled, true},
    // Tests that DLC is not installed when install error is kErrorInvalidDlc.
    {dlcservice::kErrorInvalidDlc,
     ArcDlcInstaller::InstallerState::kUninstalled, 1, Action::EnableDlc, 1,
     ArcDlcInstaller::InstallerState::kUninstalled, true},
    // Tests that DLC is not installed when install error is kErrorNeedReboot.
    {dlcservice::kErrorNeedReboot,
     ArcDlcInstaller::InstallerState::kUninstalled, 1, Action::EnableDlc, 1,
     ArcDlcInstaller::InstallerState::kUninstalled, true},
    // Tests that DLC is not installed when install error is kErrorAllocation.
    {dlcservice::kErrorAllocation,
     ArcDlcInstaller::InstallerState::kUninstalled, 1, Action::EnableDlc, 1,
     ArcDlcInstaller::InstallerState::kUninstalled, true},
    // Tests that DLC is not installed when install error is kErrorNoImageFound.
    {dlcservice::kErrorNoImageFound,
     ArcDlcInstaller::InstallerState::kUninstalled, 1, Action::EnableDlc, 1,
     ArcDlcInstaller::InstallerState::kUninstalled, true},
    // Tests that DLC is not installed when install error is kErrorInternal.
    {dlcservice::kErrorInternal, ArcDlcInstaller::InstallerState::kUninstalled,
     1, Action::EnableDlc, 1, ArcDlcInstaller::InstallerState::kUninstalled,
     true},
    // Tests that DLC is not installed when install error is kErrorBusy.
    {dlcservice::kErrorBusy, ArcDlcInstaller::InstallerState::kUninstalled, 1,
     Action::EnableDlc, 1, ArcDlcInstaller::InstallerState::kUninstalled, true},
    // Tests that DLC is not installed when current state is kUninstalling.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kUninstalling, 0,
     Action::EnableDlc, 0, ArcDlcInstaller::InstallerState::kUninstalling,
     true},
    // Tests that DLC is not installed when current state is kInstalling.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kInstalling, 0,
     Action::EnableDlc, 0, ArcDlcInstaller::InstallerState::kInstalling, true},

    // Tests that DLC is uninstalled when install error is kErrorNone.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kInstalled, 1,
     Action::DisableDlc, 0, ArcDlcInstaller::InstallerState::kUninstalled,
     false},
    // Tests that DLC is not uninstalled when install error is kErrorInternal.
    {dlcservice::kErrorInternal, ArcDlcInstaller::InstallerState::kInstalled, 1,
     Action::DisableDlc, 0, ArcDlcInstaller::InstallerState::kInstalled, false},
    // Tests that DLC is not uninstalled when current state is kUninstalling.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kUninstalling, 0,
     Action::DisableDlc, 0, ArcDlcInstaller::InstallerState::kUninstalling,
     false},
    // Tests that DLC is not uninstalled when current state is kInstalling.
    {dlcservice::kErrorNone, ArcDlcInstaller::InstallerState::kInstalling, 0,
     Action::DisableDlc, 0, ArcDlcInstaller::InstallerState::kInstalling,
     false},
};

struct CallbackTestData {
  ArcDlcInstaller::InstallerState state_to_be_set;
  int expect_num_callbacks;
};

constexpr CallbackTestData callback_test_data[] = {
    // Tests that callback is invoked immediately if current state is
    // kUninstalled.
    {ArcDlcInstaller::InstallerState::kUninstalled, 1},
    // Tests that callback is invoked immediately if current state is
    // kInstalled.
    {ArcDlcInstaller::InstallerState::kInstalled, 1},
    // Tests that callback is stored and not invoked if current state is
    // kUninstalling.
    {ArcDlcInstaller::InstallerState::kUninstalling, 0},
    // Tests that callback is stored and not invoked if current state is
    // kInstalling.
    {ArcDlcInstaller::InstallerState::kInstalling, 0},
};
}  // namespace

class ArcDlcInstallerTest : public testing::Test {
 protected:
  void SetUp() override {
    fake_dlc_client_.set_install_root_path("default_dlc_root_path_");
    arc_dlc_installer_ = std::make_unique<ArcDlcInstaller>();
  }

  void TearDown() override { arc_dlc_installer_.reset(); }

  // Get installed DLC names from |fake_dlc_client_|.
  std::vector<std::string> GetInstalledDlcNames() {
    base::RunLoop run_loop;

    std::vector<std::string> dlc_list;
    fake_dlc_client_.GetExistingDlcs(base::BindOnce(
        [](base::OnceClosure quit, std::vector<std::string>* dlc_list,
           std::string_view err,
           const dlcservice::DlcsWithContent& dlcs_with_content) {
          for (const auto& dlc : dlcs_with_content.dlc_infos()) {
            dlc_list->push_back(dlc.id());
          }
          std::move(quit).Run();
        },
        run_loop.QuitClosure(), &dlc_list));

    run_loop.Run();
    return dlc_list;
  }

  ash::FakeDlcserviceClient fake_dlc_client_;
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ArcDlcInstaller> arc_dlc_installer_;
};

// ArcDlcInstallerEnableDisableTest tests that ArcDlcInstaller's
// RequestEnable() and RequestDisable() transit to the correct
// states under different scenarios.
class ArcDlcInstallerEnableDisableTest
    : public ArcDlcInstallerTest,
      public ::testing::WithParamInterface<EnableDisableTestData> {};

TEST_P(ArcDlcInstallerEnableDisableTest, EnableDisableTest) {
  if (GetParam().action == Action::EnableDlc) {
    fake_dlc_client_.set_install_error(GetParam().dlc_error);
  } else {
    fake_dlc_client_.set_uninstall_error(GetParam().dlc_error);
  }
  arc_dlc_installer_->SetStateForTesting(GetParam().state_to_be_set);
  base::MockCallback<base::OnceClosure> callback;
  EXPECT_CALL(callback, Run()).Times(GetParam().callback_count);
  arc_dlc_installer_->WaitForStableState(callback.Get());

  if (GetParam().action == Action::EnableDlc) {
    arc_dlc_installer_->RequestEnable();
  } else {
    arc_dlc_installer_->RequestDisable();
  }
  base::RunLoop().RunUntilIdle();

  std::vector<std::string> dlc_list = GetInstalledDlcNames();
  EXPECT_EQ(dlc_list.size(), GetParam().dlc_count);
  for (const auto& dlc : dlc_list) {
    EXPECT_EQ(dlc, kHoudiniRvcDlc);
  }

  EXPECT_EQ(arc_dlc_installer_->GetStateForTesting(),
            GetParam().expected_state);
  EXPECT_EQ(arc_dlc_installer_->GetIsDlcEnabledForTesting(),
            GetParam().is_dlc_enabled);
}

INSTANTIATE_TEST_SUITE_P(ArcDlcInstallerEnableDisableTest,
                         ArcDlcInstallerEnableDisableTest,
                         ::testing::ValuesIn(enable_disable_test_data));

// ArcDlcInstallerCallbackTest tests that the callback passed
// into WaitForStableState() is invoked at the appropriate
// timing.
class ArcDlcInstallerCallbackTest
    : public ArcDlcInstallerTest,
      public ::testing::WithParamInterface<CallbackTestData> {};

TEST_P(ArcDlcInstallerCallbackTest, CallbackTest) {
  arc_dlc_installer_->SetStateForTesting(GetParam().state_to_be_set);
  base::MockCallback<base::OnceClosure> callback;
  EXPECT_CALL(callback, Run()).Times(GetParam().expect_num_callbacks);
  arc_dlc_installer_->WaitForStableState(callback.Get());
}

INSTANTIATE_TEST_SUITE_P(ArcDlcInstallerCallbackTest,
                         ArcDlcInstallerCallbackTest,
                         ::testing::ValuesIn(callback_test_data));

// Tests that installation, followed by uninstallation, are both successful.
TEST_F(ArcDlcInstallerTest, TestInstallAndUninstallSuccess) {
  fake_dlc_client_.set_install_error(dlcservice::kErrorNone);
  fake_dlc_client_.set_uninstall_error(dlcservice::kErrorNone);

  arc_dlc_installer_->RequestEnable();
  base::RunLoop().RunUntilIdle();

  std::vector<std::string> dlc_list_before = GetInstalledDlcNames();
  EXPECT_EQ(dlc_list_before.size(), 1ul);
  EXPECT_EQ(dlc_list_before[0], kHoudiniRvcDlc);

  EXPECT_EQ(arc_dlc_installer_->GetStateForTesting(),
            ArcDlcInstaller::InstallerState::kInstalled);
  EXPECT_EQ(arc_dlc_installer_->GetIsDlcEnabledForTesting(), true);

  arc_dlc_installer_->RequestDisable();
  base::RunLoop().RunUntilIdle();

  std::vector<std::string> dlc_list_after = GetInstalledDlcNames();
  EXPECT_EQ(dlc_list_after.size(), 0ul);

  EXPECT_EQ(arc_dlc_installer_->GetStateForTesting(),
            ArcDlcInstaller::InstallerState::kUninstalled);
  EXPECT_EQ(arc_dlc_installer_->GetIsDlcEnabledForTesting(), false);
}

}  // namespace arc