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.
5 #include "chrome/installer/setup/setup_util_unittest.h"
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"
34 class SetupUtilTestWithDir
: public testing::Test
{
36 virtual void SetUp() OVERRIDE
{
37 // Create a temp directory for testing.
38 ASSERT_TRUE(test_dir_
.CreateUniqueTempDir());
41 virtual void TearDown() OVERRIDE
{
42 // Clean up test directory manually so we can fail if it leaks.
43 ASSERT_TRUE(test_dir_
.Delete());
46 // The temporary directory used to contain the test operations.
47 base::ScopedTempDir test_dir_
;
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
;
55 // Returns true if the current process' token has privilege |privilege_name|
57 bool CurrentProcessHasPrivilege(const wchar_t* privilege_name
) {
59 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY
,
65 base::win::ScopedHandle
token(temp_handle
);
67 // First get the size of the buffer needed for |privileges| below.
69 EXPECT_FALSE(::GetTokenInformation(token
, TokenPrivileges
, NULL
, 0, &size
));
71 scoped_ptr
<BYTE
[]> privileges_bytes(new BYTE
[size
]);
72 TOKEN_PRIVILEGES
* privileges
=
73 reinterpret_cast<TOKEN_PRIVILEGES
*>(privileges_bytes
.get());
75 if (!::GetTokenInformation(token
, TokenPrivileges
, privileges
, size
, &size
)) {
80 // There is no point getting a buffer to store more than |privilege_name|\0 as
81 // anything longer will obviously not be equal to |privilege_name|.
82 const DWORD desired_size
= wcslen(privilege_name
);
83 const DWORD buffer_size
= desired_size
+ 1;
84 scoped_ptr
<wchar_t[]> name_buffer(new wchar_t[buffer_size
]);
85 for (int i
= privileges
->PrivilegeCount
- 1; i
>= 0 ; --i
) {
86 LUID_AND_ATTRIBUTES
& luid_and_att
= privileges
->Privileges
[i
];
87 DWORD size
= buffer_size
;
88 ::LookupPrivilegeName(NULL
, &luid_and_att
.Luid
, name_buffer
.get(), &size
);
89 if (size
== desired_size
&&
90 wcscmp(name_buffer
.get(), privilege_name
) == 0) {
91 return luid_and_att
.Attributes
== SE_PRIVILEGE_ENABLED
;
99 // Test that we are parsing Chrome version correctly.
100 TEST_F(SetupUtilTestWithDir
, GetMaxVersionFromArchiveDirTest
) {
101 // Create a version dir
102 base::FilePath chrome_dir
= test_dir_
.path().AppendASCII("1.0.0.0");
103 base::CreateDirectory(chrome_dir
);
104 ASSERT_TRUE(base::PathExists(chrome_dir
));
105 scoped_ptr
<Version
> version(
106 installer::GetMaxVersionFromArchiveDir(test_dir_
.path()));
107 ASSERT_EQ(version
->GetString(), "1.0.0.0");
109 base::DeleteFile(chrome_dir
, true);
110 ASSERT_FALSE(base::PathExists(chrome_dir
));
111 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_
.path()) == NULL
);
113 chrome_dir
= test_dir_
.path().AppendASCII("ABC");
114 base::CreateDirectory(chrome_dir
);
115 ASSERT_TRUE(base::PathExists(chrome_dir
));
116 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_
.path()) == NULL
);
118 chrome_dir
= test_dir_
.path().AppendASCII("2.3.4.5");
119 base::CreateDirectory(chrome_dir
);
120 ASSERT_TRUE(base::PathExists(chrome_dir
));
121 version
.reset(installer::GetMaxVersionFromArchiveDir(test_dir_
.path()));
122 ASSERT_EQ(version
->GetString(), "2.3.4.5");
124 // Create multiple version dirs, ensure that we select the greatest.
125 chrome_dir
= test_dir_
.path().AppendASCII("9.9.9.9");
126 base::CreateDirectory(chrome_dir
);
127 ASSERT_TRUE(base::PathExists(chrome_dir
));
128 chrome_dir
= test_dir_
.path().AppendASCII("1.1.1.1");
129 base::CreateDirectory(chrome_dir
);
130 ASSERT_TRUE(base::PathExists(chrome_dir
));
132 version
.reset(installer::GetMaxVersionFromArchiveDir(test_dir_
.path()));
133 ASSERT_EQ(version
->GetString(), "9.9.9.9");
136 TEST_F(SetupUtilTestWithDir
, DeleteFileFromTempProcess
) {
137 base::FilePath test_file
;
138 base::CreateTemporaryFileInDir(test_dir_
.path(), &test_file
);
139 ASSERT_TRUE(base::PathExists(test_file
));
140 file_util::WriteFile(test_file
, "foo", 3);
141 EXPECT_TRUE(installer::DeleteFileFromTempProcess(test_file
, 0));
142 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
143 EXPECT_FALSE(base::PathExists(test_file
));
146 // Note: This test is only valid when run at high integrity (i.e. it will fail
147 // at medium integrity).
148 TEST(SetupUtilTest
, ScopedTokenPrivilegeBasic
) {
149 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege
));
152 installer::ScopedTokenPrivilege
test_scoped_privilege(kTestedPrivilege
);
153 ASSERT_TRUE(test_scoped_privilege
.is_enabled());
154 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege
));
157 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege
));
160 // Note: This test is only valid when run at high integrity (i.e. it will fail
161 // at medium integrity).
162 TEST(SetupUtilTest
, ScopedTokenPrivilegeAlreadyEnabled
) {
163 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege
));
166 installer::ScopedTokenPrivilege
test_scoped_privilege(kTestedPrivilege
);
167 ASSERT_TRUE(test_scoped_privilege
.is_enabled());
168 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege
));
170 installer::ScopedTokenPrivilege
dup_scoped_privilege(kTestedPrivilege
);
171 ASSERT_TRUE(dup_scoped_privilege
.is_enabled());
172 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege
));
174 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege
));
177 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege
));
180 const char kAdjustProcessPriority
[] = "adjust-process-priority";
182 PriorityClassChangeResult
DoProcessPriorityAdjustment() {
183 return installer::AdjustProcessPriority() ? PCCR_CHANGED
: PCCR_UNCHANGED
;
188 // A scoper that sets/resets the current process's priority class.
189 class ScopedPriorityClass
{
191 // Applies |priority_class|, returning an instance if a change was made.
192 // Otherwise, returns an empty scoped_ptr.
193 static scoped_ptr
<ScopedPriorityClass
> Create(DWORD priority_class
);
194 ~ScopedPriorityClass();
197 explicit ScopedPriorityClass(DWORD original_priority_class
);
198 DWORD original_priority_class_
;
199 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityClass
);
202 scoped_ptr
<ScopedPriorityClass
> ScopedPriorityClass::Create(
203 DWORD priority_class
) {
204 HANDLE this_process
= ::GetCurrentProcess();
205 DWORD original_priority_class
= ::GetPriorityClass(this_process
);
206 EXPECT_NE(0U, original_priority_class
);
207 if (original_priority_class
&& original_priority_class
!= priority_class
) {
208 BOOL result
= ::SetPriorityClass(this_process
, priority_class
);
209 EXPECT_NE(FALSE
, result
);
211 return scoped_ptr
<ScopedPriorityClass
>(
212 new ScopedPriorityClass(original_priority_class
));
215 return scoped_ptr
<ScopedPriorityClass
>();
218 ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class
)
219 : original_priority_class_(original_priority_class
) {}
221 ScopedPriorityClass::~ScopedPriorityClass() {
222 BOOL result
= ::SetPriorityClass(::GetCurrentProcess(),
223 original_priority_class_
);
224 EXPECT_NE(FALSE
, result
);
227 PriorityClassChangeResult
RelaunchAndDoProcessPriorityAdjustment() {
228 CommandLine
cmd_line(*CommandLine::ForCurrentProcess());
229 cmd_line
.AppendSwitch(kAdjustProcessPriority
);
230 base::ProcessHandle process_handle
= NULL
;
232 if (!base::LaunchProcess(cmd_line
, base::LaunchOptions(),
234 ADD_FAILURE() << " to launch subprocess.";
235 } else if (!base::WaitForExitCode(process_handle
, &exit_code
)) {
236 ADD_FAILURE() << " to wait for subprocess to exit.";
238 return static_cast<PriorityClassChangeResult
>(exit_code
);
245 // Launching a subprocess at normal priority class is a noop.
246 TEST(SetupUtilTest
, AdjustFromNormalPriority
) {
247 ASSERT_EQ(NORMAL_PRIORITY_CLASS
, ::GetPriorityClass(::GetCurrentProcess()));
248 EXPECT_EQ(PCCR_UNCHANGED
, RelaunchAndDoProcessPriorityAdjustment());
251 // Launching a subprocess below normal priority class drops it to bg mode for
252 // sufficiently recent operating systems.
253 TEST(SetupUtilTest
, AdjustFromBelowNormalPriority
) {
254 scoped_ptr
<ScopedPriorityClass
> below_normal
=
255 ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS
);
256 ASSERT_TRUE(below_normal
);
257 if (base::win::GetVersion() > base::win::VERSION_SERVER_2003
)
258 EXPECT_EQ(PCCR_CHANGED
, RelaunchAndDoProcessPriorityAdjustment());
260 EXPECT_EQ(PCCR_UNCHANGED
, RelaunchAndDoProcessPriorityAdjustment());
265 // A test fixture that configures an InstallationState and an InstallerState
266 // with a product being updated.
267 class FindArchiveToPatchTest
: public SetupUtilTestWithDir
{
269 class FakeInstallationState
: public installer::InstallationState
{
272 class FakeProductState
: public installer::ProductState
{
274 static FakeProductState
* FromProductState(const ProductState
* product
) {
275 return static_cast<FakeProductState
*>(const_cast<ProductState
*>(product
));
278 void set_version(const Version
& version
) {
279 if (version
.IsValid())
280 version_
.reset(new Version(version
));
285 void set_uninstall_command(const CommandLine
& uninstall_command
) {
286 uninstall_command_
= uninstall_command
;
290 virtual void SetUp() OVERRIDE
{
291 SetupUtilTestWithDir::SetUp();
292 product_version_
= Version("30.0.1559.0");
293 max_version_
= Version("47.0.1559.0");
295 // Install the product according to the version.
296 original_state_
.reset(new FakeInstallationState());
299 // Prepare to update the product in the temp dir.
300 installer_state_
.reset(new installer::InstallerState(
301 kSystemInstall_
? installer::InstallerState::SYSTEM_LEVEL
:
302 installer::InstallerState::USER_LEVEL
));
303 installer_state_
->AddProductFromState(
305 *original_state_
->GetProductState(kSystemInstall_
, kProductType_
));
307 // Create archives in the two version dirs.
309 base::CreateDirectory(GetProductVersionArchivePath().DirName()));
310 ASSERT_EQ(1, file_util::WriteFile(GetProductVersionArchivePath(), "a", 1));
312 base::CreateDirectory(GetMaxVersionArchivePath().DirName()));
313 ASSERT_EQ(1, file_util::WriteFile(GetMaxVersionArchivePath(), "b", 1));
316 virtual void TearDown() OVERRIDE
{
317 original_state_
.reset();
318 SetupUtilTestWithDir::TearDown();
321 base::FilePath
GetArchivePath(const Version
& version
) const {
322 return test_dir_
.path()
323 .AppendASCII(version
.GetString())
324 .Append(installer::kInstallerDir
)
325 .Append(installer::kChromeArchive
);
328 base::FilePath
GetMaxVersionArchivePath() const {
329 return GetArchivePath(max_version_
);
332 base::FilePath
GetProductVersionArchivePath() const {
333 return GetArchivePath(product_version_
);
336 void InstallProduct() {
337 FakeProductState
* product
= FakeProductState::FromProductState(
338 original_state_
->GetNonVersionedProductState(kSystemInstall_
,
341 product
->set_version(product_version_
);
342 CommandLine
uninstall_command(
343 test_dir_
.path().AppendASCII(product_version_
.GetString())
344 .Append(installer::kInstallerDir
)
345 .Append(installer::kSetupExe
));
346 uninstall_command
.AppendSwitch(installer::switches::kUninstall
);
347 product
->set_uninstall_command(uninstall_command
);
350 void UninstallProduct() {
351 FakeProductState::FromProductState(
352 original_state_
->GetNonVersionedProductState(kSystemInstall_
,
354 ->set_version(Version());
357 static const bool kSystemInstall_
;
358 static const BrowserDistribution::Type kProductType_
;
359 Version product_version_
;
360 Version max_version_
;
361 scoped_ptr
<FakeInstallationState
> original_state_
;
362 scoped_ptr
<installer::InstallerState
> installer_state_
;
365 const bool FindArchiveToPatchTest::kSystemInstall_
= false;
366 const BrowserDistribution::Type
FindArchiveToPatchTest::kProductType_
=
367 BrowserDistribution::CHROME_BROWSER
;
371 // Test that the path to the advertised product version is found.
372 TEST_F(FindArchiveToPatchTest
, ProductVersionFound
) {
373 base::FilePath
patch_source(installer::FindArchiveToPatch(
374 *original_state_
, *installer_state_
));
375 EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source
.value());
378 // Test that the path to the max version is found if the advertised version is
380 TEST_F(FindArchiveToPatchTest
, MaxVersionFound
) {
381 // The patch file is absent.
382 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false));
383 base::FilePath
patch_source(installer::FindArchiveToPatch(
384 *original_state_
, *installer_state_
));
385 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source
.value());
387 // The product doesn't appear to be installed, so the max version is found.
389 patch_source
= installer::FindArchiveToPatch(
390 *original_state_
, *installer_state_
);
391 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source
.value());
394 // Test that an empty path is returned if no version is found.
395 TEST_F(FindArchiveToPatchTest
, NoVersionFound
) {
396 // The product doesn't appear to be installed and no archives are present.
398 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false));
399 ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false));
401 base::FilePath
patch_source(installer::FindArchiveToPatch(
402 *original_state_
, *installer_state_
));
403 EXPECT_EQ(base::FilePath::StringType(), patch_source
.value());
408 class MigrateMultiToSingleTest
: public testing::Test
{
410 virtual void SetUp() OVERRIDE
{
411 registry_override_manager_
.OverrideRegistry(kRootKey
,
412 L
"MigrateMultiToSingleTest");
415 static const bool kSystemLevel
= false;
416 static const HKEY kRootKey
;
417 static const wchar_t kVersionString
[];
418 static const wchar_t kMultiChannel
[];
419 registry_util::RegistryOverrideManager registry_override_manager_
;
422 const bool MigrateMultiToSingleTest::kSystemLevel
;
423 const HKEY
MigrateMultiToSingleTest::kRootKey
=
424 kSystemLevel
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
425 const wchar_t MigrateMultiToSingleTest::kVersionString
[] = L
"30.0.1574.0";
426 const wchar_t MigrateMultiToSingleTest::kMultiChannel
[] =
427 L
"2.0-dev-multi-chromeframe";
431 // Test migrating Chrome Frame from multi to single.
432 TEST_F(MigrateMultiToSingleTest
, ChromeFrame
) {
433 installer::ProductState chrome_frame
;
434 installer::ProductState binaries
;
435 DWORD usagestats
= 0;
437 // Set up a config with dev-channel multi-install GCF.
438 base::win::RegKey key
;
440 BrowserDistribution
* dist
= BrowserDistribution::GetSpecificDistribution(
441 BrowserDistribution::CHROME_BINARIES
);
442 ASSERT_EQ(ERROR_SUCCESS
,
443 base::win::RegKey(kRootKey
, dist
->GetVersionKey().c_str(),
445 .WriteValue(google_update::kRegVersionField
, kVersionString
));
446 ASSERT_EQ(ERROR_SUCCESS
,
447 base::win::RegKey(kRootKey
, dist
->GetStateKey().c_str(),
449 .WriteValue(google_update::kRegApField
, kMultiChannel
));
450 ASSERT_EQ(ERROR_SUCCESS
,
451 base::win::RegKey(kRootKey
, dist
->GetStateKey().c_str(),
453 .WriteValue(google_update::kRegUsageStatsField
, 1U));
455 dist
= BrowserDistribution::GetSpecificDistribution(
456 BrowserDistribution::CHROME_FRAME
);
457 ASSERT_EQ(ERROR_SUCCESS
,
458 base::win::RegKey(kRootKey
, dist
->GetVersionKey().c_str(),
460 .WriteValue(google_update::kRegVersionField
, kVersionString
));
461 ASSERT_EQ(ERROR_SUCCESS
,
462 base::win::RegKey(kRootKey
, dist
->GetStateKey().c_str(),
464 .WriteValue(google_update::kRegApField
, kMultiChannel
));
466 // Do the registry migration.
467 installer::InstallationState machine_state
;
468 machine_state
.Initialize();
470 installer::MigrateGoogleUpdateStateMultiToSingle(
472 BrowserDistribution::CHROME_FRAME
,
475 // Confirm that usagestats were copied to CF and that its channel was
477 ASSERT_TRUE(chrome_frame
.Initialize(kSystemLevel
,
478 BrowserDistribution::CHROME_FRAME
));
479 EXPECT_TRUE(chrome_frame
.GetUsageStats(&usagestats
));
480 EXPECT_EQ(1U, usagestats
);
481 EXPECT_EQ(L
"2.0-dev", chrome_frame
.channel().value());
483 // Confirm that the binaries' channel no longer contains GCF.
484 ASSERT_TRUE(binaries
.Initialize(kSystemLevel
,
485 BrowserDistribution::CHROME_BINARIES
));
486 EXPECT_EQ(L
"2.0-dev-multi", binaries
.channel().value());
489 TEST(SetupUtilTest
, ContainsUnsupportedSwitch
) {
490 EXPECT_FALSE(installer::ContainsUnsupportedSwitch(
491 CommandLine::FromString(L
"foo.exe")));
492 EXPECT_FALSE(installer::ContainsUnsupportedSwitch(
493 CommandLine::FromString(L
"foo.exe --multi-install --chrome")));
494 EXPECT_TRUE(installer::ContainsUnsupportedSwitch(
495 CommandLine::FromString(L
"foo.exe --chrome-frame")));