blob: 42279ca5473add7ec758c06eca2061a80887a540 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/test/integration_tests_impl.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/function_ref.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/checked_math.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/enterprise_companion/device_management_storage/dm_storage.h"
#include "chrome/enterprise_companion/enterprise_companion_version.h"
#include "chrome/enterprise_companion/global_constants.h"
#include "chrome/enterprise_companion/installer_paths.h"
#include "chrome/updater/activity.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/external_constants_builder.h"
#include "chrome/updater/external_constants_override.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/protos/omaha_settings.pb.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/service_proxy_factory.h"
#include "chrome/updater/test/dm_policy_builder.h"
#include "chrome/updater/test/request_matcher.h"
#include "chrome/updater/test/server.h"
#include "chrome/updater/test/test_scope.h"
#include "chrome/updater/test/unit_test_util.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "crypto/sha2.h"
#include "net/http/http_status_code.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/re2/src/re2/re2.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_WIN)
#include "base/file_version_info_win.h"
#include "base/win/elevation_util.h"
#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/win_constants.h"
#elif BUILDFLAG(IS_LINUX)
#include "chrome/updater/util/linux_util.h"
#include "chrome/updater/util/posix_util.h"
#endif
namespace updater::test {
namespace {
constexpr char kSelfUpdateCRXName[] = "updater_selfupdate.crx3";
#if BUILDFLAG(IS_MAC)
constexpr char kSelfUpdateCRXRun[] = PRODUCT_FULLNAME_STRING "_test.app";
constexpr char kDoNothingCRXName[] = "updater_qualification_app_dmg.crx";
constexpr char kDoNothingCRXRun[] = "updater_qualification_app_dmg.dmg";
// On Mac, the install scripts are in the root ('.') directory of the CRX.
constexpr char kEnterpriseCompanionCRXRun[] = ".";
// On Mac, the test companion app does not have a different name.
constexpr base::FilePath::CharType kCompanionAppTestExecutableName[] =
BROWSER_NAME_STRING "EnterpriseCompanion";
#elif BUILDFLAG(IS_WIN)
constexpr char kSelfUpdateCRXRun[] = "UpdaterSetup_test.exe";
constexpr char kDoNothingCRXName[] = "updater_qualification_app_exe.crx";
constexpr char kDoNothingCRXRun[] = "qualification_app.exe";
constexpr char kEnterpriseCompanionCRXRun[] = "enterprise_companion_test.exe";
constexpr base::FilePath::CharType kCompanionAppTestExecutableName[] =
FILE_PATH_LITERAL("enterprise_companion_test.exe");
#elif BUILDFLAG(IS_LINUX)
constexpr char kSelfUpdateCRXRun[] = "updater_test";
constexpr char kDoNothingCRXName[] = "updater_qualification_app.crx";
constexpr char kDoNothingCRXRun[] = "qualification_app";
constexpr char kEnterpriseCompanionCRXRun[] = "enterprise_companion_test";
constexpr base::FilePath::CharType kCompanionAppTestExecutableName[] =
FILE_PATH_LITERAL("enterprise_companion_test");
#endif
constexpr char kEnterpriseCompanionCRXName[] = "enterprise_companion_test.crx3";
constexpr char kEnterpriseCompanionCRXArguments[] = "--install";
// Returns the relative path to the enterprise companion app's binary from the
// install directory or test data directory.
base::FilePath GetEnterpriseCompanionAppExeRelativePath() {
base::FilePath exe_path;
#if BUILDFLAG(IS_MAC)
exe_path = exe_path.Append(BROWSER_NAME_STRING "EnterpriseCompanion.app")
.Append("Contents/MacOS");
#endif
return exe_path.Append(kCompanionAppTestExecutableName);
}
std::string GetHashHex(const base::FilePath& file) {
base::MemoryMappedFile mmfile;
EXPECT_TRUE(mmfile.Initialize(file)); // Note: This fails with an empty file.
return base::HexEncode(crypto::SHA256Hash(mmfile.bytes()));
}
// The fingerprint is formatted as app_id.1.hash_hex to make the fingerprint
// unique in the scope of the integration tests, which sometimes reuse the
// same update file payload for different app ids.
std::string GetUpdateResponseForAppV4(const std::string& app_id,
const std::string& install_data_index,
const std::string& codebase,
const base::Version& version,
const base::FilePath& update_file,
const std::string& run_action,
const std::string& arguments,
std::optional<std::string> file_hash,
std::optional<std::string> status,
bool use_xz) {
const std::string hash = file_hash ? *file_hash : GetHashHex(update_file);
return base::StringPrintf(
R"( {)"
R"( "appid":"%s",)"
R"( "status":"%s",)"
R"(%s)"
R"( "updatecheck":{)"
R"( "status":"ok",)"
R"( "nextversion":"%s",)"
R"( "pipelines":[)"
R"( {"operations":[)"
R"( { "type":"download",)"
R"( "urls":[{"url":"%s/%s"}],)"
R"( "out":{"sha256":"%s"},)"
// arbitrary size, must be greater than 0:
R"( "size": 10},)"
R"( %s)"
R"( { "type":"crx3",)"
R"( "arguments":"%s",)"
R"( "path":"%s",)"
R"( "in":{"sha256":"%s"}})"
R"( ]})"
R"( ])"
R"( })"
R"( })",
base::ToLowerASCII(app_id).c_str(), status ? status->c_str() : "ok",
install_data_index.empty()
? ""
: base::StringPrintf(
R"( "data":[{ "status":"ok", "name":"install", )"
R"("index":"%s", "#text":"%s_text" }],)",
install_data_index.c_str(), install_data_index.c_str())
.c_str(),
version.GetString().c_str(), codebase.c_str(),
update_file.BaseName().AsUTF8Unsafe().c_str(), hash.c_str(),
use_xz ? R"({"type":"xz"},)" : "", arguments.c_str(), run_action.c_str(),
hash.c_str());
}
std::string GetUpdateResponseForAppV3(const std::string& app_id,
const std::string& install_data_index,
const std::string& codebase,
const base::Version& version,
const base::FilePath& update_file,
const std::string& run_action,
const std::string& arguments,
std::optional<std::string> file_hash,
std::optional<std::string> status,
bool use_xz) {
const std::string hash = file_hash.value_or(GetHashHex(update_file));
return base::StringPrintf(
R"( {)"
R"( "appid":"%s",)"
R"( "status":"%s",)"
R"(%s)"
R"( "updatecheck":{)"
R"( "status":"ok",)"
R"( "urls":{"url":[{"codebase":"%s/"}]},)"
R"( "manifest":{)"
R"( "version":"%s",)"
R"( "run":"%s",)"
R"( "arguments":"%s",)"
R"( "packages":{)"
R"( "package":[)"
R"( {"name":"%s","hash_sha256":"%s", "fp":"%s"})"
R"( ])"
R"( })"
R"( })"
R"( })"
R"( })",
base::ToLowerASCII(app_id).c_str(), status.value_or("ok").c_str(),
!install_data_index.empty()
? base::StringPrintf(
R"( "data":[{ "status":"ok", "name":"install", )"
R"("index":"%s", "#text":"%s_text" }],)",
install_data_index.c_str(), install_data_index.c_str())
.c_str()
: "",
codebase.c_str(), version.GetString().c_str(), run_action.c_str(),
arguments.c_str(), update_file.BaseName().AsUTF8Unsafe().c_str(),
hash.c_str(), base::StrCat({app_id, ".1.", hash}).c_str());
}
std::string GetUpdateResponseV4(const std::vector<std::string>& app_responses) {
return base::StringPrintf(
")]}'\n"
R"({"response":{)"
R"( "protocol":"4.0",)"
R"( "apps":[)"
R"(%s)"
R"( ])"
R"(}})",
base::JoinString(app_responses, ",\n").c_str());
}
std::string GetUpdateResponseV3(const std::vector<std::string>& app_responses) {
return base::StringPrintf(
")]}'\n"
R"({"response":{)"
R"( "protocol":"3.1",)"
R"( "app":[)"
R"(%s)"
R"( ])"
R"(}})",
base::JoinString(app_responses, ",\n").c_str());
}
std::string GetUpdateResponse(const std::string& app_id,
const std::string& install_data_index,
const std::string& codebase,
const base::Version& version,
const base::FilePath& update_file,
const std::string& run_action,
const std::string& arguments,
const std::string& file_hash,
bool use_xz,
bool v4) {
auto ResponseFunc = v4 ? GetUpdateResponseV4 : GetUpdateResponseV3;
auto ResponseFuncApp =
v4 ? GetUpdateResponseForAppV4 : GetUpdateResponseForAppV3;
return ResponseFunc({ResponseFuncApp(
app_id, install_data_index, codebase, version, update_file, run_action,
arguments, file_hash, std::nullopt, use_xz)});
}
void RunUpdaterWithSwitches(const base::Version& version,
UpdaterScope scope,
const std::vector<std::string>& switches,
std::optional<int> expected_exit_code) {
const std::optional<base::FilePath> installed_executable_path =
GetVersionedInstallDirectory(scope, version)
->Append(GetExecutableRelativePath());
ASSERT_TRUE(installed_executable_path);
ASSERT_TRUE(base::PathExists(*installed_executable_path));
base::CommandLine command_line(*installed_executable_path);
for (const std::string& command_switch : switches) {
command_line.AppendSwitch(command_switch);
}
if (expected_exit_code) {
int exit_code = -1;
Run(scope, command_line, &exit_code);
ASSERT_EQ(exit_code, expected_exit_code.value());
} else {
Run(scope, command_line, nullptr);
}
}
void ExpectUpdateCheckSequence(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
UpdateService::Priority priority,
int event_type,
const base::Version& from_version,
const base::Version& to_version,
const base::Version& updater_version) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
.AppendUTF8(kDoNothingCRXName);
ASSERT_TRUE(base::PathExists(crx_path));
// First request: update check.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher(
{base::StringPrintf(R"(.*"appid":"%s".*)", app_id.c_str())}),
request::GetScopeMatcher(scope),
request::GetAppPriorityMatcher(app_id, priority),
request::GetUpdaterEnableUpdatesMatcher()},
base::BindRepeating(&GetUpdateResponse, app_id, "",
test_server->download_url().spec(), to_version,
crx_path, kDoNothingCRXRun, "", GetHashHex(crx_path),
false));
// Second request: event ping with an error because the update check response
// is ignored by the client:
// {errorCategory::kService, ServiceError::CHECK_FOR_UPDATE_ONLY}
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({base::StringPrintf(
R"(.*"errorcat":4,"errorcode":4,"eventresult":0,"eventtype":%d,)"
R"(("nextfp":.*,)?"nextversion":"%s","previousversion":"%s".*)",
event_type, to_version.GetString().c_str(),
from_version.GetString().c_str())}),
request::GetScopeMatcher(scope)},
")]}'\n");
}
void ExpectUpdateSequence(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
int event_type,
const base::Version& from_version,
const base::Version& to_version,
bool do_fault_injection,
bool skip_download,
const base::FilePath& crx_path,
const std::string& run_action,
const std::string& arguments,
const base::Version& updater_version,
const std::string& event_regex,
bool use_xz) {
ASSERT_TRUE(base::PathExists(crx_path));
// First request: update check.
if (do_fault_injection) {
test_server->ExpectOnce({}, "", net::HTTP_INTERNAL_SERVER_ERROR);
}
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher(
{base::StringPrintf(R"("appid":"%s")", app_id.c_str()),
install_data_index.empty()
? ""
: base::StringPrintf(
R"("data":\[{"index":"%s","name":"install"}],.*)",
install_data_index.c_str())
.c_str()}),
request::GetScopeMatcher(scope),
request::GetAppPriorityMatcher(app_id, priority),
request::GetUpdaterEnableUpdatesMatcher()},
base::BindRepeating(&GetUpdateResponse, app_id, install_data_index,
test_server->download_url().spec(), to_version,
crx_path, run_action, arguments, GetHashHex(crx_path),
use_xz));
// Second request: update download.
if (!skip_download) {
if (do_fault_injection) {
test_server->ExpectOnce({}, "", net::HTTP_INTERNAL_SERVER_ERROR);
}
std::string crx_bytes;
base::ReadFileToString(crx_path, &crx_bytes);
test_server->ExpectOnce(
{request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({""})},
crx_bytes);
}
// Third request: event ping.
if (do_fault_injection) {
test_server->ExpectOnce({}, "", net::HTTP_INTERNAL_SERVER_ERROR);
}
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({base::StringPrintf(
R"(.*"eventresult":1,"eventtype":%d,("nextfp":.*,)?)"
R"("nextversion":"%s",("previousfp":.*,)?)"
R"("previousversion":"%s".*)",
event_type, to_version.GetString().c_str(),
from_version.GetString().c_str())}),
request::GetContentMatcher({event_regex}),
request::GetScopeMatcher(scope)},
")]}'\n");
}
} // namespace
AppUpdateExpectation::AppUpdateExpectation(
const std::string& args,
const std::string& app_id,
const base::Version& from_version,
const base::Version& to_version,
bool is_install,
bool should_update,
bool allow_rollback,
const std::string& target_version_prefix,
const std::string& target_channel,
const base::FilePath& crx_relative_path,
bool always_serve_crx,
const UpdateService::ErrorCategory error_category,
const int error_code,
const int event_type,
const std::string& custom_app_response,
const std::string& response_status)
: args(args),
app_id(app_id),
from_version(from_version),
to_version(to_version),
is_install(is_install),
should_update(should_update),
allow_rollback(allow_rollback),
target_version_prefix(target_version_prefix),
target_channel(target_channel),
crx_relative_path(crx_relative_path),
always_serve_crx(always_serve_crx),
error_category(error_category),
error_code(error_code),
event_type(event_type),
custom_app_response(custom_app_response),
response_status(response_status.empty() ? "ok" : response_status) {}
AppUpdateExpectation::AppUpdateExpectation(const AppUpdateExpectation&) =
default;
AppUpdateExpectation::~AppUpdateExpectation() = default;
void ExitTestMode(UpdaterScope scope) {
DeleteFileAndEmptyParentDirectories(GetOverrideFilePath(scope));
}
int CountDirectoryFiles(const base::FilePath& dir) {
int res = 0;
base::FileEnumerator(dir, false, base::FileEnumerator::FILES)
.ForEach([&res](const base::FilePath& /*name*/) { ++res; });
return res;
}
void RegisterApp(UpdaterScope scope, const RegistrationRequest& registration) {
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
base::RunLoop loop;
update_service->RegisterApp(registration,
base::BindLambdaForTesting([&loop](int result) {
EXPECT_EQ(result, 0);
loop.Quit();
}));
loop.Run();
}
void RegisterAppByValue(UpdaterScope scope, const base::Value::Dict& value) {
RegistrationRequest registration;
registration.app_id = *value.FindString("app_id");
registration.brand_code = *value.FindString("brand_code");
registration.brand_path =
base::FilePath::FromUTF8Unsafe(*value.FindString("brand_path"));
registration.ap = *value.FindString("ap");
registration.ap_path =
base::FilePath::FromUTF8Unsafe(*value.FindString("ap_path"));
registration.ap_key = *value.FindString("ap_key");
registration.version = *value.FindString("version");
registration.version_path =
base::FilePath::FromUTF8Unsafe(*value.FindString("version_path"));
registration.version_key = *value.FindString("version_key");
registration.existence_checker_path = base::FilePath::FromUTF8Unsafe(
*value.FindString("existence_checker_path"));
registration.cohort = *value.FindString("cohort");
registration.cohort_name = *value.FindString("cohort_name");
registration.cohort_hint = *value.FindString("cohort_hint");
return RegisterApp(scope, registration);
}
void EnterTestMode(const GURL& update_url,
const GURL& crash_upload_url,
const GURL& app_logo_url,
const GURL& event_logging_url,
base::TimeDelta idle_timeout,
base::TimeDelta server_keep_alive_time,
base::TimeDelta ceca_connection_timeout,
std::optional<EventLoggingPermissionProvider>
event_logging_permission_provider) {
ASSERT_TRUE(
ExternalConstantsBuilder()
.SetUpdateURL(std::vector<std::string>{update_url.spec()})
.SetCrashUploadURL(crash_upload_url.spec())
.SetAppLogoURL(app_logo_url.spec())
.SetEventLoggingUrl(event_logging_url.spec())
.SetEventLoggingPermissionProvider(
std::move(event_logging_permission_provider))
.SetMinimumEventLoggingCooldown(base::Seconds(0))
.SetUseCUP(false)
.SetInitialDelay(base::Milliseconds(100))
.SetServerKeepAliveTime(server_keep_alive_time)
.SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
.SetCrxPublicKeyHash(std::nullopt)
.SetOverinstallTimeout(GetOverinstallTimeoutForEnterTestMode())
.SetIdleCheckPeriod(idle_timeout)
.SetCecaConnectionTimeout(ceca_connection_timeout)
.Modify());
}
void SetDictPolicies(const base::Value::Dict& values) {
ASSERT_TRUE(ExternalConstantsBuilder().SetDictPolicies(values).Modify());
}
void SetMachineManaged(bool is_managed_device) {
ASSERT_TRUE(
ExternalConstantsBuilder().SetMachineManaged(is_managed_device).Modify());
}
void ExpectVersionActive(UpdaterScope scope, const std::string& version) {
scoped_refptr<GlobalPrefs> prefs = CreateGlobalPrefs(scope);
ASSERT_NE(prefs, nullptr) << "Failed to acquire GlobalPrefs.";
EXPECT_EQ(prefs->GetActiveVersion(), version);
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(version, [scope] {
std::wstring version;
EXPECT_EQ(base::win::RegKey(UpdaterScopeToHKeyRoot(scope), UPDATER_KEY,
Wow6432(KEY_READ))
.ReadValue(kRegValueVersion, &version),
ERROR_SUCCESS);
return base::WideToUTF8(version);
}());
#endif // IS_WIN
}
void ExpectVersionNotActive(UpdaterScope scope, const std::string& version) {
scoped_refptr<GlobalPrefs> prefs = CreateGlobalPrefs(scope);
ASSERT_NE(prefs, nullptr) << "Failed to acquire GlobalPrefs.";
EXPECT_NE(prefs->GetActiveVersion(), version);
}
void Install(UpdaterScope scope, const base::Value::List& switches) {
const base::FilePath path = GetSetupExecutablePath();
ASSERT_FALSE(path.empty());
base::CommandLine command_line(path);
command_line.AppendSwitchUTF8(kInstallSwitch, "usagestats=1");
for (const base::Value& cmd_line_switch : switches) {
command_line.AppendSwitch(cmd_line_switch.GetString());
}
int exit_code = -1;
Run(scope, command_line, &exit_code);
ASSERT_EQ(exit_code, 0);
}
void InstallUpdaterAndApp(UpdaterScope scope,
const std::string& app_id,
const bool is_silent_install,
const std::string& tag,
const std::string& child_window_text_to_find,
const bool always_launch_cmd,
const bool verify_app_logo_loaded,
const bool expect_success,
const bool wait_for_the_installer,
const int expected_exit_code,
const base::Value::List& additional_switches,
const base::FilePath& updater_path) {
ASSERT_FALSE(updater_path.empty());
base::CommandLine command_line(updater_path);
command_line.AppendSwitchUTF8(kInstallSwitch, tag);
if (!app_id.empty()) {
command_line.AppendSwitchUTF8(kAppIdSwitch, app_id);
}
if (is_silent_install) {
ASSERT_TRUE(child_window_text_to_find.empty());
command_line.AppendSwitch(kSilentSwitch);
}
if (always_launch_cmd) {
command_line.AppendSwitch(kAlwaysLaunchCmdSwitch);
}
for (const base::Value& cmd_line_switch : additional_switches) {
command_line.AppendSwitch(cmd_line_switch.GetString());
}
if (child_window_text_to_find.empty()) {
int exit_code = -1;
Run(scope, command_line, wait_for_the_installer ? &exit_code : nullptr);
if (wait_for_the_installer) {
ASSERT_EQ(exit_code, expected_exit_code);
}
} else {
#if BUILDFLAG(IS_WIN)
ASSERT_TRUE(wait_for_the_installer);
Run(scope, command_line, nullptr);
std::u16string bundle_name;
std::wstring lang;
if (!tag.empty()) {
tagging::TagArgs tag_args;
ASSERT_EQ(tagging::ErrorCode::kSuccess,
tagging::Parse(tag, {}, tag_args));
bundle_name = base::UTF8ToUTF16(tag_args.bundle_name);
lang = base::UTF8ToWide(tag_args.language);
}
CloseInstallCompleteDialog(bundle_name, lang,
base::UTF8ToWide(child_window_text_to_find),
verify_app_logo_loaded);
#else
ADD_FAILURE();
#endif
}
}
void PrintFile(const base::FilePath& file) {
std::string contents;
if (!base::ReadFileToString(file, &contents)) {
return;
}
VLOG(0) << "Contents of " << file << " for " << GetTestName();
const std::string demarcation(72, '=');
VLOG(0) << demarcation;
VLOG(0) << contents;
VLOG(0) << "End contents of " << file << " for " << GetTestName() << ".";
VLOG(0) << demarcation;
}
std::vector<base::FilePath> GetUpdaterLogFilesInTmp() {
base::FilePath temp_dir;
#if BUILDFLAG(IS_WIN)
EXPECT_TRUE(
base::PathService::Get(IsSystemInstall(GetUpdaterScopeForTesting())
? static_cast<int>(base::DIR_SYSTEM_TEMP)
: static_cast<int>(base::DIR_TEMP),
&temp_dir));
#endif
if (temp_dir.empty()) {
return {};
}
std::vector<base::FilePath> files;
base::FileEnumerator(temp_dir, false, base::FileEnumerator::FILES,
FILE_PATH_LITERAL("updater*.log"))
.ForEach([&](const base::FilePath& item) { files.push_back(item); });
return files;
}
void PrintLog(UpdaterScope scope) {
PrintFile([&] {
std::optional<base::FilePath> path = GetInstallDirectory(scope);
if (path && base::PathExists(path->AppendUTF8("updater.log"))) {
return path->AppendUTF8("updater.log");
} else if (const std::vector<base::FilePath> files =
GetUpdaterLogFilesInTmp();
!files.empty()) {
return files[0];
} else {
VLOG(0) << "updater.log file not found for " << GetTestName();
return base::FilePath();
}
}());
}
// Copies the updater log file present in `src_dir` or `%TMP%` to a
// test-specific directory name in Swarming/Isolate. Avoids overwriting the
// destination log file if other instances of it exist in the destination
// directory. Swarming retries each failed test. It is useful to capture a few
// logs from previous failures instead of the log of the last run only. Logs
// labeled with different (optional) infixes are numbered independently. The
// infix is applied only to the output log file name; the retrieved log is
// always `updater.log`.
void CopyLog(const base::FilePath& src_dir, const std::string& infix) {
base::FilePath log_path = src_dir.AppendUTF8("updater.log");
if (!base::PathExists(log_path)) {
if (const std::vector<base::FilePath> files = GetUpdaterLogFilesInTmp();
!files.empty()) {
log_path = files[0];
}
}
const std::string real_infix =
infix.empty() ? "" : base::StrCat({".", infix});
base::FilePath dest_dir = GetLogDestinationDir();
if (!dest_dir.empty() && base::PathExists(dest_dir) &&
base::PathExists(log_path)) {
dest_dir = dest_dir.AppendUTF8(GetTestName());
EXPECT_TRUE(base::CreateDirectory(dest_dir));
const base::FilePath dest_file_path = [dest_dir, real_infix] {
base::FilePath path =
dest_dir.AppendUTF8(base::StrCat({"updater", real_infix, ".log"}));
for (int i = 1; i < 10 && base::PathExists(path); ++i) {
path = dest_dir.AppendUTF8(
base::StringPrintf("updater%s.%d.log", real_infix.c_str(), i));
}
return path;
}();
VLOG(0) << "Copying updater.log file. From: " << log_path
<< ". To: " << dest_file_path;
EXPECT_TRUE(base::CopyFile(log_path, dest_file_path));
}
}
void ExpectNoCrashes(UpdaterScope scope) {
if (scope == UpdaterScope::kSystem) {
ExpectNoCrashes(UpdaterScope::kUser);
}
std::optional<base::FilePath> database_path(GetCrashDatabasePath(scope));
if (!database_path || !base::PathExists(*database_path)) {
return;
}
base::FilePath dest_dir = GetLogDestinationDir();
if (dest_dir.empty()) {
VLOG(2) << "No log destination folder, skip copying possible crash dumps.";
return;
}
dest_dir =
dest_dir.AppendUTF8(GetTestName())
.AppendUTF8(scope == UpdaterScope::kSystem ? "system" : "user");
EXPECT_TRUE(base::CreateDirectory(dest_dir));
int count = 0;
base::FileEnumerator(*database_path, true, base::FileEnumerator::FILES,
FILE_PATH_LITERAL("*.dmp"),
base::FileEnumerator::FolderSearchPolicy::ALL)
.ForEach([&count, &dest_dir](const base::FilePath& name) {
VLOG(0) << __func__ << "Copying " << name << " to: " << dest_dir;
EXPECT_TRUE(base::CopyFile(name, dest_dir.Append(name.BaseName())));
++count;
});
EXPECT_EQ(count, 0) << ": " << count << " crashes found";
}
void ExpectAppsUpdateSequence(UpdaterScope scope,
ScopedServer* test_server,
const base::Value::Dict& request_attributes,
const std::vector<AppUpdateExpectation>& apps,
const base::Version& updater_version) {
base::FilePath exe_path;
ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_path));
// First request: update check.
std::vector<std::string> attributes;
for (const auto [key, value] : request_attributes) {
attributes.push_back(base::StringPrintf(R"("%s":"%s")", key.c_str(),
value.GetString().c_str()));
}
std::vector<std::string> app_requests;
std::vector<base::RepeatingCallback<std::string(bool)>>
app_response_providers;
for (const AppUpdateExpectation& app : apps) {
app_requests.push_back(
base::StringPrintf(R"("appid":"%s")", app.app_id.c_str()));
if (app.allow_rollback) {
app_requests.push_back(R"("rollback_allowed":true,)");
}
if (!app.target_version_prefix.empty()) {
app_requests.push_back(base::StringPrintf(
R"("targetversionprefix":"%s")", app.target_version_prefix.c_str()));
}
if (!app.target_channel.empty()) {
app_requests.push_back(base::StringPrintf(R"("release_channel":"%s",)",
app.target_channel.c_str()));
}
if (!app.custom_app_response.empty()) {
app_response_providers.push_back(base::BindRepeating(
[](const std::string& response, bool v4) { return response; },
app.custom_app_response));
continue;
}
const base::FilePath crx_path = exe_path.Append(app.crx_relative_path);
const base::FilePath base_name = crx_path.BaseName().RemoveExtension();
#if BUILDFLAG(IS_WIN)
const base::FilePath run_action =
base_name.Extension().empty()
? base_name.AddExtension(FILE_PATH_LITERAL(".exe"))
: base_name;
#else
const base::FilePath run_action =
base_name.Extension().empty() ? base::FilePath(".") : base_name;
#endif // BUILDFLAG(IS_WIN)
app_response_providers.push_back(base::BindRepeating(
[](const std::string& app_id, const std::string& url,
const base::Version& to_version, const base::FilePath& crx_path,
const std::string& run_action, const std::string& args,
std::optional<std::string> response_status, bool use_xz, bool v4) {
auto func =
v4 ? GetUpdateResponseForAppV4 : GetUpdateResponseForAppV3;
return func(app_id, "", url, to_version, crx_path, run_action, args,
std::nullopt, response_status, use_xz);
},
app.app_id, test_server->download_url().spec(), app.to_version,
crx_path, run_action.AsUTF8Unsafe(), app.args, app.response_status,
false));
}
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher(attributes),
request::GetContentMatcher(app_requests),
request::GetScopeMatcher(scope),
request::GetUpdaterEnableUpdatesMatcher()},
base::BindRepeating(
[](std::vector<base::RepeatingCallback<std::string(bool)>>
app_response_providers,
bool v4) {
std::vector<std::string> app_responses;
std::ranges::transform(
app_response_providers, std::back_inserter(app_responses),
[=](base::RepeatingCallback<std::string(bool v4)> provider) {
return provider.Run(v4);
});
return v4 ? GetUpdateResponseV4(app_responses)
: GetUpdateResponseV3(app_responses);
},
app_response_providers));
std::set<base::FilePath> downloaded_crxes;
for (const AppUpdateExpectation& app : apps) {
if ((app.should_update || app.always_serve_crx) &&
(app.from_version != app.to_version) &&
!base::Contains(downloaded_crxes, app.crx_relative_path)) {
// Download requests for apps that install/update
const base::FilePath crx_path = exe_path.Append(app.crx_relative_path);
ASSERT_TRUE(base::PathExists(crx_path));
std::string crx_bytes;
base::ReadFileToString(crx_path, &crx_bytes);
test_server->ExpectOnce(
{request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({""})},
crx_bytes);
downloaded_crxes.insert(app.crx_relative_path);
}
if (app.should_update) {
// Followed by event ping.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({base::StringPrintf(
R"(.*"appid":"%s",.*)"
R"("eventresult":1,"eventtype":%d,("nextfp":.*,)?)"
R"("nextversion":"%s","previousversion":"%s".*)"
R"("version":"%s".*)",
app.app_id.c_str(), app.is_install ? 2 : 3,
app.to_version.GetString().c_str(),
app.from_version.GetString().c_str(),
app.to_version.GetString().c_str())})},
")]}'\n");
} else if (app.custom_app_response.empty() && app.response_status == "ok") {
// Event ping for apps that doesn't update.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({base::StringPrintf(
R"(.*"appid":"%s",.*)"
R"(.*"errorcat":%d,"errorcode":%d,)"
R"("eventresult":0,"eventtype":%d,("nextfp":.*,)?)"
R"("nextversion":"%s","previousversion":"%s".*)"
R"("version":"%s".*)",
app.app_id.c_str(), static_cast<int>(app.error_category),
app.error_code, app.event_type,
app.to_version.GetString().c_str(),
app.from_version.GetString().c_str(),
app.from_version.GetString().c_str())})},
")]}'\n");
}
}
}
void RunWake(UpdaterScope scope,
int expected_exit_code,
const base::Version& version) {
RunUpdaterWithSwitches(version, scope, {kWakeSwitch}, expected_exit_code);
}
void RunWakeAll(UpdaterScope scope) {
RunUpdaterWithSwitches(base::Version(kUpdaterVersion), scope,
{kWakeAllSwitch}, kErrorOk);
}
void RunWakeActive(UpdaterScope scope, int expected_exit_code) {
// Find the active version.
base::Version active_version;
{
scoped_refptr<GlobalPrefs> prefs = CreateGlobalPrefs(scope);
ASSERT_NE(prefs, nullptr) << "Failed to acquire GlobalPrefs.";
active_version = base::Version(prefs->GetActiveVersion());
}
ASSERT_TRUE(active_version.IsValid());
// Invoke the wake client of that version.
RunUpdaterWithSwitches(active_version, scope, {kWakeSwitch},
expected_exit_code);
}
void RunCrashMe(UpdaterScope scope) {
RunUpdaterWithSwitches(base::Version(kUpdaterVersion), scope,
{kCrashMeSwitch, kMonitorSelfSwitch}, std::nullopt);
}
void RunServer(UpdaterScope scope, int expected_exit_code, bool internal) {
const std::optional<base::FilePath> installed_executable_path =
GetVersionedInstallDirectory(scope, base::Version(kUpdaterVersion))
->Append(GetExecutableRelativePath());
ASSERT_TRUE(installed_executable_path);
ASSERT_TRUE(base::PathExists(*installed_executable_path));
base::CommandLine command_line(*installed_executable_path);
command_line.AppendSwitch(kServerSwitch);
command_line.AppendSwitchUTF8(
kServerServiceSwitch, internal ? kServerUpdateServiceInternalSwitchValue
: kServerUpdateServiceSwitchValue);
int exit_code = -1;
Run(scope, command_line, &exit_code);
ASSERT_EQ(exit_code, expected_exit_code);
}
void RunUpdateApps(UpdaterScope scope,
int expected_exit_code,
const base::Version& version) {
RunUpdaterWithSwitches(version, scope, {kUpdateAppsSwitch},
expected_exit_code);
}
void CheckForUpdate(UpdaterScope scope, const std::string& app_id) {
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
base::RunLoop loop;
update_service->CheckForUpdate(
app_id, UpdateService::Priority::kForeground,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/{}, base::DoNothing(),
base::BindLambdaForTesting(
[&loop](UpdateService::Result result_unused) { loop.Quit(); }));
loop.Run();
}
void ExpectCheckForUpdateOppositeScopeFails(UpdaterScope scope,
const std::string& app_id) {
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(
IsSystemInstall(scope) ? UpdaterScope::kUser : UpdaterScope::kSystem);
base::RunLoop loop;
UpdateService::Result result = UpdateService::Result::kSuccess;
update_service->CheckForUpdate(
app_id, UpdateService::Priority::kForeground,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/{}, base::DoNothing(),
base::BindLambdaForTesting([&](UpdateService::Result result_param) {
result = result_param;
loop.Quit();
}));
loop.Run();
ASSERT_TRUE(result == UpdateService::Result::kServiceFailed ||
result == UpdateService::Result::kIPCConnectionFailed ||
result == UpdateService::Result::kInvalidArgument)
<< "result == " << result;
}
void Update(UpdaterScope scope,
const std::string& app_id,
const std::string& install_data_index) {
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
base::RunLoop loop;
update_service->Update(
app_id, install_data_index, UpdateService::Priority::kForeground,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/{}, base::DoNothing(),
base::BindLambdaForTesting(
[&loop](UpdateService::Result result_unused) { loop.Quit(); }));
loop.Run();
}
void UpdateAll(UpdaterScope scope) {
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
base::RunLoop loop;
update_service->UpdateAll(
base::DoNothing(),
base::BindLambdaForTesting(
[&loop](UpdateService::Result result_unused) { loop.Quit(); }));
loop.Run();
}
void InstallAppViaService(UpdaterScope scope,
const std::string& appid,
const base::Value::Dict& expected_final_values) {
RegistrationRequest registration;
registration.app_id = appid;
registration.version = kNullVersion;
scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
UpdateService::UpdateState final_update_state;
UpdateService::Result final_result;
base::RunLoop loop;
update_service->Install(
registration, /*client_install_data=*/"", /*install_data_index=*/"",
UpdateService::Priority::kForeground,
/*language=*/{},
base::BindLambdaForTesting(
[&](const UpdateService::UpdateState& update_state) {
final_update_state = update_state;
}),
base::BindLambdaForTesting([&](UpdateService::Result result) {
final_result = result;
loop.Quit();
}));
loop.Run();
const base::Value::Dict* expected_update_state =
expected_final_values.FindDict("expected_update_state");
if (expected_update_state) {
#define CHECK_STATE_MEMBER_STRING(p) \
if (const std::string* _state_member = \
expected_update_state->FindString(#p); \
_state_member) { \
EXPECT_EQ(final_update_state.p, *_state_member); \
}
#define CHECK_STATE_MEMBER_INT(p) \
if (const std::optional<int> _state_member = \
expected_update_state->FindInt(#p); \
_state_member) { \
EXPECT_EQ(static_cast<int>(final_update_state.p), *_state_member); \
}
#define CHECK_STATE_MEMBER_VERSION(p) \
if (const std::string* _state_member = \
expected_update_state->FindString(#p); \
_state_member) { \
EXPECT_EQ(final_update_state.p.GetString(), *_state_member); \
}
CHECK_STATE_MEMBER_STRING(app_id);
CHECK_STATE_MEMBER_INT(state);
CHECK_STATE_MEMBER_STRING(next_version);
CHECK_STATE_MEMBER_INT(downloaded_bytes);
CHECK_STATE_MEMBER_INT(total_bytes);
CHECK_STATE_MEMBER_INT(install_progress);
CHECK_STATE_MEMBER_INT(error_category);
CHECK_STATE_MEMBER_INT(error_code);
CHECK_STATE_MEMBER_INT(extra_code1);
CHECK_STATE_MEMBER_STRING(installer_text);
CHECK_STATE_MEMBER_STRING(installer_cmd_line);
#undef CHECK_STATE_MEMBER_VERSION
#undef CHECK_STATE_MEMBER_INT
#undef CHECK_STATE_MEMBER_STRING
}
if (const std::optional<int> expected_result =
expected_final_values.FindInt("expected_result");
expected_result) {
EXPECT_EQ(static_cast<int>(final_result), *expected_result);
}
}
void GetAppStates(UpdaterScope updater_scope,
const base::Value::Dict& expected_app_states) {
scoped_refptr<UpdateService> update_service =
CreateUpdateServiceProxy(updater_scope);
base::RunLoop loop;
update_service->GetAppStates(base::BindLambdaForTesting(
[&expected_app_states,
&loop](const std::vector<updater::UpdateService::AppState>& states) {
for (const auto [expected_app_id, expected_state] :
expected_app_states) {
const auto& it = std::ranges::find_if(
states, [&expected_app_id](const auto& state) {
return base::EqualsCaseInsensitiveASCII(state.app_id,
expected_app_id);
});
ASSERT_TRUE(it != std::end(states));
const base::Value::Dict* expected = expected_state.GetIfDict();
ASSERT_TRUE(expected);
EXPECT_EQ(it->app_id, *expected->FindString("app_id"));
EXPECT_EQ(it->version, *expected->FindString("version"));
EXPECT_EQ(it->ap, *expected->FindString("ap"));
EXPECT_EQ(it->brand_code, *expected->FindString("brand_code"));
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(base::WideToUTF8(it->brand_path.value()),
*expected->FindString("brand_path"));
EXPECT_EQ(base::WideToUTF8(it->ecp.value()),
*expected->FindString("ecp"));
#else
EXPECT_EQ(it->brand_path.value(),
*expected->FindString("brand_path"));
EXPECT_EQ(it->ecp.value(), *expected->FindString("ecp"));
#endif // BUILDFLAG(IS_WIN)
}
loop.Quit();
}));
loop.Run();
}
void DeleteUpdaterDirectory(UpdaterScope scope) {
std::optional<base::FilePath> install_dir = GetInstallDirectory(scope);
ASSERT_TRUE(install_dir);
ASSERT_TRUE(base::DeletePathRecursively(*install_dir));
}
void DeleteActiveUpdaterExecutable(UpdaterScope scope) {
base::Version active_version;
{
scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
ASSERT_TRUE(global_prefs) << "No global prefs.";
active_version = base::Version(global_prefs->GetActiveVersion());
ASSERT_TRUE(active_version.IsValid()) << "No active updater.";
}
std::optional<base::FilePath> exe_path =
GetUpdaterExecutablePath(scope, active_version);
ASSERT_TRUE(exe_path.has_value())
<< "No path for active updater. Version: " << active_version;
DeleteFile(*exe_path);
#if BUILDFLAG(IS_LINUX)
// On Linux, a qualified service makes a full copy of itself, so we have to
// delete the copy that systemd uses too.
std::optional<base::FilePath> launcher_path =
GetUpdateServiceLauncherPath(GetUpdaterScopeForTesting());
ASSERT_TRUE(launcher_path.has_value()) << "No launcher path.";
DeleteFile(*launcher_path);
#endif // BUILDFLAG(IS_LINUX)
// The broken updater should still be active. Tests using this method will
// probably not test the scenario they expect to test if it's not.
ExpectVersionActive(scope, active_version.GetString());
}
void DeleteFile(UpdaterScope /*scope*/, const base::FilePath& path) {
ASSERT_TRUE(base::DeleteFile(path)) << "Can't delete " << path;
}
void SetupFakeUpdaterLowerVersion(UpdaterScope scope) {
SetupFakeUpdaterVersion(scope, base::Version("100.0.0.0"),
/*major_version_offset=*/0,
/*should_create_updater_executable=*/false);
}
void SetupFakeUpdaterHigherVersion(UpdaterScope scope) {
SetupFakeUpdaterVersion(scope, base::Version(kUpdaterVersion),
/*major_version_offset=*/1,
/*should_create_updater_executable=*/false);
}
void SetExistenceCheckerPath(UpdaterScope scope,
const std::string& app_id,
const base::FilePath& path) {
scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
base::MakeRefCounted<PersistedData>(scope, global_prefs->GetPrefService(),
nullptr)
->SetExistenceCheckerPath(app_id, path);
PrefsCommitPendingWrites(global_prefs->GetPrefService());
}
void SetServerStarts(UpdaterScope scope, int value) {
scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
for (int i = 0; i <= value; ++i) {
global_prefs->CountServerStarts();
}
PrefsCommitPendingWrites(global_prefs->GetPrefService());
}
void FillLog(UpdaterScope scope) {
std::optional<base::FilePath> log = GetLogFilePath(scope);
ASSERT_TRUE(log);
std::string data = "This test string is used to fill up log space.\n";
for (int i = 0; i < 1024 * 1024 * 3; i += data.length()) {
ASSERT_TRUE(base::AppendToFile(*log, data));
}
}
void ExpectLogRotated(UpdaterScope scope) {
std::optional<base::FilePath> log = GetLogFilePath(scope);
ASSERT_TRUE(log);
EXPECT_TRUE(base::PathExists(log->AddExtension(FILE_PATH_LITERAL(".old"))));
std::optional<int64_t> size = base::GetFileSize(*log);
ASSERT_TRUE(size.has_value());
EXPECT_TRUE(size.value() < 1024 * 1024);
}
void ExpectRegistered(UpdaterScope scope, const std::string& app_id) {
ASSERT_TRUE(base::Contains(
base::MakeRefCounted<PersistedData>(
scope, CreateGlobalPrefs(scope)->GetPrefService(), nullptr)
->GetAppIds(),
base::ToLowerASCII(app_id)));
}
void ExpectNotRegistered(UpdaterScope scope, const std::string& app_id) {
ASSERT_FALSE(base::Contains(
base::MakeRefCounted<PersistedData>(
scope, CreateGlobalPrefs(scope)->GetPrefService(), nullptr)
->GetAppIds(),
base::ToLowerASCII(app_id)));
}
void ExpectAppTag(UpdaterScope scope,
const std::string& app_id,
const std::string& tag) {
EXPECT_EQ(tag, base::MakeRefCounted<PersistedData>(
scope, CreateGlobalPrefs(scope)->GetPrefService(), nullptr)
->GetAP(app_id));
}
void SetAppTag(UpdaterScope scope,
const std::string& app_id,
const std::string& tag) {
scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
base::MakeRefCounted<PersistedData>(scope, global_prefs->GetPrefService(),
nullptr)
->SetAP(app_id, tag);
PrefsCommitPendingWrites(global_prefs->GetPrefService());
}
void Run(
UpdaterScope scope,
base::CommandLine command_line,
int* exit_code,
base::FunctionRef<base::Process(const base::CommandLine&)> launch_process) {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
if (IsSystemInstall(scope)) {
command_line.AppendSwitch(kSystemSwitch);
command_line = MakeElevated(command_line);
}
VLOG(0) << " Run command: " << command_line.GetCommandLineString();
base::Process process = launch_process(command_line);
VPLOG_IF(0, !process.IsValid());
ASSERT_TRUE(process.IsValid());
if (!exit_code) {
return;
}
// macOS requires a larger timeout value for --install.
bool succeeded = process.WaitForExitWithTimeout(
2 * TestTimeouts::action_max_timeout(), exit_code);
VPLOG_IF(0, !succeeded);
ASSERT_TRUE(succeeded);
}
void RunDeElevated(UpdaterScope scope,
base::CommandLine command_line,
int* exit_code) {
#if BUILDFLAG(IS_WIN)
if (IsElevatedWithUACOn()) {
Run(scope, command_line, exit_code,
[](const base::CommandLine& command_line) {
auto process = base::win::RunDeElevated(command_line);
VPLOG_IF(0, !process.has_value() || !process->IsValid())
<< process.error();
return process.has_value() ? process->Duplicate() : base::Process();
});
return;
}
#endif
Run(scope, command_line, exit_code);
}
void ExpectCliResult(base::CommandLine command_line,
bool elevate,
std::optional<std::string> want_stdout,
std::optional<int> want_exit_code) {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
if (elevate) {
command_line = MakeElevated(command_line);
}
VLOG(0) << "Run command (with expectations): "
<< command_line.GetCommandLineString();
std::string output;
int exit_code = EXIT_FAILURE;
bool run_succeeded =
base::GetAppOutputWithExitCode(command_line, &output, &exit_code);
VPLOG_IF(0, !run_succeeded);
ASSERT_TRUE(run_succeeded);
if (want_exit_code) {
ASSERT_EQ(*want_exit_code, exit_code) << "stdout:\n" << output;
}
if (want_stdout) {
ASSERT_EQ(*want_stdout, output) << "exit code:" << exit_code;
}
}
std::vector<TestUpdaterVersion> GetRealUpdaterVersions() {
std::vector<TestUpdaterVersion> v = GetRealUpdaterLowerVersions();
v.push_back({GetSetupExecutablePath(), base::Version(kUpdaterVersion)});
return v;
}
void SetupRealUpdater(UpdaterScope scope,
const base::FilePath& updater_path,
const base::Value::List& switches) {
base::CommandLine command_line(updater_path);
command_line.AppendSwitch(kInstallSwitch);
for (const base::Value& cmd_line_switch : switches) {
command_line.AppendSwitch(cmd_line_switch.GetString());
}
int exit_code = -1;
Run(scope, command_line, &exit_code);
ASSERT_EQ(exit_code, 0);
}
void ExpectPing(UpdaterScope scope,
ScopedServer* test_server,
int event_type,
std::optional<GURL> target_url) {
ASSERT_TRUE(test_server) << "TEST ISSUE - nil `test_server` in ExpectPing";
request::MatcherGroup request_matchers = {
request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(),
request::GetContentMatcher(
{base::StringPrintf(R"(.*"eventtype":%d,.*)", event_type)}),
request::GetScopeMatcher(scope)};
if (target_url) {
request_matchers.push_back(request::GetTargetURLMatcher(*target_url));
}
test_server->ExpectOnce(request_matchers, ")]}'\n");
}
void ExpectAppCommandPing(UpdaterScope scope,
ScopedServer* test_server,
const std::string& appid,
const std::string& appcommandid,
int errorcode,
int eventresult,
int event_type,
const base::Version& version,
const base::Version& updater_version) {
test_server->ExpectOnce(
{
request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(updater_version),
request::GetContentMatcher({base::StringPrintf(
R"(.*"appid":"%s","enabled":true,"events?":\[{)"
R"("appcommandid":"%s","errorcode":%d,"eventresult":%d,)"
R"("eventtype":%d,"previousversion":"%s"}\])",
appid.c_str(), appcommandid.c_str(), errorcode, eventresult,
event_type, version.GetString().c_str())}),
request::GetScopeMatcher(scope),
},
")]}'\n");
}
void ExpectSelfUpdateSequence(UpdaterScope scope, ScopedServer* test_server) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &test_data_path));
base::FilePath crx_path = test_data_path.AppendUTF8(kSelfUpdateCRXName);
ASSERT_TRUE(base::PathExists(crx_path));
// First request: update check.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetContentMatcher(
{base::StringPrintf(R"(.*"appid":"%s".*)", kUpdaterAppId)}),
request::GetScopeMatcher(scope)},
base::BindRepeating(
&GetUpdateResponse, kUpdaterAppId, "",
test_server->download_url().spec(), base::Version(kUpdaterVersion),
crx_path, kSelfUpdateCRXRun,
base::StrCat({"--update", IsSystemInstall(scope) ? " --system" : ""}),
GetHashHex(crx_path), false));
// Second request: update download.
std::string crx_bytes;
base::ReadFileToString(crx_path, &crx_bytes);
test_server->ExpectOnce({request::GetContentMatcher({""})}, crx_bytes);
// Third request: event ping.
test_server->ExpectOnce({request::GetPathMatcher(test_server->update_path()),
request::GetContentMatcher({base::StringPrintf(
R"(.*"eventresult":1,"eventtype":3,)"
R"(("nextfp":.*,)?"nextversion":"%s".*)",
kUpdaterVersion)}),
request::GetScopeMatcher(scope)},
")]}'\n");
}
void ExpectUpdateCheckRequest(UpdaterScope scope, ScopedServer* test_server) {
test_server->ExpectOnce({request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(),
request::GetContentMatcher({R"("updatecheck":{})"}),
request::GetScopeMatcher(scope)},
GetUpdateResponseV4({}));
}
void ExpectUpdateCheckSequence(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
UpdateService::Priority priority,
const base::Version& from_version,
const base::Version& to_version,
const base::Version& updater_version) {
ExpectUpdateCheckSequence(scope, test_server, app_id, priority,
/*event_type=*/3, from_version, to_version,
updater_version);
}
void ExpectUpdateSequence(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
const base::Version& from_version,
const base::Version& to_version,
bool do_fault_injection,
bool skip_download,
const base::Version& updater_version,
const std::string& event_regex,
bool use_xz) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
.AppendASCII(kDoNothingCRXName);
if (use_xz) {
crx_path = crx_path.AddExtension(FILE_PATH_LITERAL(".xz"));
}
ExpectUpdateSequence(scope, test_server, app_id, install_data_index, priority,
/*event_type=*/3, from_version, to_version,
do_fault_injection, skip_download, crx_path,
kDoNothingCRXRun, /*arguments=*/{}, updater_version,
event_regex, use_xz);
}
void ExpectUpdateSequenceBadHash(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
const base::Version& from_version,
const base::Version& to_version) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
.AppendUTF8(kDoNothingCRXName);
ASSERT_TRUE(base::PathExists(crx_path));
// First request: update check.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(),
request::GetContentMatcher(
{base::StringPrintf(R"("appid":"%s")", app_id.c_str()),
install_data_index.empty()
? ""
: base::StringPrintf(
R"("data":\[{"index":"%s","name":"install"}],.*)",
install_data_index.c_str())
.c_str()}),
request::GetScopeMatcher(scope),
request::GetAppPriorityMatcher(app_id, priority)},
base::BindRepeating(
&GetUpdateResponse, app_id, install_data_index,
test_server->download_url().spec(), to_version, crx_path,
kDoNothingCRXRun, "",
"badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad1",
false));
// Second request: update download.
std::string crx_bytes;
base::ReadFileToString(crx_path, &crx_bytes);
test_server->ExpectOnce(
{request::GetUpdaterUserAgentMatcher(), request::GetContentMatcher({""})},
crx_bytes);
// Third request: event ping.
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->update_path()),
request::GetUpdaterUserAgentMatcher(),
request::GetContentMatcher({base::StringPrintf(
R"(.*"errorcat":1,"errorcode":12,"eventresult":0,"eventtype":3,)"
R"(("nextfp":.*,)?"nextversion":"%s","previousversion":"%s".*)",
to_version.GetString().c_str(), from_version.GetString().c_str())}),
request::GetScopeMatcher(scope)},
")]}'\n");
}
void ExpectInstallSequence(UpdaterScope scope,
ScopedServer* test_server,
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
const base::Version& from_version,
const base::Version& to_version,
bool do_fault_injection,
bool skip_download,
const base::Version& updater_version,
const std::string& event_regex) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
.AppendUTF8(kDoNothingCRXName);
ExpectUpdateSequence(scope, test_server, app_id, install_data_index, priority,
/*event_type=*/2, from_version, to_version,
do_fault_injection, skip_download, crx_path,
kDoNothingCRXRun, /*arguments=*/{}, updater_version,
event_regex, false);
}
void ExpectEnterpriseCompanionAppOTAInstallSequence(ScopedServer* test_server) {
base::FilePath test_data_path;
ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &test_data_path));
base::FilePath crx_path =
test_data_path.AppendUTF8(kEnterpriseCompanionCRXName);
ExpectUpdateSequence(
UpdaterScope::kSystem, test_server, enterprise_companion::kCompanionAppId,
/*install_data_index=*/{}, UpdateService::Priority::kForeground,
/*event_type=*/2, base::Version({0, 0, 0, 0}),
base::Version(kEnterpriseCompanionVersion),
/*do_fault_injection=*/false, /*skip_download=*/false, crx_path,
kEnterpriseCompanionCRXRun, kEnterpriseCompanionCRXArguments,
base::Version(kUpdaterVersion), ".*", false);
}
// Runs multiple cycles of instantiating the update service, calling
// `GetVersion()`, then releasing the service interface.
void StressUpdateService(UpdaterScope scope) {
base::RunLoop loop;
// Number of times to run the cycle of instantiating the service.
int n = 10;
// Delay in milliseconds between successive cycles.
#if BUILDFLAG(IS_LINUX)
// Looping too tightly causes socket connections to be dropped on Linux.
const int kDelayBetweenLoopsMS = 10;
#else
const int kDelayBetweenLoopsMS = 0;
#endif
// Runs on the main sequence.
auto loop_closure = [&] {
LOG(ERROR) << __func__ << ": n: " << n << ", " << base::Time::Now();
if (--n) {
return false;
}
loop.Quit();
return true;
};
// Creates a task runner, and runs the service instance on it.
using LoopClosure = decltype(loop_closure);
auto stress_runner = [scope, loop_closure] {
// `task_runner` is always bound on the main sequence.
struct Local {
static void GetVersion(
UpdaterScope scope,
scoped_refptr<base::SequencedTaskRunner> task_runner,
LoopClosure loop_closure) {
base::ThreadPool::CreateSequencedTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindLambdaForTesting([scope, task_runner, loop_closure] {
auto update_service = CreateUpdateServiceProxy(scope);
update_service->GetVersion(
base::BindOnce(GetVersionCallback, scope, update_service,
task_runner, loop_closure));
}),
base::Milliseconds(kDelayBetweenLoopsMS));
}
static void GetVersionCallback(
UpdaterScope scope,
scoped_refptr<UpdateService> /*update_service*/,
scoped_refptr<base::SequencedTaskRunner> task_runner,
LoopClosure loop_closure,
const base::Version& version) {
EXPECT_EQ(version, base::Version(kUpdaterVersion));
task_runner->PostTask(
FROM_HERE,
base::BindLambdaForTesting([scope, task_runner, loop_closure] {
if (loop_closure()) {
return;
}
GetVersion(scope, task_runner, loop_closure);
}));
}
};
Local::GetVersion(scope, base::SequencedTaskRunner::GetCurrentDefault(),
loop_closure);
};
stress_runner();
loop.Run();
}
void CallServiceUpdate(UpdaterScope updater_scope,
const std::string& app_id,
const std::string& install_data_index,
bool same_version_update_allowed) {
UpdateService::PolicySameVersionUpdate policy_same_version_update =
same_version_update_allowed
? UpdateService::PolicySameVersionUpdate::kAllowed
: UpdateService::PolicySameVersionUpdate::kNotAllowed;
scoped_refptr<UpdateService> service_proxy =
CreateUpdateServiceProxy(updater_scope);
base::RunLoop loop;
service_proxy->Update(
app_id, install_data_index, UpdateService::Priority::kForeground,
policy_same_version_update,
/*language=*/{},
base::BindLambdaForTesting([](const UpdateService::UpdateState&) {}),
base::BindLambdaForTesting([&](UpdateService::Result result) {
EXPECT_EQ(result, UpdateService::Result::kSuccess);
loop.Quit();
}));
loop.Run();
}
void RunRecoveryComponent(UpdaterScope scope,
const std::string& app_id,
const base::Version& version) {
base::CommandLine command(GetSetupExecutablePath());
command.AppendSwitchUTF8(kBrowserVersionSwitch, version.GetString());
command.AppendSwitchUTF8(kAppGuidSwitch, app_id);
int exit_code = -1;
Run(scope, command, &exit_code);
ASSERT_EQ(exit_code, kErrorOk);
}
void SetLastChecked(UpdaterScope updater_scope, base::Time time) {
base::MakeRefCounted<PersistedData>(
updater_scope, CreateGlobalPrefs(updater_scope)->GetPrefService(),
nullptr)
->SetLastChecked(time);
}
void ExpectLastChecked(UpdaterScope updater_scope) {
EXPECT_FALSE(base::MakeRefCounted<PersistedData>(
updater_scope,
CreateGlobalPrefs(updater_scope)->GetPrefService(), nullptr)
->GetLastChecked()
.is_null());
}
void ExpectLastStarted(UpdaterScope updater_scope) {
EXPECT_FALSE(base::MakeRefCounted<PersistedData>(
updater_scope,
CreateGlobalPrefs(updater_scope)->GetPrefService(), nullptr)
->GetLastStarted()
.is_null());
}
std::set<base::FilePath::StringType> GetTestProcessNames() {
#if BUILDFLAG(IS_MAC)
return {GetExecutableRelativePath().BaseName().value(),
GetSetupExecutablePath().BaseName().value()};
#elif BUILDFLAG(IS_WIN)
return {
GetExecutableRelativePath().BaseName().value(),
GetSetupExecutablePath().BaseName().value(),
kTestProcessExecutableName,
[] {
const base::FilePath test_executable =
base::FilePath::FromUTF8Unsafe(kExecutableName).BaseName();
return base::StrCat({test_executable.RemoveExtension().value(),
base::UTF8ToWide(kExecutableSuffix),
test_executable.Extension()});
}(),
};
#else
return {GetExecutableRelativePath().BaseName().value(), kLauncherName};
#endif
}
std::set<base::FilePath::StringType> GetCompanionAppProcessNames() {
return {base::FilePath()
.AppendUTF8(enterprise_companion::kExecutableName)
.value(),
kCompanionAppTestExecutableName};
}
#if BUILDFLAG(IS_WIN)
VersionProcessFilter::VersionProcessFilter()
: versions_([] {
std::vector<base::Version> versions;
for (const auto& updater_version : GetRealUpdaterVersions()) {
versions.push_back(updater_version.version);
}
for (const auto& updater_version :
GetRealUpdaterLowerVersions("_sans_iid")) {
versions.push_back(updater_version.version);
}
return versions;
}()) {}
VersionProcessFilter::~VersionProcessFilter() = default;
bool VersionProcessFilter::Includes(const base::ProcessEntry& entry) const {
const base::Process process(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
false, entry.th32ProcessID));
if (!process.IsValid()) {
return false;
}
DWORD path_len = MAX_PATH;
std::wstring path(path_len, '\0');
if (!::QueryFullProcessImageName(process.Handle(), 0, path.data(),
&path_len)) {
return false;
}
const std::unique_ptr<FileVersionInfoWin> version_info =
FileVersionInfoWin::CreateFileVersionInfoWin(base::FilePath(path));
if (!version_info) {
return false;
}
const base::Version version(base::UTF16ToUTF8(version_info->file_version()));
return version.IsValid() && base::Contains(versions_, version);
}
#endif // BUILDFLAG(IS_WIN)
void CleanProcesses() {
base::ProcessFilter* filter = nullptr;
#if BUILDFLAG(IS_WIN)
VersionProcessFilter version_filter;
filter = &version_filter;
#endif
for (const base::FilePath::StringType& process_name : GetTestProcessNames()) {
EXPECT_TRUE(KillProcesses(process_name, -1, filter)) << process_name;
EXPECT_TRUE(WaitForProcessesToExit(process_name,
TestTimeouts::action_timeout(), filter))
<< process_name;
EXPECT_FALSE(IsProcessRunning(process_name, filter)) << process_name;
}
}
void ExpectCleanProcesses() {
base::ProcessFilter* filter = nullptr;
#if BUILDFLAG(IS_WIN)
VersionProcessFilter version_filter;
filter = &version_filter;
#endif
for (const base::FilePath::StringType& process_name : GetTestProcessNames()) {
EXPECT_FALSE(IsProcessRunning(process_name, filter))
<< PrintProcesses(process_name, filter);
}
}
// Standalone installers are supported for Windows only.
#if !BUILDFLAG(IS_WIN)
void RunOfflineInstall(UpdaterScope scope,
bool is_legacy_install,
bool is_silent_install,
int installer_result,
int installer_error) {
ADD_FAILURE();
}
void RunOfflineInstallOsNotSupported(UpdaterScope scope,
bool is_legacy_install,
bool is_silent_install,
const std::string& language) {
ADD_FAILURE();
}
void RunMockOfflineMetaInstall(UpdaterScope scope,
const std::string& app_id,
const base::Version& version,
const std::string& tag,
const base::FilePath& installer_path,
const std::string& arguments,
bool is_silent_install,
const std::string& platform,
const std::string& installer_text,
const bool always_launch_cmd,
const int expected_exit_code,
bool expect_success) {
ADD_FAILURE();
}
#endif // !BUILDFLAG(IS_WIN)
void DMPushEnrollmentToken(const std::string& enrollment_token) {
scoped_refptr<device_management_storage::DMStorage> storage =
device_management_storage::GetDefaultDMStorage();
ASSERT_NE(storage, nullptr);
EXPECT_TRUE(storage->StoreEnrollmentToken(enrollment_token));
EXPECT_TRUE(storage->DeleteDMToken());
}
void DMDeregisterDevice(UpdaterScope scope) {
if (!IsSystemInstall(GetUpdaterScopeForTesting())) {
return;
}
EXPECT_TRUE(
device_management_storage::GetDefaultDMStorage()->InvalidateDMToken());
}
void DMCleanup(UpdaterScope scope) {
if (!IsSystemInstall(GetUpdaterScopeForTesting())) {
return;
}
scoped_refptr<device_management_storage::DMStorage> storage =
device_management_storage::GetDefaultDMStorage();
EXPECT_TRUE(storage->DeleteEnrollmentToken());
EXPECT_TRUE(storage->DeleteDMToken());
EXPECT_TRUE(base::DeletePathRecursively(storage->policy_cache_folder()));
#if BUILDFLAG(IS_WIN)
RegDeleteKey(HKEY_LOCAL_MACHINE, kRegKeyCompanyLegacyCloudManagement);
RegDeleteKey(HKEY_LOCAL_MACHINE, kRegKeyCompanyCloudManagement);
RegDeleteKey(HKEY_LOCAL_MACHINE, UPDATER_POLICIES_KEY);
RegDeleteKey(HKEY_LOCAL_MACHINE, COMPANY_POLICIES_KEY);
#endif
}
void InstallEnterpriseCompanionApp() {
base::FilePath exe_path;
ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_path));
#if BUILDFLAG(IS_MAC)
exe_path = exe_path.Append(FILE_PATH_LITERAL("EnterpriseCompanionTestApp"));
#endif
exe_path = exe_path.Append(GetEnterpriseCompanionAppExeRelativePath());
base::CommandLine command(exe_path);
command.AppendSwitch("install");
base::Process process = base::LaunchProcess(command, {});
EXPECT_TRUE(process.IsValid());
int exit_code = -1;
EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_timeout(),
&exit_code));
}
void InstallEnterpriseCompanionAppOverrides(
const base::Value::Dict& external_overrides) {
std::optional<base::FilePath> json_path =
enterprise_companion::GetOverridesFilePath();
EXPECT_TRUE(json_path);
EXPECT_TRUE(base::CreateDirectory(json_path->DirName()));
JSONFileValueSerializer json_serializer(*json_path);
#if BUILDFLAG(IS_WIN)
// Allow admin to access companion app's Mojo service named pipe.
EXPECT_TRUE(json_serializer.Serialize(external_overrides.Clone().Set(
enterprise_companion::kNamedPipeSecurityDescriptorKey,
"D:(A;;GA;;;BA)")));
#else
EXPECT_TRUE(json_serializer.Serialize(external_overrides));
#endif
VLOG(1) << "Enterprise companion app overrides installed.";
}
void ExpectEnterpriseCompanionAppNotInstalled() {
EXPECT_FALSE(enterprise_companion::FindExistingInstall());
}
void UninstallEnterpriseCompanionApp() {
std::optional<base::FilePath> exe_path =
enterprise_companion::FindExistingInstall();
if (!exe_path) {
return;
}
base::CommandLine command_line(*exe_path);
command_line.AppendSwitch(kUninstallCompanionAppSwitch);
base::Process uninstall_process = base::LaunchProcess(command_line, {});
if (uninstall_process.IsValid() && WaitForProcess(uninstall_process) == 0) {
VLOG(1) << "Enterprise companion app is removed.";
return;
}
}
void ExpectDeviceManagementRequest(ScopedServer* test_server,
const std::string& request_type,
const std::string& authorization_type,
const std::string& authorization_token,
net::HttpStatusCode response_status,
const std::string& response,
std::optional<GURL> target_url) {
request::MatcherGroup request_matchers = {
request::GetPathMatcher(base::StringPrintf(
R"(%s\?.*agent=%sEnterpriseCompanion\+%s&apptype=Chrome)"
R"(&deviceid=%s.*&platform=.*&request=%s)",
test_server->device_management_path().c_str(), BROWSER_NAME_STRING,
kUpdaterVersion,
device_management_storage::GetDefaultDMStorage()
->GetDeviceID()
.c_str(),
request_type.c_str())),
request::GetHeaderMatcher(
{{"Authorization",
base::StringPrintf("%s token=%s", authorization_type.c_str(),
authorization_token.c_str())},
{"Content-Type", "application/protobuf"}})};
if (target_url) {
request_matchers.push_back(request::GetTargetURLMatcher(*target_url));
}
test_server->ExpectOnce(request_matchers, response, response_status);
}
void ExpectDeviceManagementRegistrationRequest(
ScopedServer* test_server,
const std::string& enrollment_token,
const std::string& dm_token) {
ExpectDeviceManagementRequest(
test_server, "register_policy_agent", "GoogleEnrollmentToken",
enrollment_token, net::HTTP_OK, [&dm_token] {
enterprise_management::DeviceManagementResponse dm_response;
dm_response.mutable_register_response()->set_device_management_token(
dm_token);
return dm_response.SerializeAsString();
}());
}
void ExpectDeviceManagementPolicyFetchRequest(
ScopedServer* test_server,
const std::string& dm_token,
const ::wireless_android_enterprise_devicemanagement::
OmahaSettingsClientProto& omaha_settings,
bool first_request,
bool rotate_public_key,
std::optional<GURL> target_url) {
ExpectDeviceManagementRequest(
test_server, "policy", "GoogleDMToken", dm_token, net::HTTP_OK,
[&dm_token, &omaha_settings, first_request, rotate_public_key] {
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDMResponseForOmahaPolicy(
first_request, rotate_public_key,
DMPolicyBuilder::SigningOption::kSignNormally, dm_token,
device_management_storage::GetDefaultDMStorage()->GetDeviceID(),
omaha_settings);
return dm_response->SerializeAsString();
}(),
target_url);
}
void ExpectProxyPacScriptRequest(ScopedServer* test_server) {
test_server->ExpectOnce(
{request::GetPathMatcher(test_server->proxy_pac_path()),
request::GetHeaderMatcher(
{{"User-Agent", "WinHttp-Autoproxy-Service.*"}})},
base::StringPrintf(
"function FindProxyForURL(url, host) { return \"PROXY %s\"; }",
test_server->host_port_pair().c_str()));
}
} // namespace updater::test