blob: 3332f4d8e2d0d2c807f6904ede848148ccb94285 [file] [log] [blame]
// 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 <stddef.h>
#include <map>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.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 base::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<InstallationValidator::InstallationType> {
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<InstallationValidator::InstallationType, int>
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<MockValidationErrorRecipient> 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));