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 <map> 6 7 #include "base/command_line.h" 8 #include "base/files/file_path.h" 9 #include "base/logging.h" 10 #include "base/memory/ref_counted.h" 11 #include "base/version.h" 12 #include "chrome/common/chrome_constants.h" 13 #include "chrome/common/chrome_switches.h" 14 #include "chrome/installer/util/channel_info.h" 15 #include "chrome/installer/util/helper.h" 16 #include "chrome/installer/util/installation_state.h" 17 #include "chrome/installer/util/installation_validator.h" 18 #include "testing/gmock/include/gmock/gmock.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 21 using installer::ChannelInfo; 22 using installer::InstallationValidator; 23 using installer::InstallationState; 24 using installer::AppCommand; 25 using installer::ProductState; 26 using testing::_; 27 using testing::StrictMock; 28 using testing::Values; 29 30 namespace { 31 32 enum Channel { 33 STABLE_CHANNEL, 34 BETA_CHANNEL, 35 DEV_CHANNEL 36 }; 37 38 enum PackageType { 39 SINGLE_INSTALL, 40 MULTI_INSTALL 41 }; 42 43 enum Level { 44 USER_LEVEL, 45 SYSTEM_LEVEL 46 }; 47 48 enum Vehicle { 49 GOOGLE_UPDATE, 50 MSI 51 }; 52 53 enum ChannelModifier { 54 CM_MULTI = 0x01, 55 CM_CHROME = 0x02, 56 CM_CHROME_FRAME = 0x04, 57 CM_FULL = 0x08 58 }; 59 60 const wchar_t* const kChromeChannels[] = { 61 L"", 62 L"1.1-beta", 63 L"2.0-dev" 64 }; 65 66 const wchar_t* const kChromeFrameChannels[] = { 67 L"", 68 L"beta", 69 L"dev" 70 }; 71 72 class FakeProductState : public ProductState { 73 public: 74 void SetChannel(const wchar_t* base, int channel_modifiers); 75 void SetVersion(const char* version); 76 void SetUninstallCommand(BrowserDistribution::Type dist_type, 77 Level install_level, 78 const char* version, 79 int channel_modifiers, 80 Vehicle vehicle); 81 void AddInstallExtensionCommand(BrowserDistribution::Type dist_type, 82 Level install_level, 83 const char* version, 84 int channel_modifiers); 85 void AddOsUpgradeCommand(BrowserDistribution::Type dist_type, 86 Level install_level, 87 const char* version, 88 int channel_modifiers); 89 void AddQueryEULAAcceptanceCommand(BrowserDistribution::Type dist_type, 90 Level install_level, 91 const char* version, 92 int channel_modifiers); 93 void AddQuickEnableApplicationHostCommand(BrowserDistribution::Type dist_type, 94 Level install_level, 95 const char* version, 96 int channel_modifiers); 97 void set_multi_install(bool is_multi_install) { 98 multi_install_ = is_multi_install; 99 } 100 installer::AppCommands& commands() { return commands_; } 101 102 protected: 103 struct ChannelMethodForModifier { 104 ChannelModifier modifier; 105 bool (ChannelInfo::*method)(bool value); 106 }; 107 108 static base::FilePath GetSetupPath( 109 BrowserDistribution::Type dist_type, 110 Level install_level, 111 int channel_modifiers); 112 113 static base::FilePath GetSetupExePath( 114 BrowserDistribution::Type dist_type, 115 Level install_level, 116 const char* version, 117 int channel_modifiers); 118 119 static const ChannelMethodForModifier kChannelMethods[]; 120 }; 121 122 class FakeInstallationState : public InstallationState { 123 public: 124 void SetProductState(BrowserDistribution::Type type, 125 Level install_level, 126 const ProductState& product) { 127 GetProducts(install_level)[IndexFromDistType(type)].CopyFrom(product); 128 } 129 130 protected: 131 ProductState* GetProducts(Level install_level) { 132 return install_level == USER_LEVEL ? user_products_ : system_products_; 133 } 134 }; 135 136 // static 137 const FakeProductState::ChannelMethodForModifier 138 FakeProductState::kChannelMethods[] = { 139 { CM_MULTI, &ChannelInfo::SetMultiInstall }, 140 { CM_CHROME, &ChannelInfo::SetChrome }, 141 { CM_CHROME_FRAME, &ChannelInfo::SetChromeFrame }, 142 { CM_FULL, &ChannelInfo::SetFullSuffix } 143 }; 144 145 // static 146 base::FilePath FakeProductState::GetSetupPath( 147 BrowserDistribution::Type dist_type, 148 Level install_level, 149 int channel_modifiers) { 150 const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0; 151 return installer::GetChromeInstallPath( 152 install_level == SYSTEM_LEVEL, 153 BrowserDistribution::GetSpecificDistribution(is_multi_install ? 154 BrowserDistribution::CHROME_BINARIES : dist_type)); 155 } 156 157 // static 158 base::FilePath FakeProductState::GetSetupExePath( 159 BrowserDistribution::Type dist_type, 160 Level install_level, 161 const char* version, 162 int channel_modifiers) { 163 base::FilePath setup_path = GetSetupPath(dist_type, install_level, 164 channel_modifiers); 165 return setup_path 166 .AppendASCII(version) 167 .Append(installer::kInstallerDir) 168 .Append(installer::kSetupExe); 169 } 170 171 // Sets the channel_ member of this instance according to a base channel value 172 // and a set of modifiers. 173 void FakeProductState::SetChannel(const wchar_t* base, int channel_modifiers) { 174 channel_.set_value(base); 175 for (size_t i = 0; i < arraysize(kChannelMethods); ++i) { 176 if ((channel_modifiers & kChannelMethods[i].modifier) != 0) 177 (channel_.*kChannelMethods[i].method)(true); 178 } 179 } 180 181 void FakeProductState::SetVersion(const char* version) { 182 version_.reset(version == NULL ? NULL : new Version(version)); 183 } 184 185 // Sets the uninstall command for this object. 186 void FakeProductState::SetUninstallCommand(BrowserDistribution::Type dist_type, 187 Level install_level, 188 const char* version, 189 int channel_modifiers, 190 Vehicle vehicle) { 191 DCHECK(version); 192 193 const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0; 194 uninstall_command_ = CommandLine(GetSetupExePath(dist_type, install_level, 195 version, channel_modifiers)); 196 uninstall_command_.AppendSwitch(installer::switches::kUninstall); 197 if (install_level == SYSTEM_LEVEL) 198 uninstall_command_.AppendSwitch(installer::switches::kSystemLevel); 199 if (is_multi_install) { 200 uninstall_command_.AppendSwitch(installer::switches::kMultiInstall); 201 if (dist_type == BrowserDistribution::CHROME_BROWSER) 202 uninstall_command_.AppendSwitch(installer::switches::kChrome); 203 else if (dist_type == BrowserDistribution::CHROME_FRAME) 204 uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); 205 } else if (dist_type == BrowserDistribution::CHROME_FRAME) { 206 uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); 207 } 208 if (vehicle == MSI) 209 uninstall_command_.AppendSwitch(installer::switches::kMsi); 210 } 211 212 // Adds the "install-extension" Google Update product command. 213 void FakeProductState::AddInstallExtensionCommand( 214 BrowserDistribution::Type dist_type, 215 Level install_level, 216 const char* version, 217 int channel_modifiers) { 218 // Right now only Chrome browser uses this. 219 DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BROWSER); 220 221 CommandLine cmd_line(GetSetupPath(dist_type, install_level, 222 channel_modifiers). 223 Append(installer::kChromeExe)); 224 cmd_line.AppendSwitchASCII(::switches::kLimitedInstallFromWebstore, "%1"); 225 AppCommand app_cmd(cmd_line.GetCommandLineString()); 226 app_cmd.set_sends_pings(true); 227 app_cmd.set_is_web_accessible(true); 228 app_cmd.set_is_run_as_user(true); 229 commands_.Set(installer::kCmdInstallExtension, app_cmd); 230 } 231 232 // Adds the "on-os-upgrade" Google Update product command. 233 void FakeProductState::AddOsUpgradeCommand(BrowserDistribution::Type dist_type, 234 Level install_level, 235 const char* version, 236 int channel_modifiers) { 237 // Right now only Chrome browser uses this. 238 DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BROWSER); 239 240 CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version, 241 channel_modifiers)); 242 cmd_line.AppendSwitch(installer::switches::kOnOsUpgrade); 243 // Imitating ChromeBrowserOperations::AppendProductFlags(). 244 if ((channel_modifiers & CM_MULTI) != 0) { 245 cmd_line.AppendSwitch(installer::switches::kMultiInstall); 246 cmd_line.AppendSwitch(installer::switches::kChrome); 247 } 248 if (install_level == SYSTEM_LEVEL) 249 cmd_line.AppendSwitch(installer::switches::kSystemLevel); 250 cmd_line.AppendSwitch(installer::switches::kVerboseLogging); 251 AppCommand app_cmd(cmd_line.GetCommandLineString()); 252 app_cmd.set_is_auto_run_on_os_upgrade(true); 253 commands_.Set(installer::kCmdOnOsUpgrade, app_cmd); 254 } 255 256 // Adds the "query-eula-acceptance" Google Update product command. 257 void FakeProductState::AddQueryEULAAcceptanceCommand( 258 BrowserDistribution::Type dist_type, 259 Level install_level, 260 const char* version, 261 int channel_modifiers) { 262 DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BINARIES); 263 264 CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version, 265 channel_modifiers)); 266 cmd_line.AppendSwitch(installer::switches::kQueryEULAAcceptance); 267 if (install_level == SYSTEM_LEVEL) 268 cmd_line.AppendSwitch(installer::switches::kSystemLevel); 269 cmd_line.AppendSwitch(installer::switches::kVerboseLogging); 270 AppCommand app_cmd(cmd_line.GetCommandLineString()); 271 app_cmd.set_is_web_accessible(true); 272 app_cmd.set_is_run_as_user(true); 273 commands_.Set(installer::kCmdQueryEULAAcceptance, app_cmd); 274 } 275 276 // Adds the "quick-enable-application-host" Google Update product command. 277 void FakeProductState::AddQuickEnableApplicationHostCommand( 278 BrowserDistribution::Type dist_type, 279 Level install_level, 280 const char* version, 281 int channel_modifiers) { 282 DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BINARIES); 283 DCHECK_NE(channel_modifiers & CM_MULTI, 0); 284 285 CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version, 286 channel_modifiers)); 287 cmd_line.AppendSwitch(installer::switches::kMultiInstall); 288 cmd_line.AppendSwitch(installer::switches::kChromeAppLauncher); 289 cmd_line.AppendSwitch(installer::switches::kEnsureGoogleUpdatePresent); 290 AppCommand app_cmd(cmd_line.GetCommandLineString()); 291 app_cmd.set_sends_pings(true); 292 app_cmd.set_is_web_accessible(true); 293 app_cmd.set_is_run_as_user(true); 294 commands_.Set(installer::kCmdQuickEnableApplicationHost, app_cmd); 295 } 296 297 } // namespace 298 299 // Fixture for testing the InstallationValidator. Errors logged by the 300 // validator are sent to an optional mock recipient (see 301 // set_validation_error_recipient) upon which expectations can be placed. 302 class InstallationValidatorTest 303 : public testing::TestWithParam<InstallationValidator::InstallationType> { 304 public: 305 306 // These shouldn't need to be public, but there seems to be some interaction 307 // with parameterized tests that requires it. 308 static void SetUpTestCase(); 309 static void TearDownTestCase(); 310 311 // Returns the multi channel modifiers for a given installation type. 312 static int GetChannelModifiers(InstallationValidator::InstallationType type); 313 314 protected: 315 typedef std::map<InstallationValidator::InstallationType, int> 316 InstallationTypeToModifiers; 317 318 class ValidationErrorRecipient { 319 public: 320 virtual ~ValidationErrorRecipient() { } 321 virtual void ReceiveValidationError(const char* file, 322 int line, 323 const char* message) = 0; 324 }; 325 class MockValidationErrorRecipient : public ValidationErrorRecipient { 326 public: 327 MOCK_METHOD3(ReceiveValidationError, void(const char* file, 328 int line, 329 const char* message)); 330 }; 331 332 protected: 333 static bool HandleLogMessage(int severity, 334 const char* file, 335 int line, 336 size_t message_start, 337 const std::string& str); 338 static void set_validation_error_recipient( 339 ValidationErrorRecipient* recipient); 340 static void MakeProductState( 341 BrowserDistribution::Type prod_type, 342 InstallationValidator::InstallationType inst_type, 343 Level install_level, 344 Channel channel, 345 Vehicle vehicle, 346 FakeProductState* state); 347 static void MakeMachineState( 348 InstallationValidator::InstallationType inst_type, 349 Level install_level, 350 Channel channel, 351 Vehicle vehicle, 352 FakeInstallationState* state); 353 virtual void TearDown(); 354 355 static logging::LogMessageHandlerFunction old_log_message_handler_; 356 static ValidationErrorRecipient* validation_error_recipient_; 357 static InstallationTypeToModifiers* type_to_modifiers_; 358 }; 359 360 // static 361 logging::LogMessageHandlerFunction 362 InstallationValidatorTest::old_log_message_handler_ = NULL; 363 364 // static 365 InstallationValidatorTest::ValidationErrorRecipient* 366 InstallationValidatorTest::validation_error_recipient_ = NULL; 367 368 // static 369 InstallationValidatorTest::InstallationTypeToModifiers* 370 InstallationValidatorTest::type_to_modifiers_ = NULL; 371 372 // static 373 int InstallationValidatorTest::GetChannelModifiers( 374 InstallationValidator::InstallationType type) { 375 DCHECK(type_to_modifiers_); 376 DCHECK(type_to_modifiers_->find(type) != type_to_modifiers_->end()); 377 378 return (*type_to_modifiers_)[type]; 379 } 380 381 // static 382 void InstallationValidatorTest::SetUpTestCase() { 383 DCHECK(type_to_modifiers_ == NULL); 384 old_log_message_handler_ = logging::GetLogMessageHandler(); 385 logging::SetLogMessageHandler(&HandleLogMessage); 386 387 type_to_modifiers_ = new InstallationTypeToModifiers(); 388 InstallationTypeToModifiers& ttm = *type_to_modifiers_; 389 ttm[InstallationValidator::NO_PRODUCTS] = 0; 390 ttm[InstallationValidator::CHROME_SINGLE] = 0; 391 ttm[InstallationValidator::CHROME_MULTI] = CM_MULTI | CM_CHROME; 392 ttm[InstallationValidator::CHROME_FRAME_SINGLE] = 0; 393 ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE] = 0; 394 ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI] = 395 CM_MULTI | CM_CHROME; 396 ttm[InstallationValidator::CHROME_FRAME_MULTI] = CM_MULTI | CM_CHROME_FRAME; 397 ttm[InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI] = 398 CM_MULTI | CM_CHROME_FRAME | CM_CHROME; 399 } 400 401 // static 402 void InstallationValidatorTest::TearDownTestCase() { 403 logging::SetLogMessageHandler(old_log_message_handler_); 404 old_log_message_handler_ = NULL; 405 406 delete type_to_modifiers_; 407 type_to_modifiers_ = NULL; 408 } 409 410 // static 411 bool InstallationValidatorTest::HandleLogMessage(int severity, 412 const char* file, 413 int line, 414 size_t message_start, 415 const std::string& str) { 416 // All validation failures result in LOG(ERROR) 417 if (severity == logging::LOG_ERROR && !str.empty()) { 418 // Remove the trailing newline, if present. 419 size_t message_length = str.size() - message_start; 420 if (*str.rbegin() == '\n') 421 --message_length; 422 if (validation_error_recipient_ != NULL) { 423 validation_error_recipient_->ReceiveValidationError( 424 file, line, str.substr(message_start, message_length).c_str()); 425 } else { 426 // Fail the test if an error wasn't handled. 427 ADD_FAILURE_AT(file, line) 428 << base::StringPiece(str.c_str() + message_start, message_length); 429 } 430 return true; 431 } 432 433 if (old_log_message_handler_ != NULL) 434 return (old_log_message_handler_)(severity, file, line, message_start, str); 435 436 return false; 437 } 438 439 // static 440 void InstallationValidatorTest::set_validation_error_recipient( 441 ValidationErrorRecipient* recipient) { 442 validation_error_recipient_ = recipient; 443 } 444 445 // static 446 // Populates |state| with the state of a valid installation of product 447 // |prod_type|. |inst_type| dictates properties of the installation 448 // (multi-install, etc). 449 void InstallationValidatorTest::MakeProductState( 450 BrowserDistribution::Type prod_type, 451 InstallationValidator::InstallationType inst_type, 452 Level install_level, 453 Channel channel, 454 Vehicle vehicle, 455 FakeProductState* state) { 456 DCHECK(state); 457 458 const bool is_multi_install = 459 prod_type == BrowserDistribution::CHROME_BINARIES || 460 (prod_type == BrowserDistribution::CHROME_BROWSER && 461 (inst_type & InstallationValidator::ProductBits::CHROME_MULTI) != 0) || 462 (prod_type == BrowserDistribution::CHROME_FRAME && 463 (inst_type & 464 InstallationValidator::ProductBits::CHROME_FRAME_MULTI) != 0); 465 466 const wchar_t* const* channels = &kChromeChannels[0]; 467 if (prod_type == BrowserDistribution::CHROME_FRAME && !is_multi_install) 468 channels = &kChromeFrameChannels[0]; // SxS GCF has its own channel names. 469 const int channel_modifiers = 470 is_multi_install ? GetChannelModifiers(inst_type) : 0; 471 472 state->Clear(); 473 state->SetChannel(channels[channel], channel_modifiers); 474 state->SetVersion(chrome::kChromeVersion); 475 state->SetUninstallCommand(prod_type, install_level, chrome::kChromeVersion, 476 channel_modifiers, vehicle); 477 state->set_multi_install(is_multi_install); 478 if (prod_type == BrowserDistribution::CHROME_BINARIES) { 479 state->AddQueryEULAAcceptanceCommand(prod_type, 480 install_level, 481 chrome::kChromeVersion, 482 channel_modifiers); 483 } 484 if (prod_type == BrowserDistribution::CHROME_BINARIES) { 485 state->AddQuickEnableApplicationHostCommand(prod_type, 486 install_level, 487 chrome::kChromeVersion, 488 channel_modifiers); 489 } 490 if (prod_type == BrowserDistribution::CHROME_BROWSER) { 491 state->AddOsUpgradeCommand(prod_type, 492 install_level, 493 chrome::kChromeVersion, 494 channel_modifiers); 495 state->AddInstallExtensionCommand(prod_type, 496 install_level, 497 chrome::kChromeVersion, 498 channel_modifiers); 499 } 500 } 501 502 // static 503 // Populates |state| with the state of a valid installation of |inst_type|. 504 void InstallationValidatorTest::MakeMachineState( 505 InstallationValidator::InstallationType inst_type, 506 Level install_level, 507 Channel channel, 508 Vehicle vehicle, 509 FakeInstallationState* state) { 510 DCHECK(state); 511 512 static const int kChromeMask = 513 (InstallationValidator::ProductBits::CHROME_SINGLE | 514 InstallationValidator::ProductBits::CHROME_MULTI); 515 static const int kChromeFrameMask = 516 (InstallationValidator::ProductBits::CHROME_FRAME_SINGLE | 517 InstallationValidator::ProductBits::CHROME_FRAME_MULTI); 518 static const int kBinariesMask = 519 (InstallationValidator::ProductBits::CHROME_MULTI | 520 InstallationValidator::ProductBits::CHROME_FRAME_MULTI); 521 522 FakeProductState prod_state; 523 524 if ((inst_type & kChromeMask) != 0) { 525 MakeProductState(BrowserDistribution::CHROME_BROWSER, inst_type, 526 install_level, channel, vehicle, &prod_state); 527 state->SetProductState(BrowserDistribution::CHROME_BROWSER, install_level, 528 prod_state); 529 } 530 531 if ((inst_type & kChromeFrameMask) != 0) { 532 MakeProductState(BrowserDistribution::CHROME_FRAME, inst_type, 533 install_level, channel, vehicle, &prod_state); 534 state->SetProductState(BrowserDistribution::CHROME_FRAME, install_level, 535 prod_state); 536 } 537 538 if ((inst_type & kBinariesMask) != 0) { 539 MakeProductState(BrowserDistribution::CHROME_BINARIES, inst_type, 540 install_level, channel, vehicle, &prod_state); 541 state->SetProductState(BrowserDistribution::CHROME_BINARIES, install_level, 542 prod_state); 543 } 544 } 545 546 void InstallationValidatorTest::TearDown() { 547 validation_error_recipient_ = NULL; 548 } 549 550 // Builds a proper machine state for a given InstallationType, then validates 551 // it. 552 TEST_P(InstallationValidatorTest, TestValidInstallation) { 553 const InstallationValidator::InstallationType inst_type = GetParam(); 554 FakeInstallationState machine_state; 555 InstallationValidator::InstallationType type; 556 StrictMock<MockValidationErrorRecipient> recipient; 557 set_validation_error_recipient(&recipient); 558 559 MakeMachineState(inst_type, SYSTEM_LEVEL, STABLE_CHANNEL, GOOGLE_UPDATE, 560 &machine_state); 561 EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState( 562 machine_state, true, &type)); 563 EXPECT_EQ(inst_type, type); 564 } 565 566 // Run the test for all installation types. 567 INSTANTIATE_TEST_CASE_P( 568 AllValidInstallations, 569 InstallationValidatorTest, 570 Values(InstallationValidator::NO_PRODUCTS, 571 InstallationValidator::CHROME_SINGLE, 572 InstallationValidator::CHROME_MULTI, 573 InstallationValidator::CHROME_FRAME_SINGLE, 574 InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE, 575 InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI, 576 InstallationValidator::CHROME_FRAME_MULTI, 577 InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI)); 578