Commit f9178f09 authored by Maciek Kumorek's avatar Maciek Kumorek Committed by Commit Bot

Add a switch to simulate errors in the about page

Add --simulate-update-hresult and --simulate-update-error-code
switches. The first switch can take the HRESULT error code value,
the second one specifies a value that corresponds to the
GoolgeUpdateErrorCode enum. With these switches, when using
VersionUpdaterWin, one can simulate errors in the about page to debug
how the UI reflects certain errors conditions.

Bug: 1066529
Change-Id: I6500b81108c69d252087ebcdb1cba0ffba86dbed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2131019
Commit-Queue: Maciek Kumorek <makumo@microsoft.com>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarSorin Jianu <sorin@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#760218}
parent a68c16b3
......@@ -4082,6 +4082,8 @@ jumbo_static_library("browser") {
"google/google_update_policy_fetcher_win.h",
"google/google_update_win.cc",
"google/google_update_win.h",
"google/switches.cc",
"google/switches.h",
"win/conflicts/incompatible_applications_updater.cc",
"win/conflicts/incompatible_applications_updater.h",
"win/conflicts/installed_applications.cc",
......
......@@ -13,6 +13,7 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
......@@ -36,6 +37,7 @@
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/win_util.h"
#include "chrome/browser/google/switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/install_static/install_util.h"
......@@ -48,6 +50,11 @@
namespace {
struct UpdateCheckResult {
GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_NO_ERROR;
HRESULT hresult = S_OK;
};
// The status of the upgrade. These values are used for a histogram. Do not
// reorder.
enum GoogleUpdateUpgradeStatus {
......@@ -202,6 +209,35 @@ base::Optional<UpdateState>* GetLastUpdateStateStorage() {
return storage.get();
}
// Checks if --simulate-update-hresult is present in the command line and
// returns either: nullopt if switch not present, or E_FAIL if the switch
// was present without the value, or the value of the switch as an HRESULT.
// Additionally the returned structure contains the default error code
// GOOGLE_UPDATE_ERROR_UPDATING or the value of --simulate-update-error-code.
base::Optional<UpdateCheckResult> GetSimulatedErrorForDebugging() {
const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
if (!cmd_line.HasSwitch(switches::kSimulateUpdateHresult))
return base::nullopt;
uint32_t error_from_string = 0;
std::string error_switch_value =
cmd_line.GetSwitchValueASCII(switches::kSimulateUpdateHresult);
HRESULT hresult = E_FAIL;
if (base::HexStringToUInt(error_switch_value, &error_from_string))
hresult = error_from_string;
GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_ERROR_UPDATING;
error_switch_value =
cmd_line.GetSwitchValueASCII(switches::kSimulateUpdateErrorCode);
int32_t error_code_value = 0;
if (base::StringToInt(error_switch_value, &error_code_value) &&
error_code_value >= 0 && error_code_value < NUM_ERROR_CODES) {
error_code = static_cast<GoogleUpdateErrorCode>(error_code_value);
}
return {{error_code, hresult}};
}
// UpdateCheckDriver -----------------------------------------------------------
// A driver that is created and destroyed on the caller's thread and drives
......@@ -241,20 +277,18 @@ class UpdateCheckDriver {
// Starts an update check.
void BeginUpdateCheck();
// Returns the result of initiating an update check. Sets |error_code| if the
// result is any kind of failure. On failure, the instance is left in a
// consistent state so that this method can be invoked later to retry the
// steps that failed.
HRESULT BeginUpdateCheckInternal(GoogleUpdateErrorCode* error_code);
// Returns the result of initiating an update check. On failure, the instance
// is left in a consistent state so that this method can be invoked later to
// retry the steps that failed.
UpdateCheckResult BeginUpdateCheckInternal();
// Sets status_ to UPGRADE_ERROR, update_state_.error_code to |error_code|,
// update_state_.hresult to |hresult|, update_state_.installer_exit_code to
// |installer_exit_code|, and html_error_message_ to a composition of all
// values suitable for display to the user. This call should be followed by
// deletion of the driver, which will result in callers being notified via
// their delegates.
void OnUpgradeError(GoogleUpdateErrorCode error_code,
HRESULT hresult,
// update_state_.hresult to |check_result.hresult|,
// update_state_.installer_exit_code to |installer_exit_code|,
// and html_error_message_ to a composition of all values suitable for display
// to the user. This call should be followed by deletion of the driver, which
// will result in callers being notified via their delegates.
void OnUpgradeError(UpdateCheckResult check_result,
base::Optional<int> installer_exit_code,
const base::string16& error_string);
......@@ -467,16 +501,15 @@ void UpdateCheckDriver::NotifyUpgradeProgress(
}
void UpdateCheckDriver::BeginUpdateCheck() {
GoogleUpdateErrorCode error_code = GOOGLE_UPDATE_NO_ERROR;
HRESULT hresult = BeginUpdateCheckInternal(&error_code);
if (SUCCEEDED(hresult)) {
UpdateCheckResult result = BeginUpdateCheckInternal();
if (SUCCEEDED(result.hresult)) {
// Start polling.
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&UpdateCheckDriver::PollGoogleUpdate,
base::Unretained(this)));
return;
}
if (hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
if (result.hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
// This particular transient error is worth retrying.
if (allowed_retries_) {
--allowed_retries_;
......@@ -489,14 +522,18 @@ void UpdateCheckDriver::BeginUpdateCheck() {
}
}
DCHECK(FAILED(hresult));
OnUpgradeError(error_code, hresult, base::nullopt, base::string16());
DCHECK(FAILED(result.hresult));
OnUpgradeError(result, base::nullopt, base::string16());
result_runner_->DeleteSoon(FROM_HERE, this);
}
HRESULT UpdateCheckDriver::BeginUpdateCheckInternal(
GoogleUpdateErrorCode* error_code) {
UpdateCheckResult UpdateCheckDriver::BeginUpdateCheckInternal() {
const auto simulated_error = GetSimulatedErrorForDebugging();
if (simulated_error.has_value())
return simulated_error.value();
HRESULT hresult = S_OK;
// Instantiate GoogleUpdate3Web{Machine,User}Class.
if (!google_update_) {
base::FilePath chrome_exe;
......@@ -508,23 +545,23 @@ HRESULT UpdateCheckDriver::BeginUpdateCheckInternal(
// Make sure ATL is initialized in this module.
ui::win::CreateATLModuleIfNeeded();
*error_code = CanUpdateCurrentChrome(chrome_exe, system_level_install_);
if (*error_code != GOOGLE_UPDATE_NO_ERROR)
return E_FAIL;
const GoogleUpdateErrorCode error_code =
CanUpdateCurrentChrome(chrome_exe, system_level_install_);
if (error_code != GOOGLE_UPDATE_NO_ERROR)
return {error_code, E_FAIL};
hresult = CreateGoogleUpdate3WebClass(system_level_install_,
install_update_if_possible_,
elevation_window_, &google_update_);
if (FAILED(hresult)) {
*error_code = GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND;
return hresult;
}
if (FAILED(hresult))
return {GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND, hresult};
ConfigureProxyBlanket(google_update_.Get());
}
// The class was created, so all subsequent errors are reported as:
*error_code = GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR;
constexpr GoogleUpdateErrorCode error_code =
GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR;
// Create an app bundle.
if (!app_bundle_) {
......@@ -532,10 +569,10 @@ HRESULT UpdateCheckDriver::BeginUpdateCheckInternal(
Microsoft::WRL::ComPtr<IDispatch> dispatch;
hresult = google_update_->createAppBundleWeb(dispatch.GetAddressOf());
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
hresult = dispatch.CopyTo(app_bundle.GetAddressOf());
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
dispatch.Reset();
ConfigureProxyBlanket(app_bundle.Get());
......@@ -550,7 +587,7 @@ HRESULT UpdateCheckDriver::BeginUpdateCheckInternal(
hresult = app_bundle->initialize();
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
if (elevation_window_) {
// Likewise, a failure to set the parent window need not block an update
// check.
......@@ -572,27 +609,27 @@ HRESULT UpdateCheckDriver::BeginUpdateCheckInternal(
hresult =
app_bundle_->createInstalledApp(base::win::ScopedBstr(app_guid).Get());
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
// Move the IAppBundleWeb reference into a local now so that failures from
// this point onward result in it being released.
Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
app_bundle.Swap(app_bundle_);
hresult = app_bundle->get_appWeb(0, dispatch.GetAddressOf());
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
Microsoft::WRL::ComPtr<IAppWeb> app;
hresult = dispatch.CopyTo(app.GetAddressOf());
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
ConfigureProxyBlanket(app.Get());
hresult = app_bundle->checkForUpdate();
if (FAILED(hresult))
return hresult;
return {error_code, hresult};
app_bundle_.Swap(app_bundle);
app_.Swap(app);
}
return hresult;
return {GOOGLE_UPDATE_NO_ERROR, hresult};
}
bool UpdateCheckDriver::GetCurrentState(
......@@ -784,11 +821,11 @@ void UpdateCheckDriver::PollGoogleUpdate() {
int progress = 0;
if (!GetCurrentState(&state, &state_value, &hresult)) {
OnUpgradeError(GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR, hresult,
OnUpgradeError({GOOGLE_UPDATE_ONDEMAND_CLASS_REPORTED_ERROR, hresult},
base::nullopt, base::string16());
} else if (IsErrorState(state, state_value, &error_code, &hresult,
&installer_exit_code, &error_string)) {
OnUpgradeError(error_code, hresult, installer_exit_code, error_string);
OnUpgradeError({error_code, hresult}, installer_exit_code, error_string);
} else if (IsFinalState(state, state_value, &upgrade_status, &new_version)) {
status_ = upgrade_status;
update_state_.error_code = GOOGLE_UPDATE_NO_ERROR;
......@@ -835,17 +872,16 @@ void UpdateCheckDriver::PollGoogleUpdate() {
result_runner_->DeleteSoon(FROM_HERE, this);
}
void UpdateCheckDriver::OnUpgradeError(GoogleUpdateErrorCode error_code,
HRESULT hresult,
void UpdateCheckDriver::OnUpgradeError(UpdateCheckResult check_result,
base::Optional<int> installer_exit_code,
const base::string16& error_string) {
status_ = UPGRADE_ERROR;
update_state_.error_code = error_code;
update_state_.hresult = hresult;
update_state_.error_code = check_result.error_code;
update_state_.hresult = check_result.hresult;
update_state_.installer_exit_code = installer_exit_code;
// Some specific result codes have dedicated messages.
if (hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
if (check_result.hresult == GOOPDATE_E_APP_USING_EXTERNAL_UPDATER) {
html_error_message_ = l10n_util::GetStringUTF16(
IDS_ABOUT_BOX_EXTERNAL_UPDATE_IS_RUNNING);
return;
......
......@@ -19,6 +19,7 @@
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_path_override.h"
#include "base/test/test_reg_util_win.h"
#include "base/test/test_simple_task_runner.h"
......@@ -26,6 +27,7 @@
#include "base/version.h"
#include "base/win/atl.h"
#include "base/win/registry.h"
#include "chrome/browser/google/switches.h"
#include "chrome/common/chrome_version.h"
#include "chrome/install_static/test/scoped_install_details.h"
#include "chrome/installer/util/google_update_settings.h"
......@@ -35,6 +37,8 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/win/atl_module.h"
using ::testing::_;
using ::testing::AllOfArray;
using ::testing::DoAll;
using ::testing::HasSubstr;
using ::testing::InSequence;
......@@ -45,7 +49,6 @@ using ::testing::SetArgPointee;
using ::testing::StrEq;
using ::testing::StrictMock;
using ::testing::Values;
using ::testing::_;
namespace {
......@@ -1062,6 +1065,72 @@ TEST_P(GoogleUpdateWinTest, UpdateInstalledMultipleDelegates) {
EXPECT_EQ(GetLastUpdateState()->new_version, new_version_);
}
// Test the case where the GoogleUpdate class responds with error provided
// via the command line with specified hresult and GoogleUpdateErrorCode.
TEST_P(GoogleUpdateWinTest, SimulateHresultWithErrorCode) {
base::test::ScopedCommandLine commandline;
// Simulate GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND (3).
commandline.GetProcessCommandLine()->AppendSwitchASCII(
switches::kSimulateUpdateErrorCode, "3");
// Simulate WININET_E_INCORRECT_HANDLE_TYPE (0x80072ef2).
commandline.GetProcessCommandLine()->AppendSwitchASCII(
switches::kSimulateUpdateHresult, "0x80072ef2");
// Expect the appropriate error when the on-demand class cannot be created.
EXPECT_CALL(mock_update_check_delegate_,
OnError(GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND,
AllOfArray({HasSubstr(L"error code 3:"),
HasSubstr(L"0x80072EF2")}),
_));
BeginUpdateCheck(std::string(), false, 0,
mock_update_check_delegate_.AsWeakPtr());
task_runner_->RunUntilIdle();
ASSERT_TRUE(GetLastUpdateState());
EXPECT_EQ(GetLastUpdateState()->error_code,
GOOGLE_UPDATE_ONDEMAND_CLASS_NOT_FOUND);
}
// Test the case where the GoogleUpdate class responds with error provided
// via the command line with a specific hresult.
TEST_P(GoogleUpdateWinTest, SimulateHresultOnly) {
base::test::ScopedCommandLine commandline;
// Simulate WININET_E_INCORRECT_HANDLE_TYPE (0x80072ef2).
commandline.GetProcessCommandLine()->AppendSwitchASCII(
switches::kSimulateUpdateHresult, "0x80072ef2");
// Expect the appropriate error when the on-demand class cannot be created.
EXPECT_CALL(mock_update_check_delegate_,
OnError(GOOGLE_UPDATE_ERROR_UPDATING,
AllOfArray({HasSubstr(L"error code 7:"),
HasSubstr(L"0x80072EF2")}),
_));
BeginUpdateCheck(std::string(), false, 0,
mock_update_check_delegate_.AsWeakPtr());
task_runner_->RunUntilIdle();
ASSERT_TRUE(GetLastUpdateState());
EXPECT_EQ(GetLastUpdateState()->error_code, GOOGLE_UPDATE_ERROR_UPDATING);
}
// Test the case where the GoogleUpdate class responds with error provided
// via the command line without specific hresult.
TEST_P(GoogleUpdateWinTest, SimulateHresultDefault) {
base::test::ScopedCommandLine commandline;
commandline.GetProcessCommandLine()->AppendSwitch(
switches::kSimulateUpdateHresult);
// Expect the appropriate error when the on-demand class cannot be created.
EXPECT_CALL(mock_update_check_delegate_,
OnError(GOOGLE_UPDATE_ERROR_UPDATING,
AllOfArray({HasSubstr(L"error code 7:"),
HasSubstr(L"0x80004005")}),
_));
BeginUpdateCheck(std::string(), false, 0,
mock_update_check_delegate_.AsWeakPtr());
task_runner_->RunUntilIdle();
ASSERT_TRUE(GetLastUpdateState());
EXPECT_EQ(GetLastUpdateState()->error_code, GOOGLE_UPDATE_ERROR_UPDATING);
}
INSTANTIATE_TEST_SUITE_P(UserLevel, GoogleUpdateWinTest, Values(false));
INSTANTIATE_TEST_SUITE_P(SystemLevel, GoogleUpdateWinTest, Values(true));
// Copyright 2020 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 "chrome/browser/google/switches.h"
namespace switches {
// Simulates a specific HRESULT error code returned by the update check.
// If the switch value is not specified (as hex) then it defaults to E_FAIL.
const char kSimulateUpdateHresult[] = "simulate-update-hresult";
// Simulates a GoogleUpdateErrorCode error by the update check.
// Must be supplied with |kSimulateUpdateHresult| switch.
const char kSimulateUpdateErrorCode[] = "simulate-update-error-code";
} // namespace switches
// Copyright 2020 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_BROWSER_GOOGLE_SWITCHES_H_
#define CHROME_BROWSER_GOOGLE_SWITCHES_H_
namespace switches {
extern const char kSimulateUpdateHresult[];
extern const char kSimulateUpdateErrorCode[];
} // namespace switches
#endif // CHROME_BROWSER_GOOGLE_SWITCHES_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment