| // 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 <stdint.h> |
| #include <string> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/run_loop.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/time/time.h" |
| #include "base/version.h" |
| #include "chrome/common/mac/launchd.h" |
| #include "chrome/updater/constants.h" |
| #include "chrome/updater/external_constants_builder.h" |
| #include "chrome/updater/launchd_util.h" |
| #import "chrome/updater/mac/mac_util.h" |
| #include "chrome/updater/mac/xpc_service_names.h" |
| #include "chrome/updater/prefs.h" |
| #include "chrome/updater/test/integration_tests_impl.h" |
| #include "chrome/updater/test/test_app/constants.h" |
| #include "chrome/updater/test/test_app/test_app_version.h" |
| #include "chrome/updater/updater_branding.h" |
| #include "chrome/updater/updater_scope.h" |
| #include "chrome/updater/util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| namespace updater { |
| namespace test { |
| |
| namespace { |
| |
| Launchd::Domain LaunchdDomain(UpdaterScope scope) { |
| switch (scope) { |
| case UpdaterScope::kSystem: |
| return Launchd::Domain::Local; |
| case UpdaterScope::kUser: |
| return Launchd::Domain::User; |
| } |
| } |
| |
| Launchd::Type LaunchdType(UpdaterScope scope) { |
| switch (scope) { |
| case UpdaterScope::kSystem: |
| return Launchd::Type::Daemon; |
| case UpdaterScope::kUser: |
| return Launchd::Type::Agent; |
| } |
| } |
| |
| base::FilePath GetExecutablePath() { |
| base::FilePath test_executable; |
| if (!base::PathService::Get(base::FILE_EXE, &test_executable)) |
| return base::FilePath(); |
| return test_executable.DirName() |
| .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING ".app")) |
| .Append(FILE_PATH_LITERAL("Contents")) |
| .Append(FILE_PATH_LITERAL("MacOS")) |
| .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING)); |
| } |
| |
| base::FilePath GetTestAppExecutablePath() { |
| base::FilePath test_executable; |
| if (!base::PathService::Get(base::FILE_EXE, &test_executable)) |
| return base::FilePath(); |
| return test_executable.DirName() |
| .Append(FILE_PATH_LITERAL(TEST_APP_FULLNAME_STRING ".app")) |
| .Append(FILE_PATH_LITERAL("Contents")) |
| .Append(FILE_PATH_LITERAL("MacOS")) |
| .Append(FILE_PATH_LITERAL(TEST_APP_FULLNAME_STRING)); |
| } |
| |
| absl::optional<base::FilePath> GetProductPath(UpdaterScope scope) { |
| absl::optional<base::FilePath> path = GetLibraryFolderPath(scope); |
| if (!path) |
| return absl::nullopt; |
| |
| return path->AppendASCII(COMPANY_SHORTNAME_STRING) |
| .AppendASCII(PRODUCT_FULLNAME_STRING); |
| } |
| |
| absl::optional<base::FilePath> GetActiveFile(UpdaterScope scope, |
| const std::string& id) { |
| const absl::optional<base::FilePath> path = |
| GetLibraryFolderPath(UpdaterScope::kUser); |
| if (!path) |
| return absl::nullopt; |
| |
| return path->AppendASCII(COMPANY_SHORTNAME_STRING) |
| .AppendASCII(COMPANY_SHORTNAME_STRING "SoftwareUpdate") |
| .AppendASCII("Actives") |
| .AppendASCII(id); |
| } |
| |
| void ExpectServiceAbsent(UpdaterScope scope, const std::string& service) { |
| VLOG(0) << __func__ << " - scope: " << scope << ". service: " << service; |
| bool success = false; |
| base::RunLoop loop; |
| PollLaunchctlList(scope, service, LaunchctlPresence::kAbsent, |
| base::TimeDelta::FromSeconds(7), |
| base::BindLambdaForTesting([&](bool result) { |
| success = result; |
| loop.QuitClosure().Run(); |
| })); |
| loop.Run(); |
| EXPECT_TRUE(success) << service << " is unexpectedly present."; |
| } |
| |
| } // namespace |
| |
| void EnterTestMode(const GURL& url) { |
| ASSERT_TRUE(ExternalConstantsBuilder() |
| .SetUpdateURL(std::vector<std::string>{url.spec()}) |
| .SetUseCUP(false) |
| .SetInitialDelay(0.1) |
| .SetServerKeepAliveSeconds(1) |
| .Overwrite()); |
| } |
| |
| absl::optional<base::FilePath> GetDataDirPath(UpdaterScope scope) { |
| absl::optional<base::FilePath> app_path = |
| GetApplicationSupportDirectory(scope); |
| if (!app_path) { |
| VLOG(1) << "Failed to get Application support path."; |
| return absl::nullopt; |
| } |
| |
| return app_path->AppendASCII(COMPANY_SHORTNAME_STRING) |
| .AppendASCII(PRODUCT_FULLNAME_STRING); |
| } |
| |
| void Clean(UpdaterScope scope) { |
| Launchd::Domain launchd_domain = LaunchdDomain(scope); |
| Launchd::Type launchd_type = LaunchdType(scope); |
| |
| absl::optional<base::FilePath> path = GetProductPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| EXPECT_TRUE(Launchd::GetInstance()->DeletePlist( |
| launchd_domain, launchd_type, updater::CopyWakeLaunchdName())); |
| EXPECT_TRUE(Launchd::GetInstance()->DeletePlist( |
| launchd_domain, launchd_type, |
| updater::CopyUpdateServiceInternalLaunchdName())); |
| EXPECT_TRUE(Launchd::GetInstance()->DeletePlist( |
| launchd_domain, launchd_type, updater::CopyUpdateServiceLaunchdName())); |
| |
| path = GetDataDirPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| |
| @autoreleasepool { |
| RemoveJobFromLaunchd(scope, launchd_domain, launchd_type, |
| CopyWakeLaunchdName()); |
| RemoveJobFromLaunchd(scope, launchd_domain, launchd_type, |
| CopyUpdateServiceLaunchdName()); |
| RemoveJobFromLaunchd(scope, launchd_domain, launchd_type, |
| CopyUpdateServiceInternalLaunchdName()); |
| } |
| } |
| |
| void ExpectClean(UpdaterScope scope) { |
| Launchd::Domain launchd_domain = LaunchdDomain(scope); |
| Launchd::Type launchd_type = LaunchdType(scope); |
| |
| // Files must not exist on the file system. |
| absl::optional<base::FilePath> path = GetProductPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_FALSE(base::PathExists(*path)); |
| EXPECT_FALSE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, updater::CopyWakeLaunchdName())); |
| EXPECT_FALSE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, |
| updater::CopyUpdateServiceInternalLaunchdName())); |
| EXPECT_FALSE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, updater::CopyUpdateServiceLaunchdName())); |
| |
| path = GetDataDirPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_FALSE(base::PathExists(*path)); |
| |
| ExpectServiceAbsent(scope, GetUpdateServiceLaunchdName()); |
| ExpectServiceAbsent(scope, GetUpdateServiceInternalLaunchdName()); |
| } |
| |
| void ExpectInstalled(UpdaterScope scope) { |
| Launchd::Domain launchd_domain = LaunchdDomain(scope); |
| Launchd::Type launchd_type = LaunchdType(scope); |
| |
| // Files must exist on the file system. |
| absl::optional<base::FilePath> path = GetProductPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::PathExists(*path)); |
| |
| EXPECT_TRUE(Launchd::GetInstance()->PlistExists(launchd_domain, launchd_type, |
| CopyWakeLaunchdName())); |
| EXPECT_TRUE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, CopyUpdateServiceInternalLaunchdName())); |
| } |
| |
| void Install(UpdaterScope scope) { |
| const base::FilePath path = GetExecutablePath(); |
| ASSERT_FALSE(path.empty()); |
| base::CommandLine command_line(path); |
| command_line.AppendSwitch(kInstallSwitch); |
| int exit_code = -1; |
| ASSERT_TRUE(Run(scope, command_line, &exit_code)); |
| EXPECT_EQ(exit_code, 0); |
| } |
| |
| void ExpectActiveUpdater(UpdaterScope scope) { |
| Launchd::Domain launchd_domain = LaunchdDomain(scope); |
| Launchd::Type launchd_type = LaunchdType(scope); |
| |
| // Files must exist on the file system. |
| absl::optional<base::FilePath> path = GetProductPath(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::PathExists(*path)); |
| |
| EXPECT_TRUE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, CopyUpdateServiceLaunchdName())); |
| } |
| |
| void RegisterTestApp(UpdaterScope scope) { |
| const base::FilePath path = GetTestAppExecutablePath(); |
| ASSERT_FALSE(path.empty()); |
| base::CommandLine command_line(path); |
| command_line.AppendSwitch(kRegisterUpdaterSwitch); |
| int exit_code = -1; |
| ASSERT_TRUE(Run(scope, command_line, &exit_code)); |
| EXPECT_EQ(exit_code, 0); |
| } |
| |
| absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) { |
| return GetUpdaterExecutablePath(scope); |
| } |
| |
| void ExpectCandidateUninstalled(UpdaterScope scope) { |
| Launchd::Domain launchd_domain = LaunchdDomain(scope); |
| Launchd::Type launchd_type = LaunchdType(scope); |
| |
| absl::optional<base::FilePath> versioned_folder_path = |
| GetVersionedUpdaterFolderPath(scope); |
| EXPECT_TRUE(versioned_folder_path); |
| if (versioned_folder_path) |
| EXPECT_FALSE(base::PathExists(*versioned_folder_path)); |
| |
| EXPECT_FALSE(Launchd::GetInstance()->PlistExists(launchd_domain, launchd_type, |
| CopyWakeLaunchdName())); |
| EXPECT_FALSE(Launchd::GetInstance()->PlistExists( |
| launchd_domain, launchd_type, CopyUpdateServiceInternalLaunchdName())); |
| } |
| |
| void Uninstall(UpdaterScope scope) { |
| absl::optional<base::FilePath> path = GetExecutablePath(); |
| ASSERT_TRUE(path); |
| base::CommandLine command_line(*path); |
| command_line.AppendSwitch(kUninstallSwitch); |
| int exit_code = -1; |
| ASSERT_TRUE(Run(scope, command_line, &exit_code)); |
| EXPECT_EQ(exit_code, 0); |
| } |
| |
| absl::optional<base::FilePath> GetFakeUpdaterInstallFolderPath( |
| UpdaterScope scope, |
| const base::Version& version) { |
| return GetExecutableFolderPathForVersion(scope, version); |
| } |
| |
| void SetActive(UpdaterScope scope, const std::string& app_id) { |
| const absl::optional<base::FilePath> path = GetActiveFile(scope, app_id); |
| ASSERT_TRUE(path); |
| VLOG(0) << "Actives file: " << *path; |
| base::File::Error err = base::File::FILE_OK; |
| EXPECT_TRUE(base::CreateDirectoryAndGetError(path->DirName(), &err)) |
| << "Error: " << err; |
| EXPECT_TRUE(base::WriteFile(*path, "")); |
| } |
| |
| void ExpectActive(UpdaterScope scope, const std::string& app_id) { |
| const absl::optional<base::FilePath> path = GetActiveFile(scope, app_id); |
| ASSERT_TRUE(path); |
| EXPECT_TRUE(base::PathExists(*path)); |
| EXPECT_TRUE(base::PathIsWritable(*path)); |
| } |
| |
| void ExpectNotActive(UpdaterScope scope, const std::string& app_id) { |
| const absl::optional<base::FilePath> path = GetActiveFile(scope, app_id); |
| ASSERT_TRUE(path); |
| EXPECT_FALSE(base::PathExists(*path)); |
| EXPECT_FALSE(base::PathIsWritable(*path)); |
| } |
| |
| void WaitForServerExit(UpdaterScope scope) { |
| base::TimeTicks deadline = |
| base::TimeTicks::Now() + TestTimeouts::action_max_timeout(); |
| while (base::TimeTicks::Now() < deadline) { |
| std::string ps_stdout; |
| ASSERT_TRUE(base::GetAppOutput({"ps", "ax", "-o", "command"}, &ps_stdout)); |
| if (ps_stdout.find(GetExecutablePath().BaseName().AsUTF8Unsafe()) == |
| std::string::npos) { |
| return; |
| } |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); |
| } |
| FAIL() << __func__ << " timed out."; |
| } |
| |
| } // namespace test |
| } // namespace updater |