summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome.gyp1
-rw-r--r--chrome/chrome_installer.gypi2
-rw-r--r--chrome/chrome_installer_util.gypi2
-rw-r--r--chrome/installer/installer_tools.gyp49
-rw-r--r--chrome/installer/setup/setup_main.cc11
-rw-r--r--chrome/installer/tools/validate_installation.rc65
-rw-r--r--chrome/installer/tools/validate_installation_main.cc189
-rw-r--r--chrome/installer/tools/validate_installation_resource.h18
-rw-r--r--chrome/installer/util/installation_validator.cc409
-rw-r--r--chrome/installer/util/installation_validator.h161
-rw-r--r--chrome/installer/util/installation_validator_unittest.cc315
11 files changed, 1222 insertions, 0 deletions
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 628fc75..49e6e81d 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1618,6 +1618,7 @@
'type': 'none',
'dependencies': [
'installer/mini_installer.gyp:*',
+ 'installer/installer_tools.gyp:*',
'installer/upgrade_test.gyp:*',
'../app/app.gyp:*',
'../base/base.gyp:*',
diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi
index 45fbdde..62eebe2 100644
--- a/chrome/chrome_installer.gypi
+++ b/chrome/chrome_installer.gypi
@@ -68,6 +68,7 @@
'<(DEPTH)/base/base.gyp:base_i18n',
'<(DEPTH)/base/base.gyp:test_support_base',
'<(DEPTH)/build/temp_gyp/googleurl.gyp:googleurl',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
'include_dirs': [
@@ -87,6 +88,7 @@
'installer/util/google_chrome_distribution_unittest.cc',
'installer/util/google_update_settings_unittest.cc',
'installer/util/install_util_unittest.cc',
+ 'installer/util/installation_validator_unittest.cc',
'installer/util/installer_state_unittest.cc',
'installer/util/installer_util_unittests.rc',
'installer/util/installer_util_unittests_resource.h',
diff --git a/chrome/chrome_installer_util.gypi b/chrome/chrome_installer_util.gypi
index 3f852bc..b33b28a 100644
--- a/chrome/chrome_installer_util.gypi
+++ b/chrome/chrome_installer_util.gypi
@@ -113,6 +113,8 @@
'installer/util/google_chrome_distribution.h',
'installer/util/html_dialog.h',
'installer/util/html_dialog_impl.cc',
+ 'installer/util/installation_validator.cc',
+ 'installer/util/installation_validator.h',
'installer/util/logging_installer.cc',
'installer/util/logging_installer.h',
'installer/util/lzma_util.cc',
diff --git a/chrome/installer/installer_tools.gyp b/chrome/installer/installer_tools.gyp
new file mode 100644
index 0000000..1f7474b
--- /dev/null
+++ b/chrome/installer/installer_tools.gyp
@@ -0,0 +1,49 @@
+{
+ 'variables': {
+ 'version_py': '<(DEPTH)/chrome/tools/build/version.py',
+ 'version_path': '<(DEPTH)/chrome/VERSION',
+ 'lastchange_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE',
+ # 'branding_dir' is set in the 'conditions' section at the bottom.
+ 'msvs_use_common_release': 0,
+ 'msvs_use_common_linker_extras': 0,
+ },
+ 'conditions': [
+ ['OS=="win"', {
+ 'targets': [
+ {
+ 'target_name': 'validate_installation',
+ 'msvs_guid': '7CC08DA8-E9CA-4573-A8C4-E7F0D0CF8EBA',
+ 'type': 'executable',
+ 'dependencies': [
+ '<(DEPTH)/chrome/chrome.gyp:common_constants',
+ '<(DEPTH)/chrome/chrome.gyp:installer_util',
+ '<(DEPTH)/chrome/chrome.gyp:installer_util_strings',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'tools/validate_installation_main.cc',
+ 'tools/validate_installation.rc',
+ 'tools/validate_installation_resource.h',
+ ],
+ },
+ ],
+ }],
+ [ 'branding == "Chrome"', {
+ 'variables': {
+ 'branding_dir': '<(DEPTH)/chrome/app/theme/google_chrome',
+ },
+ }, { # else branding!="Chrome"
+ 'variables': {
+ 'branding_dir': '<(DEPTH)/chrome/app/theme/chromium',
+ },
+ }],
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 1c2d648..6377114 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -41,6 +41,7 @@
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/html_dialog.h"
#include "chrome/installer/util/install_util.h"
+#include "chrome/installer/util/installation_validator.h"
#include "chrome/installer/util/installer_state.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/l10n_string_util.h"
@@ -55,6 +56,7 @@
using installer::InstallerState;
using installer::InstallationState;
+using installer::InstallationValidator;
using installer::Product;
using installer::ProductState;
using installer::Products;
@@ -1120,6 +1122,15 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
&installer_state);
}
+ // Validate that the machine is now in a good state following the operation.
+ // TODO(grt): change this to log at DFATAL once we're convinced that the
+ // validator handles all cases properly.
+ InstallationValidator::InstallationType installation_type =
+ InstallationValidator::NO_PRODUCTS;
+ LOG_IF(ERROR,
+ !InstallationValidator::ValidateInstallationType(system_install,
+ &installation_type));
+
const Product* cf_install =
installer_state.FindProduct(BrowserDistribution::CHROME_FRAME);
diff --git a/chrome/installer/tools/validate_installation.rc b/chrome/installer/tools/validate_installation.rc
new file mode 100644
index 0000000..e6d2858
--- /dev/null
+++ b/chrome/installer/tools/validate_installation.rc
@@ -0,0 +1,65 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "validate_installation_resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE 9, 1
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "validate_installation_resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+#include "installer_util_strings.rc"
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/chrome/installer/tools/validate_installation_main.cc b/chrome/installer/tools/validate_installation_main.cc
new file mode 100644
index 0000000..5661457
--- /dev/null
+++ b/chrome/installer/tools/validate_installation_main.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2011 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.
+
+// A command-line tool that inspects the current system, displaying information
+// about installed products. Violations are dumped to stderr. The process
+// exit code is 0 if there are no violations, or 1 otherwise.
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "chrome/installer/util/installation_validator.h"
+
+using installer::InstallationValidator;
+
+namespace {
+
+// A helper class that initializes logging and installs a log message handler to
+// direct ERROR messages to stderr. Only one instance of this class may be live
+// at a time.
+class ConsoleLogHelper {
+ public:
+ ConsoleLogHelper();
+ ~ConsoleLogHelper();
+
+ private:
+ static FilePath GetLogFilePath();
+ static bool DumpLogMessage(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str);
+
+ static const wchar_t kLogFileName_[];
+ static FILE* const kOutputStream_;
+ static const logging::LogSeverity kViolationSeverity_;
+ static logging::LogMessageHandlerFunction old_message_handler_;
+ FilePath log_file_path_;
+};
+
+// static
+const wchar_t ConsoleLogHelper::kLogFileName_[] = L"validate_installation.log";
+
+// Dump violations to stderr.
+// static
+FILE* const ConsoleLogHelper::kOutputStream_ = stderr;
+
+// InstallationValidator logs all violations at ERROR level.
+// static
+const logging::LogSeverity
+ ConsoleLogHelper::kViolationSeverity_ = logging::LOG_ERROR;
+
+// static
+logging::LogMessageHandlerFunction
+ ConsoleLogHelper::old_message_handler_ = NULL;
+
+ConsoleLogHelper::ConsoleLogHelper() : log_file_path_(GetLogFilePath()) {
+ LOG_ASSERT(old_message_handler_ == NULL);
+ logging::InitLogging(
+ log_file_path_.value().c_str(),
+ logging::LOG_ONLY_TO_FILE,
+ logging::DONT_LOCK_LOG_FILE,
+ logging::DELETE_OLD_LOG_FILE,
+ logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
+
+ old_message_handler_ = logging::GetLogMessageHandler();
+ logging::SetLogMessageHandler(&DumpLogMessage);
+}
+
+ConsoleLogHelper::~ConsoleLogHelper() {
+ logging::SetLogMessageHandler(old_message_handler_);
+ old_message_handler_ = NULL;
+
+ logging::CloseLogFile();
+
+ // Delete the log file if it wasn't written to (this is expected).
+ int64 file_size = 0;
+ if (file_util::GetFileSize(log_file_path_, &file_size) && file_size == 0)
+ file_util::Delete(log_file_path_, false);
+}
+
+// Returns the path to the log file to create. The file should be empty at
+// process exit since we redirect log messages to stderr.
+// static
+FilePath ConsoleLogHelper::GetLogFilePath() {
+ FilePath log_path;
+
+ if (PathService::Get(base::DIR_TEMP, &log_path))
+ return log_path.Append(kLogFileName_);
+ else
+ return FilePath(kLogFileName_);
+}
+
+// A logging::LogMessageHandlerFunction that sends the body of messages logged
+// at the severity of validation violations to stderr. All other messages are
+// sent through the default logging pipeline.
+// static
+bool ConsoleLogHelper::DumpLogMessage(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str) {
+ if (severity == kViolationSeverity_) {
+ fprintf(kOutputStream_, "%s", str.c_str() + message_start);
+ return true;
+ }
+
+ if (old_message_handler_ != NULL)
+ return (old_message_handler_)(severity, file, line, message_start, str);
+
+ return false;
+}
+
+const char* LevelToString(bool system_level) {
+ return system_level ? "System-level" : "User-level";
+}
+
+std::string InstallationTypeToString(
+ InstallationValidator::InstallationType type) {
+ std::string result;
+
+ static const struct ProductData {
+ int bit;
+ const char* name;
+ } kProdBitToName[] = {
+ {
+ InstallationValidator::ProductBits::CHROME_SINGLE,
+ "Chrome"
+ }, {
+ InstallationValidator::ProductBits::CHROME_MULTI,
+ "Chrome (multi)"
+ }, {
+ InstallationValidator::ProductBits::CHROME_FRAME_SINGLE,
+ "Chrome Frame"
+ }, {
+ InstallationValidator::ProductBits::CHROME_FRAME_MULTI,
+ "Chrome Frame (multi)"
+ }, {
+ InstallationValidator::ProductBits::CHROME_FRAME_READY_MODE,
+ "Ready-mode Chrome Frame"
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(kProdBitToName); ++i) {
+ const ProductData& product_data = kProdBitToName[i];
+ if ((type & product_data.bit) != 0) {
+ if (!result.empty())
+ result.append(", ");
+ result.append(product_data.name);
+ }
+ }
+
+ return result;
+}
+
+} // namespace
+
+// The main program.
+int wmain(int argc, wchar_t *argv[]) {
+ int result = EXIT_SUCCESS;
+ base::AtExitManager exit_manager;
+
+ CommandLine::Init(0, NULL);
+ ConsoleLogHelper log_helper;
+
+ // Check user-level and system-level for products.
+ for (int i = 0; i < 2; ++i) {
+ const bool system_level = (i != 0);
+ InstallationValidator::InstallationType type =
+ InstallationValidator::NO_PRODUCTS;
+ bool is_valid =
+ InstallationValidator::ValidateInstallationType(system_level, &type);
+ if (type != InstallationValidator::NO_PRODUCTS) {
+ FILE* stream = is_valid ? stdout : stderr;
+ fprintf(stream, "%s installations%s: %s\n", LevelToString(system_level),
+ (is_valid ? "" : " (with errors)"),
+ InstallationTypeToString(type).c_str());
+ }
+ if (!is_valid)
+ result = EXIT_FAILURE;
+ }
+
+ return result;
+}
diff --git a/chrome/installer/tools/validate_installation_resource.h b/chrome/installer/tools/validate_installation_resource.h
new file mode 100644
index 0000000..4998008
--- /dev/null
+++ b/chrome/installer/tools/validate_installation_resource.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2011 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.
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by validate_installation.rc
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/chrome/installer/util/installation_validator.cc b/chrome/installer/util/installation_validator.cc
new file mode 100644
index 0000000..1f76d61
--- /dev/null
+++ b/chrome/installer/util/installation_validator.cc
@@ -0,0 +1,409 @@
+// Copyright (c) 2011 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.
+
+// Implementation of the installation validator.
+
+#include "chrome/installer/util/installation_validator.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/version.h"
+#include "chrome/installer/util/browser_distribution.h"
+#include "chrome/installer/util/helper.h"
+#include "chrome/installer/util/installation_state.h"
+
+namespace installer {
+
+BrowserDistribution::Type
+ InstallationValidator::ChromeRules::distribution_type() const {
+ return BrowserDistribution::CHROME_BROWSER;
+}
+
+void InstallationValidator::ChromeRules::AddUninstallSwitchExpectations(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ SwitchExpectations* expectations) const {
+ const bool is_multi_install =
+ product_state.uninstall_command().HasSwitch(switches::kMultiInstall);
+
+ // --chrome should be present iff --multi-install.
+ expectations->push_back(std::make_pair(std::string(switches::kChrome),
+ is_multi_install));
+ // --chrome-frame --ready-mode should be present iff CF in ready mode.
+ const ProductState* cf_state =
+ machine_state.GetProductState(system_install,
+ BrowserDistribution::CHROME_FRAME);
+ const bool ready_mode =
+ cf_state != NULL &&
+ cf_state->uninstall_command().HasSwitch(switches::kChromeFrameReadyMode);
+ expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
+ ready_mode));
+ expectations->push_back(
+ std::make_pair(std::string(switches::kChromeFrameReadyMode), ready_mode));
+}
+
+BrowserDistribution::Type
+ InstallationValidator::ChromeFrameRules::distribution_type() const {
+ return BrowserDistribution::CHROME_FRAME;
+}
+
+void InstallationValidator::ChromeFrameRules::AddUninstallSwitchExpectations(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ SwitchExpectations* expectations) const {
+ // --chrome-frame must be present.
+ expectations->push_back(std::make_pair(std::string(switches::kChromeFrame),
+ true));
+ // --chrome must not be present.
+ expectations->push_back(std::make_pair(std::string(switches::kChrome),
+ false));
+}
+
+// static
+const InstallationValidator::InstallationType
+ InstallationValidator::kInstallationTypes[] = {
+ NO_PRODUCTS,
+ CHROME_SINGLE,
+ CHROME_MULTI,
+ CHROME_FRAME_SINGLE,
+ CHROME_FRAME_SINGLE_CHROME_SINGLE,
+ CHROME_FRAME_SINGLE_CHROME_MULTI,
+ CHROME_FRAME_MULTI,
+ CHROME_FRAME_MULTI_CHROME_MULTI,
+ CHROME_FRAME_READY_MODE_CHROME_MULTI
+};
+
+// Validates the multi-install binaries at level |system_level|.
+void InstallationValidator::ValidateBinaries(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& binaries_state,
+ bool* is_valid) {
+ const ChannelInfo& channel = binaries_state.channel();
+
+ // ap must have -multi
+ if (!channel.IsMultiInstall()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are missing \"-multi\" in channel name: \""
+ << channel.value() << "\"";
+ }
+
+ // ap must have -chrome iff Chrome is installed
+ const ProductState* chrome_state = machine_state.GetProductState(
+ system_install, BrowserDistribution::CHROME_BROWSER);
+ if (chrome_state != NULL) {
+ if (!channel.IsChrome()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are missing \"chrome\" in channel name:"
+ << " \"" << channel.value() << "\"";
+ }
+ } else if (channel.IsChrome()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries have \"-chrome\" in channel name, yet Chrome"
+ " is not installed: \"" << channel.value() << "\"";
+ }
+
+ // ap must have -chromeframe iff Chrome Frame is installed multi
+ const ProductState* cf_state = machine_state.GetProductState(
+ system_install, BrowserDistribution::CHROME_FRAME);
+ if (cf_state != NULL && cf_state->is_multi_install()) {
+ if (!channel.IsChromeFrame()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are missing \"-chromeframe\" in channel"
+ " name: \"" << channel.value() << "\"";
+ }
+ } else if (channel.IsChromeFrame()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries have \"-chromeframe\" in channel name, yet "
+ "Chrome Frame is not installed multi: \"" << channel.value()
+ << "\"";
+ }
+
+ // ap must have -readymode iff Chrome Frame is installed in ready-mode
+ if (cf_state != NULL &&
+ cf_state->uninstall_command().HasSwitch(
+ installer::switches::kChromeFrameReadyMode)) {
+ if (!channel.IsReadyMode()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are missing \"-readymode\" in channel"
+ " name: \"" << channel.value() << "\"";
+ }
+ } else if (channel.IsReadyMode()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries have \"-readymode\" in channel name, yet "
+ "Chrome Frame is not in ready mode: \"" << channel.value()
+ << "\"";
+ }
+
+ // Chrome or Chrome Frame must be present
+ if (chrome_state == NULL && cf_state == NULL) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are present with no other products.";
+ }
+
+ // Chrome must be multi-install if present.
+ if (chrome_state != NULL && !chrome_state->is_multi_install()) {
+ *is_valid = false;
+ LOG(ERROR)
+ << "Chrome Binaries are present yet Chrome is not multi-install.";
+ }
+
+ // Chrome Frame must be multi-install if Chrome is not present.
+ if (cf_state != NULL && chrome_state == NULL &&
+ !cf_state->is_multi_install()) {
+ *is_valid = false;
+ LOG(ERROR) << "Chrome Binaries are present without Chrome yet Chrome Frame "
+ "is not multi-install.";
+ }
+}
+
+// Validates the path to |setup_exe| for the product described by |ctx|.
+void InstallationValidator::ValidateSetupPath(const ProductContext& ctx,
+ const FilePath& setup_exe,
+ const char* purpose,
+ bool* is_valid) {
+ DCHECK(is_valid);
+
+ BrowserDistribution* bins_dist = ctx.dist;
+ if (ctx.state.is_multi_install()) {
+ bins_dist = BrowserDistribution::GetSpecificDistribution(
+ BrowserDistribution::CHROME_BINARIES);
+ }
+
+ FilePath expected_path = installer::GetChromeInstallPath(ctx.system_install,
+ bins_dist);
+ expected_path = expected_path
+ .AppendASCII(ctx.state.version().GetString())
+ .Append(installer::kInstallerDir)
+ .Append(installer::kSetupExe);
+ if (!FilePath::CompareEqualIgnoreCase(expected_path.value(),
+ setup_exe.value())) {
+ *is_valid = false;
+ LOG(ERROR) << ctx.dist->GetApplicationName() << " path to " << purpose
+ << " is not " << expected_path.value() << ": "
+ << setup_exe.value();
+ }
+}
+
+// Validates that |command| meets the expectations described in |expected|.
+void InstallationValidator::ValidateCommandExpectations(
+ const ProductContext& ctx,
+ const CommandLine& command,
+ const SwitchExpectations& expected,
+ const char* source,
+ bool* is_valid) {
+ for (SwitchExpectations::size_type i = 0, size = expected.size(); i < size;
+ ++i) {
+ const SwitchExpectations::value_type& expectation = expected[i];
+ if (command.HasSwitch(expectation.first) != expectation.second) {
+ *is_valid = false;
+ LOG(ERROR) << ctx.dist->GetApplicationName() << " " << source
+ << (expectation.second ? " is missing" : " has") << " \""
+ << expectation.first << "\""
+ << (expectation.second ? "" : " but shouldn't") << ": "
+ << command.command_line_string();
+ }
+ }
+}
+
+// Validates that |command|, originating from |source|, is formed properly for
+// the product described by |ctx|
+void InstallationValidator::ValidateUninstallCommand(const ProductContext& ctx,
+ const CommandLine& command,
+ const char* source,
+ bool* is_valid) {
+ DCHECK(is_valid);
+
+ ValidateSetupPath(ctx, command.GetProgram(), "uninstaller", is_valid);
+
+ const bool is_multi_install = ctx.state.is_multi_install();
+ SwitchExpectations expected;
+
+ expected.push_back(std::make_pair(std::string(switches::kUninstall), true));
+ expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
+ ctx.system_install));
+ expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
+ is_multi_install));
+ ctx.rules.AddUninstallSwitchExpectations(ctx.machine_state,
+ ctx.system_install,
+ ctx.state, &expected);
+
+ ValidateCommandExpectations(ctx, command, expected, source, is_valid);
+}
+
+// Validates the rename command for the product described by |ctx|.
+void InstallationValidator::ValidateRenameCommand(const ProductContext& ctx,
+ bool* is_valid) {
+ DCHECK(is_valid);
+ DCHECK(!ctx.state.rename_cmd().empty());
+
+ CommandLine command = CommandLine::FromString(ctx.state.rename_cmd());
+
+ ValidateSetupPath(ctx, command.GetProgram(), "in-use renamer", is_valid);
+
+ SwitchExpectations expected;
+
+ expected.push_back(std::make_pair(std::string(switches::kRenameChromeExe),
+ true));
+ expected.push_back(std::make_pair(std::string(switches::kSystemLevel),
+ ctx.system_install));
+ expected.push_back(std::make_pair(std::string(switches::kMultiInstall),
+ ctx.state.is_multi_install()));
+
+ ValidateCommandExpectations(ctx, command, expected, "in-use renamer",
+ is_valid);
+}
+
+// Validates the "opv" and "cmd" values for the product described in |ctx|.
+void InstallationValidator::ValidateOldVersionValues(
+ const ProductContext& ctx,
+ bool* is_valid) {
+ DCHECK(is_valid);
+
+ // opv and cmd must both be present or both absent
+ if (ctx.state.old_version() == NULL) {
+ if (!ctx.state.rename_cmd().empty()) {
+ *is_valid = false;
+ LOG(ERROR) << ctx.dist->GetApplicationName()
+ << " has a rename command but no opv: "
+ << ctx.state.rename_cmd();
+ }
+ } else {
+ if (ctx.state.rename_cmd().empty()) {
+ *is_valid = false;
+ LOG(ERROR) << ctx.dist->GetApplicationName()
+ << " has an opv but no rename command: "
+ << ctx.state.old_version()->GetString();
+ } else {
+ ValidateRenameCommand(ctx, is_valid);
+ }
+ }
+}
+
+// Validates the multi-install state of the product described in |ctx|.
+void InstallationValidator::ValidateMultiInstallProduct(
+ const ProductContext& ctx,
+ bool* is_valid) {
+ DCHECK(is_valid);
+
+ const ProductState* binaries =
+ ctx.machine_state.GetProductState(ctx.system_install,
+ BrowserDistribution::CHROME_BINARIES);
+ DCHECK(binaries);
+
+ // Version must match that of binaries.
+ if (ctx.state.version().CompareTo(binaries->version()) != 0) {
+ *is_valid = false;
+ LOG(ERROR) << "Version of " << ctx.dist->GetApplicationName()
+ << " (" << ctx.state.version().GetString() << ") does not "
+ "match that of Chrome Binaries ("
+ << binaries->version().GetString() << ").";
+ }
+
+ // Channel value must match that of binaries.
+ if (!ctx.state.channel().Equals(binaries->channel())) {
+ *is_valid = false;
+ LOG(ERROR) << "Channel name of " << ctx.dist->GetApplicationName()
+ << " (" << ctx.state.channel().value()
+ << ") does not match that of Chrome Binaries ("
+ << binaries->channel().value() << ").";
+ }
+}
+
+// Validates the product described in |product_state| according to |rules|.
+void InstallationValidator::ValidateProduct(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ const ProductRules& rules,
+ bool* is_valid) {
+ DCHECK(is_valid);
+ ProductContext ctx = {
+ machine_state,
+ system_install,
+ BrowserDistribution::GetSpecificDistribution(rules.distribution_type()),
+ product_state,
+ rules
+ };
+
+ ValidateUninstallCommand(ctx, product_state.uninstall_command(),
+ "Google Update uninstall command", is_valid);
+
+ ValidateOldVersionValues(ctx, is_valid);
+
+ if (product_state.is_multi_install())
+ ValidateMultiInstallProduct(ctx, is_valid);
+}
+
+// static
+bool InstallationValidator::ValidateInstallationTypeForState(
+ const InstallationState& machine_state,
+ bool system_level,
+ InstallationType* type) {
+ DCHECK(type);
+ bool rock_on = true;
+ *type = NO_PRODUCTS;
+
+ // Does the system have any multi-installed products?
+ const ProductState* multi_state =
+ machine_state.GetProductState(system_level,
+ BrowserDistribution::CHROME_BINARIES);
+ if (multi_state != NULL)
+ ValidateBinaries(machine_state, system_level, *multi_state, &rock_on);
+
+ // Is Chrome installed?
+ const ProductState* product_state =
+ machine_state.GetProductState(system_level,
+ BrowserDistribution::CHROME_BROWSER);
+ if (product_state != NULL) {
+ ChromeRules chrome_rules;
+ ValidateProduct(machine_state, system_level, *product_state,
+ chrome_rules, &rock_on);
+ *type = static_cast<InstallationType>(
+ *type | (product_state->is_multi_install() ?
+ ProductBits::CHROME_MULTI :
+ ProductBits::CHROME_SINGLE));
+ }
+
+ // Is Chrome Frame installed?
+ product_state =
+ machine_state.GetProductState(system_level,
+ BrowserDistribution::CHROME_FRAME);
+ if (product_state != NULL) {
+ ChromeFrameRules chrome_frame_rules;
+ ValidateProduct(machine_state, system_level, *product_state,
+ chrome_frame_rules, &rock_on);
+ int cf_bit = !product_state->is_multi_install() ?
+ ProductBits::CHROME_FRAME_SINGLE :
+ (product_state->uninstall_command().HasSwitch(
+ switches::kChromeFrameReadyMode) ?
+ ProductBits::CHROME_FRAME_READY_MODE :
+ ProductBits::CHROME_FRAME_MULTI);
+ *type = static_cast<InstallationType>(*type | cf_bit);
+ }
+
+ DCHECK_NE(std::find(&kInstallationTypes[0],
+ &kInstallationTypes[arraysize(kInstallationTypes)],
+ *type),
+ &kInstallationTypes[arraysize(kInstallationTypes)])
+ << "Invalid combination of products found on system (" << *type << ")";
+
+ return rock_on;
+}
+
+// static
+bool InstallationValidator::ValidateInstallationType(bool system_level,
+ InstallationType* type) {
+ DCHECK(type);
+ InstallationState machine_state;
+
+ machine_state.Initialize();
+
+ return ValidateInstallationTypeForState(machine_state, system_level, type);
+}
+
+} // namespace installer
diff --git a/chrome/installer/util/installation_validator.h b/chrome/installer/util/installation_validator.h
new file mode 100644
index 0000000..101a249
--- /dev/null
+++ b/chrome/installer/util/installation_validator.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_INSTALLER_UTIL_INSTALLATION_VALIDATOR_H_
+#define CHROME_INSTALLER_UTIL_INSTALLATION_VALIDATOR_H_
+#pragma once
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "chrome/installer/util/browser_distribution.h"
+#include "chrome/installer/util/channel_info.h"
+
+class FilePath;
+class Version;
+
+namespace installer {
+
+class InstallationState;
+class ProductState;
+
+// A class that validates the state of an installation. Violations are logged
+// via LOG(ERROR).
+class InstallationValidator {
+ public:
+ class ProductBits {
+ public:
+ // Bits that form the components of an installation type.
+ enum {
+ CHROME_SINGLE = 0x01,
+ CHROME_MULTI = 0x02,
+ CHROME_FRAME_SINGLE = 0x04,
+ CHROME_FRAME_MULTI = 0x08,
+ CHROME_FRAME_READY_MODE = 0x10,
+ };
+ }; // class ProductBits
+
+ // Identifiers of all valid installation types.
+ enum InstallationType {
+ NO_PRODUCTS = 0,
+ CHROME_SINGLE =
+ ProductBits::CHROME_SINGLE,
+ CHROME_MULTI =
+ ProductBits::CHROME_MULTI,
+ CHROME_FRAME_SINGLE =
+ ProductBits::CHROME_FRAME_SINGLE,
+ CHROME_FRAME_SINGLE_CHROME_SINGLE =
+ ProductBits::CHROME_FRAME_SINGLE | ProductBits::CHROME_SINGLE,
+ CHROME_FRAME_SINGLE_CHROME_MULTI =
+ ProductBits::CHROME_FRAME_SINGLE | ProductBits::CHROME_MULTI,
+ CHROME_FRAME_MULTI =
+ ProductBits::CHROME_FRAME_MULTI,
+ CHROME_FRAME_MULTI_CHROME_MULTI =
+ ProductBits::CHROME_FRAME_MULTI | ProductBits::CHROME_MULTI,
+ CHROME_FRAME_READY_MODE_CHROME_MULTI =
+ ProductBits::CHROME_FRAME_READY_MODE | ProductBits::CHROME_MULTI,
+ };
+
+ // Validates |machine_state| at user or system level, returning true if valid.
+ // |type| is populated in either case, although it is a best-guess when the
+ // method returns false.
+ static bool ValidateInstallationTypeForState(
+ const InstallationState& machine_state,
+ bool system_level,
+ InstallationType* type);
+
+ // Validates the machine's current installation at user or system level,
+ // returning true and populating |type| if valid.
+ static bool ValidateInstallationType(bool system_level,
+ InstallationType* type);
+
+ protected:
+ typedef std::vector<std::pair<std::string, bool> > SwitchExpectations;
+
+ // An interface to product-specific validation rules.
+ class ProductRules {
+ public:
+ virtual ~ProductRules() { }
+ virtual BrowserDistribution::Type distribution_type() const = 0;
+ virtual void AddUninstallSwitchExpectations(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ SwitchExpectations* expectations) const = 0;
+ };
+
+ // Validation rules for the Chrome browser.
+ class ChromeRules : public ProductRules {
+ public:
+ virtual BrowserDistribution::Type distribution_type() const OVERRIDE;
+ virtual void AddUninstallSwitchExpectations(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ SwitchExpectations* expectations) const OVERRIDE;
+ };
+
+ // Validation rules for Chrome Frame.
+ class ChromeFrameRules : public ProductRules {
+ public:
+ virtual BrowserDistribution::Type distribution_type() const OVERRIDE;
+ virtual void AddUninstallSwitchExpectations(
+ const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ SwitchExpectations* expectations) const OVERRIDE;
+ };
+
+ struct ProductContext {
+ const InstallationState& machine_state;
+ bool system_install;
+ BrowserDistribution* dist;
+ const ProductState& state;
+ const ProductRules& rules;
+ };
+
+ static void ValidateBinaries(const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& binaries_state,
+ bool* is_valid);
+ static void ValidateSetupPath(const ProductContext& ctx,
+ const FilePath& setup_exe,
+ const char* purpose,
+ bool* is_valid);
+ static void ValidateCommandExpectations(const ProductContext& ctx,
+ const CommandLine& command,
+ const SwitchExpectations& expected,
+ const char* source,
+ bool* is_valid);
+ static void ValidateUninstallCommand(const ProductContext& ctx,
+ const CommandLine& command,
+ const char* source,
+ bool* is_valid);
+ static void ValidateRenameCommand(const ProductContext& ctx,
+ bool* is_valid);
+ static void ValidateOldVersionValues(const ProductContext& ctx,
+ bool* is_valid);
+ static void ValidateMultiInstallProduct(const ProductContext& ctx,
+ bool* is_valid);
+ static void ValidateProduct(const InstallationState& machine_state,
+ bool system_install,
+ const ProductState& product_state,
+ const ProductRules& rules,
+ bool* is_valid);
+
+ // A collection of all valid installation types.
+ static const InstallationType kInstallationTypes[];
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(InstallationValidator);
+};
+
+} // namespace installer
+
+#endif // CHROME_INSTALLER_UTIL_INSTALLATION_VALIDATOR_H_
diff --git a/chrome/installer/util/installation_validator_unittest.cc b/chrome/installer/util/installation_validator_unittest.cc
new file mode 100644
index 0000000..2bd5459
--- /dev/null
+++ b/chrome/installer/util/installation_validator_unittest.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2011 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 "base/command_line.h"
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "base/version.h"
+#include "chrome/common/chrome_constants.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::ProductState;
+using testing::_;
+using testing::StrictMock;
+
+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_READY_MODE = 0x08,
+ CM_FULL = 0x10
+};
+
+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 Clear();
+ 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 set_multi_install(bool is_multi_install) {
+ multi_install_ = is_multi_install;
+ }
+
+ protected:
+ struct ChannelMethodForModifier {
+ ChannelModifier modifier;
+ bool (ChannelInfo::*method)(bool value);
+ };
+
+ 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_READY_MODE, &ChannelInfo::SetReadyMode },
+ { CM_FULL, &ChannelInfo::SetFullSuffix }
+};
+
+void FakeProductState::Clear() {
+ channel_.set_value(std::wstring());
+ version_.reset();
+ old_version_.reset();
+ rename_cmd_.clear();
+ uninstall_command_ = CommandLine(CommandLine::NO_PROGRAM);
+ msi_ = false;
+ multi_install_ = false;
+}
+
+// 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 : Version::GetVersionFromString(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;
+ FilePath setup_path = installer::GetChromeInstallPath(
+ install_level == SYSTEM_LEVEL,
+ BrowserDistribution::GetSpecificDistribution(is_multi_install ?
+ BrowserDistribution::CHROME_BINARIES : dist_type));
+ setup_path = setup_path
+ .AppendASCII(version)
+ .Append(installer::kInstallerDir)
+ .Append(installer::kSetupExe);
+ uninstall_command_ = CommandLine(setup_path);
+ 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);
+ if ((channel_modifiers & CM_READY_MODE) != 0) {
+ uninstall_command_.AppendSwitch(installer::switches::kChromeFrame);
+ uninstall_command_.AppendSwitch(
+ installer::switches::kChromeFrameReadyMode);
+ }
+ }
+ } else if (dist_type == BrowserDistribution::CHROME_FRAME) {
+ uninstall_command_.AppendSwitch(installer::switches::kChromeFrame);
+ }
+ if (vehicle == MSI)
+ uninstall_command_.AppendSwitch(installer::switches::kMsi);
+}
+
+// Populates |chrome_state| with the state of a valid Chrome browser
+// installation. |channel_modifiers|, a field of bits defined by enum
+// ChannelModifier, dictate properties of the installation (multi-install,
+// ready-mode, etc).
+void MakeChromeState(Level install_level,
+ Channel channel,
+ int channel_modifiers,
+ Vehicle vehicle,
+ FakeProductState* chrome_state) {
+ chrome_state->Clear();
+ chrome_state->SetChannel(kChromeChannels[channel], channel_modifiers);
+ chrome_state->SetVersion(chrome::kChromeVersion);
+ chrome_state->SetUninstallCommand(BrowserDistribution::CHROME_BROWSER,
+ install_level, chrome::kChromeVersion,
+ channel_modifiers, vehicle);
+ chrome_state->set_multi_install((channel_modifiers & CM_MULTI) != 0);
+}
+
+} // 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::Test {
+ protected:
+ 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));
+ };
+
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+ 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);
+
+ virtual void TearDown();
+
+ static logging::LogMessageHandlerFunction old_log_message_handler_;
+ static ValidationErrorRecipient* validation_error_recipient_;
+};
+
+// static
+logging::LogMessageHandlerFunction
+ InstallationValidatorTest::old_log_message_handler_ = NULL;
+
+// static
+InstallationValidatorTest::ValidationErrorRecipient*
+ InstallationValidatorTest::validation_error_recipient_ = NULL;
+
+// static
+void InstallationValidatorTest::SetUpTestCase() {
+ old_log_message_handler_ = logging::GetLogMessageHandler();
+ logging::SetLogMessageHandler(&HandleLogMessage);
+}
+
+// static
+void InstallationValidatorTest::TearDownTestCase() {
+ logging::SetLogMessageHandler(old_log_message_handler_);
+ old_log_message_handler_ = 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) {
+ if (validation_error_recipient_ != NULL) {
+ validation_error_recipient_->ReceiveValidationError(
+ file, line, str.c_str() + message_start);
+ } else {
+ // Fail the test if an error wasn't handled.
+ ADD_FAILURE_AT(file, line) << (str.c_str() + message_start);
+ }
+ 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;
+}
+
+void InstallationValidatorTest::TearDown() {
+ validation_error_recipient_ = NULL;
+}
+
+// Test that NO_PRODUCTS is returned.
+TEST_F(InstallationValidatorTest, NoProducts) {
+ InstallationState empty_state;
+ InstallationValidator::InstallationType type =
+ static_cast<InstallationValidator::InstallationType>(-1);
+ StrictMock<MockValidationErrorRecipient> recipient;
+ set_validation_error_recipient(&recipient);
+
+ EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState(
+ empty_state, true, &type));
+ EXPECT_EQ(InstallationValidator::NO_PRODUCTS, type);
+}
+
+// Test valid single Chrome.
+TEST_F(InstallationValidatorTest, ChromeVersion) {
+ FakeProductState chrome_state;
+ FakeInstallationState machine_state;
+ InstallationValidator::InstallationType type;
+ StrictMock<MockValidationErrorRecipient> recipient;
+ set_validation_error_recipient(&recipient);
+
+ MakeChromeState(SYSTEM_LEVEL, STABLE_CHANNEL, 0, GOOGLE_UPDATE,
+ &chrome_state);
+ machine_state.SetProductState(BrowserDistribution::CHROME_BROWSER,
+ SYSTEM_LEVEL, chrome_state);
+ EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState(
+ machine_state, true, &type));
+ EXPECT_EQ(InstallationValidator::CHROME_SINGLE, type);
+}