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