| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <shlobj.h> |
| #include <wrl/client.h> |
| |
| #include <regstr.h> |
| |
| #include <algorithm> |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/process/process.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/waitable_event.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/timer/elapsed_timer.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_bstr.h" |
| #include "base/win/scoped_variant.h" |
| #include "build/build_config.h" |
| #include "chrome/updater/app/server/win/updater_idl.h" |
| #include "chrome/updater/app/server/win/updater_internal_idl.h" |
| #include "chrome/updater/app/server/win/updater_legacy_idl.h" |
| #include "chrome/updater/constants.h" |
| #include "chrome/updater/external_constants_builder.h" |
| #include "chrome/updater/persisted_data.h" |
| #include "chrome/updater/prefs.h" |
| #include "chrome/updater/test/integration_tests_impl.h" |
| #include "chrome/updater/unittest_util.h" |
| #include "chrome/updater/unittest_util_win.h" |
| #include "chrome/updater/updater_branding.h" |
| #include "chrome/updater/updater_scope.h" |
| #include "chrome/updater/updater_version.h" |
| #include "chrome/updater/util.h" |
| #include "chrome/updater/win/setup/setup_util.h" |
| #include "chrome/updater/win/task_scheduler.h" |
| #include "chrome/updater/win/test/test_executables.h" |
| #include "chrome/updater/win/test/test_strings.h" |
| #include "chrome/updater/win/ui/l10n_util.h" |
| #include "chrome/updater/win/ui/resources/updater_installer_strings.h" |
| #include "chrome/updater/win/win_constants.h" |
| #include "chrome/updater/win/win_util.h" |
| #include "components/crx_file/crx_verifier.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| namespace updater::test { |
| namespace { |
| |
| constexpr wchar_t kDidRun[] = L"dr"; |
| |
| enum class CheckInstallationStatus { |
| kCheckIsNotInstalled = 0, |
| kCheckIsInstalled = 1, |
| }; |
| |
| enum class CheckInstallationVersions { |
| kCheckSxSOnly = 0, |
| kCheckActiveAndSxS = 1, |
| }; |
| |
| // Returns the root directory where the updater product is installed. This |
| // is the parent directory where the versioned directories of the |
| // updater instances are. |
| absl::optional<base::FilePath> GetProductPath(UpdaterScope scope) { |
| base::FilePath app_data_dir; |
| if (!base::PathService::Get(scope == UpdaterScope::kSystem |
| ? base::DIR_PROGRAM_FILES |
| : base::DIR_LOCAL_APP_DATA, |
| &app_data_dir)) { |
| return absl::nullopt; |
| } |
| return app_data_dir.AppendASCII(COMPANY_SHORTNAME_STRING) |
| .AppendASCII(PRODUCT_FULLNAME_STRING); |
| } |
| |
| // Returns the versioned directory of this updater instances. |
| absl::optional<base::FilePath> GetProductVersionPath(UpdaterScope scope) { |
| absl::optional<base::FilePath> product_path = GetProductPath(scope); |
| return product_path ? product_path->AppendASCII(kUpdaterVersion) |
| : product_path; |
| } |
| |
| bool RegKeyExists(HKEY root, const std::wstring& path) { |
| return base::win::RegKey(root, path.c_str(), Wow6432(KEY_QUERY_VALUE)) |
| .Valid(); |
| } |
| |
| bool RegKeyExistsCOM(HKEY root, const std::wstring& path) { |
| return base::win::RegKey(root, path.c_str(), KEY_QUERY_VALUE).Valid(); |
| } |
| |
| bool DeleteRegKey(HKEY root, const std::wstring& path) { |
| LONG result = |
| base::win::RegKey(root, L"", Wow6432(KEY_READ)).DeleteKey(path.c_str()); |
| return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND; |
| } |
| |
| bool DeleteRegKeyCOM(HKEY root, const std::wstring& path) { |
| LONG result = base::win::RegKey(root, L"", KEY_READ).DeleteKey(path.c_str()); |
| return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND; |
| } |
| |
| bool DeleteRegValue(HKEY root, |
| const std::wstring& path, |
| const std::wstring& value) { |
| if (!base::win::RegKey(root, path.c_str(), Wow6432(KEY_QUERY_VALUE)) |
| .Valid()) { |
| return true; |
| } |
| |
| LONG result = base::win::RegKey(root, path.c_str(), Wow6432(KEY_WRITE)) |
| .DeleteValue(value.c_str()); |
| return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND; |
| } |
| |
| bool DeleteService(const std::wstring& service_name) { |
| SC_HANDLE scm = ::OpenSCManager( |
| nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); |
| if (!scm) |
| return false; |
| |
| SC_HANDLE service = ::OpenService(scm, service_name.c_str(), DELETE); |
| bool is_service_deleted = !service; |
| if (!is_service_deleted) { |
| is_service_deleted = |
| ::DeleteService(service) |
| ? true |
| : ::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE; |
| |
| ::CloseServiceHandle(service); |
| } |
| |
| DeleteRegValue(HKEY_LOCAL_MACHINE, UPDATER_KEY, service_name); |
| |
| ::CloseServiceHandle(scm); |
| |
| return is_service_deleted; |
| } |
| |
| bool IsServiceGone(const std::wstring& service_name) { |
| SC_HANDLE scm = ::OpenSCManager( |
| nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); |
| if (!scm) |
| return false; |
| |
| SC_HANDLE service = ::OpenService( |
| scm, service_name.c_str(), SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG); |
| bool is_service_gone = !service; |
| if (!is_service_gone) { |
| if (!::ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, |
| SERVICE_NO_CHANGE, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, |
| L"Test Service Display Name")) { |
| is_service_gone = ::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE; |
| } |
| |
| ::CloseServiceHandle(service); |
| } |
| |
| ::CloseServiceHandle(scm); |
| |
| return is_service_gone && |
| !base::win::RegKey(HKEY_LOCAL_MACHINE, UPDATER_KEY, Wow6432(KEY_READ)) |
| .HasValue(service_name.c_str()); |
| } |
| |
| // Checks the installation states (installed or uninstalled) and versions (SxS |
| // only, or both active and SxS). The installation state includes |
| // Client/ClientState registry, COM server registration, COM service |
| // registration, COM interfaces, wake tasks, and files on the file system. |
| void CheckInstallation(UpdaterScope scope, |
| CheckInstallationStatus check_installation_status, |
| CheckInstallationVersions check_installation_versions) { |
| const bool is_installed = |
| check_installation_status == CheckInstallationStatus::kCheckIsInstalled; |
| const bool is_active_and_sxs = check_installation_versions == |
| CheckInstallationVersions::kCheckActiveAndSxS; |
| |
| const HKEY root = UpdaterScopeToHKeyRoot(scope); |
| |
| if (is_active_and_sxs) { |
| for (const wchar_t* key : {CLIENT_STATE_KEY, CLIENTS_KEY, UPDATER_KEY}) { |
| EXPECT_EQ(is_installed, RegKeyExists(root, key)); |
| } |
| |
| EXPECT_EQ(is_installed, base::PathExists(*GetGoogleUpdateExePath(scope))); |
| |
| if (is_installed) { |
| std::wstring pv; |
| EXPECT_EQ(ERROR_SUCCESS, |
| base::win::RegKey( |
| root, |
| GetAppClientsKey(L"{430FD4D0-B729-4F61-AA34-91526481799D}") |
| .c_str(), |
| Wow6432(KEY_READ)) |
| .ReadValue(kRegValuePV, &pv)); |
| EXPECT_STREQ(kUpdaterVersionUtf16, pv.c_str()); |
| |
| std::wstring uninstall_cmd_line_string; |
| EXPECT_EQ(ERROR_SUCCESS, |
| base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ)) |
| .ReadValue(kRegValueUninstallCmdLine, |
| &uninstall_cmd_line_string)); |
| EXPECT_TRUE(base::CommandLine::FromString(uninstall_cmd_line_string) |
| .HasSwitch(kUninstallIfUnusedSwitch)); |
| |
| if (scope == UpdaterScope::kUser) { |
| std::wstring run_updater_wake_command; |
| EXPECT_EQ(ERROR_SUCCESS, |
| base::win::RegKey(root, REGSTR_PATH_RUN, KEY_READ) |
| .ReadValue(GetTaskNamePrefix(scope).c_str(), |
| &run_updater_wake_command)); |
| EXPECT_TRUE(base::CommandLine::FromString(run_updater_wake_command) |
| .HasSwitch(kWakeSwitch)); |
| } |
| } else { |
| if (::IsUserAnAdmin()) { |
| for (const wchar_t* key : |
| {kRegKeyCompanyCloudManagement, kRegKeyCompanyEnrollment, |
| UPDATER_POLICIES_KEY}) { |
| EXPECT_FALSE(RegKeyExists(HKEY_LOCAL_MACHINE, key)); |
| } |
| } |
| |
| EXPECT_FALSE(RegKeyExists(root, UPDATER_KEY)); |
| |
| if (scope == UpdaterScope::kUser) { |
| EXPECT_FALSE(base::win::RegKey(root, REGSTR_PATH_RUN, KEY_READ) |
| .HasValue(GetTaskNamePrefix(scope).c_str())); |
| } |
| } |
| } |
| |
| for (const CLSID& clsid : |
| JoinVectors(GetSideBySideServers(scope), is_active_and_sxs |
| ? GetActiveServers(scope) |
| : std::vector<CLSID>())) { |
| EXPECT_EQ(is_installed, |
| RegKeyExistsCOM(root, GetComServerClsidRegistryPath(clsid))); |
| if (scope == UpdaterScope::kSystem) { |
| EXPECT_EQ(is_installed, |
| RegKeyExistsCOM(root, GetComServerAppidRegistryPath(clsid))); |
| } |
| } |
| |
| for (const IID& iid : JoinVectors( |
| GetSideBySideInterfaces(), |
| is_active_and_sxs ? GetActiveInterfaces() : std::vector<IID>())) { |
| EXPECT_EQ(is_installed, RegKeyExistsCOM(root, GetComIidRegistryPath(iid))); |
| EXPECT_EQ(is_installed, |
| RegKeyExistsCOM(root, GetComTypeLibRegistryPath(iid))); |
| } |
| |
| if (scope == UpdaterScope::kSystem) { |
| for (const bool is_internal_service : {false, true}) { |
| if (!is_active_and_sxs && !is_internal_service) |
| continue; |
| |
| EXPECT_EQ(is_installed, |
| !IsServiceGone(GetServiceName(is_internal_service))); |
| } |
| } |
| |
| if (is_installed) { |
| std::unique_ptr<TaskScheduler> task_scheduler = |
| TaskScheduler::CreateInstance(); |
| const std::wstring task_name = |
| task_scheduler->FindFirstTaskName(GetTaskNamePrefix(scope)); |
| EXPECT_TRUE(!task_name.empty()); |
| EXPECT_TRUE(task_scheduler->IsTaskRegistered(task_name.c_str())); |
| |
| TaskScheduler::TaskInfo task_info; |
| ASSERT_TRUE(task_scheduler->GetTaskInfo(task_name.c_str(), &task_info)); |
| ASSERT_EQ(task_info.exec_actions.size(), 1u); |
| EXPECT_STREQ( |
| task_info.exec_actions[0].arguments.c_str(), |
| base::StrCat( |
| {L"--wake ", scope == UpdaterScope::kSystem ? L"--system " : L"", |
| L"--enable-logging " |
| L"--vmodule=*/chrome/updater/*=2,*/components/winhttp/*=2"}) |
| .c_str()); |
| } |
| |
| const absl::optional<base::FilePath> product_version_path = |
| GetProductVersionPath(scope); |
| const absl::optional<base::FilePath> data_dir_path = GetDataDirPath(scope); |
| |
| for (const absl::optional<base::FilePath>& path : |
| {product_version_path, data_dir_path}) { |
| if (!is_active_and_sxs && path == data_dir_path) |
| continue; |
| |
| EXPECT_TRUE(path); |
| EXPECT_TRUE(WaitFor(base::BindLambdaForTesting( |
| [&]() { return is_installed == base::PathExists(*path); }))); |
| } |
| } |
| |
| // Returns true if any updater process is found running in any session in the |
| // system, regardless of its path. |
| bool IsUpdaterRunning() { |
| return test::IsProcessRunning(GetExecutableRelativePath().value()); |
| } |
| |
| void SleepFor(int seconds) { |
| VLOG(2) << "Sleeping " << seconds << " seconds..."; |
| base::WaitableEvent().TimedWait(base::Seconds(seconds)); |
| VLOG(2) << "Sleep complete."; |
| } |
| |
| void SetupAppCommand(UpdaterScope scope, |
| const std::wstring& app_id, |
| const std::wstring& command_id, |
| base::ScopedTempDir& temp_dir) { |
| base::CommandLine cmd_exe_command_line(base::CommandLine::NO_PROGRAM); |
| SetupCmdExe(scope, cmd_exe_command_line, temp_dir); |
| CreateAppCommandRegistry( |
| scope, app_id, command_id, |
| base::StrCat( |
| {cmd_exe_command_line.GetCommandLineString(), L" /c \"exit %1\""})); |
| } |
| |
| class WindowEnumerator { |
| public: |
| WindowEnumerator(HWND parent, |
| base::RepeatingCallback<bool(HWND hwnd)> filter, |
| base::RepeatingCallback<void(HWND hwnd)> action) |
| : parent_(parent), filter_(filter), action_(action) {} |
| |
| WindowEnumerator(const WindowEnumerator&) = delete; |
| WindowEnumerator& operator=(const WindowEnumerator&) = delete; |
| |
| void Run() const { |
| ::EnumChildWindows(parent_, &OnWindowProc, reinterpret_cast<LPARAM>(this)); |
| } |
| |
| static std::wstring GetWindowClass(HWND hwnd) { |
| constexpr int kMaxWindowClassNameLength = 256; |
| wchar_t buffer[kMaxWindowClassNameLength + 1] = {0}; |
| int name_len = ::GetClassName(hwnd, buffer, std::size(buffer)); |
| if (name_len <= 0 || name_len > kMaxWindowClassNameLength) |
| return std::wstring(); |
| |
| return std::wstring(&buffer[0], name_len); |
| } |
| |
| static bool IsSystemDialog(HWND hwnd) { |
| constexpr wchar_t kSystemDialogClass[] = L"#32770"; |
| return GetWindowClass(hwnd) == kSystemDialogClass; |
| } |
| |
| static std::wstring GetWindowText(HWND hwnd) { |
| const int num_chars = ::GetWindowTextLength(hwnd); |
| if (!num_chars) |
| return std::wstring(); |
| std::vector<wchar_t> text(num_chars + 1); |
| if (!::GetWindowText(hwnd, &text.front(), text.size())) |
| return std::wstring(); |
| return std::wstring(text.begin(), text.end()); |
| } |
| |
| private: |
| bool OnWindow(HWND hwnd) const { |
| if (filter_.Run(hwnd)) |
| action_.Run(hwnd); |
| |
| // Returns true to keep enumerating. |
| return true; |
| } |
| |
| static BOOL CALLBACK OnWindowProc(HWND hwnd, LPARAM lparam) { |
| return reinterpret_cast<WindowEnumerator*>(lparam)->OnWindow(hwnd); |
| } |
| |
| const HWND parent_; |
| base::RepeatingCallback<bool(HWND hwnd)> filter_; |
| base::RepeatingCallback<void(HWND hwnd)> action_; |
| }; |
| |
| DISPID GetDispId(Microsoft::WRL::ComPtr<IDispatch> dispatch, |
| std::wstring name) { |
| DISPID id = 0; |
| LPOLESTR name_ptr = &name[0]; |
| EXPECT_HRESULT_SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name_ptr, 1, |
| LOCALE_USER_DEFAULT, &id)); |
| VLOG(2) << __func__ << ": " << name << ": " << id; |
| return id; |
| } |
| |
| void CallDispatchMethod( |
| Microsoft::WRL::ComPtr<IDispatch> dispatch, |
| const std::wstring& method_name, |
| const std::vector<base::win::ScopedVariant>& variant_params) { |
| std::vector<VARIANT> params; |
| params.reserve(variant_params.size()); |
| |
| // IDispatch::Invoke() expects the parameters in reverse order. |
| std::transform(variant_params.rbegin(), variant_params.rend(), |
| std::back_inserter(params), |
| [](const auto& param) { return param.Copy(); }); |
| |
| DISPPARAMS dp = {}; |
| if (!params.empty()) { |
| dp.rgvarg = ¶ms[0]; |
| dp.cArgs = params.size(); |
| } |
| |
| EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke( |
| GetDispId(dispatch, method_name), IID_NULL, LOCALE_USER_DEFAULT, |
| DISPATCH_METHOD, &dp, nullptr, nullptr, nullptr)); |
| |
| std::for_each(params.begin(), params.end(), |
| [&](auto& param) { ::VariantClear(¶m); }); |
| return; |
| } |
| |
| base::win::ScopedVariant GetDispatchProperty( |
| Microsoft::WRL::ComPtr<IDispatch> dispatch, |
| const std::wstring& property_name) { |
| DISPPARAMS dp = {}; |
| base::win::ScopedVariant result; |
| |
| EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke( |
| GetDispId(dispatch, property_name), IID_NULL, LOCALE_USER_DEFAULT, |
| DISPATCH_PROPERTYGET, &dp, result.Receive(), nullptr, nullptr)); |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| base::FilePath GetSetupExecutablePath() { |
| base::FilePath test_executable; |
| if (!base::PathService::Get(base::FILE_EXE, &test_executable)) |
| return base::FilePath(); |
| return test_executable.DirName().AppendASCII("UpdaterSetup_test.exe"); |
| } |
| |
| absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) { |
| absl::optional<base::FilePath> path = GetProductVersionPath(scope); |
| if (!path) |
| return absl::nullopt; |
| return path->Append(GetExecutableRelativePath()); |
| } |
| |
| absl::optional<base::FilePath> GetFakeUpdaterInstallFolderPath( |
| UpdaterScope scope, |
| const base::Version& version) { |
| absl::optional<base::FilePath> path = GetProductVersionPath(scope); |
| if (!path) |
| return absl::nullopt; |
| return path->AppendASCII(version.GetString()); |
| } |
| |
| absl::optional<base::FilePath> GetDataDirPath(UpdaterScope scope) { |
| return GetProductPath(scope); |
| } |
| |
| void Clean(UpdaterScope scope) { |
| CleanProcesses(); |
| |
| const HKEY root = UpdaterScopeToHKeyRoot(scope); |
| for (const wchar_t* key : {CLIENT_STATE_KEY, CLIENTS_KEY, UPDATER_KEY}) { |
| EXPECT_TRUE(DeleteRegKey(root, key)); |
| } |
| |
| if (::IsUserAnAdmin()) { |
| for (const wchar_t* key : |
| {kRegKeyCompanyCloudManagement, kRegKeyCompanyEnrollment, |
| UPDATER_POLICIES_KEY}) { |
| EXPECT_TRUE(DeleteRegKey(HKEY_LOCAL_MACHINE, key)); |
| } |
| } |
| |
| for (const CLSID& clsid : |
| JoinVectors(GetSideBySideServers(scope), GetActiveServers(scope))) { |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComServerClsidRegistryPath(clsid))); |
| if (scope == UpdaterScope::kSystem) |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComServerAppidRegistryPath(clsid))); |
| } |
| |
| for (const IID& iid : |
| JoinVectors(GetSideBySideInterfaces(), GetActiveInterfaces())) { |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComIidRegistryPath(iid))); |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComTypeLibRegistryPath(iid))); |
| } |
| |
| if (scope == UpdaterScope::kUser) { |
| base::win::RegKey(root, REGSTR_PATH_RUN, KEY_READ) |
| .DeleteValue(GetTaskNamePrefix(scope).c_str()); |
| } |
| |
| if (scope == UpdaterScope::kSystem) { |
| for (const bool is_internal_service : {true, false}) { |
| EXPECT_TRUE(DeleteService(GetServiceName(is_internal_service))); |
| } |
| } |
| |
| std::unique_ptr<TaskScheduler> task_scheduler = |
| TaskScheduler::CreateInstance(); |
| const std::wstring task_name = |
| task_scheduler->FindFirstTaskName(GetTaskNamePrefix(scope)); |
| if (!task_name.empty()) |
| task_scheduler->DeleteTask(task_name.c_str()); |
| EXPECT_TRUE( |
| task_scheduler->FindFirstTaskName(GetTaskNamePrefix(scope)).empty()); |
| |
| absl::optional<base::FilePath> path = GetProductPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| path = GetDataDirPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| |
| const absl::optional<base::FilePath> target_path = |
| GetGoogleUpdateExePath(scope); |
| if (target_path) |
| base::DeleteFile(*target_path); |
| } |
| |
| void EnterTestMode(const GURL& url) { |
| ASSERT_TRUE(ExternalConstantsBuilder() |
| .SetUpdateURL(std::vector<std::string>{url.spec()}) |
| .SetUseCUP(false) |
| .SetInitialDelay(0.1) |
| .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3) |
| .SetOverinstallTimeout(base::Seconds(11)) |
| .Modify()); |
| } |
| |
| void ExpectInstalled(UpdaterScope scope) { |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsInstalled, |
| CheckInstallationVersions::kCheckSxSOnly); |
| } |
| |
| void ExpectClean(UpdaterScope scope) { |
| ExpectCleanProcesses(); |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled, |
| CheckInstallationVersions::kCheckActiveAndSxS); |
| } |
| |
| void ExpectCandidateUninstalled(UpdaterScope scope) { |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled, |
| CheckInstallationVersions::kCheckSxSOnly); |
| } |
| |
| void ExpectActiveUpdater(UpdaterScope scope) { |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsInstalled, |
| CheckInstallationVersions::kCheckActiveAndSxS); |
| } |
| |
| void Uninstall(UpdaterScope scope) { |
| // Note: "updater.exe --uninstall" is run from the build dir, not the install |
| // dir, because it is useful for tests to be able to run it to clean the |
| // system even if installation has failed or the installed binaries have |
| // already been removed. |
| base::FilePath path = |
| GetSetupExecutablePath().DirName().Append(GetExecutableRelativePath()); |
| ASSERT_FALSE(path.empty()); |
| base::CommandLine command_line(path); |
| command_line.AppendSwitch("uninstall"); |
| int exit_code = -1; |
| ASSERT_TRUE(Run(scope, command_line, &exit_code)); |
| EXPECT_EQ(0, exit_code); |
| |
| // Uninstallation involves a race with the uninstall.cmd script and the |
| // process exit. Sleep to allow the script to complete its work. |
| // TODO(crbug.com/1217765): Figure out a way to replace this. |
| SleepFor(5); |
| } |
| |
| void SetActive(UpdaterScope /*scope*/, const std::string& id) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| base::win::RegKey key; |
| ASSERT_EQ(key.Create(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), |
| Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| EXPECT_EQ(key.WriteValue(kDidRun, L"1"), ERROR_SUCCESS); |
| } |
| |
| void ExpectActive(UpdaterScope /*scope*/, const std::string& id) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| base::win::RegKey key; |
| ASSERT_EQ(key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), |
| Wow6432(KEY_READ)), |
| ERROR_SUCCESS); |
| std::wstring value; |
| ASSERT_EQ(key.ReadValue(kDidRun, &value), ERROR_SUCCESS); |
| EXPECT_EQ(value, L"1"); |
| } |
| |
| void ExpectNotActive(UpdaterScope /*scope*/, const std::string& id) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| base::win::RegKey key; |
| if (key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(), |
| Wow6432(KEY_READ)) == ERROR_SUCCESS) { |
| std::wstring value; |
| if (key.ReadValue(kDidRun, &value) == ERROR_SUCCESS) |
| EXPECT_EQ(value, L"0"); |
| } |
| } |
| |
| // Waits for all updater processes to end, including the server process holding |
| // the prefs lock. |
| void WaitForUpdaterExit(UpdaterScope /*scope*/) { |
| WaitFor(base::BindRepeating([]() { return !IsUpdaterRunning(); })); |
| } |
| |
| // Tests if the typelibs and some of the public, internal, and |
| // legacy interfaces are available. Failure to query these interfaces indicates |
| // an issue with typelib registration. |
| void ExpectInterfacesRegistered(UpdaterScope scope) { |
| { // IUpdater, IGoogleUpdate3Web and IAppBundleWeb. |
| // The block is necessary so that updater_server goes out of scope and |
| // releases the prefs lock before updater_internal_server tries to acquire |
| // it to mode-check. |
| Microsoft::WRL::ComPtr<IUnknown> updater_server; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance( |
| scope == UpdaterScope::kSystem ? __uuidof(UpdaterSystemClass) |
| : __uuidof(UpdaterUserClass), |
| nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&updater_server))); |
| Microsoft::WRL::ComPtr<IUpdater> updater; |
| EXPECT_HRESULT_SUCCEEDED(updater_server.As(&updater)); |
| |
| Microsoft::WRL::ComPtr<IUnknown> updater_legacy_server; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance( |
| scope == UpdaterScope::kSystem ? __uuidof(GoogleUpdate3WebSystemClass) |
| : __uuidof(GoogleUpdate3WebUserClass), |
| nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&updater_legacy_server))); |
| Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update; |
| ASSERT_HRESULT_SUCCEEDED(updater_legacy_server.As(&google_update)); |
| Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle; |
| Microsoft::WRL::ComPtr<IDispatch> dispatch; |
| ASSERT_HRESULT_SUCCEEDED(google_update->createAppBundleWeb(&dispatch)); |
| EXPECT_HRESULT_SUCCEEDED(dispatch.As(&app_bundle)); |
| } |
| |
| // IUpdaterInternal. |
| Microsoft::WRL::ComPtr<IUnknown> updater_internal_server; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance( |
| scope == UpdaterScope::kSystem ? __uuidof(UpdaterInternalSystemClass) |
| : __uuidof(UpdaterInternalUserClass), |
| nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&updater_internal_server))); |
| Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal; |
| EXPECT_HRESULT_SUCCEEDED(updater_internal_server.As(&updater_internal)); |
| } |
| |
| void InitializeBundle(UpdaterScope scope, |
| Microsoft::WRL::ComPtr<IAppBundleWeb>& bundle_web) { |
| Microsoft::WRL::ComPtr<IGoogleUpdate3Web> update3web; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance( |
| scope == UpdaterScope::kSystem ? __uuidof(GoogleUpdate3WebSystemClass) |
| : __uuidof(GoogleUpdate3WebUserClass), |
| nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&update3web))); |
| |
| Microsoft::WRL::ComPtr<IAppBundleWeb> bundle; |
| Microsoft::WRL::ComPtr<IDispatch> dispatch; |
| ASSERT_HRESULT_SUCCEEDED(update3web->createAppBundleWeb(&dispatch)); |
| ASSERT_HRESULT_SUCCEEDED(dispatch.As(&bundle)); |
| |
| EXPECT_HRESULT_SUCCEEDED(bundle->initialize()); |
| |
| bundle_web = bundle; |
| } |
| |
| HRESULT DoLoopUntilDone(Microsoft::WRL::ComPtr<IAppBundleWeb> bundle, |
| int expected_final_state, |
| HRESULT expected_error_code) { |
| bool done = false; |
| static constexpr base::TimeDelta kExpirationTimeout = base::Minutes(1); |
| base::ElapsedTimer timer; |
| |
| EXPECT_TRUE(timer.Elapsed() < kExpirationTimeout); |
| |
| LONG state_value = 0; |
| LONG error_code = 0; |
| while (!done && (timer.Elapsed() < kExpirationTimeout)) { |
| EXPECT_TRUE(bundle); |
| |
| Microsoft::WRL::ComPtr<IDispatch> app_dispatch; |
| EXPECT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch)); |
| Microsoft::WRL::ComPtr<IAppWeb> app; |
| EXPECT_HRESULT_SUCCEEDED(app_dispatch.As(&app)); |
| |
| Microsoft::WRL::ComPtr<IDispatch> state_dispatch; |
| EXPECT_HRESULT_SUCCEEDED(app->get_currentState(&state_dispatch)); |
| Microsoft::WRL::ComPtr<ICurrentState> state; |
| EXPECT_HRESULT_SUCCEEDED(state_dispatch.As(&state)); |
| |
| std::wstring stateDescription; |
| std::wstring extraData; |
| |
| EXPECT_HRESULT_SUCCEEDED(state->get_stateValue(&state_value)); |
| |
| switch (state_value) { |
| case STATE_INIT: |
| stateDescription = L"Initializating..."; |
| break; |
| |
| case STATE_WAITING_TO_CHECK_FOR_UPDATE: |
| case STATE_CHECKING_FOR_UPDATE: { |
| stateDescription = L"Checking for update..."; |
| break; |
| } |
| |
| case STATE_UPDATE_AVAILABLE: { |
| stateDescription = L"Update available!"; |
| EXPECT_HRESULT_SUCCEEDED(bundle->download()); |
| break; |
| } |
| |
| case STATE_WAITING_TO_DOWNLOAD: |
| case STATE_RETRYING_DOWNLOAD: |
| stateDescription = L"Contacting server..."; |
| break; |
| |
| case STATE_DOWNLOADING: { |
| stateDescription = L"Downloading..."; |
| |
| ULONG bytes_downloaded = 0; |
| state->get_bytesDownloaded(&bytes_downloaded); |
| |
| ULONG total_bytes_to_download = 0; |
| state->get_totalBytesToDownload(&total_bytes_to_download); |
| |
| LONG download_time_remaining_ms = 0; |
| state->get_downloadTimeRemainingMs(&download_time_remaining_ms); |
| |
| extraData = base::StringPrintf( |
| L"[Bytes downloaded: %d][Bytes total: %d][Time remaining: %d]", |
| bytes_downloaded, total_bytes_to_download, |
| download_time_remaining_ms); |
| break; |
| } |
| |
| case STATE_DOWNLOAD_COMPLETE: |
| case STATE_EXTRACTING: |
| case STATE_APPLYING_DIFFERENTIAL_PATCH: |
| case STATE_READY_TO_INSTALL: { |
| stateDescription = L"Download completed!"; |
| ULONG bytes_downloaded = 0; |
| state->get_bytesDownloaded(&bytes_downloaded); |
| |
| ULONG total_bytes_to_download = 0; |
| state->get_totalBytesToDownload(&total_bytes_to_download); |
| |
| extraData = |
| base::StringPrintf(L"[Bytes downloaded: %d][Bytes total: %d]", |
| bytes_downloaded, total_bytes_to_download); |
| |
| EXPECT_HRESULT_SUCCEEDED(bundle->install()); |
| |
| break; |
| } |
| |
| case STATE_WAITING_TO_INSTALL: |
| case STATE_INSTALLING: { |
| stateDescription = L"Installing..."; |
| |
| LONG install_progress = 0; |
| state->get_installProgress(&install_progress); |
| LONG install_time_remaining_ms = 0; |
| state->get_installTimeRemainingMs(&install_time_remaining_ms); |
| |
| extraData = |
| base::StringPrintf(L"[Install Progress: %d][Time remaining: %d]", |
| install_progress, install_time_remaining_ms); |
| break; |
| } |
| |
| case STATE_INSTALL_COMPLETE: |
| stateDescription = L"Done!"; |
| done = true; |
| break; |
| |
| case STATE_PAUSED: |
| stateDescription = L"Paused..."; |
| break; |
| |
| case STATE_NO_UPDATE: |
| stateDescription = L"No update available!"; |
| done = true; |
| break; |
| |
| case STATE_ERROR: { |
| stateDescription = L"Error!"; |
| |
| EXPECT_HRESULT_SUCCEEDED(state->get_errorCode(&error_code)); |
| |
| base::win::ScopedBstr completion_message; |
| EXPECT_HRESULT_SUCCEEDED( |
| state->get_completionMessage(completion_message.Receive())); |
| |
| LONG installer_result_code = 0; |
| EXPECT_HRESULT_SUCCEEDED( |
| state->get_installerResultCode(&installer_result_code)); |
| |
| extraData = base::StringPrintf( |
| L"[errorCode: %d][completionMessage: %ls][installerResultCode: %d]", |
| error_code, completion_message.Get(), installer_result_code); |
| done = true; |
| break; |
| } |
| |
| default: |
| stateDescription = L"Unhandled state..."; |
| break; |
| } |
| |
| // TODO(1245992): Remove this logging once we get some confidence that the |
| // code is working correctly and no further debugging is needed. |
| LOG(ERROR) << base::StringPrintf(L"[State: %d][%ls][%ls]\n", state_value, |
| stateDescription.c_str(), |
| extraData.c_str()); |
| ::Sleep(100); |
| } |
| |
| EXPECT_TRUE(done); |
| EXPECT_EQ(expected_final_state, state_value); |
| EXPECT_EQ(expected_error_code, error_code); |
| |
| return S_OK; |
| } |
| |
| HRESULT DoUpdate(UpdaterScope scope, |
| const base::win::ScopedBstr& appid, |
| int expected_final_state, |
| HRESULT expected_error_code) { |
| Microsoft::WRL::ComPtr<IAppBundleWeb> bundle; |
| InitializeBundle(scope, bundle); |
| EXPECT_HRESULT_SUCCEEDED(bundle->createInstalledApp(appid.Get())); |
| EXPECT_HRESULT_SUCCEEDED(bundle->checkForUpdate()); |
| return DoLoopUntilDone(bundle, expected_final_state, expected_error_code); |
| } |
| |
| void ExpectLegacyUpdate3WebSucceeds(UpdaterScope scope, |
| const std::string& app_id, |
| int expected_final_state, |
| int expected_error_code) { |
| EXPECT_HRESULT_SUCCEEDED( |
| DoUpdate(scope, base::win::ScopedBstr(base::UTF8ToWide(app_id).c_str()), |
| expected_final_state, expected_error_code)); |
| } |
| |
| void SetupLaunchCommandElevated(const std::wstring& app_id, |
| const std::wstring& command_id, |
| const std::wstring& command_parameters, |
| base::ScopedTempDir& temp_dir) { |
| base::CommandLine cmd_exe_command_line(base::CommandLine::NO_PROGRAM); |
| SetupCmdExe(UpdaterScope::kSystem, cmd_exe_command_line, temp_dir); |
| EXPECT_EQ( |
| CreateAppClientKey(UpdaterScope::kSystem, app_id) |
| .WriteValue(command_id.c_str(), |
| base::StrCat({cmd_exe_command_line.GetCommandLineString(), |
| command_parameters.c_str()}) |
| .c_str()), |
| ERROR_SUCCESS); |
| } |
| |
| void DeleteLaunchCommandElevated(const std::wstring& app_id, |
| const std::wstring& command_id) { |
| EXPECT_EQ(CreateAppClientKey(UpdaterScope::kSystem, app_id) |
| .DeleteValue(command_id.c_str()), |
| ERROR_SUCCESS); |
| } |
| |
| void ExpectLegacyProcessLauncherSucceeds(UpdaterScope scope) { |
| // ProcessLauncher is only implemented for kSystem at the moment. |
| if (scope != UpdaterScope::kSystem) |
| return; |
| |
| Microsoft::WRL::ComPtr<IProcessLauncher> process_launcher; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance(__uuidof(ProcessLauncherClass), |
| nullptr, CLSCTX_LOCAL_SERVER, |
| IID_PPV_ARGS(&process_launcher))); |
| |
| constexpr wchar_t kAppId1[] = L"{831EF4D0-B729-4F61-AA34-91526481799D}"; |
| constexpr wchar_t kCommandId[] = L"CmdExit0"; |
| ULONG_PTR proc_handle = 0; |
| DWORD caller_proc_id = ::GetCurrentProcessId(); |
| |
| // Succeeds when the command is present in the registry. |
| base::ScopedTempDir temp_dir; |
| SetupLaunchCommandElevated(kAppId1, kCommandId, L" /c \"exit 5420\"", |
| temp_dir); |
| EXPECT_HRESULT_SUCCEEDED(process_launcher->LaunchCmdElevated( |
| kAppId1, kCommandId, caller_proc_id, &proc_handle)); |
| EXPECT_NE(static_cast<ULONG_PTR>(0), proc_handle); |
| |
| base::Process process = base::Process(reinterpret_cast<HANDLE>(proc_handle)); |
| int exit_code = 0; |
| EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(), |
| &exit_code)); |
| EXPECT_EQ(exit_code, 5420); |
| |
| DeleteLaunchCommandElevated(kAppId1, kCommandId); |
| |
| EXPECT_EQ(HRESULT_FROM_WIN32(ERROR_BAD_COMMAND), |
| process_launcher->LaunchCmdElevated(kAppId1, kCommandId, |
| caller_proc_id, &proc_handle)); |
| EXPECT_EQ(static_cast<ULONG_PTR>(0), proc_handle); |
| } |
| |
| void ExpectLegacyAppCommandWebSucceeds(UpdaterScope scope, |
| const std::string& app_id, |
| const std::string& command_id, |
| const base::Value::List& parameters, |
| int expected_exit_code) { |
| const size_t kMaxParameters = 9; |
| ASSERT_LE(parameters.size(), kMaxParameters); |
| |
| base::ScopedTempDir temp_dir; |
| const std::wstring appid = base::UTF8ToWide(app_id); |
| const std::wstring commandid = base::UTF8ToWide(command_id); |
| |
| SetupAppCommand(scope, appid, commandid, temp_dir); |
| |
| Microsoft::WRL::ComPtr<IAppBundleWeb> bundle; |
| InitializeBundle(scope, bundle); |
| ASSERT_HRESULT_SUCCEEDED( |
| bundle->createInstalledApp(base::win::ScopedBstr(appid).Get())); |
| |
| Microsoft::WRL::ComPtr<IDispatch> app_dispatch; |
| ASSERT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch)); |
| Microsoft::WRL::ComPtr<IAppWeb> app; |
| ASSERT_HRESULT_SUCCEEDED(app_dispatch.As(&app)); |
| |
| Microsoft::WRL::ComPtr<IDispatch> command_dispatch; |
| ASSERT_HRESULT_SUCCEEDED(app->get_command( |
| base::win::ScopedBstr(commandid).Get(), &command_dispatch)); |
| Microsoft::WRL::ComPtr<IAppCommandWeb> app_command_web; |
| ASSERT_HRESULT_SUCCEEDED(command_dispatch.As(&app_command_web)); |
| |
| std::vector<base::win::ScopedVariant> variant_params; |
| variant_params.reserve(kMaxParameters); |
| std::transform(parameters.begin(), parameters.end(), |
| std::back_inserter(variant_params), [](const auto& param) { |
| return base::win::ScopedVariant( |
| base::UTF8ToWide(param.GetString()).c_str()); |
| }); |
| for (size_t i = parameters.size(); i < kMaxParameters; ++i) |
| variant_params.emplace_back(base::win::ScopedVariant::kEmptyVariant); |
| |
| ASSERT_HRESULT_SUCCEEDED(app_command_web->execute( |
| variant_params[0], variant_params[1], variant_params[2], |
| variant_params[3], variant_params[4], variant_params[5], |
| variant_params[6], variant_params[7], variant_params[8])); |
| |
| EXPECT_TRUE(WaitFor(base::BindLambdaForTesting([&]() { |
| UINT status = 0; |
| EXPECT_HRESULT_SUCCEEDED(app_command_web->get_status(&status)); |
| return status == COMMAND_STATUS_COMPLETE; |
| }))); |
| |
| DWORD exit_code = 0; |
| EXPECT_HRESULT_SUCCEEDED(app_command_web->get_exitCode(&exit_code)); |
| EXPECT_EQ(exit_code, static_cast<DWORD>(expected_exit_code)); |
| |
| // Now also run the AppCommand using the IDispatch methods. |
| command_dispatch.Reset(); |
| ASSERT_HRESULT_SUCCEEDED(app->get_command( |
| base::win::ScopedBstr(commandid).Get(), &command_dispatch)); |
| |
| CallDispatchMethod(command_dispatch, L"execute", variant_params); |
| |
| EXPECT_TRUE(WaitFor(base::BindLambdaForTesting([&]() { |
| base::win::ScopedVariant status = |
| GetDispatchProperty(command_dispatch, L"status"); |
| return V_UINT(status.ptr()) == COMMAND_STATUS_COMPLETE; |
| }))); |
| |
| base::win::ScopedVariant command_exit_code = |
| GetDispatchProperty(command_dispatch, L"exitCode"); |
| EXPECT_EQ(V_UI4(command_exit_code.ptr()), |
| static_cast<DWORD>(expected_exit_code)); |
| |
| DeleteAppClientKey(scope, appid); |
| } |
| |
| namespace { |
| |
| void ExpectPolicyStatusValues( |
| Microsoft::WRL::ComPtr<IPolicyStatusValue> policy_status_value, |
| const std::wstring& expected_source, |
| const std::wstring& expected_value, |
| VARIANT_BOOL expected_has_conflict) { |
| base::win::ScopedBstr source; |
| base::win::ScopedBstr value; |
| VARIANT_BOOL has_conflict = VARIANT_FALSE; |
| |
| ASSERT_NE(policy_status_value.Get(), nullptr); |
| EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_source(source.Receive())); |
| EXPECT_EQ(source.Get(), expected_source); |
| EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_value(value.Receive())); |
| EXPECT_EQ(value.Get(), expected_value); |
| EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_hasConflict(&has_conflict)); |
| EXPECT_EQ(has_conflict, expected_has_conflict); |
| } |
| |
| } // namespace |
| |
| void ExpectLegacyPolicyStatusSucceeds(UpdaterScope scope) { |
| Microsoft::WRL::ComPtr<IUnknown> policy_status_server; |
| ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance( |
| scope == UpdaterScope::kSystem ? __uuidof(PolicyStatusSystemClass) |
| : __uuidof(PolicyStatusUserClass), |
| nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&policy_status_server))); |
| Microsoft::WRL::ComPtr<IPolicyStatus2> policy_status2; |
| ASSERT_HRESULT_SUCCEEDED(policy_status_server.As(&policy_status2)); |
| |
| base::win::ScopedBstr updater_version; |
| ASSERT_HRESULT_SUCCEEDED( |
| policy_status2->get_updaterVersion(updater_version.Receive())); |
| EXPECT_STREQ(updater_version.Get(), kUpdaterVersionUtf16); |
| |
| DATE last_checked = 0; |
| EXPECT_HRESULT_SUCCEEDED(policy_status2->get_lastCheckedTime(&last_checked)); |
| EXPECT_GT(static_cast<int>(last_checked), 0); |
| |
| Microsoft::WRL::ComPtr<IPolicyStatusValue> policy_status_value; |
| ASSERT_HRESULT_SUCCEEDED( |
| policy_status2->get_lastCheckPeriodMinutes(&policy_status_value)); |
| ExpectPolicyStatusValues(policy_status_value, L"default", L"14430", |
| VARIANT_FALSE); |
| |
| const base::win::ScopedBstr test_app(L"test1"); |
| policy_status_value.Reset(); |
| ASSERT_HRESULT_SUCCEEDED(policy_status2->get_effectivePolicyForAppInstalls( |
| test_app.Get(), &policy_status_value)); |
| ExpectPolicyStatusValues(policy_status_value, L"default", L"1", |
| VARIANT_FALSE); |
| |
| policy_status_value.Reset(); |
| ASSERT_HRESULT_SUCCEEDED(policy_status2->get_effectivePolicyForAppUpdates( |
| test_app.Get(), &policy_status_value)); |
| ExpectPolicyStatusValues(policy_status_value, L"default", L"1", |
| VARIANT_FALSE); |
| |
| policy_status_value.Reset(); |
| ASSERT_HRESULT_SUCCEEDED(policy_status2->get_isRollbackToTargetVersionAllowed( |
| test_app.Get(), &policy_status_value)); |
| ExpectPolicyStatusValues(policy_status_value, L"default", L"false", |
| VARIANT_FALSE); |
| } |
| |
| int RunVPythonCommand(const base::CommandLine& command_line) { |
| base::CommandLine python_command = command_line; |
| python_command.PrependWrapper(FILE_PATH_LITERAL("vpython3.bat")); |
| |
| int exit_code = -1; |
| base::Process process = base::LaunchProcess(python_command, {}); |
| EXPECT_TRUE(process.IsValid()); |
| EXPECT_TRUE(process.WaitForExitWithTimeout(base::Seconds(60), &exit_code)); |
| return exit_code; |
| } |
| |
| void RunTestServiceCommand(const std::string& sub_command) { |
| base::FilePath path(base::CommandLine::ForCurrentProcess()->GetProgram()); |
| path = path.DirName(); |
| path = MakeAbsoluteFilePath(path); |
| path = path.Append(FILE_PATH_LITERAL("test_service")) |
| .Append(FILE_PATH_LITERAL("updater_test_service_control.py")); |
| EXPECT_TRUE(base::PathExists(path)); |
| |
| base::CommandLine command(path); |
| command.AppendArg(sub_command); |
| |
| EXPECT_EQ(RunVPythonCommand(command), 0); |
| } |
| |
| void InvokeTestServiceFunction(const std::string& function_name, |
| const base::Value::Dict& arguments) { |
| std::string arguments_json_string; |
| EXPECT_TRUE(base::JSONWriter::Write(arguments, &arguments_json_string)); |
| |
| base::FilePath path(base::CommandLine::ForCurrentProcess()->GetProgram()); |
| path = path.DirName(); |
| path = MakeAbsoluteFilePath(path); |
| path = path.Append(FILE_PATH_LITERAL("test_service")) |
| .Append(FILE_PATH_LITERAL("service_client.py")); |
| EXPECT_TRUE(base::PathExists(path)); |
| |
| base::CommandLine command(path); |
| command.AppendSwitchASCII("--function", function_name); |
| command.AppendSwitchASCII("--args", arguments_json_string); |
| EXPECT_EQ(RunVPythonCommand(command), 0); |
| } |
| |
| void SetupRealUpdaterLowerVersion(UpdaterScope scope) { |
| base::FilePath exe_path; |
| ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_path)); |
| base::FilePath old_updater_path = |
| exe_path.Append(FILE_PATH_LITERAL("old_updater")); |
| #if BUILDFLAG(CHROMIUM_BRANDING) |
| #if defined(ARCH_CPU_X86_64) |
| old_updater_path = |
| old_updater_path.Append(FILE_PATH_LITERAL("chromium_win_x86_64")); |
| #elif defined(ARCH_CPU_X86) |
| old_updater_path = |
| old_updater_path.Append(FILE_PATH_LITERAL("chromium_win_x86")); |
| #endif |
| #elif BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #if defined(ARCH_CPU_X86_64) |
| old_updater_path = |
| old_updater_path.Append(FILE_PATH_LITERAL("chrome_win_x86_64")); |
| #elif defined(ARCH_CPU_X86) |
| old_updater_path = |
| old_updater_path.Append(FILE_PATH_LITERAL("chrome_win_x86")); |
| #endif |
| #endif |
| base::CommandLine command_line( |
| old_updater_path.Append(FILE_PATH_LITERAL("UpdaterSetup_test.exe"))); |
| command_line.AppendSwitch(kInstallSwitch); |
| int exit_code = -1; |
| ASSERT_TRUE(Run(scope, command_line, &exit_code)); |
| ASSERT_EQ(exit_code, 0); |
| } |
| |
| void RunUninstallCmdLine(UpdaterScope scope) { |
| std::wstring uninstall_cmd_line_string; |
| EXPECT_EQ(ERROR_SUCCESS, base::win::RegKey(UpdaterScopeToHKeyRoot(scope), |
| UPDATER_KEY, Wow6432(KEY_READ)) |
| .ReadValue(kRegValueUninstallCmdLine, |
| &uninstall_cmd_line_string)); |
| base::CommandLine command_line = |
| base::CommandLine::FromString(uninstall_cmd_line_string); |
| |
| base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process; |
| |
| base::Process process = base::LaunchProcess(command_line, {}); |
| EXPECT_TRUE(process.IsValid()); |
| |
| int exit_code = 0; |
| EXPECT_TRUE(process.WaitForExitWithTimeout(base::Seconds(45), &exit_code)); |
| EXPECT_EQ(0, exit_code); |
| } |
| |
| void SetupFakeLegacyUpdaterData(UpdaterScope scope) { |
| const HKEY root = UpdaterScopeToHKeyRoot(scope); |
| |
| base::win::RegKey key; |
| ASSERT_EQ( |
| key.Create(root, GetAppClientsKey(kLegacyGoogleUpdaterAppID).c_str(), |
| Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValuePV, L"1.1.1.1"), ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GOOG"), ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS); |
| key.Close(); |
| |
| ASSERT_EQ( |
| key.Create( |
| root, |
| GetAppClientsKey(L"{8A69D345-D564-463C-AFF1-A69D9E530F96}").c_str(), |
| Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValuePV, L"99.0.0.1"), ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GGLS"), ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS); |
| key.Close(); |
| |
| ASSERT_EQ( |
| key.Create( |
| root, |
| GetAppClientsKey(L"{fc54d8f9-b6fd-4274-92eb-c4335cd8761e}").c_str(), |
| Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GGLS"), ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS); |
| key.Close(); |
| } |
| |
| void ExpectLegacyUpdaterDataMigrated(UpdaterScope scope) { |
| scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope); |
| auto persisted_data = |
| base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService()); |
| |
| // Legacy updater itself should not be migrated. |
| const std::string kLegacyUpdaterAppId = |
| base::SysWideToUTF8(kLegacyGoogleUpdaterAppID); |
| EXPECT_FALSE( |
| persisted_data->GetProductVersion(kLegacyUpdaterAppId).IsValid()); |
| EXPECT_TRUE(persisted_data->GetAP(kLegacyUpdaterAppId).empty()); |
| EXPECT_TRUE(persisted_data->GetBrandCode(kLegacyUpdaterAppId).empty()); |
| EXPECT_TRUE(persisted_data->GetFingerprint(kLegacyUpdaterAppId).empty()); |
| |
| // App without 'pv' should not be migrated. |
| const std::string kNoPVAppId("{fc54d8f9-b6fd-4274-92eb-c4335cd8761e}"); |
| EXPECT_FALSE(persisted_data->GetProductVersion(kNoPVAppId).IsValid()); |
| EXPECT_TRUE(persisted_data->GetAP(kNoPVAppId).empty()); |
| EXPECT_TRUE(persisted_data->GetBrandCode(kNoPVAppId).empty()); |
| EXPECT_TRUE(persisted_data->GetFingerprint(kNoPVAppId).empty()); |
| |
| const std::string kChromeAppId = "{8A69D345-D564-463C-AFF1-A69D9E530F96}"; |
| EXPECT_EQ(persisted_data->GetProductVersion(kChromeAppId), |
| base::Version("99.0.0.1")); |
| EXPECT_EQ(persisted_data->GetAP(kChromeAppId), "TestAP"); |
| EXPECT_EQ(persisted_data->GetBrandCode(kChromeAppId), "GGLS"); |
| EXPECT_TRUE(persisted_data->GetFingerprint(kChromeAppId).empty()); |
| } |
| |
| void InstallApp(UpdaterScope scope, const std::string& app_id) { |
| base::win::RegKey key; |
| ASSERT_EQ(key.Create(UpdaterScopeToHKeyRoot(scope), |
| GetAppClientsKey(app_id).c_str(), Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| RegisterApp(scope, app_id); |
| } |
| |
| void UninstallApp(UpdaterScope scope, const std::string& app_id) { |
| base::win::RegKey key; |
| ASSERT_EQ( |
| key.Open(UpdaterScopeToHKeyRoot(scope), CLIENTS_KEY, Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| ASSERT_EQ(key.DeleteKey(base::SysUTF8ToWide(app_id).c_str()), ERROR_SUCCESS); |
| } |
| |
| void RunOfflineInstall(UpdaterScope scope) { |
| constexpr wchar_t kTestRegKey[] = L"software\\updater\\test"; |
| constexpr wchar_t kTestRegValue[] = L"install_result"; |
| constexpr char kManifestFormat[] = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
| "<response protocol=\"3.0\">" |
| " <app appid=\"{CDABE316-39CD-43BA-8440-6D1E0547AEE6}\" status=\"ok\">" |
| " <updatecheck status=\"ok\">" |
| " <manifest version=\"1.2.3.4\">" |
| " <packages>" |
| " <package hash_sha256=\"sha256hash_foobar\"" |
| " name=\"reg.exe\" required=\"true\" size=\"%lld\"/>" |
| " </packages>" |
| " <actions>" |
| " <action event=\"install\" needsadmin=\"false\"" |
| " run=\"reg.exe\"" |
| " arguments=\"ADD %s\\%ls /t REG_DWORD /v %ls /d 123 /f\"/>" |
| " </actions>" |
| " </manifest>" |
| " </updatecheck>" |
| " <data index=\"verboselogging\" name=\"install\" status=\"ok\">" |
| " {\"distribution\": { \"verbose_logging\": true}}" |
| " </data>" |
| " </app>" |
| "</response>"; |
| |
| HKEY key_hive = |
| scope == UpdaterScope::kSystem ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
| |
| DeleteRegKey(key_hive, kTestRegKey); |
| |
| wchar_t reg_exe_path[MAX_PATH] = {0}; |
| DWORD size = ExpandEnvironmentStrings(L"%SystemRoot%\\System32\\reg.exe", |
| reg_exe_path, std::size(reg_exe_path)); |
| ASSERT_TRUE(size > 0 && size < MAX_PATH); |
| const base::FilePath exe_path(reg_exe_path); |
| ASSERT_TRUE(base::PathExists(exe_path)); |
| |
| base::ScopedTempDir temp_dir; |
| EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| const base::FilePath& offline_dir = temp_dir.GetPath(); |
| |
| // Create manifest file. |
| base::FilePath manifest_path = |
| offline_dir.Append(FILE_PATH_LITERAL("OfflineManifest.gup")); |
| int64_t exe_size = 0; |
| EXPECT_TRUE(base::GetFileSize(exe_path, &exe_size)); |
| const std::string manifest = |
| base::StringPrintf(kManifestFormat, exe_size, |
| scope == UpdaterScope::kSystem ? "HKLM" : "HKCU", |
| kTestRegKey, kTestRegValue); |
| EXPECT_TRUE(base::WriteFile(manifest_path, manifest)); |
| |
| // Copy app installer. |
| ASSERT_TRUE(base::CopyFile(exe_path, |
| offline_dir.Append(FILE_PATH_LITERAL("reg.exe")))); |
| |
| // Trigger offline install. |
| const absl::optional<base::FilePath> updater_exe = |
| GetInstalledExecutablePath(scope); |
| ASSERT_TRUE(updater_exe.has_value()); |
| base::CommandLine offline_install_cmd(updater_exe.value()); |
| |
| offline_install_cmd.AppendSwitch(kEnableLoggingSwitch); |
| offline_install_cmd.AppendSwitchASCII(kLoggingModuleSwitch, |
| kLoggingModuleSwitchValue); |
| if (scope == UpdaterScope::kSystem) |
| offline_install_cmd.AppendSwitch(kSystemSwitch); |
| |
| offline_install_cmd.AppendSwitchASCII( |
| updater::kHandoffSwitch, |
| "appguid={CDABE316-39CD-43BA-8440-6D1E0547AEE6}&lang=en"); |
| offline_install_cmd.AppendSwitchASCII( |
| updater::kSessionIdSwitch, "{E85204C6-6F2F-40BF-9E6C-4952208BB977}"); |
| offline_install_cmd.AppendSwitchNative(updater::kOfflineDirSwitch, |
| offline_dir.value()); |
| |
| base::Process process = base::LaunchProcess(offline_install_cmd, {}); |
| EXPECT_TRUE(process.IsValid()); |
| |
| // Dismiss the installation completion dialog, then wait for the process exit. |
| EXPECT_TRUE(WaitFor(base::BindRepeating( |
| [](HKEY key_hive, const wchar_t* test_key_name, |
| const wchar_t* test_value_name) { |
| // Enumerate the top-level dialogs to find the setup dialog. |
| WindowEnumerator( |
| ::GetDesktopWindow(), base::BindRepeating([](HWND hwnd) { |
| return WindowEnumerator::IsSystemDialog(hwnd) && |
| base::Contains(WindowEnumerator::GetWindowText(hwnd), |
| GetLocalizedStringF( |
| IDS_INSTALLER_DISPLAY_NAME_BASE, |
| GetLocalizedString( |
| IDS_FRIENDLY_COMPANY_NAME_BASE))); |
| }), |
| base::BindRepeating([](HWND hwnd) { |
| // Enumerates the dialog items to search for installation complete |
| // message. Once found, close the dialog. |
| WindowEnumerator( |
| hwnd, base::BindRepeating([](HWND hwnd) { |
| return base::Contains( |
| WindowEnumerator::GetWindowText(hwnd), |
| GetLocalizedString( |
| IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE)); |
| }), |
| base::BindRepeating([](HWND hwnd) { |
| ::PostMessage(::GetParent(hwnd), WM_CLOSE, 0, 0); |
| })) |
| .Run(); |
| })) |
| .Run(); |
| |
| if (IsUpdaterRunning()) |
| return false; |
| |
| // Wait for the app installer writes the expected reg value. |
| base::win::RegKey key; |
| DWORD value = 0; |
| return (ERROR_SUCCESS == |
| key.Open(key_hive, test_key_name, KEY_QUERY_VALUE) && |
| ERROR_SUCCESS == key.ReadValueDW(test_value_name, &value) && |
| value == 123); |
| }, |
| key_hive, kTestRegKey, kTestRegValue))); |
| |
| DeleteRegKey(key_hive, kTestRegKey); |
| } |
| |
| } // namespace updater::test |