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