chromium/chrome/browser/ash/crosapi/browser_data_migrator_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 "chrome/browser/ash/crosapi/browser_data_migrator.h"

#include <optional>
#include <string>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/version.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/ash/components/standalone_browser/browser_support.h"
#include "chromeos/ash/components/standalone_browser/fake_migration_progress_tracker.h"
#include "chromeos/ash/components/standalone_browser/migrator_util.h"
#include "chromeos/ash/components/standalone_browser/standalone_browser_features.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/version_info/version_info.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {
constexpr char kFirstRun[] = "First Run";
}  // namespace

class BrowserDataMigratorImplTest : public ::testing::Test {
 public:
  void SetUp() override {
    // Setup `user_data_dir_` as below.
    // ./                             /* user_data_dir_ */
    // |- user/                       /* from_dir_ */
    //     |- Preferences

    ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
    from_dir_ = user_data_dir_.GetPath().Append("user");

    ASSERT_TRUE(base::CreateDirectory(from_dir_));
    ASSERT_TRUE(
        base::WriteFile(from_dir_.Append(chrome::kPreferencesFilename), "{}"));

    BrowserDataMigratorImpl::RegisterLocalStatePrefs(pref_service_.registry());
    crosapi::browser_util::RegisterLocalStatePrefs(pref_service_.registry());
    ash::standalone_browser::migrator_util::RegisterLocalStatePrefs(
        pref_service_.registry());
  }

  void TearDown() override { EXPECT_TRUE(user_data_dir_.Delete()); }

  base::ScopedTempDir user_data_dir_;
  base::FilePath from_dir_;
  TestingPrefServiceSimple pref_service_;
};

TEST_F(BrowserDataMigratorImplTest, Migrate) {
  base::test::TaskEnvironment task_environment;
  std::unique_ptr<standalone_browser::MigrationProgressTracker>
      progress_tracker =
          std::make_unique<standalone_browser::FakeMigrationProgressTracker>();
  const std::string user_id_hash = "abcd";
  BrowserDataMigratorImpl::SetMigrationStep(
      &pref_service_, BrowserDataMigratorImpl::MigrationStep::kRestartCalled);
  // Set migration attempt count to 1.
  ash::standalone_browser::migrator_util::UpdateMigrationAttemptCountForUser(
      &pref_service_, user_id_hash);

  base::test::TestFuture<BrowserDataMigrator::Result> future;
  std::unique_ptr<BrowserDataMigratorImpl> migrator =
      std::make_unique<BrowserDataMigratorImpl>(
          from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
  migrator->Migrate(future.GetCallback());
  BrowserDataMigrator::Result result = future.Take();

  const base::FilePath new_user_data_dir =
      from_dir_.Append(browser_data_migrator_util::kLacrosDir);
  const base::FilePath new_profile_data_dir =
      new_user_data_dir.Append("Default");
  // Check that `First Run` file is created inside the new data directory.
  EXPECT_TRUE(base::PathExists(new_user_data_dir.Append(kFirstRun)));
  // Check that migration is marked as completed for the user.
  EXPECT_TRUE(
      ash::standalone_browser::migrator_util::
          IsProfileMigrationCompletedForUser(&pref_service_, user_id_hash));
  EXPECT_EQ(BrowserDataMigrator::ResultKind::kSucceeded, result.kind);
  EXPECT_EQ(BrowserDataMigratorImpl::GetMigrationStep(&pref_service_),
            BrowserDataMigratorImpl::MigrationStep::kEnded);
  // Successful migration should clear the migration attempt count.
  EXPECT_EQ(
      ash::standalone_browser::migrator_util::GetMigrationAttemptCountForUser(
          &pref_service_, user_id_hash),
      0);
  // Data version should be updated to the current version after a migration.
  EXPECT_EQ(ash::standalone_browser::migrator_util::GetDataVer(&pref_service_,
                                                               user_id_hash),
            version_info::GetVersion());
}

TEST_F(BrowserDataMigratorImplTest, MigrateCancelled) {
  base::test::TaskEnvironment task_environment;
  std::unique_ptr<standalone_browser::MigrationProgressTracker>
      progress_tracker =
          std::make_unique<standalone_browser::FakeMigrationProgressTracker>();
  const std::string user_id_hash = "abcd";
  BrowserDataMigratorImpl::SetMigrationStep(
      &pref_service_, BrowserDataMigratorImpl::MigrationStep::kRestartCalled);
  // Set migration attempt count to 1.
  ash::standalone_browser::migrator_util::UpdateMigrationAttemptCountForUser(
      &pref_service_, user_id_hash);

  base::test::TestFuture<BrowserDataMigrator::Result> future;
  std::unique_ptr<BrowserDataMigratorImpl> migrator =
      std::make_unique<BrowserDataMigratorImpl>(
          from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
  migrator->Migrate(future.GetCallback());
  migrator->Cancel();
  BrowserDataMigrator::Result result = future.Take();

  const base::FilePath new_user_data_dir =
      from_dir_.Append(browser_data_migrator_util::kLacrosDir);
  const base::FilePath new_profile_data_dir =
      new_user_data_dir.Append("Default");
  EXPECT_FALSE(base::PathExists(new_user_data_dir.Append(kFirstRun)));
  EXPECT_FALSE(
      ash::standalone_browser::migrator_util::
          IsProfileMigrationCompletedForUser(&pref_service_, user_id_hash));
  EXPECT_EQ(BrowserDataMigrator::ResultKind::kCancelled, result.kind);
  EXPECT_EQ(BrowserDataMigratorImpl::GetMigrationStep(&pref_service_),
            BrowserDataMigratorImpl::MigrationStep::kEnded);
  // If migration fails, migration attempt count should not be cleared thus
  // should remain as 1.
  EXPECT_EQ(
      ash::standalone_browser::migrator_util::GetMigrationAttemptCountForUser(
          &pref_service_, user_id_hash),
      1);
  // Even if migration is cancelled, lacros data dir is cleared and thus data
  // version should be updated.
  EXPECT_EQ(ash::standalone_browser::migrator_util::GetDataVer(&pref_service_,
                                                               user_id_hash),
            version_info::GetVersion());
}

TEST_F(BrowserDataMigratorImplTest, MigrateOutOfDisk) {
  // Emulate the situation of out-of-disk.
  browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
      scoped_extra_bytes(100);

  base::test::TaskEnvironment task_environment;
  const std::string user_id_hash = "abcd";
  BrowserDataMigratorImpl::SetMigrationStep(
      &pref_service_, BrowserDataMigratorImpl::MigrationStep::kRestartCalled);

  base::test::TestFuture<BrowserDataMigrator::Result> future;
  std::unique_ptr<BrowserDataMigratorImpl> migrator =
      std::make_unique<BrowserDataMigratorImpl>(
          from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
  migrator->Migrate(future.GetCallback());
  BrowserDataMigrator::Result result = future.Take();

  EXPECT_EQ(BrowserDataMigrator::ResultKind::kFailed, result.kind);
  // |required_size| should carry the data.
  EXPECT_EQ(100u, result.required_size);
}

class BrowserDataMigratorRestartTest : public ::testing::Test {
 public:
  BrowserDataMigratorRestartTest() = default;
  ~BrowserDataMigratorRestartTest() override = default;

  void SetUp() override {
    fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
  }

  void AddRegularUser(const std::string& email) {
    AccountId account_id = AccountId::FromUserEmail(email);
    const user_manager::User* user = fake_user_manager_->AddUser(account_id);
    fake_user_manager_->UserLoggedIn(account_id, user->username_hash(),
                                     /*browser_restart=*/false,
                                     /*is_child=*/false);
  }

 protected:
  ash::FakeChromeUserManager* user_manager() {
    return fake_user_manager_.Get();
  }
  PrefService* local_state() { return fake_user_manager_->GetLocalState(); }

 private:
  content::BrowserTaskEnvironment task_environment_;
  ScopedTestingLocalState scoped_local_state_{
      TestingBrowserProcess::GetGlobal()};
  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
      fake_user_manager_;
  FakeSessionManagerClient session_manager_;
};

TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateWithMigrationStep) {
  bool restart_called = false;
  ScopedRestartAttemptForTesting scoped_restart_attempt(
      base::BindLambdaForTesting(
          [&restart_called]() { restart_called = true; }));

  BrowserDataMigratorImpl::SetMigrationStep(
      local_state(), BrowserDataMigratorImpl::MigrationStep::kRestartCalled);
  EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrate(
      AccountId::FromUserEmail("[email protected]"), "abcde",
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));

  BrowserDataMigratorImpl::SetMigrationStep(
      local_state(), BrowserDataMigratorImpl::MigrationStep::kStarted);
  EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrate(
      AccountId::FromUserEmail("[email protected]"), "abcde",
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));

  BrowserDataMigratorImpl::SetMigrationStep(
      local_state(), BrowserDataMigratorImpl::MigrationStep::kEnded);
  EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrate(
      AccountId::FromUserEmail("[email protected]"), "abcde",
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
}

TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateWithCommandLine) {
  bool restart_called = false;
  ScopedRestartAttemptForTesting scoped_restart_attempt(
      base::BindLambdaForTesting(
          [&restart_called]() { restart_called = true; }));

  base::test::ScopedFeatureList feature_list;
  AddRegularUser("[email protected]");
  const user_manager::User* const user = user_manager()->GetPrimaryUser();
  {
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitchASCII(
        switches::kForceBrowserDataMigrationForTesting, "force-skip");
    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrate(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }
  {
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitchASCII(
        switches::kForceBrowserDataMigrationForTesting, "force-migration");
    EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrate(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }
}

TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateWithDiskCheck) {
  bool restart_called = false;
  ScopedRestartAttemptForTesting scoped_restart_attempt(
      base::BindLambdaForTesting(
          [&restart_called]() { restart_called = true; }));

  base::test::ScopedFeatureList feature_list;
  AddRegularUser("[email protected]");
  const user_manager::User* const user = user_manager()->GetPrimaryUser();
  // If MaybeRestartToMigrate will skip the restarting, WithDiskCheck variation
  // also skips it.
  {
    restart_called = false;
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitchASCII(
        switches::kForceBrowserDataMigrationForTesting, "force-skip");
    base::test::TestFuture<bool, const std::optional<uint64_t>&> future;
    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
        user->GetAccountId(), user->username_hash(), future.GetCallback());

    bool success = future.Get<0>();
    EXPECT_FALSE(success);
    EXPECT_FALSE(restart_called);
  }

  // If MaybeRestartToMigrate will trigger the restarting, WithDiskCheck
  // variation will see additional disk size check.
  {
    restart_called = false;
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitchASCII(
        switches::kForceBrowserDataMigrationForTesting, "force-migration");
    // Inject the behavior that the disk does not have enough space.
    browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
        scoped_extra_bytes(1024 * 1024);

    base::test::TestFuture<bool, const std::optional<uint64_t>> future;
    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
        user->GetAccountId(), user->username_hash(),
        future.GetCallback<bool, const std::optional<uint64_t>&>());

    bool success = future.Get<0>();
    std::optional<uint64_t> out_size = future.Get<1>();

    EXPECT_FALSE(success);
    EXPECT_EQ(1024u * 1024u, out_size);
    EXPECT_FALSE(restart_called);
  }

  {
    restart_called = false;
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitchASCII(
        switches::kForceBrowserDataMigrationForTesting, "force-migration");
    // Inject the behavior that the disk has enough space for the migration.
    browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
        scoped_extra_bytes(0);

    base::test::TestFuture<bool, const std::optional<uint64_t>> future;
    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
        user->GetAccountId(), user->username_hash(),
        future.GetCallback<bool, const std::optional<uint64_t>&>());

    bool success = future.Get<0>();

    EXPECT_TRUE(success);
    EXPECT_TRUE(restart_called);
  }
}

TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateMoveAfterCopy) {
  // Check that `MaybeRestartToMigrateInternal()` returns false after completion
  // of copy migration even if move migration is enabled.
  AddRegularUser("[email protected]");
  const user_manager::User* const user = user_manager()->GetPrimaryUser();

  {
    // If Lacros is not enabled, migration should not run.
    base::test::ScopedFeatureList feature_list;
    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }

  {
    // If Lacros is enabled, migration should run.
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
    EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }

  // Mark copy migration as completed.
  ash::standalone_browser::migrator_util::SetProfileMigrationCompletedForUser(
      local_state(), user->username_hash(),
      ash::standalone_browser::migrator_util::MigrationMode::kCopy);
  {
    // If copy migration is marked as completed then migration should not run
    // even if move migration is not completed.
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }

  // Mark move migration as completed.
  ash::standalone_browser::migrator_util::ClearProfileMigrationCompletedForUser(
      local_state(), user->username_hash());
  ash::standalone_browser::migrator_util::SetProfileMigrationCompletedForUser(
      local_state(), user->username_hash(),
      ash::standalone_browser::migrator_util::MigrationMode::kMove);
  {
    // If move migration is marked as completed, move migration should not run.
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        user->GetAccountId(), user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }
}

TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateSecondaryUser) {
  // Add two users to simulate multi user session.
  AddRegularUser("[email protected]");
  AddRegularUser("[email protected]");
  const auto* const primary_user = user_manager()->GetPrimaryUser();
  const auto* const secondary_user =
      user_manager()->FindUser(AccountId::FromUserEmail("[email protected]"));
  EXPECT_NE(primary_user, secondary_user);

  {
    base::test::ScopedCommandLine command_line;
    command_line.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
    // Migration should be triggered for the primary user.
    EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        primary_user->GetAccountId(), primary_user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
    // But not for secondary users.
    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
        secondary_user->GetAccountId(), secondary_user->username_hash(),
        ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
  }
}

TEST_F(BrowserDataMigratorRestartTest,
       MaybeRestartToMigrateWithMaximumRetryAttempts) {
  // Check that `MaybeRestartToMigrateInternal()` returns false if maximum retry
  // attempts have been reached.
  AddRegularUser("[email protected]");
  const user_manager::User* const user = user_manager()->GetPrimaryUser();

  // If Lacros is enabled, migration should run.
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      ash::switches::kEnableLacrosForTesting);
  EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
      user->GetAccountId(), user->username_hash(),
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));

  for (int i = 0;
       i < ash::standalone_browser::migrator_util::kMaxMigrationAttemptCount;
       i++) {
    ash::standalone_browser::migrator_util::UpdateMigrationAttemptCountForUser(
        local_state(), user->username_hash());
  }
  // If maximum attempts have been reached then profile migration should be
  // skipped.
  EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
      user->GetAccountId(), user->username_hash(),
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));

  ash::standalone_browser::migrator_util::ClearMigrationAttemptCountForUser(
      local_state(), user->username_hash());
  EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
      user->GetAccountId(), user->username_hash(),
      ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit));
}

}  // namespace ash