// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <windows.h>

#include <fstream>

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "base/version.h"
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/installer/test/alternate_version_generator.h"
#include "chrome/installer/util/fake_installation_state.h"
#include "chrome/installer/util/fake_product_state.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/installer_state.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/product_unittest.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item.h"
#include "testing/gtest/include/gtest/gtest.h"

#include "installer_util_strings.h"  // NOLINT

using base::win::RegKey;
using installer::InstallationState;
using installer::InstallerState;
using installer::MasterPreferences;
using registry_util::RegistryOverrideManager;

class InstallerStateTest : public TestWithTempDirAndDeleteTempOverrideKeys {
 protected:
};

// An installer state on which we can access otherwise protected members.
class MockInstallerState : public InstallerState {
 public:
  MockInstallerState() : InstallerState() { }
  void set_target_path(const base::FilePath& target_path) {
    target_path_ = target_path;
  }
  static bool IsFileInUse(const base::FilePath& file) {
    return InstallerState::IsFileInUse(file);
  }
  const Version& critical_update_version() const {
    return critical_update_version_;
  }
  void GetExistingExeVersions(std::set<std::string>* existing_version_strings) {
    return InstallerState::GetExistingExeVersions(existing_version_strings);
  }
};

// Simple function to dump some text into a new file.
void CreateTextFile(const std::wstring& filename,
                    const std::wstring& contents) {
  std::ofstream file;
  file.open(filename.c_str());
  ASSERT_TRUE(file.is_open());
  file << contents;
  file.close();
}

void BuildSingleChromeState(const base::FilePath& target_dir,
                            MockInstallerState* installer_state) {
  CommandLine cmd_line = CommandLine::FromString(L"setup.exe");
  MasterPreferences prefs(cmd_line);
  InstallationState machine_state;
  machine_state.Initialize();
  installer_state->Initialize(cmd_line, prefs, machine_state);
  installer_state->set_target_path(target_dir);
  EXPECT_TRUE(installer_state->FindProduct(BrowserDistribution::CHROME_BROWSER)
      != NULL);
}

wchar_t text_content_1[] = L"delete me";
wchar_t text_content_2[] = L"delete me as well";

// Delete version directories. Everything lower than the given version
// should be deleted.
TEST_F(InstallerStateTest, Delete) {
  // TODO(grt): move common stuff into the test fixture.
  // Create a Chrome dir
  base::FilePath chrome_dir(test_dir_.path());
  chrome_dir = chrome_dir.AppendASCII("chrome");
  base::CreateDirectory(chrome_dir);
  ASSERT_TRUE(base::PathExists(chrome_dir));

  base::FilePath chrome_dir_1(chrome_dir);
  chrome_dir_1 = chrome_dir_1.AppendASCII("1.0.1.0");
  base::CreateDirectory(chrome_dir_1);
  ASSERT_TRUE(base::PathExists(chrome_dir_1));

  base::FilePath chrome_dir_2(chrome_dir);
  chrome_dir_2 = chrome_dir_2.AppendASCII("1.0.2.0");
  base::CreateDirectory(chrome_dir_2);
  ASSERT_TRUE(base::PathExists(chrome_dir_2));

  base::FilePath chrome_dir_3(chrome_dir);
  chrome_dir_3 = chrome_dir_3.AppendASCII("1.0.3.0");
  base::CreateDirectory(chrome_dir_3);
  ASSERT_TRUE(base::PathExists(chrome_dir_3));

  base::FilePath chrome_dir_4(chrome_dir);
  chrome_dir_4 = chrome_dir_4.AppendASCII("1.0.4.0");
  base::CreateDirectory(chrome_dir_4);
  ASSERT_TRUE(base::PathExists(chrome_dir_4));

  base::FilePath chrome_dll_1(chrome_dir_1);
  chrome_dll_1 = chrome_dll_1.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_1.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_1));

  base::FilePath chrome_dll_2(chrome_dir_2);
  chrome_dll_2 = chrome_dll_2.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_2.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_2));

  base::FilePath chrome_dll_3(chrome_dir_3);
  chrome_dll_3 = chrome_dll_3.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_3.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_3));

  base::FilePath chrome_dll_4(chrome_dir_4);
  chrome_dll_4 = chrome_dll_4.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_4.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_4));

  MockInstallerState installer_state;
  BuildSingleChromeState(chrome_dir, &installer_state);
  Version latest_version("1.0.4.0");
  {
    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    installer_state.RemoveOldVersionDirectories(latest_version, NULL,
                                                temp_dir.path());
  }

  // old versions should be gone
  EXPECT_FALSE(base::PathExists(chrome_dir_1));
  EXPECT_FALSE(base::PathExists(chrome_dir_2));
  EXPECT_FALSE(base::PathExists(chrome_dir_3));
  // the latest version should stay
  EXPECT_TRUE(base::PathExists(chrome_dll_4));
}

// Delete older version directories, keeping the one in used intact.
TEST_F(InstallerStateTest, DeleteInUsed) {
  // Create a Chrome dir
  base::FilePath chrome_dir(test_dir_.path());
  chrome_dir = chrome_dir.AppendASCII("chrome");
  base::CreateDirectory(chrome_dir);
  ASSERT_TRUE(base::PathExists(chrome_dir));

  base::FilePath chrome_dir_1(chrome_dir);
  chrome_dir_1 = chrome_dir_1.AppendASCII("1.0.1.0");
  base::CreateDirectory(chrome_dir_1);
  ASSERT_TRUE(base::PathExists(chrome_dir_1));

  base::FilePath chrome_dir_2(chrome_dir);
  chrome_dir_2 = chrome_dir_2.AppendASCII("1.0.2.0");
  base::CreateDirectory(chrome_dir_2);
  ASSERT_TRUE(base::PathExists(chrome_dir_2));

  base::FilePath chrome_dir_3(chrome_dir);
  chrome_dir_3 = chrome_dir_3.AppendASCII("1.0.3.0");
  base::CreateDirectory(chrome_dir_3);
  ASSERT_TRUE(base::PathExists(chrome_dir_3));

  base::FilePath chrome_dir_4(chrome_dir);
  chrome_dir_4 = chrome_dir_4.AppendASCII("1.0.4.0");
  base::CreateDirectory(chrome_dir_4);
  ASSERT_TRUE(base::PathExists(chrome_dir_4));

  base::FilePath chrome_dll_1(chrome_dir_1);
  chrome_dll_1 = chrome_dll_1.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_1.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_1));

  base::FilePath chrome_dll_2(chrome_dir_2);
  chrome_dll_2 = chrome_dll_2.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_2.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_2));

  // Open the file to make it in use.
  std::ofstream file;
  file.open(chrome_dll_2.value().c_str());

  base::FilePath chrome_othera_2(chrome_dir_2);
  chrome_othera_2 = chrome_othera_2.AppendASCII("othera.dll");
  CreateTextFile(chrome_othera_2.value(), text_content_2);
  ASSERT_TRUE(base::PathExists(chrome_othera_2));

  base::FilePath chrome_otherb_2(chrome_dir_2);
  chrome_otherb_2 = chrome_otherb_2.AppendASCII("otherb.dll");
  CreateTextFile(chrome_otherb_2.value(), text_content_2);
  ASSERT_TRUE(base::PathExists(chrome_otherb_2));

  base::FilePath chrome_dll_3(chrome_dir_3);
  chrome_dll_3 = chrome_dll_3.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_3.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_3));

  base::FilePath chrome_dll_4(chrome_dir_4);
  chrome_dll_4 = chrome_dll_4.AppendASCII("chrome.dll");
  CreateTextFile(chrome_dll_4.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(chrome_dll_4));

  MockInstallerState installer_state;
  BuildSingleChromeState(chrome_dir, &installer_state);
  Version latest_version("1.0.4.0");
  Version existing_version("1.0.1.0");
  {
    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    installer_state.RemoveOldVersionDirectories(latest_version,
                                                &existing_version,
                                                temp_dir.path());
  }

  // the version defined as the existing version should stay
  EXPECT_TRUE(base::PathExists(chrome_dir_1));
  // old versions not in used should be gone
  EXPECT_FALSE(base::PathExists(chrome_dir_3));
  // every thing under in used version should stay
  EXPECT_TRUE(base::PathExists(chrome_dir_2));
  EXPECT_TRUE(base::PathExists(chrome_dll_2));
  EXPECT_TRUE(base::PathExists(chrome_othera_2));
  EXPECT_TRUE(base::PathExists(chrome_otherb_2));
  // the latest version should stay
  EXPECT_TRUE(base::PathExists(chrome_dll_4));
}

// Tests a few basic things of the Package class.  Makes sure that the path
// operations are correct
TEST_F(InstallerStateTest, Basic) {
  const bool multi_install = false;
  const bool system_level = true;
  CommandLine cmd_line = CommandLine::FromString(
      std::wstring(L"setup.exe") +
      (multi_install ? L" --multi-install --chrome" : L"") +
      (system_level ? L" --system-level" : L""));
  MasterPreferences prefs(cmd_line);
  InstallationState machine_state;
  machine_state.Initialize();
  MockInstallerState installer_state;
  installer_state.Initialize(cmd_line, prefs, machine_state);
  installer_state.set_target_path(test_dir_.path());
  EXPECT_EQ(test_dir_.path().value(), installer_state.target_path().value());
  EXPECT_EQ(1U, installer_state.products().size());

  const char kOldVersion[] = "1.2.3.4";
  const char kNewVersion[] = "2.3.4.5";

  Version new_version(kNewVersion);
  Version old_version(kOldVersion);
  ASSERT_TRUE(new_version.IsValid());
  ASSERT_TRUE(old_version.IsValid());

  base::FilePath installer_dir(
      installer_state.GetInstallerDirectory(new_version));
  EXPECT_FALSE(installer_dir.empty());

  base::FilePath new_version_dir(installer_state.target_path().Append(
      base::UTF8ToWide(new_version.GetString())));
  base::FilePath old_version_dir(installer_state.target_path().Append(
      base::UTF8ToWide(old_version.GetString())));

  EXPECT_FALSE(base::PathExists(new_version_dir));
  EXPECT_FALSE(base::PathExists(old_version_dir));

  EXPECT_FALSE(base::PathExists(installer_dir));
  base::CreateDirectory(installer_dir);
  EXPECT_TRUE(base::PathExists(new_version_dir));

  base::CreateDirectory(old_version_dir);
  EXPECT_TRUE(base::PathExists(old_version_dir));

  // Create a fake chrome.dll key file in the old version directory.  This
  // should prevent the old version directory from getting deleted.
  base::FilePath old_chrome_dll(old_version_dir.Append(installer::kChromeDll));
  EXPECT_FALSE(base::PathExists(old_chrome_dll));

  // Hold on to the file exclusively to prevent the directory from
  // being deleted.
  base::win::ScopedHandle file(
    ::CreateFile(old_chrome_dll.value().c_str(), GENERIC_READ,
                 0, NULL, OPEN_ALWAYS, 0, NULL));
  EXPECT_TRUE(file.IsValid());
  EXPECT_TRUE(base::PathExists(old_chrome_dll));

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  // Don't explicitly tell the directory cleanup logic not to delete the
  // old version, rely on the key files to keep it around.
  installer_state.RemoveOldVersionDirectories(new_version,
                                              NULL,
                                              temp_dir.path());

  // The old directory should still exist.
  EXPECT_TRUE(base::PathExists(old_version_dir));
  EXPECT_TRUE(base::PathExists(new_version_dir));

  // Now close the file handle to make it possible to delete our key file.
  file.Close();

  installer_state.RemoveOldVersionDirectories(new_version,
                                              NULL,
                                              temp_dir.path());
  // The new directory should still exist.
  EXPECT_TRUE(base::PathExists(new_version_dir));

  // Now, the old directory and key file should be gone.
  EXPECT_FALSE(base::PathExists(old_chrome_dll));
  EXPECT_FALSE(base::PathExists(old_version_dir));
}

TEST_F(InstallerStateTest, WithProduct) {
  const bool multi_install = false;
  const bool system_level = true;
  CommandLine cmd_line = CommandLine::FromString(
      std::wstring(L"setup.exe") +
      (multi_install ? L" --multi-install --chrome" : L"") +
      (system_level ? L" --system-level" : L""));
  MasterPreferences prefs(cmd_line);
  InstallationState machine_state;
  machine_state.Initialize();
  MockInstallerState installer_state;
  installer_state.Initialize(cmd_line, prefs, machine_state);
  installer_state.set_target_path(test_dir_.path());
  EXPECT_EQ(1U, installer_state.products().size());
  EXPECT_EQ(system_level, installer_state.system_install());

  const char kCurrentVersion[] = "1.2.3.4";
  Version current_version(kCurrentVersion);

  HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  EXPECT_EQ(root, installer_state.root_key());

  {
    RegistryOverrideManager override_manager;
    override_manager.OverrideRegistry(root, L"root_pit");
    BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution(
        BrowserDistribution::CHROME_BROWSER);
    RegKey chrome_key(root, dist->GetVersionKey().c_str(), KEY_ALL_ACCESS);
    EXPECT_TRUE(chrome_key.Valid());
    if (chrome_key.Valid()) {
      chrome_key.WriteValue(google_update::kRegVersionField,
                            base::UTF8ToWide(
                                current_version.GetString()).c_str());
      machine_state.Initialize();
      // TODO(tommi): Also test for when there exists a new_chrome.exe.
      Version found_version(*installer_state.GetCurrentVersion(machine_state));
      EXPECT_TRUE(found_version.IsValid());
      if (found_version.IsValid())
        EXPECT_TRUE(current_version.Equals(found_version));
    }
  }
}

TEST_F(InstallerStateTest, InstallerResult) {
  const bool system_level = true;
  bool multi_install = false;
  HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;

  RegKey key;
  std::wstring launch_cmd = L"hey diddle diddle";
  std::wstring value;
  DWORD dw_value;

  // check results for a fresh install of single Chrome
  {
    RegistryOverrideManager override_manager;
    override_manager.OverrideRegistry(root, L"root_inst_res");
    CommandLine cmd_line = CommandLine::FromString(L"setup.exe --system-level");
    const MasterPreferences prefs(cmd_line);
    InstallationState machine_state;
    machine_state.Initialize();
    InstallerState state;
    state.Initialize(cmd_line, prefs, machine_state);
    state.WriteInstallerResult(installer::FIRST_INSTALL_SUCCESS,
                               IDS_INSTALL_OS_ERROR_BASE, &launch_cmd);
    BrowserDistribution* distribution =
        BrowserDistribution::GetSpecificDistribution(
            BrowserDistribution::CHROME_BROWSER);
    EXPECT_EQ(ERROR_SUCCESS,
        key.Open(root, distribution->GetStateKey().c_str(), KEY_READ));
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValueDW(installer::kInstallerResult, &dw_value));
    EXPECT_EQ(static_cast<DWORD>(0), dw_value);
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValueDW(installer::kInstallerError, &dw_value));
    EXPECT_EQ(static_cast<DWORD>(installer::FIRST_INSTALL_SUCCESS), dw_value);
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValue(installer::kInstallerResultUIString, &value));
    EXPECT_FALSE(value.empty());
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValue(installer::kInstallerSuccessLaunchCmdLine, &value));
    EXPECT_EQ(launch_cmd, value);
  }

  // check results for a fresh install of multi Chrome
  {
    RegistryOverrideManager override_manager;
    override_manager.OverrideRegistry(root, L"root_inst_res");
    CommandLine cmd_line = CommandLine::FromString(
        L"setup.exe --system-level --multi-install --chrome");
    const MasterPreferences prefs(cmd_line);
    InstallationState machine_state;
    machine_state.Initialize();
    InstallerState state;
    state.Initialize(cmd_line, prefs, machine_state);
    state.WriteInstallerResult(installer::FIRST_INSTALL_SUCCESS, 0,
                               &launch_cmd);
    BrowserDistribution* distribution =
        BrowserDistribution::GetSpecificDistribution(
            BrowserDistribution::CHROME_BROWSER);
    BrowserDistribution* binaries =
        BrowserDistribution::GetSpecificDistribution(
            BrowserDistribution::CHROME_BINARIES);
    EXPECT_EQ(ERROR_SUCCESS,
        key.Open(root, distribution->GetStateKey().c_str(), KEY_READ));
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValue(installer::kInstallerSuccessLaunchCmdLine, &value));
    EXPECT_EQ(launch_cmd, value);
    EXPECT_EQ(ERROR_SUCCESS,
        key.Open(root, binaries->GetStateKey().c_str(), KEY_READ));
    EXPECT_EQ(ERROR_SUCCESS,
        key.ReadValue(installer::kInstallerSuccessLaunchCmdLine, &value));
    EXPECT_EQ(launch_cmd, value);
    key.Close();
  }
}

// Test GetCurrentVersion when migrating single Chrome to multi
TEST_F(InstallerStateTest, GetCurrentVersionMigrateChrome) {
  using installer::FakeInstallationState;

  const bool system_install = false;
  FakeInstallationState machine_state;

  // Pretend that this version of single-install Chrome is already installed.
  machine_state.AddChrome(system_install, false,
                          new Version(chrome::kChromeVersion));

  // Now we're invoked to install multi Chrome.
  CommandLine cmd_line(
      CommandLine::FromString(L"setup.exe --multi-install --chrome"));
  MasterPreferences prefs(cmd_line);
  InstallerState installer_state;
  installer_state.Initialize(cmd_line, prefs, machine_state);

  // Is the Chrome version picked up?
  scoped_ptr<Version> version(installer_state.GetCurrentVersion(machine_state));
  EXPECT_TRUE(version.get() != NULL);
}

TEST_F(InstallerStateTest, IsFileInUse) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  base::FilePath temp_file;
  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &temp_file));

  EXPECT_FALSE(MockInstallerState::IsFileInUse(temp_file));

  {
    // Open a handle to the file with the same access mode and sharing options
    // as the loader.
    base::win::ScopedHandle temp_handle(
        CreateFile(temp_file.value().c_str(),
                   SYNCHRONIZE | FILE_EXECUTE,
                   FILE_SHARE_DELETE | FILE_SHARE_READ,
                   NULL, OPEN_EXISTING, 0, 0));
    ASSERT_TRUE(temp_handle != NULL);

    // The file should now be in use.
    EXPECT_TRUE(MockInstallerState::IsFileInUse(temp_file));
  }

  // And once the handle is gone, it should no longer be in use.
  EXPECT_FALSE(MockInstallerState::IsFileInUse(temp_file));
}

TEST_F(InstallerStateTest, RemoveOldVersionDirs) {
  MockInstallerState installer_state;
  installer_state.set_target_path(test_dir_.path());
  EXPECT_EQ(test_dir_.path().value(), installer_state.target_path().value());

  const char kOldVersion[] = "2.0.0.0";
  const char kNewVersion[] = "3.0.0.0";
  const char kOldChromeExeVersion[] = "2.1.0.0";
  const char kChromeExeVersion[] = "2.1.1.1";
  const char kNewChromeExeVersion[] = "3.0.0.0";

  Version new_version(kNewVersion);
  Version old_version(kOldVersion);
  Version old_chrome_exe_version(kOldChromeExeVersion);
  Version chrome_exe_version(kChromeExeVersion);
  Version new_chrome_exe_version(kNewChromeExeVersion);

  ASSERT_TRUE(new_version.IsValid());
  ASSERT_TRUE(old_version.IsValid());
  ASSERT_TRUE(old_chrome_exe_version.IsValid());
  ASSERT_TRUE(chrome_exe_version.IsValid());
  ASSERT_TRUE(new_chrome_exe_version.IsValid());

  // Set up a bunch of version dir paths.
  base::FilePath version_dirs[] = {
    installer_state.target_path().Append(L"1.2.3.4"),
    installer_state.target_path().Append(L"1.2.3.5"),
    installer_state.target_path().Append(L"1.2.3.6"),
    installer_state.target_path().Append(base::ASCIIToWide(kOldVersion)),
    installer_state.target_path().Append(
        base::ASCIIToWide(kOldChromeExeVersion)),
    installer_state.target_path().Append(L"2.1.1.0"),
    installer_state.target_path().Append(base::ASCIIToWide(kChromeExeVersion)),
    installer_state.target_path().Append(base::ASCIIToWide(kNewVersion)),
    installer_state.target_path().Append(L"3.9.1.1"),
  };

  // Create the version directories.
  for (int i = 0; i < arraysize(version_dirs); i++) {
    base::CreateDirectory(version_dirs[i]);
    EXPECT_TRUE(base::PathExists(version_dirs[i]));
  }

  // Create exes with the appropriate version resource.
  // Use the current test exe as a baseline.
  base::FilePath exe_path;
  ASSERT_TRUE(PathService::Get(base::FILE_EXE, &exe_path));

  struct target_info {
    base::FilePath target_file;
    const Version& target_version;
  } targets[] = {
    { installer_state.target_path().Append(installer::kChromeOldExe),
      old_chrome_exe_version },
    { installer_state.target_path().Append(installer::kChromeExe),
      chrome_exe_version },
    { installer_state.target_path().Append(installer::kChromeNewExe),
      new_chrome_exe_version },
  };
  for (int i = 0; i < arraysize(targets); ++i) {
    ASSERT_TRUE(upgrade_test::GenerateSpecificPEFileVersion(
        exe_path, targets[i].target_file, targets[i].target_version));
  }

  // Call GetExistingExeVersions, validate that picks up the
  // exe resources.
  std::set<std::string> expected_exe_versions;
  expected_exe_versions.insert(kOldChromeExeVersion);
  expected_exe_versions.insert(kChromeExeVersion);
  expected_exe_versions.insert(kNewChromeExeVersion);

  std::set<std::string> actual_exe_versions;
  installer_state.GetExistingExeVersions(&actual_exe_versions);
  EXPECT_EQ(expected_exe_versions, actual_exe_versions);

  // Call RemoveOldVersionDirectories
  installer_state.RemoveOldVersionDirectories(new_version,
                                              &old_version,
                                              installer_state.target_path());

  // What we expect to have left.
  std::set<std::string> expected_remaining_dirs;
  expected_remaining_dirs.insert(kOldVersion);
  expected_remaining_dirs.insert(kNewVersion);
  expected_remaining_dirs.insert(kOldChromeExeVersion);
  expected_remaining_dirs.insert(kChromeExeVersion);
  expected_remaining_dirs.insert(kNewChromeExeVersion);

  // Enumerate dirs in target_path(), ensure only desired remain.
  base::FileEnumerator version_enum(installer_state.target_path(), false,
                                    base::FileEnumerator::DIRECTORIES);
  for (base::FilePath next_version = version_enum.Next(); !next_version.empty();
       next_version = version_enum.Next()) {
    base::FilePath dir_name(next_version.BaseName());
    Version version(base::UTF16ToASCII(dir_name.value()));
    if (version.IsValid()) {
      EXPECT_TRUE(expected_remaining_dirs.erase(version.GetString()))
          << "Unexpected version dir found: " << version.GetString();
    }
  }

  std::set<std::string>::const_iterator iter(
      expected_remaining_dirs.begin());
  for (; iter != expected_remaining_dirs.end(); ++iter)
    ADD_FAILURE() << "Expected to find version dir for " << *iter;
}

TEST_F(InstallerStateTest, InitializeTwice) {
  InstallationState machine_state;
  machine_state.Initialize();

  InstallerState installer_state;

  // Initialize the instance to install multi Chrome.
  {
    CommandLine cmd_line(
        CommandLine::FromString(L"setup.exe --multi-install --chrome"));
    MasterPreferences prefs(cmd_line);
    installer_state.Initialize(cmd_line, prefs, machine_state);
  }
  // Confirm the expected state.
  EXPECT_EQ(InstallerState::USER_LEVEL, installer_state.level());
  EXPECT_EQ(InstallerState::MULTI_PACKAGE, installer_state.package_type());
  EXPECT_EQ(InstallerState::MULTI_INSTALL, installer_state.operation());
  EXPECT_TRUE(wcsstr(installer_state.target_path().value().c_str(),
                     BrowserDistribution::GetSpecificDistribution(
                         BrowserDistribution::CHROME_BINARIES)->
                         GetInstallSubDir().c_str()));
  EXPECT_FALSE(installer_state.verbose_logging());
  EXPECT_EQ(installer_state.state_key(),
            BrowserDistribution::GetSpecificDistribution(
                BrowserDistribution::CHROME_BROWSER)->GetStateKey());
  EXPECT_EQ(installer_state.state_type(), BrowserDistribution::CHROME_BROWSER);
  EXPECT_TRUE(installer_state.multi_package_binaries_distribution());
  EXPECT_TRUE(installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER));

  // Now initialize it to install system-level single Chrome.
  {
    CommandLine cmd_line(
        CommandLine::FromString(L"setup.exe --system-level --verbose-logging"));
    MasterPreferences prefs(cmd_line);
    installer_state.Initialize(cmd_line, prefs, machine_state);
  }

  // Confirm that the old state is gone.
  EXPECT_EQ(InstallerState::SYSTEM_LEVEL, installer_state.level());
  EXPECT_EQ(InstallerState::SINGLE_PACKAGE, installer_state.package_type());
  EXPECT_EQ(InstallerState::SINGLE_INSTALL_OR_UPDATE,
            installer_state.operation());
  EXPECT_TRUE(wcsstr(installer_state.target_path().value().c_str(),
                     BrowserDistribution::GetSpecificDistribution(
                         BrowserDistribution::CHROME_BROWSER)->
                         GetInstallSubDir().c_str()));
  EXPECT_TRUE(installer_state.verbose_logging());
  EXPECT_EQ(installer_state.state_key(),
            BrowserDistribution::GetSpecificDistribution(
                BrowserDistribution::CHROME_BROWSER)->GetStateKey());
  EXPECT_EQ(installer_state.state_type(), BrowserDistribution::CHROME_BROWSER);
  EXPECT_TRUE(installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER));
}

// A fixture for testing InstallerState::DetermineCriticalVersion.  Individual
// tests must invoke Initialize() with a critical version.
class InstallerStateCriticalVersionTest : public ::testing::Test {
 protected:
  InstallerStateCriticalVersionTest() : cmd_line_(CommandLine::NO_PROGRAM) {}

  // Creates a set of versions for use by all test runs.
  static void SetUpTestCase() {
    low_version_    = new Version("15.0.874.106");
    opv_version_    = new Version("15.0.874.255");
    middle_version_ = new Version("16.0.912.32");
    pv_version_     = new Version("16.0.912.255");
    high_version_   = new Version("17.0.932.0");
  }

  // Cleans up versions used by all test runs.
  static void TearDownTestCase() {
    delete low_version_;
    delete opv_version_;
    delete middle_version_;
    delete pv_version_;
    delete high_version_;
  }

  // Initializes the InstallerState to use for a test run.  The returned
  // instance's critical update version is set to |version|.  |version| may be
  // NULL, in which case the critical update version is unset.
  MockInstallerState& Initialize(const Version* version) {
    cmd_line_ = version == NULL ?
        CommandLine::FromString(L"setup.exe") :
        CommandLine::FromString(
            L"setup.exe --critical-update-version=" +
            base::ASCIIToWide(version->GetString()));
    prefs_.reset(new MasterPreferences(cmd_line_));
    machine_state_.Initialize();
    installer_state_.Initialize(cmd_line_, *prefs_, machine_state_);
    return installer_state_;
  }

  static Version* low_version_;
  static Version* opv_version_;
  static Version* middle_version_;
  static Version* pv_version_;
  static Version* high_version_;

  CommandLine cmd_line_;
  scoped_ptr<MasterPreferences> prefs_;
  InstallationState machine_state_;
  MockInstallerState installer_state_;
};

Version* InstallerStateCriticalVersionTest::low_version_ = NULL;
Version* InstallerStateCriticalVersionTest::opv_version_ = NULL;
Version* InstallerStateCriticalVersionTest::middle_version_ = NULL;
Version* InstallerStateCriticalVersionTest::pv_version_ = NULL;
Version* InstallerStateCriticalVersionTest::high_version_ = NULL;

// Test the case where the critical version is less than the currently-running
// Chrome.  The critical version is ignored since it doesn't apply.
TEST_F(InstallerStateCriticalVersionTest, CriticalBeforeOpv) {
  MockInstallerState& installer_state(Initialize(low_version_));

  EXPECT_TRUE(installer_state.critical_update_version().Equals(*low_version_));
  // Unable to determine the installed version, so assume critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(NULL, *pv_version_).IsValid());
  // Installed version is past the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(opv_version_, *pv_version_)
          .IsValid());
  // Installed version is past the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(pv_version_, *pv_version_)
          .IsValid());
}

// Test the case where the critical version is equal to the currently-running
// Chrome.  The critical version is ignored since it doesn't apply.
TEST_F(InstallerStateCriticalVersionTest, CriticalEqualsOpv) {
  MockInstallerState& installer_state(Initialize(opv_version_));

  EXPECT_TRUE(installer_state.critical_update_version().Equals(*opv_version_));
  // Unable to determine the installed version, so assume critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(NULL, *pv_version_).IsValid());
  // Installed version equals the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(opv_version_, *pv_version_)
          .IsValid());
  // Installed version equals the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(pv_version_, *pv_version_)
          .IsValid());
}

// Test the case where the critical version is between the currently-running
// Chrome and the to-be-installed Chrome.
TEST_F(InstallerStateCriticalVersionTest, CriticalBetweenOpvAndPv) {
  MockInstallerState& installer_state(Initialize(middle_version_));

  EXPECT_TRUE(installer_state.critical_update_version().Equals(
      *middle_version_));
  // Unable to determine the installed version, so assume critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(NULL, *pv_version_).IsValid());
  // Installed version before the critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(opv_version_, *pv_version_)
          .IsValid());
  // Installed version is past the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(pv_version_, *pv_version_)
          .IsValid());
}

// Test the case where the critical version is the same as the to-be-installed
// Chrome.
TEST_F(InstallerStateCriticalVersionTest, CriticalEqualsPv) {
  MockInstallerState& installer_state(Initialize(pv_version_));

  EXPECT_TRUE(installer_state.critical_update_version().Equals(
      *pv_version_));
  // Unable to determine the installed version, so assume critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(NULL, *pv_version_).IsValid());
  // Installed version before the critical update.
  EXPECT_TRUE(
      installer_state.DetermineCriticalVersion(opv_version_, *pv_version_)
          .IsValid());
  // Installed version equals the critical update.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(pv_version_, *pv_version_)
          .IsValid());
}

// Test the case where the critical version is greater than the to-be-installed
// Chrome.
TEST_F(InstallerStateCriticalVersionTest, CriticalAfterPv) {
  MockInstallerState& installer_state(Initialize(high_version_));

  EXPECT_TRUE(installer_state.critical_update_version().Equals(
      *high_version_));
  // Critical update newer than the new version.
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(NULL, *pv_version_).IsValid());
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(opv_version_, *pv_version_)
          .IsValid());
  EXPECT_FALSE(
      installer_state.DetermineCriticalVersion(pv_version_, *pv_version_)
          .IsValid());
}