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