// 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 "chrome/installer/setup/setup_util_unittest.h" #include <windows.h> #include <string> #include "base/command_line.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "base/process/process_handle.h" #include "base/test/test_reg_util_win.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/version.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" #include "chrome/installer/setup/setup_util.h" #include "chrome/installer/setup/setup_constants.h" #include "chrome/installer/util/google_update_constants.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/installer_state.h" #include "chrome/installer/util/util_constants.h" #include "testing/gtest/include/gtest/gtest.h" namespace { class SetupUtilTestWithDir : public testing::Test { protected: virtual void SetUp() OVERRIDE { // Create a temp directory for testing. ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); } virtual void TearDown() OVERRIDE { // Clean up test directory manually so we can fail if it leaks. ASSERT_TRUE(test_dir_.Delete()); } // The temporary directory used to contain the test operations. base::ScopedTempDir test_dir_; }; // The privilege tested in ScopeTokenPrivilege tests below. // Use SE_RESTORE_NAME as it is one of the many privileges that is available, // but not enabled by default on processes running at high integrity. static const wchar_t kTestedPrivilege[] = SE_RESTORE_NAME; // Returns true if the current process' token has privilege |privilege_name| // enabled. bool CurrentProcessHasPrivilege(const wchar_t* privilege_name) { HANDLE temp_handle; if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &temp_handle)) { ADD_FAILURE(); return false; } base::win::ScopedHandle token(temp_handle); // First get the size of the buffer needed for |privileges| below. DWORD size; EXPECT_FALSE(::GetTokenInformation(token, TokenPrivileges, NULL, 0, &size)); scoped_ptr<BYTE[]> privileges_bytes(new BYTE[size]); TOKEN_PRIVILEGES* privileges = reinterpret_cast<TOKEN_PRIVILEGES*>(privileges_bytes.get()); if (!::GetTokenInformation(token, TokenPrivileges, privileges, size, &size)) { ADD_FAILURE(); return false; } // There is no point getting a buffer to store more than |privilege_name|\0 as // anything longer will obviously not be equal to |privilege_name|. const DWORD desired_size = wcslen(privilege_name); const DWORD buffer_size = desired_size + 1; scoped_ptr<wchar_t[]> name_buffer(new wchar_t[buffer_size]); for (int i = privileges->PrivilegeCount - 1; i >= 0 ; --i) { LUID_AND_ATTRIBUTES& luid_and_att = privileges->Privileges[i]; DWORD size = buffer_size; ::LookupPrivilegeName(NULL, &luid_and_att.Luid, name_buffer.get(), &size); if (size == desired_size && wcscmp(name_buffer.get(), privilege_name) == 0) { return luid_and_att.Attributes == SE_PRIVILEGE_ENABLED; } } return false; } } // namespace // Test that we are parsing Chrome version correctly. TEST_F(SetupUtilTestWithDir, GetMaxVersionFromArchiveDirTest) { // Create a version dir base::FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0"); base::CreateDirectory(chrome_dir); ASSERT_TRUE(base::PathExists(chrome_dir)); scoped_ptr<Version> version( installer::GetMaxVersionFromArchiveDir(test_dir_.path())); ASSERT_EQ(version->GetString(), "1.0.0.0"); base::DeleteFile(chrome_dir, true); ASSERT_FALSE(base::PathExists(chrome_dir)); ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); chrome_dir = test_dir_.path().AppendASCII("ABC"); base::CreateDirectory(chrome_dir); ASSERT_TRUE(base::PathExists(chrome_dir)); ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); chrome_dir = test_dir_.path().AppendASCII("2.3.4.5"); base::CreateDirectory(chrome_dir); ASSERT_TRUE(base::PathExists(chrome_dir)); version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); ASSERT_EQ(version->GetString(), "2.3.4.5"); // Create multiple version dirs, ensure that we select the greatest. chrome_dir = test_dir_.path().AppendASCII("9.9.9.9"); base::CreateDirectory(chrome_dir); ASSERT_TRUE(base::PathExists(chrome_dir)); chrome_dir = test_dir_.path().AppendASCII("1.1.1.1"); base::CreateDirectory(chrome_dir); ASSERT_TRUE(base::PathExists(chrome_dir)); version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); ASSERT_EQ(version->GetString(), "9.9.9.9"); } TEST_F(SetupUtilTestWithDir, DeleteFileFromTempProcess) { base::FilePath test_file; base::CreateTemporaryFileInDir(test_dir_.path(), &test_file); ASSERT_TRUE(base::PathExists(test_file)); base::WriteFile(test_file, "foo", 3); EXPECT_TRUE(installer::DeleteFileFromTempProcess(test_file, 0)); base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); EXPECT_FALSE(base::PathExists(test_file)); } // Note: This test is only valid when run at high integrity (i.e. it will fail // at medium integrity). TEST(SetupUtilTest, ScopedTokenPrivilegeBasic) { ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); { installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); ASSERT_TRUE(test_scoped_privilege.is_enabled()); ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); } ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); } // Note: This test is only valid when run at high integrity (i.e. it will fail // at medium integrity). TEST(SetupUtilTest, ScopedTokenPrivilegeAlreadyEnabled) { ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); { installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); ASSERT_TRUE(test_scoped_privilege.is_enabled()); ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); { installer::ScopedTokenPrivilege dup_scoped_privilege(kTestedPrivilege); ASSERT_TRUE(dup_scoped_privilege.is_enabled()); ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); } ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); } ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); } const char kAdjustProcessPriority[] = "adjust-process-priority"; PriorityClassChangeResult DoProcessPriorityAdjustment() { return installer::AdjustProcessPriority() ? PCCR_CHANGED : PCCR_UNCHANGED; } namespace { // A scoper that sets/resets the current process's priority class. class ScopedPriorityClass { public: // Applies |priority_class|, returning an instance if a change was made. // Otherwise, returns an empty scoped_ptr. static scoped_ptr<ScopedPriorityClass> Create(DWORD priority_class); ~ScopedPriorityClass(); private: explicit ScopedPriorityClass(DWORD original_priority_class); DWORD original_priority_class_; DISALLOW_COPY_AND_ASSIGN(ScopedPriorityClass); }; scoped_ptr<ScopedPriorityClass> ScopedPriorityClass::Create( DWORD priority_class) { HANDLE this_process = ::GetCurrentProcess(); DWORD original_priority_class = ::GetPriorityClass(this_process); EXPECT_NE(0U, original_priority_class); if (original_priority_class && original_priority_class != priority_class) { BOOL result = ::SetPriorityClass(this_process, priority_class); EXPECT_NE(FALSE, result); if (result) { return scoped_ptr<ScopedPriorityClass>( new ScopedPriorityClass(original_priority_class)); } } return scoped_ptr<ScopedPriorityClass>(); } ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class) : original_priority_class_(original_priority_class) {} ScopedPriorityClass::~ScopedPriorityClass() { BOOL result = ::SetPriorityClass(::GetCurrentProcess(), original_priority_class_); EXPECT_NE(FALSE, result); } PriorityClassChangeResult RelaunchAndDoProcessPriorityAdjustment() { CommandLine cmd_line(*CommandLine::ForCurrentProcess()); cmd_line.AppendSwitch(kAdjustProcessPriority); base::ProcessHandle process_handle = NULL; int exit_code = 0; if (!base::LaunchProcess(cmd_line, base::LaunchOptions(), &process_handle)) { ADD_FAILURE() << " to launch subprocess."; } else if (!base::WaitForExitCode(process_handle, &exit_code)) { ADD_FAILURE() << " to wait for subprocess to exit."; } else { return static_cast<PriorityClassChangeResult>(exit_code); } return PCCR_UNKNOWN; } } // namespace // Launching a subprocess at normal priority class is a noop. TEST(SetupUtilTest, AdjustFromNormalPriority) { ASSERT_EQ(NORMAL_PRIORITY_CLASS, ::GetPriorityClass(::GetCurrentProcess())); EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); } // Launching a subprocess below normal priority class drops it to bg mode for // sufficiently recent operating systems. TEST(SetupUtilTest, AdjustFromBelowNormalPriority) { scoped_ptr<ScopedPriorityClass> below_normal = ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS); ASSERT_TRUE(below_normal); if (base::win::GetVersion() > base::win::VERSION_SERVER_2003) EXPECT_EQ(PCCR_CHANGED, RelaunchAndDoProcessPriorityAdjustment()); else EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); } namespace { // A test fixture that configures an InstallationState and an InstallerState // with a product being updated. class FindArchiveToPatchTest : public SetupUtilTestWithDir { protected: class FakeInstallationState : public installer::InstallationState { }; class FakeProductState : public installer::ProductState { public: static FakeProductState* FromProductState(const ProductState* product) { return static_cast<FakeProductState*>(const_cast<ProductState*>(product)); } void set_version(const Version& version) { if (version.IsValid()) version_.reset(new Version(version)); else version_.reset(); } void set_uninstall_command(const CommandLine& uninstall_command) { uninstall_command_ = uninstall_command; } }; virtual void SetUp() OVERRIDE { SetupUtilTestWithDir::SetUp(); product_version_ = Version("30.0.1559.0"); max_version_ = Version("47.0.1559.0"); // Install the product according to the version. original_state_.reset(new FakeInstallationState()); InstallProduct(); // Prepare to update the product in the temp dir. installer_state_.reset(new installer::InstallerState( kSystemInstall_ ? installer::InstallerState::SYSTEM_LEVEL : installer::InstallerState::USER_LEVEL)); installer_state_->AddProductFromState( kProductType_, *original_state_->GetProductState(kSystemInstall_, kProductType_)); // Create archives in the two version dirs. ASSERT_TRUE( base::CreateDirectory(GetProductVersionArchivePath().DirName())); ASSERT_EQ(1, base::WriteFile(GetProductVersionArchivePath(), "a", 1)); ASSERT_TRUE( base::CreateDirectory(GetMaxVersionArchivePath().DirName())); ASSERT_EQ(1, base::WriteFile(GetMaxVersionArchivePath(), "b", 1)); } virtual void TearDown() OVERRIDE { original_state_.reset(); SetupUtilTestWithDir::TearDown(); } base::FilePath GetArchivePath(const Version& version) const { return test_dir_.path() .AppendASCII(version.GetString()) .Append(installer::kInstallerDir) .Append(installer::kChromeArchive); } base::FilePath GetMaxVersionArchivePath() const { return GetArchivePath(max_version_); } base::FilePath GetProductVersionArchivePath() const { return GetArchivePath(product_version_); } void InstallProduct() { FakeProductState* product = FakeProductState::FromProductState( original_state_->GetNonVersionedProductState(kSystemInstall_, kProductType_)); product->set_version(product_version_); CommandLine uninstall_command( test_dir_.path().AppendASCII(product_version_.GetString()) .Append(installer::kInstallerDir) .Append(installer::kSetupExe)); uninstall_command.AppendSwitch(installer::switches::kUninstall); product->set_uninstall_command(uninstall_command); } void UninstallProduct() { FakeProductState::FromProductState( original_state_->GetNonVersionedProductState(kSystemInstall_, kProductType_)) ->set_version(Version()); } static const bool kSystemInstall_; static const BrowserDistribution::Type kProductType_; Version product_version_; Version max_version_; scoped_ptr<FakeInstallationState> original_state_; scoped_ptr<installer::InstallerState> installer_state_; }; const bool FindArchiveToPatchTest::kSystemInstall_ = false; const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ = BrowserDistribution::CHROME_BROWSER; } // namespace // Test that the path to the advertised product version is found. TEST_F(FindArchiveToPatchTest, ProductVersionFound) { base::FilePath patch_source(installer::FindArchiveToPatch( *original_state_, *installer_state_)); EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value()); } // Test that the path to the max version is found if the advertised version is // missing. TEST_F(FindArchiveToPatchTest, MaxVersionFound) { // The patch file is absent. ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); base::FilePath patch_source(installer::FindArchiveToPatch( *original_state_, *installer_state_)); EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); // The product doesn't appear to be installed, so the max version is found. UninstallProduct(); patch_source = installer::FindArchiveToPatch( *original_state_, *installer_state_); EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); } // Test that an empty path is returned if no version is found. TEST_F(FindArchiveToPatchTest, NoVersionFound) { // The product doesn't appear to be installed and no archives are present. UninstallProduct(); ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false)); base::FilePath patch_source(installer::FindArchiveToPatch( *original_state_, *installer_state_)); EXPECT_EQ(base::FilePath::StringType(), patch_source.value()); } namespace { class MigrateMultiToSingleTest : public testing::Test { protected: virtual void SetUp() OVERRIDE { registry_override_manager_.OverrideRegistry(kRootKey, L"MigrateMultiToSingleTest"); } static const bool kSystemLevel = false; static const HKEY kRootKey; static const wchar_t kVersionString[]; static const wchar_t kMultiChannel[]; registry_util::RegistryOverrideManager registry_override_manager_; }; const bool MigrateMultiToSingleTest::kSystemLevel; const HKEY MigrateMultiToSingleTest::kRootKey = kSystemLevel ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; const wchar_t MigrateMultiToSingleTest::kVersionString[] = L"30.0.1574.0"; const wchar_t MigrateMultiToSingleTest::kMultiChannel[] = L"2.0-dev-multi-chromeframe"; } // namespace // Test migrating Chrome Frame from multi to single. TEST_F(MigrateMultiToSingleTest, ChromeFrame) { installer::ProductState chrome_frame; installer::ProductState binaries; DWORD usagestats = 0; // Set up a config with dev-channel multi-install GCF. base::win::RegKey key; BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_BINARIES); ASSERT_EQ(ERROR_SUCCESS, base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), KEY_SET_VALUE) .WriteValue(google_update::kRegVersionField, kVersionString)); ASSERT_EQ(ERROR_SUCCESS, base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), KEY_SET_VALUE) .WriteValue(google_update::kRegApField, kMultiChannel)); ASSERT_EQ(ERROR_SUCCESS, base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), KEY_SET_VALUE) .WriteValue(google_update::kRegUsageStatsField, 1U)); dist = BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_FRAME); ASSERT_EQ(ERROR_SUCCESS, base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), KEY_SET_VALUE) .WriteValue(google_update::kRegVersionField, kVersionString)); ASSERT_EQ(ERROR_SUCCESS, base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), KEY_SET_VALUE) .WriteValue(google_update::kRegApField, kMultiChannel)); // Do the registry migration. installer::InstallationState machine_state; machine_state.Initialize(); installer::MigrateGoogleUpdateStateMultiToSingle( kSystemLevel, BrowserDistribution::CHROME_FRAME, machine_state); // Confirm that usagestats were copied to CF and that its channel was // stripped. ASSERT_TRUE(chrome_frame.Initialize(kSystemLevel, BrowserDistribution::CHROME_FRAME)); EXPECT_TRUE(chrome_frame.GetUsageStats(&usagestats)); EXPECT_EQ(1U, usagestats); EXPECT_EQ(L"2.0-dev", chrome_frame.channel().value()); // Confirm that the binaries' channel no longer contains GCF. ASSERT_TRUE(binaries.Initialize(kSystemLevel, BrowserDistribution::CHROME_BINARIES)); EXPECT_EQ(L"2.0-dev-multi", binaries.channel().value()); } TEST(SetupUtilTest, ContainsUnsupportedSwitch) { EXPECT_FALSE(installer::ContainsUnsupportedSwitch( CommandLine::FromString(L"foo.exe"))); EXPECT_FALSE(installer::ContainsUnsupportedSwitch( CommandLine::FromString(L"foo.exe --multi-install --chrome"))); EXPECT_TRUE(installer::ContainsUnsupportedSwitch( CommandLine::FromString(L"foo.exe --chrome-frame"))); }