| // Copyright 2024 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/browser/ash/sparky/sparky_delegate_impl.h" |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/path_service.h" |
| #include "base/system/sys_info.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_running_on_chromeos.h" |
| #include "base/test/task_environment.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/file_manager/path_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/ash/components/dbus/concierge/concierge_client.h" |
| #include "chromeos/ash/components/dbus/spaced/spaced_client.h" |
| #include "chromeos/ash/components/disks/disk_mount_manager.h" |
| #include "chromeos/ash/components/disks/fake_disk_mount_manager.h" |
| #include "chromeos/ash/components/sparky/sparky_util.h" |
| #include "components/manta/sparky/sparky_delegate.h" |
| #include "components/manta/sparky/system_info_delegate.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/text/bytes_formatting.h" |
| |
| namespace ash { |
| |
| namespace { |
| using SettingsPrivatePrefType = extensions::api::settings_private::PrefType; |
| using ::testing::UnorderedElementsAre; |
| |
| MATCHER_P2(File, path, name, "File matches") { |
| return arg.path == path && arg.name == name; |
| } |
| |
| MATCHER_P5(FileWithSummary, |
| path, |
| name, |
| summary, |
| date_modified, |
| size_in_bytes, |
| "File with summary matches") { |
| return arg.path == path && arg.name == name && arg.summary == summary && |
| arg.date_modified == date_modified && |
| arg.size_in_bytes == size_in_bytes; |
| } |
| |
| // Get the path to file manager's test data directory. |
| base::FilePath GetTestDataFilePath(const std::string& file_name) { |
| // Get the path to file manager's test data directory. |
| base::FilePath source_dir; |
| CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_dir)); |
| base::FilePath test_data_dir = source_dir.AppendASCII("chrome") |
| .AppendASCII("test") |
| .AppendASCII("data") |
| .AppendASCII("chromeos") |
| .AppendASCII("file_manager"); |
| |
| // Return full test data path to the given |file_name|. |
| return test_data_dir.Append(base::FilePath::FromUTF8Unsafe(file_name)); |
| } |
| |
| // Copy a file from the file manager's test data directory to the specified |
| // target_path. |
| void AddFile(const std::string& file_name, |
| int64_t expected_size, |
| base::FilePath target_path) { |
| const base::FilePath entry_path = GetTestDataFilePath(file_name); |
| target_path = target_path.AppendASCII(file_name); |
| ASSERT_TRUE(base::CopyFile(entry_path, target_path)) |
| << "Copy from " << entry_path.value() << " to " << target_path.value() |
| << " failed."; |
| // Verify file size. |
| base::stat_wrapper_t stat; |
| const int res = base::File::Lstat(target_path, &stat); |
| ASSERT_FALSE(res < 0) << "Couldn't stat" << target_path.value(); |
| ASSERT_EQ(expected_size, stat.st_size); |
| } |
| } // namespace |
| |
| class SparkyDelegateImplTest : public testing::Test { |
| public: |
| SparkyDelegateImplTest() = default; |
| |
| SparkyDelegateImplTest(const SparkyDelegateImplTest&) = delete; |
| SparkyDelegateImplTest& operator=(const SparkyDelegateImplTest&) = delete; |
| |
| ~SparkyDelegateImplTest() override = default; |
| |
| SparkyDelegateImpl* GetSparkyDelegateImpl() { |
| return sparky_delegate_impl_.get(); |
| } |
| |
| const base::Value& GetPref(const std::string& setting_id) { |
| return profile_->GetPrefs()->GetValue(setting_id); |
| } |
| |
| void SetBool(const std::string& setting_id, bool bool_val) { |
| profile_->GetPrefs()->SetBoolean(setting_id, bool_val); |
| } |
| |
| void AddToMap(const std::string& pref_name, |
| SettingsPrivatePrefType settings_pref_type, |
| std::optional<base::Value> value) { |
| sparky_delegate_impl_->AddPrefToMap(pref_name, settings_pref_type, |
| std::move(value)); |
| } |
| |
| SparkyDelegateImpl::SettingsDataList* GetCurrentPrefs() { |
| return &sparky_delegate_impl_->current_prefs_; |
| } |
| |
| // testing::Test: |
| void SetUp() override { |
| profile_ = std::make_unique<TestingProfile>(); |
| sparky_delegate_impl_ = |
| std::make_unique<SparkyDelegateImpl>(profile_.get()); |
| |
| // Initialize fake DBus clients. |
| ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr); |
| ash::SpacedClient::InitializeFake(); |
| |
| ash::disks::DiskMountManager::InitializeForTesting( |
| new ash::disks::FakeDiskMountManager); |
| |
| // Create and register MyFiles directory. |
| // By emulating chromeos running, GetMyFilesFolderForProfile will return the |
| // profile's temporary location instead of $HOME/Downloads. |
| base::test::ScopedRunningOnChromeOS running_on_chromeos; |
| const base::FilePath my_files_path = |
| file_manager::util::GetMyFilesFolderForProfile(profile_.get()); |
| CHECK(base::CreateDirectory(my_files_path)); |
| CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( |
| file_manager::util::GetDownloadsMountPointName(profile_.get()), |
| storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), |
| my_files_path)); |
| |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| sparky_delegate_impl_->SetRootPathForTesting(scoped_temp_dir_.GetPath()); |
| |
| RunUntilIdle(); |
| } |
| |
| void TearDown() override { |
| sparky_delegate_impl_.reset(); |
| profile_.reset(); |
| ash::disks::DiskMountManager::Shutdown(); |
| storage::ExternalMountPoints::GetSystemInstance()->RevokeAllFileSystems(); |
| ash::SpacedClient::Shutdown(); |
| ash::ConciergeClient::Shutdown(); |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| protected: |
| base::FilePath Path(const std::string& filename) { |
| return scoped_temp_dir_.GetPath().Append(filename); |
| } |
| |
| void WriteFile(const std::string& filename, const std::string& data) { |
| ASSERT_TRUE(base::WriteFile(Path(filename), data)); |
| ASSERT_TRUE(base::PathExists(Path(filename))); |
| RunUntilIdle(); |
| } |
| |
| void CreateDirectory(const std::string& directory_name) { |
| ASSERT_TRUE(base::CreateDirectory(Path(directory_name))); |
| ASSERT_TRUE(base::PathExists(Path(directory_name))); |
| RunUntilIdle(); |
| } |
| |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<TestingProfile> profile_; |
| |
| private: |
| std::unique_ptr<SparkyDelegateImpl> sparky_delegate_impl_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| TEST_F(SparkyDelegateImplTest, SetSettings) { |
| SetBool(prefs::kDarkModeEnabled, false); |
| SetBool(prefs::kPowerAdaptiveChargingEnabled, true); |
| ASSERT_TRUE(GetSparkyDelegateImpl()->SetSettings( |
| std::make_unique<manta::SettingsData>(prefs::kDarkModeEnabled, |
| manta::PrefType::kBoolean, |
| base::Value(true)))); |
| ASSERT_TRUE(GetSparkyDelegateImpl()->SetSettings( |
| std::make_unique<manta::SettingsData>( |
| prefs::kPowerAdaptiveChargingEnabled, manta::PrefType::kBoolean, |
| base::Value(false)))); |
| const base::Value& dark_mode_val = GetPref(prefs::kDarkModeEnabled); |
| const base::Value& adaptive_charging_val = |
| GetPref(prefs::kPowerAdaptiveChargingEnabled); |
| RunUntilIdle(); |
| ASSERT_TRUE(dark_mode_val.is_bool()); |
| ASSERT_TRUE(dark_mode_val.GetBool()); |
| ASSERT_TRUE(adaptive_charging_val.is_bool()); |
| ASSERT_FALSE(adaptive_charging_val.GetBool()); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, AddPrefToMap) { |
| AddToMap("bool pref", SettingsPrivatePrefType::kBoolean, |
| std::make_optional<base::Value>(true)); |
| AddToMap("int pref", SettingsPrivatePrefType::kNumber, |
| std::make_optional<base::Value>(1)); |
| AddToMap("double pref", SettingsPrivatePrefType::kNumber, |
| std::make_optional<base::Value>(0.5)); |
| AddToMap("string pref", SettingsPrivatePrefType::kString, |
| std::make_optional<base::Value>("my string")); |
| RunUntilIdle(); |
| ASSERT_TRUE(GetCurrentPrefs()->contains("bool pref")); |
| ASSERT_TRUE(GetCurrentPrefs()->contains("int pref")); |
| ASSERT_TRUE(GetCurrentPrefs()->contains("double pref")); |
| ASSERT_TRUE(GetCurrentPrefs()->contains("string pref")); |
| ASSERT_EQ(GetCurrentPrefs()->find("bool pref")->second->pref_name, |
| "bool pref"); |
| ASSERT_EQ(GetCurrentPrefs()->find("int pref")->second->pref_name, "int pref"); |
| ASSERT_EQ(GetCurrentPrefs()->find("double pref")->second->pref_name, |
| "double pref"); |
| ASSERT_EQ(GetCurrentPrefs()->find("string pref")->second->pref_name, |
| "string pref"); |
| ASSERT_EQ(GetCurrentPrefs()->find("bool pref")->second->pref_type, |
| manta::PrefType::kBoolean); |
| ASSERT_EQ(GetCurrentPrefs()->find("int pref")->second->pref_type, |
| manta::PrefType::kInt); |
| ASSERT_EQ(GetCurrentPrefs()->find("double pref")->second->pref_type, |
| manta::PrefType::kDouble); |
| ASSERT_EQ(GetCurrentPrefs()->find("string pref")->second->pref_type, |
| manta::PrefType::kString); |
| ASSERT_TRUE(GetCurrentPrefs()->find("bool pref")->second->bool_val); |
| ASSERT_EQ(GetCurrentPrefs()->find("int pref")->second->int_val, 1); |
| ASSERT_EQ(GetCurrentPrefs()->find("double pref")->second->double_val, 0.5); |
| ASSERT_EQ(GetCurrentPrefs()->find("string pref")->second->string_val, |
| "my string"); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, ObtainStorageInfo) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| // Get local filesystem storage statistics. |
| const base::FilePath mount_path = |
| file_manager::util::GetMyFilesFolderForProfile(profile_.get()); |
| const base::FilePath downloads_path = |
| file_manager::util::GetDownloadsFolderForProfile(profile_.get()); |
| |
| const base::FilePath android_files_path = |
| profile_->GetPath().Append("AndroidFiles"); |
| const base::FilePath android_files_download_path = |
| android_files_path.Append("Download"); |
| |
| // Create directories. |
| CHECK(base::CreateDirectory(downloads_path)); |
| CHECK(base::CreateDirectory(android_files_path)); |
| |
| // Register android files mount point. |
| CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( |
| file_manager::util::GetAndroidFilesMountPointName(), |
| storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), |
| android_files_path)); |
| |
| const int kMountPathBytes = 8092; |
| const int kAndroidPathBytes = 15271; |
| const int kDownloadsPathBytes = 56758; |
| |
| // Add files in MyFiles and Android files. |
| AddFile("random.bin", kMountPathBytes, mount_path); // ~7.9 KB |
| AddFile("tall.pdf", kAndroidPathBytes, android_files_path); // ~14.9 KB |
| // Add file in Downloads and simulate bind mount with |
| // [android files]/Download. |
| AddFile("video.ogv", kDownloadsPathBytes, downloads_path); // ~55.4 KB |
| |
| int64_t total_bytes = base::SysInfo::AmountOfTotalDiskSpace(mount_path); |
| int64_t available_bytes = base::SysInfo::AmountOfFreeDiskSpace(mount_path); |
| int64_t rounded_total_size = sparky::RoundByteSize(total_bytes); |
| |
| std::string available_size = |
| base::UTF16ToUTF8(ui::FormatBytes(available_bytes)); |
| std::string total_size = |
| base::UTF16ToUTF8(ui::FormatBytes(rounded_total_size)); |
| auto quit_closure = task_environment_.QuitClosure(); |
| |
| GetSparkyDelegateImpl()->ObtainStorageInfo(base::BindLambdaForTesting( |
| [&quit_closure, this, available_size, |
| total_size](std::unique_ptr<manta::StorageData> storage_data) { |
| RunUntilIdle(); |
| ASSERT_TRUE(storage_data); |
| ASSERT_EQ(storage_data->free_bytes, available_size); |
| ASSERT_EQ(storage_data->total_bytes, total_size); |
| quit_closure.Run(); |
| })); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, GetMyFiles_Simple) { |
| WriteFile("cat.txt", "I like cats"); |
| WriteFile("dog.txt", "dog text"); |
| WriteFile("turtle.txt", "turtle text"); |
| auto quit_closure = task_environment_.QuitClosure(); |
| RunUntilIdle(); |
| |
| std::string path1 = Path("cat.txt").MaybeAsASCII(); |
| std::string path2 = Path("dog.txt").MaybeAsASCII(); |
| std::string path3 = Path("turtle.txt").MaybeAsASCII(); |
| |
| // Simple test without bytes and with all files allowed. |
| GetSparkyDelegateImpl()->GetMyFiles( |
| base::BindLambdaForTesting([&quit_closure, path1, path2, path3]( |
| std::vector<manta::FileData> files_data) { |
| EXPECT_EQ((int)files_data.size(), 3); |
| EXPECT_THAT( |
| files_data, |
| UnorderedElementsAre(File(path1, "cat.txt"), File(path2, "dog.txt"), |
| File(path3, "turtle.txt"))); |
| for (manta::FileData file : files_data) { |
| EXPECT_FALSE(file.bytes.has_value()); |
| } |
| quit_closure.Run(); |
| }), |
| false, std::set<std::string>()); |
| |
| task_environment_.RunUntilQuit(); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, GetMyFiles_WithFiler) { |
| WriteFile("cat.txt", "I like cats"); |
| WriteFile("dog.txt", "dog text"); |
| WriteFile("turtle.txt", "turtle text"); |
| auto quit_closure = task_environment_.QuitClosure(); |
| RunUntilIdle(); |
| |
| std::string path1 = Path("cat.txt").MaybeAsASCII(); |
| std::string path2 = Path("dog.txt").MaybeAsASCII(); |
| std::string path3 = Path("turtle.txt").MaybeAsASCII(); |
| |
| GetSparkyDelegateImpl()->GetMyFiles( |
| base::BindLambdaForTesting([&quit_closure, path1, path3]( |
| std::vector<manta::FileData> files_data) { |
| EXPECT_EQ((int)files_data.size(), 2); |
| EXPECT_THAT(files_data, |
| UnorderedElementsAre(File(path1, "cat.txt"), |
| File(path3, "turtle.txt"))); |
| quit_closure.Run(); |
| }), |
| false, std::set<std::string>({path1, path3})); |
| |
| task_environment_.RunUntilQuit(); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, GetMyFiles_WithBytes) { |
| WriteFile("cat.txt", "I like cats"); |
| WriteFile("dog.txt", "dog text"); |
| WriteFile("turtle.txt", "turtle text"); |
| auto quit_closure = task_environment_.QuitClosure(); |
| RunUntilIdle(); |
| |
| std::string path1 = Path("cat.txt").MaybeAsASCII(); |
| std::string path2 = Path("dog.txt").MaybeAsASCII(); |
| std::string path3 = Path("turtle.txt").MaybeAsASCII(); |
| |
| GetSparkyDelegateImpl()->GetMyFiles( |
| base::BindLambdaForTesting([&quit_closure, path1, path2, path3]( |
| std::vector<manta::FileData> files_data) { |
| EXPECT_EQ((int)files_data.size(), 3); |
| EXPECT_THAT( |
| files_data, |
| UnorderedElementsAre(File(path1, "cat.txt"), File(path2, "dog.txt"), |
| File(path3, "turtle.txt"))); |
| for (manta::FileData file : files_data) { |
| EXPECT_TRUE(file.bytes.has_value()); |
| } |
| quit_closure.Run(); |
| }), |
| true, std::set<std::string>()); |
| |
| task_environment_.RunUntilQuit(); |
| } |
| |
| TEST_F(SparkyDelegateImplTest, FilesSummary) { |
| // If no summary data has yet be inserted, then the requested file vector |
| // should be empty. |
| std::vector<manta::FileData> empty_files_summary = |
| GetSparkyDelegateImpl()->GetFileSummaries(); |
| EXPECT_TRUE(empty_files_summary.empty()); |
| |
| std::vector<manta::FileData> files_data; |
| auto file_1 = manta::FileData("path1", "name1.pdf", "2024"); |
| file_1.summary = "file 1 summary"; |
| file_1.size_in_bytes = (int64_t)8234; |
| files_data.emplace_back(file_1); |
| |
| auto file_2 = manta::FileData("path2", "name2", "2023"); |
| file_2.summary = "my second file"; |
| file_2.size_in_bytes = (int64_t)1287; |
| files_data.emplace_back(file_2); |
| |
| GetSparkyDelegateImpl()->UpdateFileSummaries(files_data); |
| std::vector<manta::FileData> files_summary = |
| GetSparkyDelegateImpl()->GetFileSummaries(); |
| EXPECT_EQ(2, (int)files_summary.size()); |
| |
| EXPECT_THAT(files_summary, |
| UnorderedElementsAre( |
| FileWithSummary("path1", "name1.pdf", "file 1 summary", |
| "2024", (int64_t)8234), |
| FileWithSummary("path2", "name2", "my second file", "2023", |
| (int64_t)1287))); |
| |
| std::vector<manta::FileData> files_data_updated; |
| auto file_2_updated = manta::FileData("path2", "name2", "2024"); |
| file_2_updated.summary = "my second file with extra cool stuff"; |
| file_2_updated.size_in_bytes = (int64_t)1987; |
| files_data_updated.emplace_back(file_2_updated); |
| |
| auto file_3 = |
| manta::FileData("my/last/path/tree.png", "tree.png", "yesterday"); |
| file_3.summary = "a photo of a eucalyptus tree"; |
| file_3.size_in_bytes = (int64_t)456243; |
| files_data_updated.emplace_back(file_3); |
| |
| GetSparkyDelegateImpl()->UpdateFileSummaries(files_data_updated); |
| std::vector<manta::FileData> files_summary_updated = |
| GetSparkyDelegateImpl()->GetFileSummaries(); |
| |
| EXPECT_EQ(3, (int)files_summary_updated.size()); |
| |
| EXPECT_THAT(files_summary_updated, |
| UnorderedElementsAre( |
| FileWithSummary("path1", "name1.pdf", "file 1 summary", |
| "2024", (int64_t)8234), |
| FileWithSummary("path2", "name2", |
| "my second file with extra cool stuff", |
| "2024", (int64_t)1987), |
| FileWithSummary("my/last/path/tree.png", "tree.png", |
| "a photo of a eucalyptus tree", "yesterday", |
| (int64_t)456243))); |
| } |
| |
| } // namespace ash |