Home | History | Annotate | Download | only in setup
      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 
     32 namespace {
     33 
     34 class 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.
     53 static const wchar_t kTestedPrivilege[] = SE_RESTORE_NAME;
     54 
     55 // Returns true if the current process' token has privilege |privilege_name|
     56 // enabled.
     57 bool 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.
     98 TEST_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 
    134 TEST_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).
    146 TEST(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).
    160 TEST(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 
    178 const char kAdjustProcessPriority[] = "adjust-process-priority";
    179 
    180 PriorityClassChangeResult DoProcessPriorityAdjustment() {
    181   return installer::AdjustProcessPriority() ? PCCR_CHANGED : PCCR_UNCHANGED;
    182 }
    183 
    184 namespace {
    185 
    186 // A scoper that sets/resets the current process's priority class.
    187 class 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 
    200 scoped_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 
    216 ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class)
    217     : original_priority_class_(original_priority_class) {}
    218 
    219 ScopedPriorityClass::~ScopedPriorityClass() {
    220   BOOL result = ::SetPriorityClass(::GetCurrentProcess(),
    221                                    original_priority_class_);
    222   EXPECT_NE(FALSE, result);
    223 }
    224 
    225 PriorityClassChangeResult 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.
    244 TEST(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.
    251 TEST(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 
    261 namespace {
    262 
    263 // A test fixture that configures an InstallationState and an InstallerState
    264 // with a product being updated.
    265 class 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 
    363 const bool FindArchiveToPatchTest::kSystemInstall_ = false;
    364 const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ =
    365     BrowserDistribution::CHROME_BROWSER;
    366 
    367 }  // namespace
    368 
    369 // Test that the path to the advertised product version is found.
    370 TEST_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.
    378 TEST_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.
    393 TEST_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 
    404 namespace {
    405 
    406 class 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 
    424 const bool MigrateMultiToSingleTest::kSystemLevel;
    425 const HKEY MigrateMultiToSingleTest::kRootKey =
    426     kSystemLevel ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    427 const wchar_t MigrateMultiToSingleTest::kVersionString[] = L"30.0.1574.0";
    428 const wchar_t MigrateMultiToSingleTest::kMultiChannel[] =
    429     L"2.0-dev-multi-chromeframe";
    430 
    431 }  // namespace
    432 
    433 // Test migrating Chrome Frame from multi to single.
    434 TEST_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