| // Copyright 2020 The Chromium Authors |
| // 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/apple/foundation_util.h" |
| #include "base/base_paths.h" |
| #include "base/command_line.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.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 "build/build_config.h" |
| #include "chrome/common/chrome_paths.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/updater_branding.h" |
| #include "chrome/updater/updater_scope.h" |
| #import "chrome/updater/util/mac_util.h" |
| #include "chrome/updater/util/unit_test_util.h" |
| #include "chrome/updater/util/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 { |
| |
| base::FilePath GetExecutablePath() { |
| base::FilePath out_dir; |
| if (!base::PathService::Get(base::DIR_EXE, &out_dir)) |
| return base::FilePath(); |
| return out_dir.Append(GetExecutableRelativePath()); |
| } |
| |
| absl::optional<base::FilePath> GetActiveFile(UpdaterScope /*scope*/, |
| const std::string& id) { |
| // The active user is always managed in the updater scope for the user. |
| 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); |
| } |
| |
| } // namespace |
| |
| base::FilePath GetSetupExecutablePath() { |
| // There is no metainstaller on mac, use the main executable for setup. |
| return GetExecutablePath(); |
| } |
| |
| void EnterTestMode(const GURL& update_url, |
| const GURL& crash_upload_url, |
| const GURL& device_management_url, |
| const base::TimeDelta& idle_timeout) { |
| ASSERT_TRUE(ExternalConstantsBuilder() |
| .SetUpdateURL(std::vector<std::string>{update_url.spec()}) |
| .SetCrashUploadURL(crash_upload_url.spec()) |
| .SetDeviceManagementURL(device_management_url.spec()) |
| .SetUseCUP(false) |
| .SetInitialDelay(base::Milliseconds(100)) |
| .SetServerKeepAliveTime(base::Seconds(2)) |
| .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3) |
| .SetOverinstallTimeout(base::Seconds(5)) |
| .SetIdleCheckPeriod(idle_timeout) |
| .Modify()); |
| } |
| |
| void Clean(UpdaterScope scope) { |
| CleanProcesses(); |
| |
| absl::optional<base::FilePath> path = GetInstallDirectory(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| EXPECT_TRUE(base::DeleteFile(*GetWakeTaskPlistPath(scope))); |
| |
| path = GetInstallDirectory(scope); |
| EXPECT_TRUE(path); |
| if (path) |
| EXPECT_TRUE(base::DeletePathRecursively(*path)); |
| |
| absl::optional<base::FilePath> keystone_path = GetKeystoneFolderPath(scope); |
| EXPECT_TRUE(keystone_path); |
| if (keystone_path) |
| EXPECT_TRUE(base::DeletePathRecursively(*keystone_path)); |
| |
| EXPECT_TRUE(RemoveWakeJobFromLaunchd(scope)); |
| |
| // Also clean up any other versions of the updater that are around. |
| base::CommandLine launchctl(base::FilePath("/bin/launchctl")); |
| launchctl.AppendArg("list"); |
| std::string out; |
| ASSERT_TRUE(base::GetAppOutput(launchctl, &out)); |
| for (const auto& token : base::SplitStringPiece(out, base::kWhitespaceASCII, |
| base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| if (base::StartsWith(token, MAC_BUNDLE_IDENTIFIER_STRING)) { |
| std::string out_rm; |
| base::CommandLine launchctl_rm(base::FilePath("/bin/launchctl")); |
| launchctl_rm.AppendArg("remove"); |
| launchctl_rm.AppendArg(token); |
| ASSERT_TRUE(base::GetAppOutput(launchctl_rm, &out_rm)); |
| } |
| } |
| } |
| |
| void ExpectClean(UpdaterScope scope) { |
| ExpectCleanProcesses(); |
| |
| // Files must not exist on the file system. |
| EXPECT_FALSE(base::PathExists(*GetWakeTaskPlistPath(scope))); |
| |
| absl::optional<base::FilePath> path = GetInstallDirectory(scope); |
| EXPECT_TRUE(path); |
| if (path && base::PathExists(*path)) { |
| // If the path exists, then expect only the log and json files to be |
| // present. |
| int count = CountDirectoryFiles(*path); |
| EXPECT_LE(count, 1) << base::JoinString( |
| [](const base::FilePath& dir) { |
| std::vector<base::FilePath::StringType> files; |
| base::FileEnumerator(dir, false, base::FileEnumerator::FILES) |
| .ForEach([&files](const base::FilePath& name) { |
| files.push_back(name.value()); |
| }); |
| |
| return files; |
| }(*path), |
| FILE_PATH_LITERAL(",")); |
| |
| if (count >= 1) { |
| EXPECT_TRUE(base::PathExists(path->AppendASCII("updater.log"))); |
| } |
| } |
| // Keystone must not exist on the file system. |
| absl::optional<base::FilePath> keystone_path = GetKeystoneFolderPath(scope); |
| EXPECT_TRUE(keystone_path); |
| if (keystone_path) |
| EXPECT_FALSE( |
| base::PathExists(keystone_path->AppendASCII(KEYSTONE_NAME ".bundle"))); |
| } |
| |
| void ExpectInstalled(UpdaterScope scope) { |
| absl::optional<base::FilePath> keystone_path = GetKeystoneFolderPath(scope); |
| ASSERT_TRUE(keystone_path); |
| |
| // Files must exist on the file system. |
| for (const auto& path : |
| {GetInstallDirectory(scope), keystone_path, GetKSAdminPath(scope)}) { |
| ASSERT_TRUE(path) << path; |
| EXPECT_TRUE(base::PathExists(*path)) << path; |
| } |
| |
| EXPECT_TRUE(base::PathExists(*GetWakeTaskPlistPath(scope))); |
| } |
| |
| absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) { |
| return GetUpdaterExecutablePath(scope); |
| } |
| |
| void ExpectCandidateUninstalled(UpdaterScope scope) { |
| absl::optional<base::FilePath> versioned_folder_path = |
| GetVersionedInstallDirectory(scope); |
| ASSERT_TRUE(versioned_folder_path); |
| EXPECT_FALSE(base::PathExists(*versioned_folder_path)); |
| } |
| |
| 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; |
| Run(scope, command_line, &exit_code); |
| ASSERT_EQ(exit_code, 0); |
| } |
| |
| 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)); |
| } |
| |
| bool WaitForUpdaterExit(UpdaterScope /*scope*/) { |
| return WaitFor( |
| [] { |
| std::string ps_stdout; |
| EXPECT_TRUE( |
| base::GetAppOutput({"ps", "ax", "-o", "command"}, &ps_stdout)); |
| if (ps_stdout.find(GetExecutablePath().BaseName().AsUTF8Unsafe()) == |
| std::string::npos) { |
| return true; |
| } |
| return false; |
| }, |
| [] { VLOG(0) << "Still waiting for updater to exit..."; }); |
| } |
| |
| 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("old_updater"); |
| #if BUILDFLAG(CHROMIUM_BRANDING) |
| #if defined(ARCH_CPU_ARM64) |
| old_updater_path = old_updater_path.Append("chromium_mac_arm64"); |
| #elif defined(ARCH_CPU_X86_64) |
| old_updater_path = old_updater_path.Append("chromium_mac_amd64"); |
| #endif |
| #elif BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| old_updater_path = old_updater_path.Append("chrome_mac_universal"); |
| #endif |
| base::CommandLine command_line( |
| old_updater_path.Append(PRODUCT_FULLNAME_STRING "_test.app") |
| .Append("Contents") |
| .Append("MacOS") |
| .Append(PRODUCT_FULLNAME_STRING "_test")); |
| command_line.AppendSwitch(kInstallSwitch); |
| int exit_code = -1; |
| Run(scope, command_line, &exit_code); |
| ASSERT_EQ(exit_code, 0); |
| } |
| |
| void SetupFakeLegacyUpdater(UpdaterScope scope) { |
| base::FilePath updater_test_data_path; |
| ASSERT_TRUE( |
| base::PathService::Get(chrome::DIR_TEST_DATA, &updater_test_data_path)); |
| updater_test_data_path = |
| updater_test_data_path.Append(FILE_PATH_LITERAL("updater")); |
| |
| base::FilePath keystone_path = GetKeystoneFolderPath(scope).value(); |
| base::FilePath keystone_ticket_store_path = |
| keystone_path.Append(FILE_PATH_LITERAL("TicketStore")); |
| ASSERT_TRUE(base::CreateDirectory(keystone_ticket_store_path)); |
| ASSERT_TRUE(base::CopyFile( |
| updater_test_data_path.AppendASCII("Keystone.legacy.ticketstore"), |
| keystone_ticket_store_path.AppendASCII("Keystone.ticketstore"))); |
| ASSERT_TRUE(base::CopyFile( |
| updater_test_data_path.AppendASCII("CountingMetrics.plist"), |
| keystone_path.AppendASCII("CountingMetrics.plist"))); |
| } |
| |
| void ExpectLegacyUpdaterMigrated(UpdaterScope scope) { |
| scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope); |
| auto persisted_data = base::MakeRefCounted<PersistedData>( |
| scope, global_prefs->GetPrefService()); |
| |
| // Keystone should not be migrated. |
| EXPECT_FALSE( |
| persisted_data->GetProductVersion("com.google.keystone").IsValid()); |
| |
| // Uninstalled app should be migrated. |
| EXPECT_TRUE( |
| persisted_data->GetProductVersion("com.chromium.NonExistApp").IsValid()); |
| |
| // App Kipple. |
| const std::string kKippleApp = "com.chromium.kipple"; |
| EXPECT_EQ(persisted_data->GetProductVersion(kKippleApp), |
| base::Version("1.2.3.4")); |
| EXPECT_EQ(persisted_data->GetExistenceCheckerPath(kKippleApp), |
| base::FilePath("/")); |
| EXPECT_TRUE(persisted_data->GetAP(kKippleApp).empty()); |
| EXPECT_TRUE(persisted_data->GetBrandCode(kKippleApp).empty()); |
| EXPECT_TRUE(persisted_data->GetBrandPath(kKippleApp).empty()); |
| EXPECT_TRUE(persisted_data->GetFingerprint(kKippleApp).empty()); |
| EXPECT_FALSE(persisted_data->GetDateLastActive(kKippleApp)); // no data. |
| EXPECT_FALSE(persisted_data->GetDateLastRollcall(kKippleApp)); // wrong type. |
| |
| // App PopularApp. |
| const std::string kPopularApp = "com.chromium.PopularApp"; |
| EXPECT_EQ(persisted_data->GetProductVersion(kPopularApp), |
| base::Version("101.100.1000.9999")); |
| EXPECT_EQ(persisted_data->GetExistenceCheckerPath(kPopularApp), |
| base::FilePath("/")); |
| EXPECT_EQ(persisted_data->GetAP(kPopularApp), "GOOG"); |
| EXPECT_TRUE(persisted_data->GetBrandCode(kPopularApp).empty()); |
| EXPECT_EQ(persisted_data->GetBrandPath(kPopularApp), base::FilePath("/")); |
| EXPECT_TRUE(persisted_data->GetFingerprint(kPopularApp).empty()); |
| EXPECT_EQ(persisted_data->GetDateLastActive(kPopularApp).value(), 5921); |
| EXPECT_EQ(persisted_data->GetDateLastRollcall(kPopularApp).value(), 5922); |
| |
| EXPECT_EQ(persisted_data->GetCohort(kPopularApp), "TestCohort"); |
| EXPECT_EQ(persisted_data->GetCohortName(kPopularApp), "TestCohortName"); |
| EXPECT_EQ(persisted_data->GetCohortHint(kPopularApp), "TestCohortHint"); |
| |
| // App CorruptedApp (client-regulated counting data is corrupted). |
| const std::string kCorruptedApp = "com.chromium.CorruptedApp"; |
| EXPECT_EQ(persisted_data->GetProductVersion(kCorruptedApp), |
| base::Version("1.2.1")); |
| EXPECT_EQ(persisted_data->GetExistenceCheckerPath(kCorruptedApp), |
| base::FilePath("/")); |
| EXPECT_EQ(persisted_data->GetAP(kCorruptedApp), "canary"); |
| EXPECT_FALSE(persisted_data->GetDateLastActive(kCorruptedApp)); |
| EXPECT_FALSE(persisted_data->GetDateLastRollcall(kCorruptedApp)); |
| } |
| |
| void InstallApp(UpdaterScope scope, |
| const std::string& app_id, |
| const base::Version& version) { |
| RegisterApp(scope, app_id, version); |
| } |
| |
| void UninstallApp(UpdaterScope scope, const std::string& app_id) { |
| SetExistenceCheckerPath(scope, app_id, |
| base::FilePath(FILE_PATH_LITERAL("NONE"))); |
| } |
| |
| base::CommandLine MakeElevated(base::CommandLine command_line) { |
| command_line.PrependWrapper("/usr/bin/sudo"); |
| return command_line; |
| } |
| |
| } // namespace updater::test |