// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/command_line.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/version.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/installer/util/channel_info.h" #include "chrome/installer/util/helper.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/installation_validator.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using installer::ChannelInfo; using installer::InstallationValidator; using installer::InstallationState; using installer::AppCommand; using installer::ProductState; using testing::_; using testing::StrictMock; using testing::Values; namespace { enum Channel { STABLE_CHANNEL, BETA_CHANNEL, DEV_CHANNEL }; enum PackageType { SINGLE_INSTALL, MULTI_INSTALL }; enum Level { USER_LEVEL, SYSTEM_LEVEL }; enum Vehicle { GOOGLE_UPDATE, MSI }; enum ChannelModifier { CM_MULTI = 0x01, CM_CHROME = 0x02, CM_CHROME_FRAME = 0x04, CM_FULL = 0x08 }; const wchar_t* const kChromeChannels[] = { L"", L"1.1-beta", L"2.0-dev" }; const wchar_t* const kChromeFrameChannels[] = { L"", L"beta", L"dev" }; class FakeProductState : public ProductState { public: void SetChannel(const wchar_t* base, int channel_modifiers); void SetVersion(const char* version); void SetUninstallCommand(BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers, Vehicle vehicle); void AddOsUpgradeCommand(BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers); void set_multi_install(bool is_multi_install) { multi_install_ = is_multi_install; } installer::AppCommands& commands() { return commands_; } protected: struct ChannelMethodForModifier { ChannelModifier modifier; bool (ChannelInfo::*method)(bool value); }; static base::FilePath GetSetupPath( BrowserDistribution::Type dist_type, Level install_level, int channel_modifiers); static base::FilePath GetSetupExePath( BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers); static const ChannelMethodForModifier kChannelMethods[]; }; class FakeInstallationState : public InstallationState { public: void SetProductState(BrowserDistribution::Type type, Level install_level, const ProductState& product) { GetProducts(install_level)[IndexFromDistType(type)].CopyFrom(product); } protected: ProductState* GetProducts(Level install_level) { return install_level == USER_LEVEL ? user_products_ : system_products_; } }; // static const FakeProductState::ChannelMethodForModifier FakeProductState::kChannelMethods[] = { { CM_MULTI, &ChannelInfo::SetMultiInstall }, { CM_CHROME, &ChannelInfo::SetChrome }, { CM_CHROME_FRAME, &ChannelInfo::SetChromeFrame }, { CM_FULL, &ChannelInfo::SetFullSuffix } }; // static base::FilePath FakeProductState::GetSetupPath( BrowserDistribution::Type dist_type, Level install_level, int channel_modifiers) { const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0; return installer::GetChromeInstallPath( install_level == SYSTEM_LEVEL, BrowserDistribution::GetSpecificDistribution(is_multi_install ? BrowserDistribution::CHROME_BINARIES : dist_type)); } // static base::FilePath FakeProductState::GetSetupExePath( BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers) { base::FilePath setup_path = GetSetupPath(dist_type, install_level, channel_modifiers); return setup_path .AppendASCII(version) .Append(installer::kInstallerDir) .Append(installer::kSetupExe); } // Sets the channel_ member of this instance according to a base channel value // and a set of modifiers. void FakeProductState::SetChannel(const wchar_t* base, int channel_modifiers) { channel_.set_value(base); for (size_t i = 0; i < arraysize(kChannelMethods); ++i) { if ((channel_modifiers & kChannelMethods[i].modifier) != 0) (channel_.*kChannelMethods[i].method)(true); } } void FakeProductState::SetVersion(const char* version) { version_.reset(version == NULL ? NULL : new Version(version)); } // Sets the uninstall command for this object. void FakeProductState::SetUninstallCommand(BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers, Vehicle vehicle) { DCHECK(version); const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0; uninstall_command_ = base::CommandLine( GetSetupExePath(dist_type, install_level, version, channel_modifiers)); uninstall_command_.AppendSwitch(installer::switches::kUninstall); if (install_level == SYSTEM_LEVEL) uninstall_command_.AppendSwitch(installer::switches::kSystemLevel); if (is_multi_install) { uninstall_command_.AppendSwitch(installer::switches::kMultiInstall); if (dist_type == BrowserDistribution::CHROME_BROWSER) uninstall_command_.AppendSwitch(installer::switches::kChrome); else if (dist_type == BrowserDistribution::CHROME_FRAME) uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); } else if (dist_type == BrowserDistribution::CHROME_FRAME) { uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); } if (vehicle == MSI) uninstall_command_.AppendSwitch(installer::switches::kMsi); } // Adds the "on-os-upgrade" Google Update product command. void FakeProductState::AddOsUpgradeCommand(BrowserDistribution::Type dist_type, Level install_level, const char* version, int channel_modifiers) { // Right now only Chrome browser uses this. DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BROWSER); base::CommandLine cmd_line( GetSetupExePath(dist_type, install_level, version, channel_modifiers)); cmd_line.AppendSwitch(installer::switches::kOnOsUpgrade); // Imitating ChromeBrowserOperations::AppendProductFlags(). if ((channel_modifiers & CM_MULTI) != 0) { cmd_line.AppendSwitch(installer::switches::kMultiInstall); cmd_line.AppendSwitch(installer::switches::kChrome); } if (install_level == SYSTEM_LEVEL) cmd_line.AppendSwitch(installer::switches::kSystemLevel); cmd_line.AppendSwitch(installer::switches::kVerboseLogging); AppCommand app_cmd(cmd_line.GetCommandLineString()); app_cmd.set_is_auto_run_on_os_upgrade(true); commands_.Set(installer::kCmdOnOsUpgrade, app_cmd); } } // namespace // Fixture for testing the InstallationValidator. Errors logged by the // validator are sent to an optional mock recipient (see // set_validation_error_recipient) upon which expectations can be placed. class InstallationValidatorTest : public testing::TestWithParam { public: // These shouldn't need to be public, but there seems to be some interaction // with parameterized tests that requires it. static void SetUpTestCase(); static void TearDownTestCase(); // Returns the multi channel modifiers for a given installation type. static int GetChannelModifiers(InstallationValidator::InstallationType type); protected: typedef std::map InstallationTypeToModifiers; class ValidationErrorRecipient { public: virtual ~ValidationErrorRecipient() { } virtual void ReceiveValidationError(const char* file, int line, const char* message) = 0; }; class MockValidationErrorRecipient : public ValidationErrorRecipient { public: MOCK_METHOD3(ReceiveValidationError, void(const char* file, int line, const char* message)); }; protected: static bool HandleLogMessage(int severity, const char* file, int line, size_t message_start, const std::string& str); static void set_validation_error_recipient( ValidationErrorRecipient* recipient); static void MakeProductState( BrowserDistribution::Type prod_type, InstallationValidator::InstallationType inst_type, Level install_level, Channel channel, Vehicle vehicle, FakeProductState* state); static void MakeMachineState( InstallationValidator::InstallationType inst_type, Level install_level, Channel channel, Vehicle vehicle, FakeInstallationState* state); void TearDown() override; static logging::LogMessageHandlerFunction old_log_message_handler_; static ValidationErrorRecipient* validation_error_recipient_; static InstallationTypeToModifiers* type_to_modifiers_; }; // static logging::LogMessageHandlerFunction InstallationValidatorTest::old_log_message_handler_ = NULL; // static InstallationValidatorTest::ValidationErrorRecipient* InstallationValidatorTest::validation_error_recipient_ = NULL; // static InstallationValidatorTest::InstallationTypeToModifiers* InstallationValidatorTest::type_to_modifiers_ = NULL; // static int InstallationValidatorTest::GetChannelModifiers( InstallationValidator::InstallationType type) { DCHECK(type_to_modifiers_); DCHECK(type_to_modifiers_->find(type) != type_to_modifiers_->end()); return (*type_to_modifiers_)[type]; } // static void InstallationValidatorTest::SetUpTestCase() { DCHECK(type_to_modifiers_ == NULL); old_log_message_handler_ = logging::GetLogMessageHandler(); logging::SetLogMessageHandler(&HandleLogMessage); type_to_modifiers_ = new InstallationTypeToModifiers(); InstallationTypeToModifiers& ttm = *type_to_modifiers_; ttm[InstallationValidator::NO_PRODUCTS] = 0; ttm[InstallationValidator::CHROME_SINGLE] = 0; ttm[InstallationValidator::CHROME_MULTI] = CM_MULTI | CM_CHROME; ttm[InstallationValidator::CHROME_FRAME_SINGLE] = 0; ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE] = 0; ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI] = CM_MULTI | CM_CHROME; ttm[InstallationValidator::CHROME_FRAME_MULTI] = CM_MULTI | CM_CHROME_FRAME; ttm[InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI] = CM_MULTI | CM_CHROME_FRAME | CM_CHROME; } // static void InstallationValidatorTest::TearDownTestCase() { logging::SetLogMessageHandler(old_log_message_handler_); old_log_message_handler_ = NULL; delete type_to_modifiers_; type_to_modifiers_ = NULL; } // static bool InstallationValidatorTest::HandleLogMessage(int severity, const char* file, int line, size_t message_start, const std::string& str) { // All validation failures result in LOG(ERROR) if (severity == logging::LOG_ERROR && !str.empty()) { // Remove the trailing newline, if present. size_t message_length = str.size() - message_start; if (*str.rbegin() == '\n') --message_length; if (validation_error_recipient_ != NULL) { validation_error_recipient_->ReceiveValidationError( file, line, str.substr(message_start, message_length).c_str()); } else { // Fail the test if an error wasn't handled. ADD_FAILURE_AT(file, line) << base::StringPiece(str.c_str() + message_start, message_length); } return true; } if (old_log_message_handler_ != NULL) return (old_log_message_handler_)(severity, file, line, message_start, str); return false; } // static void InstallationValidatorTest::set_validation_error_recipient( ValidationErrorRecipient* recipient) { validation_error_recipient_ = recipient; } // static // Populates |state| with the state of a valid installation of product // |prod_type|. |inst_type| dictates properties of the installation // (multi-install, etc). void InstallationValidatorTest::MakeProductState( BrowserDistribution::Type prod_type, InstallationValidator::InstallationType inst_type, Level install_level, Channel channel, Vehicle vehicle, FakeProductState* state) { DCHECK(state); const bool is_multi_install = prod_type == BrowserDistribution::CHROME_BINARIES || (prod_type == BrowserDistribution::CHROME_BROWSER && (inst_type & InstallationValidator::ProductBits::CHROME_MULTI) != 0) || (prod_type == BrowserDistribution::CHROME_FRAME && (inst_type & InstallationValidator::ProductBits::CHROME_FRAME_MULTI) != 0); const wchar_t* const* channels = &kChromeChannels[0]; if (prod_type == BrowserDistribution::CHROME_FRAME && !is_multi_install) channels = &kChromeFrameChannels[0]; // SxS GCF has its own channel names. const int channel_modifiers = is_multi_install ? GetChannelModifiers(inst_type) : 0; state->Clear(); state->SetChannel(channels[channel], channel_modifiers); state->SetVersion(chrome::kChromeVersion); state->SetUninstallCommand(prod_type, install_level, chrome::kChromeVersion, channel_modifiers, vehicle); state->set_multi_install(is_multi_install); if (prod_type == BrowserDistribution::CHROME_BROWSER) { state->AddOsUpgradeCommand(prod_type, install_level, chrome::kChromeVersion, channel_modifiers); } } // static // Populates |state| with the state of a valid installation of |inst_type|. void InstallationValidatorTest::MakeMachineState( InstallationValidator::InstallationType inst_type, Level install_level, Channel channel, Vehicle vehicle, FakeInstallationState* state) { DCHECK(state); static const int kChromeMask = (InstallationValidator::ProductBits::CHROME_SINGLE | InstallationValidator::ProductBits::CHROME_MULTI); static const int kChromeFrameMask = (InstallationValidator::ProductBits::CHROME_FRAME_SINGLE | InstallationValidator::ProductBits::CHROME_FRAME_MULTI); static const int kBinariesMask = (InstallationValidator::ProductBits::CHROME_MULTI | InstallationValidator::ProductBits::CHROME_FRAME_MULTI); FakeProductState prod_state; if ((inst_type & kChromeMask) != 0) { MakeProductState(BrowserDistribution::CHROME_BROWSER, inst_type, install_level, channel, vehicle, &prod_state); state->SetProductState(BrowserDistribution::CHROME_BROWSER, install_level, prod_state); } if ((inst_type & kChromeFrameMask) != 0) { MakeProductState(BrowserDistribution::CHROME_FRAME, inst_type, install_level, channel, vehicle, &prod_state); state->SetProductState(BrowserDistribution::CHROME_FRAME, install_level, prod_state); } if ((inst_type & kBinariesMask) != 0) { MakeProductState(BrowserDistribution::CHROME_BINARIES, inst_type, install_level, channel, vehicle, &prod_state); state->SetProductState(BrowserDistribution::CHROME_BINARIES, install_level, prod_state); } } void InstallationValidatorTest::TearDown() { validation_error_recipient_ = NULL; } // Builds a proper machine state for a given InstallationType, then validates // it. TEST_P(InstallationValidatorTest, TestValidInstallation) { const InstallationValidator::InstallationType inst_type = GetParam(); FakeInstallationState machine_state; InstallationValidator::InstallationType type; StrictMock recipient; set_validation_error_recipient(&recipient); MakeMachineState(inst_type, SYSTEM_LEVEL, STABLE_CHANNEL, GOOGLE_UPDATE, &machine_state); EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState( machine_state, true, &type)); EXPECT_EQ(inst_type, type); } // Run the test for all installation types. INSTANTIATE_TEST_CASE_P( AllValidInstallations, InstallationValidatorTest, Values(InstallationValidator::NO_PRODUCTS, InstallationValidator::CHROME_SINGLE, InstallationValidator::CHROME_MULTI, InstallationValidator::CHROME_FRAME_SINGLE, InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE, InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI, InstallationValidator::CHROME_FRAME_MULTI, InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI));