chromium/chrome/browser/ash/extensions/install_limiter_unittest.cc

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

#include "chrome/browser/ash/extensions/install_limiter.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_test_helper.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/mock_crx_installer.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/crx_file_info.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/verifier_formats.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using extensions::CrxInstaller;
using extensions::CrxInstallError;
using extensions::InstallLimiter;
using testing::_;
using testing::Field;
using testing::Invoke;
using testing::Mock;

namespace {

constexpr char kRandomExtensionId[] = "abacabadabacabaeabacabadabacabaf";

constexpr int kLargeExtensionSize = 2000000;
constexpr int kSmallExtensionSize = 200000;

constexpr char kLargeExtensionCrx[] = "large.crx";
constexpr char kSmallExtensionCrx[] = "small.crx";

constexpr base::TimeDelta kLessThanExpectedWaitTime = base::Seconds(4);
constexpr base::TimeDelta kTimeDeltaUntilExpectedWaitTime = base::Seconds(1);

}  // namespace

class InstallLimiterShouldDeferInstallTest
    : public testing::TestWithParam<ash::DemoSession::DemoModeConfig> {
 public:
  InstallLimiterShouldDeferInstallTest()
      : scoped_user_manager_(std::make_unique<ash::FakeChromeUserManager>()) {}

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

  ~InstallLimiterShouldDeferInstallTest() override = default;

 protected:
  ash::ScopedStubInstallAttributes test_install_attributes_;

 private:
  content::BrowserTaskEnvironment task_environment_;
  user_manager::ScopedUserManager scoped_user_manager_;
};

TEST_P(InstallLimiterShouldDeferInstallTest, ShouldDeferInstall) {
  const std::vector<std::string> screensaver_ids = {
      extension_misc::kScreensaverAppId, extension_misc::kNewAttractLoopAppId};

  ash::DemoModeTestHelper demo_mode_test_helper;
  if (GetParam() != ash::DemoSession::DemoModeConfig::kNone) {
    test_install_attributes_.Get()->SetDemoMode();
    demo_mode_test_helper.InitializeSession(GetParam());
  }

  // In demo mode, all apps larger than 1MB except for the screensaver
  // should be deferred.
  for (const std::string& id : screensaver_ids) {
    bool expected_defer_install =
        GetParam() == ash::DemoSession::DemoModeConfig::kNone ||
        id != ash::DemoSession::GetScreensaverAppId();
    EXPECT_EQ(expected_defer_install,
              InstallLimiter::ShouldDeferInstall(kLargeExtensionSize, id));
  }
  EXPECT_TRUE(InstallLimiter::ShouldDeferInstall(kLargeExtensionSize,
                                                 kRandomExtensionId));
  EXPECT_FALSE(InstallLimiter::ShouldDeferInstall(kSmallExtensionSize,
                                                  kRandomExtensionId));
}

INSTANTIATE_TEST_SUITE_P(
    DemoModeConfig,
    InstallLimiterShouldDeferInstallTest,
    ::testing::Values(ash::DemoSession::DemoModeConfig::kNone,
                      ash::DemoSession::DemoModeConfig::kOnline));

class InstallLimiterTest : public extensions::ExtensionServiceTestBase {
 public:
  InstallLimiterTest()
      : extensions::ExtensionServiceTestBase(
            std::make_unique<content::BrowserTaskEnvironment>(
                base::test::TaskEnvironment::MainThreadType::IO,
                content::BrowserTaskEnvironment::TimeSource::MOCK_TIME)) {}

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

  ~InstallLimiterTest() override = default;

 protected:
  void SetUp() override {
    extensions::ExtensionServiceTestBase::SetUp();

    ExtensionServiceInitParams params;
    params.enable_install_limiter = true;
    InitializeExtensionService(std::move(params));

    install_limiter_ = InstallLimiter::Get(profile());

    mock_installer_ =
        base::MakeRefCounted<extensions::MockCrxInstaller>(service());
  }

  extensions::CRXFileInfo CreateTestExtensionCrx(const base::FilePath& path,
                                                 int extension_size) {
    const std::string data(extension_size, 0);
    EXPECT_TRUE(base::WriteFile(path, data));
    extensions::CRXFileInfo crx_info(path, extensions::GetTestVerifierFormat());
    crx_info.extension_id = kRandomExtensionId;
    return crx_info;
  }

  raw_ptr<InstallLimiter> install_limiter_;
  scoped_refptr<extensions::MockCrxInstaller> mock_installer_;
};

// Test that small extensions are installed immediately.
TEST_F(InstallLimiterTest, DontDeferSmallExtensionInstallation) {
  const base::FilePath path =
      extensions_install_dir().AppendASCII(kSmallExtensionCrx);
  extensions::CRXFileInfo crx_info_small =
      CreateTestExtensionCrx(path, kSmallExtensionSize);

  EXPECT_CALL(*mock_installer_,
              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
  install_limiter_->Add(mock_installer_, crx_info_small);
  task_environment()->RunUntilIdle();
  Mock::VerifyAndClearExpectations(mock_installer_.get());
}

// Test that large extension installations are deferred.
TEST_F(InstallLimiterTest, DeferLargeExtensionInstallation) {
  const base::FilePath path =
      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
  extensions::CRXFileInfo crx_info_large =
      CreateTestExtensionCrx(path, kLargeExtensionSize);

  // Check that the large extension will not be installed immediately.
  EXPECT_CALL(*mock_installer_,
              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)))
      .Times(0);
  install_limiter_->Add(mock_installer_, crx_info_large);
  task_environment()->FastForwardBy(kLessThanExpectedWaitTime);
  task_environment()->RunUntilIdle();
  Mock::VerifyAndClearExpectations(mock_installer_.get());

  // The installation starts only after the wait time is elapsed.
  EXPECT_CALL(*mock_installer_,
              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
  task_environment()->FastForwardBy(kTimeDeltaUntilExpectedWaitTime);
  task_environment()->RunUntilIdle();
  Mock::VerifyAndClearExpectations(mock_installer_.get());
}

// Test that deferred installations are run before the wait time expires if the
// OnAllExternalProvidersReady() signal was called.
TEST_F(InstallLimiterTest, RunDeferredInstallsWhenAllExternalProvidersReady) {
  const base::FilePath path =
      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
  extensions::CRXFileInfo crx_info_large =
      CreateTestExtensionCrx(path, kLargeExtensionSize);

  // Check that the large extension will not be installed immediately.
  EXPECT_CALL(*mock_installer_,
              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)))
      .Times(0);
  install_limiter_->Add(mock_installer_, crx_info_large);
  task_environment()->FastForwardBy(kLessThanExpectedWaitTime);
  task_environment()->RunUntilIdle();
  Mock::VerifyAndClearExpectations(mock_installer_.get());

  // The installation starts before the wait time is elapsed if
  // OnAllExternalProvidersReady() is called.
  EXPECT_CALL(*mock_installer_,
              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
  install_limiter_->OnAllExternalProvidersReady();
  task_environment()->RunUntilIdle();
  Mock::VerifyAndClearExpectations(mock_installer_.get());
}

// Test that small extensions are installed before large extensions.
TEST_F(InstallLimiterTest, InstallSmallBeforeLargeExtensions) {
  // Create a large test extension crx file.
  const base::FilePath crx_path_large =
      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
  extensions::CRXFileInfo crx_info_large =
      CreateTestExtensionCrx(crx_path_large, kLargeExtensionSize);

  // Create a small test extension crx file.
  const base::FilePath crx_path_small =
      extensions_install_dir().AppendASCII(kSmallExtensionCrx);
  extensions::CRXFileInfo crx_info_small =
      CreateTestExtensionCrx(crx_path_small, kSmallExtensionSize);

  CrxInstaller::InstallerResultCallback installer_callback;

  base::RunLoop run_loop;

  // When adding a large extension and then a small extension, the small
  // extension will be installed first. The mock function call will trigger a
  // installer_callback which will notify the install limiter to
  // continue with any deferred installations. This will then start the
  // installation of the large extension.
  {
    testing::InSequence s;

    EXPECT_CALL(*mock_installer_, AddInstallerCallback(_))
        .WillOnce(Invoke([&](CrxInstaller::InstallerResultCallback callback) {
          installer_callback = std::move(callback);
        }));
    EXPECT_CALL(
        *mock_installer_,
        InstallCrxFile(Field(&extensions::CRXFileInfo::path, crx_path_small)))
        .WillOnce(Invoke([&] {
          std::optional<CrxInstallError> error;
          task_environment()->GetMainThreadTaskRunner()->PostTask(
              FROM_HERE, base::BindOnce(std::move(installer_callback), error));
        }));

    EXPECT_CALL(*mock_installer_, AddInstallerCallback(_));
    EXPECT_CALL(
        *mock_installer_,
        InstallCrxFile(Field(&extensions::CRXFileInfo::path, crx_path_large)))
        .WillOnce(Invoke(&run_loop, &base::RunLoop::Quit));
  }

  install_limiter_->Add(mock_installer_, crx_info_large);
  // Ensure that AddWithSize() is called for the large extension before also
  // adding the small extension.
  task_environment()->RunUntilIdle();
  install_limiter_->Add(mock_installer_, crx_info_small);

  run_loop.Run();

  Mock::VerifyAndClearExpectations(mock_installer_.get());
}