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