chromium/chrome/browser/ui/startup/startup_browser_creator_corrupt_profiles_browsertest_win.cc

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

#include <string>
#include <vector>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/test/test_file_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"

namespace {

void OnCloseAllBrowsersSucceeded(base::OnceClosure quit_closure,
                                 const base::FilePath& path) {
  std::move(quit_closure).Run();
}

void CreateAndSwitchToProfile(const std::string& basepath) {
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  ASSERT_TRUE(profile_manager);
  base::FilePath path = profile_manager->user_data_dir().AppendASCII(basepath);
  profiles::testing::CreateProfileSync(profile_manager, path);

  profiles::SwitchToProfile(path, false);
}

void CheckBrowserWindows(const std::vector<std::string>& expected_basepaths) {
  std::vector<std::string> actual_basepaths;
  for (const Browser* browser : *BrowserList::GetInstance()) {
    actual_basepaths.push_back(
        browser->profile()->GetBaseName().AsUTF8Unsafe());
  }

  if (!base::ranges::is_permutation(actual_basepaths, expected_basepaths)) {
    ADD_FAILURE()
        << "Expected profile paths are different from actual profile paths."
           "\n  Actual profile paths: "
        << base::JoinString(actual_basepaths, ", ")
        << "\n  Expected profile paths: "
        << base::JoinString(expected_basepaths, ", ");
  }
}

void ExpectUserManagerToShow() {
  // If the user manager is not shown yet, wait for the user manager to appear.
  if (!ProfilePicker::IsOpen()) {
    base::RunLoop run_loop;
    ProfilePicker::AddOnProfilePickerOpenedCallbackForTesting(
        run_loop.QuitClosure());
    run_loop.Run();
  }
  ASSERT_TRUE(ProfilePicker::IsOpen());

  // We must hide the user manager before the test ends.
  ProfilePicker::Hide();
}

}  // namespace

class StartupBrowserCreatorCorruptProfileTest : public InProcessBrowserTest {
 public:
  StartupBrowserCreatorCorruptProfileTest() = default;
  StartupBrowserCreatorCorruptProfileTest(
      const StartupBrowserCreatorCorruptProfileTest&) = delete;
  StartupBrowserCreatorCorruptProfileTest& operator=(
      const StartupBrowserCreatorCorruptProfileTest&) = delete;

  void SetExpectTestBodyToRun(bool expected_result) {
    expect_test_body_to_run_ = expected_result;
  }

  bool DeleteProfileData(const std::string& basepath) {
    base::FilePath user_data_dir;
    if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
      return false;

    base::FilePath dir_to_delete = user_data_dir.AppendASCII(basepath);
    return base::DirectoryExists(dir_to_delete) &&
           base::DeletePathRecursively(dir_to_delete);
  }

  bool RemoveCreateDirectoryPermissionForUserDataDirectory() {
    base::FilePath user_data_dir;
    return base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) &&
           base::DenyFilePermission(user_data_dir, FILE_ADD_SUBDIRECTORY);
  }

 protected:
  // For each test, declare a bool SetUpUserDataDirectory[testname] function.
  bool SetUpUserDataDirectoryForLastOpenedProfileMissing();
  bool SetUpUserDataDirectoryForLastUsedProfileFallbackToLastOpenedProfiles();
  bool SetUpUserDataDirectoryForLastUsedProfileFallbackToUserManager();
  bool SetUpUserDataDirectoryForCannotCreateSystemProfile();
  bool SetUpUserDataDirectoryForLastUsedProfileFallbackToAnyProfile();
  bool SetUpUserDataDirectoryForLastUsedProfileFallbackFail();
  bool SetUpUserDataDirectoryForDoNotStartLockedProfile();
  bool SetUpUserDataDirectoryForNoFallbackForUserSelectedProfile();
  bool SetUpUserDataDirectoryForDeletedProfileFallbackToUserManager();

  void CloseBrowsersSynchronouslyForProfileBasePath(
           const std::string& basepath) {
    ProfileManager* profile_manager = g_browser_process->profile_manager();
    ASSERT_TRUE(profile_manager);

    Profile* profile =
        profile_manager->GetProfileByPath(
            profile_manager->user_data_dir().AppendASCII(basepath));
    ASSERT_TRUE(profile);

    base::RunLoop run_loop;
    BrowserList::GetInstance()->CloseAllBrowsersWithProfile(
        profile,
        base::BindRepeating(&OnCloseAllBrowsersSucceeded,
                            run_loop.QuitClosure()),
        BrowserList::CloseCallback(), false);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kRestoreLastSession);
    command_line->AppendSwitch(switches::kNoErrorDialogs);
  }

  // In this test fixture, SetUpUserDataDirectory must be handled for all
  // non-PRE_ tests.
  bool SetUpUserDataDirectory() override {
#define SET_UP_USER_DATA_DIRECTORY_FOR(testname)                       \
  if (testing::UnitTest::GetInstance()->current_test_info()->name() == \
      std::string(#testname)) {                                        \
    return this->SetUpUserDataDirectoryFor##testname();                \
  }

    SET_UP_USER_DATA_DIRECTORY_FOR(LastOpenedProfileMissing);
    SET_UP_USER_DATA_DIRECTORY_FOR(LastUsedProfileFallbackToLastOpenedProfiles);
    SET_UP_USER_DATA_DIRECTORY_FOR(LastUsedProfileFallbackToUserManager);
    SET_UP_USER_DATA_DIRECTORY_FOR(CannotCreateSystemProfile);
    SET_UP_USER_DATA_DIRECTORY_FOR(LastUsedProfileFallbackToAnyProfile);
    SET_UP_USER_DATA_DIRECTORY_FOR(LastUsedProfileFallbackFail);
    SET_UP_USER_DATA_DIRECTORY_FOR(DoNotStartLockedProfile);
    SET_UP_USER_DATA_DIRECTORY_FOR(NoFallbackForUserSelectedProfile);
    SET_UP_USER_DATA_DIRECTORY_FOR(DeletedProfileFallbackToUserManager);

#undef SET_UP_USER_DATA_DIRECTORY_FOR

    // If control goes here, it means SetUpUserDataDirectory is not handled.
    // This is okay for PRE_ tests, but not acceptable for main tests.
    if (content::IsPreTest())
      return true;

    ADD_FAILURE() << "SetUpUserDataDirectory is not handled by the test.";
    return false;
  }

  void TearDownOnMainThread() override {
    test_body_has_run_ = true;
  }

  void TearDown() override {
    EXPECT_EQ(expect_test_body_to_run_, test_body_has_run_);
    InProcessBrowserTest::TearDown();
    signin_util::ResetForceSigninForTesting();
  }

 private:
  bool test_body_has_run_ = false;
  bool expect_test_body_to_run_ = true;
};

// Most of the tests below have three sections:
// (1) PRE_ test, which is used to create profiles. Most of these profiles are
//     meant to be opened in (3) during startup.
// (2) StartupBrowserCreatorCorruptProfileTest::SetUpUserDataDirectoryFor...
//     which sets up the user data directory, i.e. to remove profile directories
//     and to prevent the profile directories from being created again in (3).
//     We cannot remove these directories while the browser process is running,
//     so that cannot be done during (1) or (3).
// (3) The test itself. With the |kRestoreLastSession| switch set, profiles
//     opened in (1) are reopened. However, since the directories of some
//     profiles are removed in (2), these profiles are unable to be initialized.
//     We are testing whether the correct fallback action is taken.

// LastOpenedProfileMissing : If any last opened profile is missing, that
// profile is skipped, but other last opened profiles should be opened in the
// browser.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_LastOpenedProfileMissing) {
  CreateAndSwitchToProfile("Profile 1");
  CreateAndSwitchToProfile("Profile 2");
}

bool StartupBrowserCreatorCorruptProfileTest::
         SetUpUserDataDirectoryForLastOpenedProfileMissing() {
  return DeleteProfileData("Profile 1") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       LastOpenedProfileMissing) {
  CheckBrowserWindows({"Default", "Profile 2"});
}

// LastUsedProfileFallbackToLastOpenedProfiles : If the last used profile is
// missing, it should fall back to any last opened profiles.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_LastUsedProfileFallbackToLastOpenedProfiles) {
  CreateAndSwitchToProfile("Profile 1");
  CreateAndSwitchToProfile("Profile 2");
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForLastUsedProfileFallbackToLastOpenedProfiles() {
  return DeleteProfileData("Profile 2") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       LastUsedProfileFallbackToLastOpenedProfiles) {
  CheckBrowserWindows({"Default", "Profile 1"});
}

// LastUsedProfileFallbackToUserManager : If all last opened profiles are
// missing, it should fall back to user manager. To open the user manager, both
// the guest profile and the system profile must be creatable.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_LastUsedProfileFallbackToUserManager) {
  CreateAndSwitchToProfile("Profile 1");
  CloseBrowsersSynchronouslyForProfileBasePath("Profile 1");
  CreateAndSwitchToProfile("Profile 2");

  base::ScopedAllowBlockingForTesting allow_blocking;
  ASSERT_TRUE(base::CreateDirectory(ProfileManager::GetGuestProfilePath()));
  ASSERT_TRUE(base::CreateDirectory(ProfileManager::GetSystemProfilePath()));
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForLastUsedProfileFallbackToUserManager() {
  return DeleteProfileData("Default") &&
         DeleteProfileData("Profile 2") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       LastUsedProfileFallbackToUserManager) {
  CheckBrowserWindows({});
  ExpectUserManagerToShow();
}

// CannotCreateSystemProfile : If the system profile cannot be created, the user
// manager should not be shown. Fallback to any other profile.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_CannotCreateSystemProfile) {
  CreateAndSwitchToProfile("Profile 1");
  CloseBrowsersSynchronouslyForProfileBasePath("Profile 1");
  CreateAndSwitchToProfile("Profile 2");

  // Create the guest profile path, but not the system profile one. This will
  // make it impossible to create the system profile once the permissions are
  // locked down during setup.
  base::ScopedAllowBlockingForTesting allow_blocking;
  ASSERT_TRUE(base::CreateDirectory(ProfileManager::GetGuestProfilePath()));
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForCannotCreateSystemProfile() {
  return DeleteProfileData("Default") &&
         DeleteProfileData("Profile 2") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       CannotCreateSystemProfile) {
  CheckBrowserWindows({"Profile 1"});
}

// LastUsedProfileFallbackToAnyProfile : If all the last opened profiles and the
// guest profile cannot be opened, fall back to any profile that is not locked.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_LastUsedProfileFallbackToAnyProfile) {
  CreateAndSwitchToProfile("Profile 1");
  CloseBrowsersSynchronouslyForProfileBasePath("Profile 1");
  CreateAndSwitchToProfile("Profile 2");
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForLastUsedProfileFallbackToAnyProfile() {
  return DeleteProfileData("Default") &&
         DeleteProfileData("Profile 2") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       LastUsedProfileFallbackToAnyProfile) {
  CheckBrowserWindows({"Profile 1"});
}

// LastUsedProfileFallbackFail : If no startup option is feasible, the browser
// should quit cleanly.
bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForLastUsedProfileFallbackFail() {
  SetExpectTestBodyToRun(false);
  return RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       LastUsedProfileFallbackFail) {
  ADD_FAILURE() << "Test body is not expected to run.";
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForDoNotStartLockedProfile() {
  SetExpectTestBodyToRun(false);
  signin_util::SetForceSigninForTesting(true);
  return RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       DoNotStartLockedProfile) {
  ADD_FAILURE() << "Test body is not expected to run.";
}

// NoFallbackForUserSelectedProfile : No fallback should be attempted if the
// profile is selected by the --profile-directory switch. The browser should not
// start if the specified profile could not be initialized.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_NoFallbackForUserSelectedProfile) {
  CreateAndSwitchToProfile("Profile 1");
  CreateAndSwitchToProfile("Profile 2");
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForNoFallbackForUserSelectedProfile() {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  command_line->AppendSwitchASCII(switches::kProfileDirectory, "Profile 1");
  SetExpectTestBodyToRun(false);
  return DeleteProfileData("Profile 1") &&
         RemoveCreateDirectoryPermissionForUserDataDirectory();
}

IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       NoFallbackForUserSelectedProfile) {
  ADD_FAILURE() << "Test body is not expected to run.";
}

// DeletedProfileFallbackToUserManager : If the profile's entry in
// ProfileAttributesStorage is missing, it means the profile is deleted. The
// browser should not attempt to open the profile, but should show the user
// manager instead.
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       PRE_DeletedProfileFallbackToUserManager) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  ASSERT_TRUE(base::CreateDirectory(ProfileManager::GetGuestProfilePath()));
  ASSERT_TRUE(base::CreateDirectory(ProfileManager::GetSystemProfilePath()));
}

bool StartupBrowserCreatorCorruptProfileTest::
    SetUpUserDataDirectoryForDeletedProfileFallbackToUserManager() {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  // Simulate a deleted profile by not creating the profile at the first place.
  command_line->AppendSwitchASCII(switches::kProfileDirectory, "Not Found");
  return RemoveCreateDirectoryPermissionForUserDataDirectory();
}

// Flaky: https://crbug.com/951787
IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorCorruptProfileTest,
                       DISABLED_DeletedProfileFallbackToUserManager) {
  CheckBrowserWindows({});
  ExpectUserManagerToShow();
}