setup_util_unittest.cc revision 558790d6acca3451cf3a6b497803a5f07d0bec58
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/installer/setup/setup_util_unittest.h" 6 7#include <windows.h> 8 9#include <string> 10 11#include "base/command_line.h" 12#include "base/file_util.h" 13#include "base/files/scoped_temp_dir.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/process/kill.h" 16#include "base/process/launch.h" 17#include "base/process/process_handle.h" 18#include "base/test/test_reg_util_win.h" 19#include "base/threading/platform_thread.h" 20#include "base/time/time.h" 21#include "base/version.h" 22#include "base/win/scoped_handle.h" 23#include "base/win/windows_version.h" 24#include "chrome/installer/setup/setup_util.h" 25#include "chrome/installer/setup/setup_constants.h" 26#include "chrome/installer/util/google_update_constants.h" 27#include "chrome/installer/util/installation_state.h" 28#include "chrome/installer/util/installer_state.h" 29#include "chrome/installer/util/util_constants.h" 30#include "testing/gtest/include/gtest/gtest.h" 31 32namespace { 33 34class SetupUtilTestWithDir : public testing::Test { 35 protected: 36 virtual void SetUp() OVERRIDE { 37 // Create a temp directory for testing. 38 ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); 39 } 40 41 virtual void TearDown() OVERRIDE { 42 // Clean up test directory manually so we can fail if it leaks. 43 ASSERT_TRUE(test_dir_.Delete()); 44 } 45 46 // The temporary directory used to contain the test operations. 47 base::ScopedTempDir test_dir_; 48}; 49 50// The privilege tested in ScopeTokenPrivilege tests below. 51// Use SE_RESTORE_NAME as it is one of the many privileges that is available, 52// but not enabled by default on processes running at high integrity. 53static const wchar_t kTestedPrivilege[] = SE_RESTORE_NAME; 54 55// Returns true if the current process' token has privilege |privilege_name| 56// enabled. 57bool CurrentProcessHasPrivilege(const wchar_t* privilege_name) { 58 base::win::ScopedHandle token; 59 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, 60 token.Receive())) { 61 ADD_FAILURE(); 62 return false; 63 } 64 65 // First get the size of the buffer needed for |privileges| below. 66 DWORD size; 67 EXPECT_FALSE(::GetTokenInformation(token, TokenPrivileges, NULL, 0, &size)); 68 69 scoped_ptr<BYTE[]> privileges_bytes(new BYTE[size]); 70 TOKEN_PRIVILEGES* privileges = 71 reinterpret_cast<TOKEN_PRIVILEGES*>(privileges_bytes.get()); 72 73 if (!::GetTokenInformation(token, TokenPrivileges, privileges, size, &size)) { 74 ADD_FAILURE(); 75 return false; 76 } 77 78 // There is no point getting a buffer to store more than |privilege_name|\0 as 79 // anything longer will obviously not be equal to |privilege_name|. 80 const DWORD desired_size = wcslen(privilege_name); 81 const DWORD buffer_size = desired_size + 1; 82 scoped_ptr<wchar_t[]> name_buffer(new wchar_t[buffer_size]); 83 for (int i = privileges->PrivilegeCount - 1; i >= 0 ; --i) { 84 LUID_AND_ATTRIBUTES& luid_and_att = privileges->Privileges[i]; 85 DWORD size = buffer_size; 86 ::LookupPrivilegeName(NULL, &luid_and_att.Luid, name_buffer.get(), &size); 87 if (size == desired_size && 88 wcscmp(name_buffer.get(), privilege_name) == 0) { 89 return luid_and_att.Attributes == SE_PRIVILEGE_ENABLED; 90 } 91 } 92 return false; 93} 94 95} // namespace 96 97// Test that we are parsing Chrome version correctly. 98TEST_F(SetupUtilTestWithDir, GetMaxVersionFromArchiveDirTest) { 99 // Create a version dir 100 base::FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0"); 101 file_util::CreateDirectory(chrome_dir); 102 ASSERT_TRUE(base::PathExists(chrome_dir)); 103 scoped_ptr<Version> version( 104 installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 105 ASSERT_EQ(version->GetString(), "1.0.0.0"); 106 107 base::DeleteFile(chrome_dir, true); 108 ASSERT_FALSE(base::PathExists(chrome_dir)); 109 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); 110 111 chrome_dir = test_dir_.path().AppendASCII("ABC"); 112 file_util::CreateDirectory(chrome_dir); 113 ASSERT_TRUE(base::PathExists(chrome_dir)); 114 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); 115 116 chrome_dir = test_dir_.path().AppendASCII("2.3.4.5"); 117 file_util::CreateDirectory(chrome_dir); 118 ASSERT_TRUE(base::PathExists(chrome_dir)); 119 version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 120 ASSERT_EQ(version->GetString(), "2.3.4.5"); 121 122 // Create multiple version dirs, ensure that we select the greatest. 123 chrome_dir = test_dir_.path().AppendASCII("9.9.9.9"); 124 file_util::CreateDirectory(chrome_dir); 125 ASSERT_TRUE(base::PathExists(chrome_dir)); 126 chrome_dir = test_dir_.path().AppendASCII("1.1.1.1"); 127 file_util::CreateDirectory(chrome_dir); 128 ASSERT_TRUE(base::PathExists(chrome_dir)); 129 130 version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 131 ASSERT_EQ(version->GetString(), "9.9.9.9"); 132} 133 134TEST_F(SetupUtilTestWithDir, DeleteFileFromTempProcess) { 135 base::FilePath test_file; 136 file_util::CreateTemporaryFileInDir(test_dir_.path(), &test_file); 137 ASSERT_TRUE(base::PathExists(test_file)); 138 file_util::WriteFile(test_file, "foo", 3); 139 EXPECT_TRUE(installer::DeleteFileFromTempProcess(test_file, 0)); 140 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); 141 EXPECT_FALSE(base::PathExists(test_file)); 142} 143 144// Note: This test is only valid when run at high integrity (i.e. it will fail 145// at medium integrity). 146TEST(SetupUtilTest, ScopedTokenPrivilegeBasic) { 147 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 148 149 { 150 installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); 151 ASSERT_TRUE(test_scoped_privilege.is_enabled()); 152 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 153 } 154 155 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 156} 157 158// Note: This test is only valid when run at high integrity (i.e. it will fail 159// at medium integrity). 160TEST(SetupUtilTest, ScopedTokenPrivilegeAlreadyEnabled) { 161 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 162 163 { 164 installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); 165 ASSERT_TRUE(test_scoped_privilege.is_enabled()); 166 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 167 { 168 installer::ScopedTokenPrivilege dup_scoped_privilege(kTestedPrivilege); 169 ASSERT_TRUE(dup_scoped_privilege.is_enabled()); 170 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 171 } 172 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 173 } 174 175 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 176} 177 178const char kAdjustProcessPriority[] = "adjust-process-priority"; 179 180PriorityClassChangeResult DoProcessPriorityAdjustment() { 181 return installer::AdjustProcessPriority() ? PCCR_CHANGED : PCCR_UNCHANGED; 182} 183 184namespace { 185 186// A scoper that sets/resets the current process's priority class. 187class ScopedPriorityClass { 188 public: 189 // Applies |priority_class|, returning an instance if a change was made. 190 // Otherwise, returns an empty scoped_ptr. 191 static scoped_ptr<ScopedPriorityClass> Create(DWORD priority_class); 192 ~ScopedPriorityClass(); 193 194 private: 195 explicit ScopedPriorityClass(DWORD original_priority_class); 196 DWORD original_priority_class_; 197 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityClass); 198}; 199 200scoped_ptr<ScopedPriorityClass> ScopedPriorityClass::Create( 201 DWORD priority_class) { 202 HANDLE this_process = ::GetCurrentProcess(); 203 DWORD original_priority_class = ::GetPriorityClass(this_process); 204 EXPECT_NE(0U, original_priority_class); 205 if (original_priority_class && original_priority_class != priority_class) { 206 BOOL result = ::SetPriorityClass(this_process, priority_class); 207 EXPECT_NE(FALSE, result); 208 if (result) { 209 return scoped_ptr<ScopedPriorityClass>( 210 new ScopedPriorityClass(original_priority_class)); 211 } 212 } 213 return scoped_ptr<ScopedPriorityClass>(); 214} 215 216ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class) 217 : original_priority_class_(original_priority_class) {} 218 219ScopedPriorityClass::~ScopedPriorityClass() { 220 BOOL result = ::SetPriorityClass(::GetCurrentProcess(), 221 original_priority_class_); 222 EXPECT_NE(FALSE, result); 223} 224 225PriorityClassChangeResult RelaunchAndDoProcessPriorityAdjustment() { 226 CommandLine cmd_line(*CommandLine::ForCurrentProcess()); 227 cmd_line.AppendSwitch(kAdjustProcessPriority); 228 base::ProcessHandle process_handle = NULL; 229 int exit_code = 0; 230 if (!base::LaunchProcess(cmd_line, base::LaunchOptions(), 231 &process_handle)) { 232 ADD_FAILURE() << " to launch subprocess."; 233 } else if (!base::WaitForExitCode(process_handle, &exit_code)) { 234 ADD_FAILURE() << " to wait for subprocess to exit."; 235 } else { 236 return static_cast<PriorityClassChangeResult>(exit_code); 237 } 238 return PCCR_UNKNOWN; 239} 240 241} // namespace 242 243// Launching a subprocess at normal priority class is a noop. 244TEST(SetupUtilTest, AdjustFromNormalPriority) { 245 ASSERT_EQ(NORMAL_PRIORITY_CLASS, ::GetPriorityClass(::GetCurrentProcess())); 246 EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); 247} 248 249// Launching a subprocess below normal priority class drops it to bg mode for 250// sufficiently recent operating systems. 251TEST(SetupUtilTest, AdjustFromBelowNormalPriority) { 252 scoped_ptr<ScopedPriorityClass> below_normal = 253 ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS); 254 ASSERT_TRUE(below_normal); 255 if (base::win::GetVersion() > base::win::VERSION_SERVER_2003) 256 EXPECT_EQ(PCCR_CHANGED, RelaunchAndDoProcessPriorityAdjustment()); 257 else 258 EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); 259} 260 261namespace { 262 263// A test fixture that configures an InstallationState and an InstallerState 264// with a product being updated. 265class FindArchiveToPatchTest : public SetupUtilTestWithDir { 266 protected: 267 class FakeInstallationState : public installer::InstallationState { 268 }; 269 270 class FakeProductState : public installer::ProductState { 271 public: 272 static FakeProductState* FromProductState(const ProductState* product) { 273 return static_cast<FakeProductState*>(const_cast<ProductState*>(product)); 274 } 275 276 void set_version(const Version& version) { 277 if (version.IsValid()) 278 version_.reset(new Version(version)); 279 else 280 version_.reset(); 281 } 282 283 void set_uninstall_command(const CommandLine& uninstall_command) { 284 uninstall_command_ = uninstall_command; 285 } 286 }; 287 288 virtual void SetUp() OVERRIDE { 289 SetupUtilTestWithDir::SetUp(); 290 product_version_ = Version("30.0.1559.0"); 291 max_version_ = Version("47.0.1559.0"); 292 293 // Install the product according to the version. 294 original_state_.reset(new FakeInstallationState()); 295 InstallProduct(); 296 297 // Prepare to update the product in the temp dir. 298 installer_state_.reset(new installer::InstallerState( 299 kSystemInstall_ ? installer::InstallerState::SYSTEM_LEVEL : 300 installer::InstallerState::USER_LEVEL)); 301 installer_state_->AddProductFromState( 302 kProductType_, 303 *original_state_->GetProductState(kSystemInstall_, kProductType_)); 304 305 // Create archives in the two version dirs. 306 ASSERT_TRUE( 307 file_util::CreateDirectory(GetProductVersionArchivePath().DirName())); 308 ASSERT_EQ(1, file_util::WriteFile(GetProductVersionArchivePath(), "a", 1)); 309 ASSERT_TRUE( 310 file_util::CreateDirectory(GetMaxVersionArchivePath().DirName())); 311 ASSERT_EQ(1, file_util::WriteFile(GetMaxVersionArchivePath(), "b", 1)); 312 } 313 314 virtual void TearDown() OVERRIDE { 315 original_state_.reset(); 316 SetupUtilTestWithDir::TearDown(); 317 } 318 319 base::FilePath GetArchivePath(const Version& version) const { 320 return test_dir_.path() 321 .AppendASCII(version.GetString()) 322 .Append(installer::kInstallerDir) 323 .Append(installer::kChromeArchive); 324 } 325 326 base::FilePath GetMaxVersionArchivePath() const { 327 return GetArchivePath(max_version_); 328 } 329 330 base::FilePath GetProductVersionArchivePath() const { 331 return GetArchivePath(product_version_); 332 } 333 334 void InstallProduct() { 335 FakeProductState* product = FakeProductState::FromProductState( 336 original_state_->GetNonVersionedProductState(kSystemInstall_, 337 kProductType_)); 338 339 product->set_version(product_version_); 340 CommandLine uninstall_command( 341 test_dir_.path().AppendASCII(product_version_.GetString()) 342 .Append(installer::kInstallerDir) 343 .Append(installer::kSetupExe)); 344 uninstall_command.AppendSwitch(installer::switches::kUninstall); 345 product->set_uninstall_command(uninstall_command); 346 } 347 348 void UninstallProduct() { 349 FakeProductState::FromProductState( 350 original_state_->GetNonVersionedProductState(kSystemInstall_, 351 kProductType_)) 352 ->set_version(Version()); 353 } 354 355 static const bool kSystemInstall_; 356 static const BrowserDistribution::Type kProductType_; 357 Version product_version_; 358 Version max_version_; 359 scoped_ptr<FakeInstallationState> original_state_; 360 scoped_ptr<installer::InstallerState> installer_state_; 361}; 362 363const bool FindArchiveToPatchTest::kSystemInstall_ = false; 364const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ = 365 BrowserDistribution::CHROME_BROWSER; 366 367} // namespace 368 369// Test that the path to the advertised product version is found. 370TEST_F(FindArchiveToPatchTest, ProductVersionFound) { 371 base::FilePath patch_source(installer::FindArchiveToPatch( 372 *original_state_, *installer_state_)); 373 EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value()); 374} 375 376// Test that the path to the max version is found if the advertised version is 377// missing. 378TEST_F(FindArchiveToPatchTest, MaxVersionFound) { 379 // The patch file is absent. 380 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); 381 base::FilePath patch_source(installer::FindArchiveToPatch( 382 *original_state_, *installer_state_)); 383 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); 384 385 // The product doesn't appear to be installed, so the max version is found. 386 UninstallProduct(); 387 patch_source = installer::FindArchiveToPatch( 388 *original_state_, *installer_state_); 389 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); 390} 391 392// Test that an empty path is returned if no version is found. 393TEST_F(FindArchiveToPatchTest, NoVersionFound) { 394 // The product doesn't appear to be installed and no archives are present. 395 UninstallProduct(); 396 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); 397 ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false)); 398 399 base::FilePath patch_source(installer::FindArchiveToPatch( 400 *original_state_, *installer_state_)); 401 EXPECT_EQ(base::FilePath::StringType(), patch_source.value()); 402} 403 404namespace { 405 406class MigrateMultiToSingleTest : public testing::Test { 407 protected: 408 virtual void SetUp() OVERRIDE { 409 registry_override_manager_.OverrideRegistry(kRootKey, 410 L"MigrateMultiToSingleTest"); 411 } 412 413 virtual void TearDown() OVERRIDE { 414 registry_override_manager_.RemoveAllOverrides(); 415 } 416 417 static const bool kSystemLevel = false; 418 static const HKEY kRootKey; 419 static const wchar_t kVersionString[]; 420 static const wchar_t kMultiChannel[]; 421 registry_util::RegistryOverrideManager registry_override_manager_; 422}; 423 424const bool MigrateMultiToSingleTest::kSystemLevel; 425const HKEY MigrateMultiToSingleTest::kRootKey = 426 kSystemLevel ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 427const wchar_t MigrateMultiToSingleTest::kVersionString[] = L"30.0.1574.0"; 428const wchar_t MigrateMultiToSingleTest::kMultiChannel[] = 429 L"2.0-dev-multi-chromeframe"; 430 431} // namespace 432 433// Test migrating Chrome Frame from multi to single. 434TEST_F(MigrateMultiToSingleTest, ChromeFrame) { 435 installer::ProductState chrome_frame; 436 installer::ProductState binaries; 437 DWORD usagestats = 0; 438 439 // Set up a config with dev-channel multi-install GCF. 440 base::win::RegKey key; 441 442 BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( 443 BrowserDistribution::CHROME_BINARIES); 444 ASSERT_EQ(ERROR_SUCCESS, 445 base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), 446 KEY_SET_VALUE) 447 .WriteValue(google_update::kRegVersionField, kVersionString)); 448 ASSERT_EQ(ERROR_SUCCESS, 449 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 450 KEY_SET_VALUE) 451 .WriteValue(google_update::kRegApField, kMultiChannel)); 452 ASSERT_EQ(ERROR_SUCCESS, 453 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 454 KEY_SET_VALUE) 455 .WriteValue(google_update::kRegUsageStatsField, 1U)); 456 457 dist = BrowserDistribution::GetSpecificDistribution( 458 BrowserDistribution::CHROME_FRAME); 459 ASSERT_EQ(ERROR_SUCCESS, 460 base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), 461 KEY_SET_VALUE) 462 .WriteValue(google_update::kRegVersionField, kVersionString)); 463 ASSERT_EQ(ERROR_SUCCESS, 464 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 465 KEY_SET_VALUE) 466 .WriteValue(google_update::kRegApField, kMultiChannel)); 467 468 // Do the registry migration. 469 installer::InstallationState machine_state; 470 machine_state.Initialize(); 471 472 installer::MigrateGoogleUpdateStateMultiToSingle( 473 kSystemLevel, 474 BrowserDistribution::CHROME_FRAME, 475 machine_state); 476 477 // Confirm that usagestats were copied to CF and that its channel was 478 // stripped. 479 ASSERT_TRUE(chrome_frame.Initialize(kSystemLevel, 480 BrowserDistribution::CHROME_FRAME)); 481 EXPECT_TRUE(chrome_frame.GetUsageStats(&usagestats)); 482 EXPECT_EQ(1U, usagestats); 483 EXPECT_EQ(L"2.0-dev", chrome_frame.channel().value()); 484 485 // Confirm that the binaries' channel no longer contains GCF. 486 ASSERT_TRUE(binaries.Initialize(kSystemLevel, 487 BrowserDistribution::CHROME_BINARIES)); 488 EXPECT_EQ(L"2.0-dev-multi", binaries.channel().value()); 489} 490