| // 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 |